diff --git a/cli/src/feature.rs b/cli/src/feature.rs index 785ed93425e..c6e397d2e01 100644 --- a/cli/src/feature.rs +++ b/cli/src/feature.rs @@ -819,7 +819,7 @@ fn feature_activation_allowed( )) } -fn status_from_account(account: Account) -> Option { +pub(super) fn status_from_account(account: Account) -> Option { feature::from_account(&account).map(|feature| match feature.activated_at { None => CliFeatureStatus::Pending, Some(activation_slot) => CliFeatureStatus::Active(activation_slot), diff --git a/cli/src/program.rs b/cli/src/program.rs index df4aa8731bd..f4988a9cf31 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -9,6 +9,7 @@ use { simulate_and_update_compute_unit_limit, ComputeUnitConfig, UpdateComputeUnitLimitResult, WithComputeUnitConfig, }, + feature::{status_from_account, CliFeatureStatus}, }, bip39::{Language, Mnemonic, MnemonicType, Seed}, clap::{App, AppSettings, Arg, ArgMatches, SubCommand}, @@ -47,6 +48,7 @@ use { client_error::ErrorKind as ClientErrorKind, config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig}, filter::{Memcmp, RpcFilterType}, + request::MAX_MULTIPLE_ACCOUNTS, }, solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery, solana_sdk::{ @@ -56,7 +58,7 @@ use { bpf_loader_upgradeable::{self, get_program_data_address, UpgradeableLoaderState}, commitment_config::CommitmentConfig, compute_budget, - feature_set::FeatureSet, + feature_set::{FeatureSet, FEATURE_NAMES}, instruction::{Instruction, InstructionError}, message::Message, packet::PACKET_DATA_SIZE, @@ -99,6 +101,7 @@ pub enum ProgramCliCommand { max_sign_attempts: usize, auto_extend: bool, use_rpc: bool, + skip_feature_verification: bool, }, Upgrade { fee_payer_signer_index: SignerIndex, @@ -108,6 +111,7 @@ pub enum ProgramCliCommand { sign_only: bool, dump_transaction_message: bool, blockhash_query: BlockhashQuery, + skip_feature_verification: bool, }, WriteBuffer { program_location: String, @@ -279,6 +283,14 @@ impl ProgramSubCommands for App<'_, '_> { .long("no-auto-extend") .takes_value(false) .help("Don't automatically extend the program's data account size"), + ) + .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( @@ -309,6 +321,14 @@ impl ProgramSubCommands for App<'_, '_> { "Upgrade authority [default: the default configured keypair]", ), ) + .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.") + ) .offline_args(), ) .subcommand( @@ -673,6 +693,8 @@ pub fn parse_program_subcommand( let auto_extend = !matches.is_present("no_auto_extend"); + let skip_feature_verify = matches.is_present("skip_feature_verify"); + CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { program_location, @@ -691,6 +713,7 @@ pub fn parse_program_subcommand( max_sign_attempts, use_rpc: matches.is_present("use_rpc"), auto_extend, + skip_feature_verification: skip_feature_verify, }), signers: signer_info.signers, } @@ -720,6 +743,8 @@ pub fn parse_program_subcommand( let signer_info = default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; + let skip_feature_verify = matches.is_present("skip_feature_verify"); + CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Upgrade { fee_payer_signer_index: signer_info.index_of(fee_payer_pubkey).unwrap(), @@ -731,6 +756,7 @@ pub fn parse_program_subcommand( sign_only, dump_transaction_message, blockhash_query, + skip_feature_verification: skip_feature_verify, }), signers: signer_info.signers, } @@ -979,6 +1005,7 @@ pub fn process_program_subcommand( max_sign_attempts, auto_extend, use_rpc, + skip_feature_verification, } => process_program_deploy( rpc_client, config, @@ -996,6 +1023,7 @@ pub fn process_program_subcommand( *max_sign_attempts, *auto_extend, *use_rpc, + *skip_feature_verification, ), ProgramCliCommand::Upgrade { fee_payer_signer_index, @@ -1005,6 +1033,7 @@ pub fn process_program_subcommand( sign_only, dump_transaction_message, blockhash_query, + skip_feature_verification, } => process_program_upgrade( rpc_client, config, @@ -1015,6 +1044,7 @@ pub fn process_program_subcommand( *sign_only, *dump_transaction_message, blockhash_query, + *skip_feature_verification, ), ProgramCliCommand::WriteBuffer { program_location, @@ -1174,6 +1204,7 @@ fn process_program_deploy( max_sign_attempts: usize, auto_extend: bool, use_rpc: bool, + skip_feature_verification: bool, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let upgrade_authority_signer = config.signers[upgrade_authority_signer_index]; @@ -1265,9 +1296,15 @@ fn process_program_deploy( true }; + let feature_set = if skip_feature_verification { + FeatureSet::all_enabled() + } else { + fetch_feature_set(&rpc_client)? + }; + let (program_data, program_len, buffer_program_data) = if let Some(program_location) = program_location { - let program_data = read_and_verify_elf(program_location)?; + let program_data = read_and_verify_elf(program_location, feature_set)?; let program_len = program_data.len(); // If a buffer was provided, check if it has already been created and set up properly @@ -1290,6 +1327,7 @@ fn process_program_deploy( config, buffer_pubkey, upgrade_authority_signer.pubkey(), + feature_set, )?; (vec![], buffer_program_data.len(), Some(buffer_program_data)) @@ -1382,6 +1420,7 @@ fn fetch_verified_buffer_program_data( config: &CliConfig, buffer_pubkey: Pubkey, buffer_authority: Pubkey, + feature_set: FeatureSet, ) -> Result, Box> { let Some(buffer_program_data) = fetch_buffer_program_data(rpc_client, config, None, buffer_pubkey, buffer_authority)? @@ -1389,7 +1428,7 @@ fn fetch_verified_buffer_program_data( return Err(format!("Buffer account {buffer_pubkey} not found").into()); }; - verify_elf(&buffer_program_data).map_err(|err| { + verify_elf(&buffer_program_data, feature_set).map_err(|err| { format!( "Buffer account {buffer_pubkey} has invalid program data: {:?}", err @@ -1466,6 +1505,7 @@ fn process_program_upgrade( sign_only: bool, dump_transaction_message: bool, blockhash_query: &BlockhashQuery, + skip_feature_verification: bool, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let upgrade_authority_signer = config.signers[upgrade_authority_signer_index]; @@ -1496,11 +1536,18 @@ fn process_program_upgrade( }, ) } else { + let feature_set = if skip_feature_verification { + FeatureSet::all_enabled() + } else { + fetch_feature_set(&rpc_client)? + }; + fetch_verified_buffer_program_data( &rpc_client, config, buffer_pubkey, upgrade_authority_signer.pubkey(), + feature_set, )?; let fee = rpc_client.get_fee_for_message(&message)?; @@ -1543,7 +1590,7 @@ fn process_write_buffer( 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)?; + let program_data = read_and_verify_elf(program_location, FeatureSet::all_enabled())?; let program_len = program_data.len(); // Create ephemeral keypair to use for Buffer account, if not provided @@ -2749,27 +2796,29 @@ fn extend_program_data_if_needed( Ok(()) } -fn read_and_verify_elf(program_location: &str) -> Result, Box> { +fn read_and_verify_elf( + program_location: &str, + feature_set: FeatureSet, +) -> Result, Box> { let mut file = File::open(program_location) .map_err(|err| format!("Unable to open program file: {err}"))?; let mut program_data = Vec::new(); file.read_to_end(&mut program_data) .map_err(|err| format!("Unable to read program file: {err}"))?; - verify_elf(&program_data)?; + verify_elf(&program_data, feature_set)?; Ok(program_data) } -fn verify_elf(program_data: &[u8]) -> Result<(), Box> { +fn verify_elf( + program_data: &[u8], + feature_set: FeatureSet, +) -> Result<(), Box> { // Verify the program - let program_runtime_environment = create_program_runtime_environment_v1( - &FeatureSet::all_enabled(), - &ComputeBudget::default(), - true, - false, - ) - .unwrap(); + let program_runtime_environment = + create_program_runtime_environment_v1(&feature_set, &ComputeBudget::default(), true, false) + .unwrap(); let executable = Executable::::from_elf(program_data, Arc::new(program_runtime_environment)) .map_err(|err| format!("ELF error: {err}"))?; @@ -2982,6 +3031,30 @@ fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic) { eprintln!("[BUFFER_ACCOUNT_ADDRESS] argument to `solana program close`.\n{divider}"); } +fn fetch_feature_set(rpc_client: &RpcClient) -> Result> { + let mut feature_set = FeatureSet::default(); + for feature_ids in FEATURE_NAMES + .keys() + .cloned() + .collect::>() + .chunks(MAX_MULTIPLE_ACCOUNTS) + { + rpc_client + .get_multiple_accounts(feature_ids)? + .into_iter() + .zip(feature_ids) + .for_each(|(account, feature_id)| { + let activation_slot = account.and_then(status_from_account); + + if let Some(CliFeatureStatus::Active(slot)) = activation_slot { + feature_set.activate(feature_id, slot); + } + }); + } + + Ok(feature_set) +} + #[cfg(test)] mod tests { use { @@ -3043,6 +3116,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3074,6 +3148,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3107,6 +3182,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3142,6 +3218,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3176,6 +3253,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3213,6 +3291,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![ Box::new(read_keypair_file(&keypair_file).unwrap()), @@ -3246,6 +3325,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3277,6 +3357,7 @@ mod tests { max_sign_attempts: 1, auto_extend: true, use_rpc: false, + skip_feature_verification: false, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -3307,6 +3388,75 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: true, + skip_feature_verification: false, + }), + signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], + } + ); + + let test_command = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "deploy", + "/Users/test/program.so", + "--skip-feature-verify", + ]); + assert_eq!( + parse_command(&test_command, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some("/Users/test/program.so".to_string()), + fee_payer_signer_index: 0, + buffer_signer_index: None, + buffer_pubkey: None, + program_signer_index: None, + program_pubkey: None, + upgrade_authority_signer_index: 0, + is_final: false, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: true, + }), + signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], + } + ); + } + + #[test] + fn test_cli_parse_upgrade() { + let test_commands = get_clap_app("test", "desc", "version"); + + let default_keypair = Keypair::new(); + let keypair_file = make_tmp_path("keypair_file"); + write_keypair_file(&default_keypair, &keypair_file).unwrap(); + let default_signer = DefaultSigner::new("", &keypair_file); + + let program_key = Pubkey::new_unique(); + let buffer_key = Pubkey::new_unique(); + let test_command = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "upgrade", + format!("{}", buffer_key).as_str(), + format!("{}", program_key).as_str(), + "--skip-feature-verify", + ]); + assert_eq!( + parse_command(&test_command, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_key, + buffer_pubkey: buffer_key, + upgrade_authority_signer_index: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::default(), + skip_feature_verification: true, }), signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], } @@ -4050,6 +4200,7 @@ mod tests { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }), signers: vec![&default_keypair], output_format: OutputFormat::JsonCompact, diff --git a/cli/tests/fixtures/alt_bn128.so b/cli/tests/fixtures/alt_bn128.so new file mode 100755 index 00000000000..6d20e91a8ac Binary files /dev/null and b/cli/tests/fixtures/alt_bn128.so differ diff --git a/cli/tests/fixtures/build.sh b/cli/tests/fixtures/build.sh index 872ddd7a849..76f248b1d03 100755 --- a/cli/tests/fixtures/build.sh +++ b/cli/tests/fixtures/build.sh @@ -6,3 +6,4 @@ cd "$(dirname "$0")" make -C ../../../programs/sbf/c/ cp ../../../programs/sbf/c/out/noop.so . cat noop.so noop.so noop.so > noop_large.so +cp ../../../programs/sbf/c/out/alt_bn128.so . diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 7a6bc4ee30c..adaa0aef336 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -25,6 +25,7 @@ use { bpf_loader_upgradeable::{self, UpgradeableLoaderState}, commitment_config::CommitmentConfig, compute_budget::{self, ComputeBudgetInstruction}, + feature_set::enable_alt_bn128_syscall, fee_calculator::FeeRateGovernor, pubkey::Pubkey, rent::Rent, @@ -125,6 +126,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -174,6 +176,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let account1 = rpc_client @@ -232,6 +235,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -258,6 +262,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -330,6 +335,7 @@ fn test_cli_program_deploy_no_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -360,6 +366,7 @@ fn test_cli_program_deploy_no_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -368,6 +375,298 @@ fn test_cli_program_deploy_no_authority() { ); } +#[test_case(true; "Feature enabled")] +#[test_case(false; "Feature disabled")] +fn test_cli_program_deploy_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_programdata = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata( + max_len, + )) + .unwrap(); + let minimum_balance_for_program = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()) + .unwrap(); + let upgrade_authority = Keypair::new(); + + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::new(); + config.json_rpc_url = test_validator.rpc_url(); + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program, + }; + config.signers = vec![&keypair]; + process_command(&config).unwrap(); + + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(program_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: None, + program_pubkey: None, + buffer_signer_index: None, + buffer_pubkey: None, + upgrade_authority_signer_index: 1, + is_final: true, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + + if enable_feature { + let res = process_command(&config); + assert!(res.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::Deploy { + program_location: Some(program_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: None, + program_pubkey: None, + buffer_signer_index: None, + buffer_pubkey: None, + upgrade_authority_signer_index: 1, + is_final: true, + max_len: None, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: true, + }); + + // When we skip verification, we fail at a later stage + let response = process_command(&config); + assert!(response + .err() + .unwrap() + .to_string() + .contains("Deploying program failed: RPC response error -32002:")); + } +} + +#[test_case(true; "Feature enabled")] +#[test_case(false; "Feature disabled")] +fn test_cli_program_upgrade_with_feature(enable_feature: bool) { + solana_logger::setup(); + + let mut noop_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + noop_path.push("tests"); + noop_path.push("fixtures"); + noop_path.push("noop"); + noop_path.set_extension("so"); + + let mut syscall_program_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + syscall_program_path.push("tests"); + syscall_program_path.push("fixtures"); + syscall_program_path.push("alt_bn128"); + syscall_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 blockhash = rpc_client.get_latest_blockhash().unwrap(); + + let mut file = File::open(syscall_program_path.to_str().unwrap()).unwrap(); + let mut large_program_data = Vec::new(); + file.read_to_end(&mut large_program_data).unwrap(); + let max_program_data_len = large_program_data.len(); + let minimum_balance_for_large_buffer = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_programdata( + max_program_data_len, + )) + .unwrap(); + + let mut config = CliConfig::recent_for_tests(); + config.json_rpc_url = test_validator.rpc_url(); + + let online_signer = Keypair::new(); + let offline_signer = Keypair::new(); + let buffer_signer = Keypair::new(); + // Typically, keypair for program signer should be different from online signer or + // offline signer keypairs. + let program_signer = Keypair::new(); + + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_large_buffer, // gotta be enough for this test + }; + config.signers = vec![&online_signer]; + process_command(&config).unwrap(); + config.command = CliCommand::Airdrop { + pubkey: None, + lamports: 100 * minimum_balance_for_large_buffer, // gotta be enough for this test + }; + config.signers = vec![&offline_signer]; + process_command(&config).unwrap(); + + // Deploy upgradeable program with authority set to offline signer + config.signers = vec![&online_signer, &offline_signer, &program_signer]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(noop_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: Some(2), + program_pubkey: Some(program_signer.pubkey()), + buffer_signer_index: None, + buffer_pubkey: None, + upgrade_authority_signer_index: 1, // must be offline signer for security reasons + is_final: false, + max_len: Some(max_program_data_len), // allows for larger program size with future upgrades + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 5, + auto_extend: true, + use_rpc: false, + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + process_command(&config).unwrap(); + + // Prepare buffer to upgrade deployed program to a larger program + create_buffer_with_offline_authority( + &rpc_client, + &syscall_program_path, + &mut config, + &online_signer, + &offline_signer, + &buffer_signer, + ); + + config.signers = vec![&offline_signer]; + config.command = CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_signer.pubkey(), + buffer_pubkey: buffer_signer.pubkey(), + upgrade_authority_signer_index: 0, + sign_only: true, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + let sig_response = process_command(&config).unwrap(); + let sign_only = parse_sign_only_reply_string(&sig_response); + let offline_pre_signer = sign_only.presigner_of(&offline_signer.pubkey()).unwrap(); + // Attempt to deploy from buffer using signature over correct message (should succeed) + config.signers = vec![&offline_pre_signer, &program_signer]; + + config.command = CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_signer.pubkey(), + buffer_pubkey: buffer_signer.pubkey(), + upgrade_authority_signer_index: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: false, + }); + config.output_format = OutputFormat::JsonCompact; + if enable_feature { + let res = process_command(&config); + assert!(res.is_ok()); + } else { + expect_command_failure( + &config, + "Program contains a syscall to a disabled feature", + format!("Buffer account {} has invalid program data: \"ELF error: ELF error: Unresolved symbol (sol_alt_bn128_group_op) at instruction #49 (ELF file offset 0x188)\"", buffer_signer.pubkey()).as_str(), + ); + + // If we skip verification, the failure should be at a later stage + config.command = CliCommand::Program(ProgramCliCommand::Upgrade { + fee_payer_signer_index: 0, + program_pubkey: program_signer.pubkey(), + buffer_pubkey: buffer_signer.pubkey(), + upgrade_authority_signer_index: 0, + sign_only: false, + dump_transaction_message: false, + blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, + }); + config.output_format = OutputFormat::JsonCompact; + + let response = process_command(&config); + assert!(response + .err() + .unwrap() + .to_string() + .contains("Upgrading program failed: RPC response error -32002")); + } +} + #[test] fn test_cli_program_deploy_with_authority() { solana_logger::setup(); @@ -429,6 +728,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -481,6 +781,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -527,6 +828,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -605,6 +907,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -687,6 +990,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -711,6 +1015,7 @@ fn test_cli_program_deploy_with_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -830,6 +1135,7 @@ fn test_cli_program_upgrade_auto_extend() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -852,6 +1158,7 @@ fn test_cli_program_upgrade_auto_extend() { max_sign_attempts: 5, auto_extend: false, // --no-auto-extend flag is present use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -884,6 +1191,7 @@ fn test_cli_program_upgrade_auto_extend() { max_sign_attempts: 5, auto_extend: true, // --no-auto-extend flag is absent use_rpc: false, + skip_feature_verification: true, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -974,6 +1282,7 @@ fn test_cli_program_close_program() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1092,6 +1401,7 @@ fn test_cli_program_extend_program() { max_sign_attempts: 5, auto_extend: false, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1142,6 +1452,7 @@ fn test_cli_program_extend_program() { max_sign_attempts: 5, auto_extend: false, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -1188,6 +1499,7 @@ fn test_cli_program_extend_program() { max_sign_attempts: 5, auto_extend: false, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); } @@ -1551,6 +1863,7 @@ fn test_cli_program_write_buffer() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let buffer_account_len = { @@ -1681,6 +1994,7 @@ fn test_cli_program_set_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; expect_command_failure( @@ -1737,6 +2051,7 @@ fn test_cli_program_set_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1823,6 +2138,7 @@ fn test_cli_program_mismatch_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); expect_command_failure( &config, @@ -1851,6 +2167,7 @@ fn test_cli_program_mismatch_buffer_authority() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); process_command(&config).unwrap(); } @@ -1937,6 +2254,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1967,6 +2285,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: true, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let sig_response = process_command(&config).unwrap(); @@ -1988,6 +2307,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; expect_command_failure( @@ -2012,6 +2332,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: true, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let sig_response = process_command(&config).unwrap(); @@ -2033,6 +2354,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: sign_only: false, dump_transaction_message: false, blockhash_query: BlockhashQuery::new(Some(blockhash), true, None), + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -2174,6 +2496,7 @@ fn test_cli_program_show() { max_sign_attempts: 5, auto_extend: true, use_rpc: false, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let min_slot = rpc_client.get_slot().unwrap(); @@ -2451,6 +2774,7 @@ fn test_cli_program_deploy_with_args(compute_unit_price: Option, use_rpc: b max_sign_attempts: 5, auto_extend: true, use_rpc, + skip_feature_verification: true, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); diff --git a/transaction-dos/src/main.rs b/transaction-dos/src/main.rs index d006972dca5..72deea1422f 100644 --- a/transaction-dos/src/main.rs +++ b/transaction-dos/src/main.rs @@ -251,6 +251,7 @@ fn run_transactions_dos( use_rpc: false, skip_fee_check: true, // skip_fee_check auto_extend: true, + skip_feature_verification: true, }); process_command(&config).expect("deploy didn't pass");