Skip to content

Commit 4009489

Browse files
committed
fix: get-program-hash for programs on legacy BPF loaders
1 parent d3811ef commit 4009489

File tree

4 files changed

+115
-25
lines changed

4 files changed

+115
-25
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ solana-rpc-client = "=2.3.1"
3131
solana-rpc-client-api = "=2.3.1"
3232
solana-transaction-status-client-types = "=2.3.1"
3333
solana-loader-v3-interface = "5.0.0"
34+
solana-sdk-ids = "2.2.1"
3435
solana-system-interface = "1.0.0"
3536
tokio = { version = "1.29.1", features = ["full"] }
3637
bincode = "1.3.3"

src/main.rs

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use solana_loader_v3_interface::{get_program_data_address, state::UpgradeableLoa
1616
use solana_program::get_address_from_keypair_or_config;
1717
use solana_rpc_client::rpc_client::RpcClient;
1818
use solana_sdk::pubkey::Pubkey;
19+
use solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable};
1920
use solana_transaction_status_client_types::UiTransactionEncoding;
2021
use std::{
2122
io::Read,
@@ -725,33 +726,83 @@ pub fn get_file_hash(filepath: &str) -> Result<String, std::io::Error> {
725726
pub fn get_buffer_hash(url: Option<String>, buffer_address: Pubkey) -> anyhow::Result<String> {
726727
let client = get_client(url, None);
727728
let offset = UpgradeableLoaderState::size_of_buffer_metadata();
728-
let account_data = client.get_account_data(&buffer_address)?[offset..].to_vec();
729+
let data = client.get_account_data(&buffer_address)?;
730+
let account_data = data
731+
.get(offset..)
732+
.ok_or_else(|| {
733+
anyhow::anyhow!(
734+
"Buffer account {} appears invalid or incomplete. Expected at least {} bytes for metadata.",
735+
buffer_address,
736+
offset
737+
)
738+
})?
739+
.to_vec();
740+
729741
let program_hash = get_binary_hash(account_data);
730742
Ok(program_hash)
731743
}
732744

733745
pub fn get_program_hash(client: &RpcClient, program_id: Pubkey) -> anyhow::Result<String> {
734-
// First check if the program account exists
735-
if client.get_account(&program_id).is_err() {
736-
return Err(anyhow!("Program {} is not deployed", program_id));
737-
}
738-
739-
let program_buffer = get_program_data_address(&program_id);
746+
let account = client
747+
.get_account(&program_id)
748+
.map_err(|e| anyhow!("Program {} is not deployed: {}", program_id, e))?;
749+
750+
let owner = account.owner;
751+
752+
match owner {
753+
// Check if the program is owned by the upgradeable loader (Loader-v3)
754+
// If so, the program data is in a separate program data account
755+
owner_id if owner_id == bpf_loader_upgradeable::id() => {
756+
let program_buffer = get_program_data_address(&program_id);
757+
758+
// Get the program data account
759+
let data = client.get_account_data(&program_buffer).map_err(|e| {
760+
anyhow!(
761+
"Could not find program data for {}: {}. This could mean:\n\
762+
1. The program is not deployed\n\
763+
2. The program is not upgradeable\n\
764+
3. The program was deployed with a different loader",
765+
program_id,
766+
e
767+
)
768+
})?;
740769

741-
// Then check if the program data account exists
742-
match client.get_account_data(&program_buffer) {
743-
Ok(data) => {
744770
let offset = UpgradeableLoaderState::size_of_programdata_metadata();
745-
let account_data = data[offset..].to_vec();
746-
let program_hash = get_binary_hash(account_data);
747-
Ok(program_hash)
771+
772+
let account_data = data
773+
.get(offset..)
774+
.ok_or_else(|| {
775+
anyhow!(
776+
"Program data account appears corrupted or incomplete. Expected at least {} bytes for metadata.",
777+
offset
778+
)
779+
})?
780+
.to_vec();
781+
782+
Ok(get_binary_hash(account_data))
748783
}
749-
Err(_) => Err(anyhow!(
750-
"Could not find program data for {}. This could mean:\n\
751-
1. The program is not deployed\n\
752-
2. The program is not upgradeable\n\
753-
3. The program was deployed with a different loader",
754-
program_id
784+
785+
// Check if the program is owned by the legacy BPF loaders (v1/v2)
786+
// If so, the program data is stored in the program account's data
787+
owner_id if owner_id == bpf_loader_deprecated::id() || owner_id == bpf_loader::id() => {
788+
let program_data = account.data;
789+
790+
if program_data.is_empty() {
791+
return Err(anyhow!(
792+
"Program {} has no data (legacy loader account empty)",
793+
program_id
794+
));
795+
}
796+
797+
Ok(get_binary_hash(program_data))
798+
}
799+
800+
// Unsupported loader
801+
_ => Err(anyhow!(
802+
"Unknown or unsupported program loader. \
803+
Program {} is owned by {}. Supported loaders: BPF Loader v1, v2, or Upgradeable (loader-v3).",
804+
program_id,
805+
owner
755806
)),
756807
}
757808
}
@@ -772,7 +823,9 @@ pub fn get_docker_resource_limits() -> Option<(String, String)> {
772823
} else {
773824
// Print message to user that they can set these environment variables to limit docker resources
774825
println!("No Docker resource limits are set.");
775-
println!("You can set the SVB_DOCKER_MEMORY_LIMIT and SVB_DOCKER_CPU_LIMIT environment variables to limit Docker resources.");
826+
println!(
827+
"You can set the SVB_DOCKER_MEMORY_LIMIT and SVB_DOCKER_CPU_LIMIT environment variables to limit Docker resources."
828+
);
776829
println!("For example: SVB_DOCKER_MEMORY_LIMIT=2g SVB_DOCKER_CPU_LIMIT=2.");
777830
}
778831
memory.zip(cpus)
@@ -1086,10 +1139,7 @@ pub fn verify_from_image(
10861139

10871140
let executable_hash: String = get_file_hash(program_filepath.as_str())?;
10881141
let client = get_client(network, config_path);
1089-
let program_buffer = get_program_data_address(&program_id);
1090-
let offset = UpgradeableLoaderState::size_of_programdata_metadata();
1091-
let account_data = &client.get_account_data(&program_buffer)?[offset..];
1092-
let program_hash = get_binary_hash(account_data.to_vec());
1142+
let program_hash = get_program_hash(&client, program_id)?;
10931143
println!("Executable hash: {}", executable_hash);
10941144
println!("Program hash: {}", program_hash);
10951145

@@ -1392,7 +1442,9 @@ pub async fn verify_from_repo(
13921442
check_signal(container_id_opt, temp_dir_opt);
13931443
let genesis_hash = get_genesis_hash(connection)?;
13941444
if genesis_hash != MAINNET_GENESIS_HASH {
1395-
return Err(anyhow!("Remote verification only works with mainnet. Please omit the --remote flag to verify locally."));
1445+
return Err(anyhow!(
1446+
"Remote verification only works with mainnet. Please omit the --remote flag to verify locally."
1447+
));
13961448
}
13971449

13981450
let uploader = get_address_from_keypair_or_config(

src/test.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,40 @@ mod tests {
180180
}
181181
Ok(())
182182
}
183+
184+
#[test]
185+
fn test_get_program_hash_legacy_loader() -> anyhow::Result<()> {
186+
const SPL_TOKEN_PROGRAM_ID: &str = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
187+
const EXPECTED_HASH: &str =
188+
"d5a1793250f0f22efc7174bd8399570636e667655179642b2e90b0fb80e09106";
189+
let args = ["get-program-hash", SPL_TOKEN_PROGRAM_ID];
190+
let child = std::process::Command::new("./target/debug/solana-verify")
191+
.args(args)
192+
.stdin(Stdio::piped())
193+
.stdout(Stdio::piped())
194+
.stderr(Stdio::piped())
195+
.spawn()
196+
.context("Failed to execute solana-verify command")?;
197+
198+
let output = child
199+
.wait_with_output()
200+
.context("Failed to wait for solana-verify command")?;
201+
202+
if !output.status.success() {
203+
let stderr = String::from_utf8_lossy(&output.stderr);
204+
anyhow::bail!(
205+
"get-program-hash for legacy loader program failed: {}",
206+
stderr
207+
);
208+
}
209+
210+
let stdout = String::from_utf8_lossy(&output.stdout);
211+
let hash = stdout.trim();
212+
assert_eq!(
213+
hash, EXPECTED_HASH,
214+
"Program hash {} does not match expected value {}",
215+
hash, EXPECTED_HASH
216+
);
217+
Ok(())
218+
}
183219
}

0 commit comments

Comments
 (0)