Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify elf locally for write buffer cli command #1794

Merged
merged 1 commit into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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