Skip to content

Commit

Permalink
Verify elf locally for write buffer cli command (anza-xyz#1794)
Browse files Browse the repository at this point in the history
Verify elf locally for write buffer command
  • Loading branch information
LucasSte authored and neutrinoks committed Jul 17, 2024
1 parent 9c03990 commit 0acf26d
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 2 deletions.
58 changes: 56 additions & 2 deletions cli/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ pub enum ProgramCliCommand {
compute_unit_price: Option<u64>,
max_sign_attempts: usize,
use_rpc: bool,
skip_feature_verification: bool,
},
SetBufferAuthority {
buffer_pubkey: Pubkey,
Expand Down Expand Up @@ -395,7 +396,15 @@ impl ProgramSubCommands for App<'_, '_> {
.arg(Arg::with_name("use_rpc").long("use-rpc").help(
"Send transactions to the configured RPC instead of validator TPUs",
))
.arg(compute_unit_price_arg()),
.arg(compute_unit_price_arg())
.arg(
Arg::with_name("skip_feature_verify")
.long("skip-feature-verify")
.takes_value(false)
.help("Don't verify program against the activated feature set. \
This setting means a program containing a syscall not yet active on \
mainnet will succeed local verification, but fail during the last step of deployment.")
),
)
.subcommand(
SubCommand::with_name("set-buffer-authority")
Expand Down Expand Up @@ -790,6 +799,7 @@ pub fn parse_program_subcommand(

let compute_unit_price = value_of(matches, "compute_unit_price");
let max_sign_attempts = value_of(matches, "max_sign_attempts").unwrap();
let skip_feature_verify = matches.is_present("skip_feature_verify");

CliCommandInfo {
command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
Expand All @@ -805,6 +815,7 @@ pub fn parse_program_subcommand(
compute_unit_price,
max_sign_attempts,
use_rpc: matches.is_present("use_rpc"),
skip_feature_verification: skip_feature_verify,
}),
signers: signer_info.signers,
}
Expand Down Expand Up @@ -1057,6 +1068,7 @@ pub fn process_program_subcommand(
compute_unit_price,
max_sign_attempts,
use_rpc,
skip_feature_verification,
} => process_write_buffer(
rpc_client,
config,
Expand All @@ -1070,6 +1082,7 @@ pub fn process_program_subcommand(
*compute_unit_price,
*max_sign_attempts,
*use_rpc,
*skip_feature_verification,
),
ProgramCliCommand::SetBufferAuthority {
buffer_pubkey,
Expand Down Expand Up @@ -1586,11 +1599,18 @@ fn process_write_buffer(
compute_unit_price: Option<u64>,
max_sign_attempts: usize,
use_rpc: bool,
skip_feature_verification: bool,
) -> ProcessResult {
let fee_payer_signer = config.signers[fee_payer_signer_index];
let buffer_authority = config.signers[buffer_authority_signer_index];

let program_data = read_and_verify_elf(program_location, FeatureSet::all_enabled())?;
let feature_set = if skip_feature_verification {
FeatureSet::all_enabled()
} else {
fetch_feature_set(&rpc_client)?
};

let program_data = read_and_verify_elf(program_location, feature_set)?;
let program_len = program_data.len();

// Create ephemeral keypair to use for Buffer account, if not provided
Expand Down Expand Up @@ -3494,6 +3514,7 @@ mod tests {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: false,
}),
signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
}
Expand Down Expand Up @@ -3522,6 +3543,7 @@ mod tests {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: false,
}),
signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
}
Expand Down Expand Up @@ -3553,6 +3575,7 @@ mod tests {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: false,
}),
signers: vec![
Box::new(read_keypair_file(&keypair_file).unwrap()),
Expand Down Expand Up @@ -3587,6 +3610,7 @@ mod tests {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: false,
}),
signers: vec![
Box::new(read_keypair_file(&keypair_file).unwrap()),
Expand Down Expand Up @@ -3626,6 +3650,7 @@ mod tests {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: false,
}),
signers: vec![
Box::new(read_keypair_file(&keypair_file).unwrap()),
Expand Down Expand Up @@ -3658,6 +3683,35 @@ mod tests {
compute_unit_price: None,
max_sign_attempts: 10,
use_rpc: false,
skip_feature_verification: false
}),
signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
}
);

// skip feature verification
let test_command = test_commands.clone().get_matches_from(vec![
"test",
"program",
"write-buffer",
"/Users/test/program.so",
"--skip-feature-verify",
]);
assert_eq!(
parse_command(&test_command, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: "/Users/test/program.so".to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
}),
signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
}
Expand Down
116 changes: 116 additions & 0 deletions cli/tests/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,7 @@ fn test_cli_program_write_buffer() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
Expand Down Expand Up @@ -1606,6 +1607,7 @@ fn test_cli_program_write_buffer() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
Expand Down Expand Up @@ -1672,6 +1674,7 @@ fn test_cli_program_write_buffer() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
Expand Down Expand Up @@ -1714,6 +1717,7 @@ fn test_cli_program_write_buffer() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
Expand Down Expand Up @@ -1796,6 +1800,7 @@ fn test_cli_program_write_buffer() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
config.output_format = OutputFormat::JsonCompact;
let response = process_command(&config);
Expand Down Expand Up @@ -1845,6 +1850,7 @@ fn test_cli_program_write_buffer() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
config.signers = vec![&keypair, &buffer_keypair];
Expand Down Expand Up @@ -1886,6 +1892,111 @@ fn test_cli_program_write_buffer() {
);
}

#[test_case(true; "Feature enabled")]
#[test_case(false; "Feature disabled")]
fn test_cli_program_write_buffer_feature(enable_feature: bool) {
solana_logger::setup();

let mut program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
program_path.push("tests");
program_path.push("fixtures");
program_path.push("alt_bn128");
program_path.set_extension("so");

let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
let mut genesis = TestValidatorGenesis::default();
let mut test_validator_builder = genesis
.fee_rate_governor(FeeRateGovernor::new(0, 0))
.rent(Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
})
.faucet_addr(Some(faucet_addr));

// Deactivate the enable alt bn128 syscall and try to submit a program with that syscall
if !enable_feature {
test_validator_builder =
test_validator_builder.deactivate_features(&[enable_alt_bn128_syscall::id()]);
}

let test_validator = test_validator_builder
.start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified)
.expect("validator start failed");

let rpc_client =
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());

let mut file = File::open(program_path.to_str().unwrap()).unwrap();
let mut program_data = Vec::new();
file.read_to_end(&mut program_data).unwrap();
let max_len = program_data.len();
let minimum_balance_for_buffer = rpc_client
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata(
max_len,
))
.unwrap();

let mut config = CliConfig::recent_for_tests();
let keypair = Keypair::new();
config.json_rpc_url = test_validator.rpc_url();
config.signers = vec![&keypair];
config.command = CliCommand::Airdrop {
pubkey: None,
lamports: 100 * minimum_balance_for_buffer,
};
process_command(&config).unwrap();

// Write a buffer with default params
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: program_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: false,
});
config.output_format = OutputFormat::JsonCompact;

if enable_feature {
let response = process_command(&config);
assert!(response.is_ok());
} else {
expect_command_failure(
&config,
"Program contains a syscall from a deactivated feature",
"ELF error: ELF error: Unresolved symbol (sol_alt_bn128_group_op) at instruction #49 (ELF file offset 0x188)"
);

// If we bypass the verification, there should be no error
config.command = CliCommand::Program(ProgramCliCommand::WriteBuffer {
program_location: program_path.to_str().unwrap().to_string(),
fee_payer_signer_index: 0,
buffer_signer_index: None,
buffer_pubkey: None,
buffer_authority_signer_index: 0,
max_len: None,
skip_fee_check: false,
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});

// When we skip verification, we won't fail
let response = process_command(&config);
assert!(response.is_ok());
}
}

#[test]
fn test_cli_program_set_buffer_authority() {
solana_logger::setup();
Expand Down Expand Up @@ -1939,6 +2050,7 @@ fn test_cli_program_set_buffer_authority() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
Expand Down Expand Up @@ -2111,6 +2223,7 @@ fn test_cli_program_mismatch_buffer_authority() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
Expand Down Expand Up @@ -2432,6 +2545,7 @@ fn test_cli_program_show() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();

Expand Down Expand Up @@ -2628,6 +2742,7 @@ fn test_cli_program_dump() {
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(&config).unwrap();

Expand Down Expand Up @@ -2674,6 +2789,7 @@ fn create_buffer_with_offline_authority<'a>(
compute_unit_price: None,
max_sign_attempts: 5,
use_rpc: false,
skip_feature_verification: true,
});
process_command(config).unwrap();
let buffer_account = rpc_client.get_account(&buffer_signer.pubkey()).unwrap();
Expand Down

0 comments on commit 0acf26d

Please sign in to comment.