From 5a45f5aa3caa78c7811e13ce8cdfb0b2ac8bc2a5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:00:45 +0800 Subject: [PATCH] v1.18: cli: Customize max sign attempts for deploy and write-buffer (backport of #526) (#538) * cli: Customize max sign attempts for deploy and write-buffer (#526) * cli: Customize max sign attempts for deploy and write-buffer * Update changelog * Improve help message * Fixup line break (cherry picked from commit 2643ae85c3262f5bee62931bee8a51e11090c72a) # Conflicts: # CHANGELOG.md # cli/src/program.rs * Fix merge conflicts --------- Co-authored-by: Jon C --- CHANGELOG.md | 1 + cli/src/program.rs | 157 +++++++++++++++++++++++++++++++++--- cli/tests/program.rs | 35 ++++++++ transaction-dos/src/main.rs | 1 + 4 files changed, 181 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d52beaa0b83e..9303c74e6c6d32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Release channels have their own copy of this changelog: * New program deployments default to the exact size of a program, instead of double the size. Program accounts must be extended with `solana program extend` before an upgrade if they need to accommodate larger programs. + * CLI: Can specify `--with-compute-unit-price` and `--max-sign-attempts` during program deployment * Upgrade Notes * `solana-program` and `solana-sdk` default to support for Borsh v1, with limited backward compatibility for v0.10 and v0.9. Please upgrade to Borsh v1. diff --git a/cli/src/program.rs b/cli/src/program.rs index 3a84cc3d8b775f..2cb2cd4289bbdf 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -98,6 +98,7 @@ pub enum ProgramCliCommand { allow_excessive_balance: bool, skip_fee_check: bool, compute_unit_price: Option, + max_sign_attempts: usize, }, Upgrade { fee_payer_signer_index: SignerIndex, @@ -117,6 +118,7 @@ pub enum ProgramCliCommand { max_len: Option, skip_fee_check: bool, compute_unit_price: Option, + max_sign_attempts: usize, }, SetBufferAuthority { buffer_pubkey: Pubkey, @@ -240,6 +242,26 @@ impl ProgramSubCommands for App<'_, '_> { holds a large balance of SOL", ), ) + .arg( + Arg::with_name("max_sign_attempts") + .long("max-sign-attempts") + .takes_value(true) + .validator(is_parsable::) + .default_value("5") + .help( + "Maximum number of attempts to sign or resign transactions \ + after blockhash expiration. \ + If any transactions sent during the program deploy are still \ + unconfirmed after the initially chosen recent blockhash \ + expires, those transactions will be resigned with a new \ + recent blockhash and resent. Use this setting to adjust \ + the maximum number of transaction signing iterations. Each \ + blockhash is valid for about 60 seconds, which means using \ + the default value of 5 will lead to sending transactions \ + for at least 5 minutes or until all transactions are confirmed,\ + whichever comes first.", + ), + ) .arg(compute_unit_price_arg()), ) .subcommand( @@ -313,6 +335,26 @@ impl ProgramSubCommands for App<'_, '_> { [default: the length of the original deployed program]", ), ) + .arg( + Arg::with_name("max_sign_attempts") + .long("max-sign-attempts") + .takes_value(true) + .validator(is_parsable::) + .default_value("5") + .help( + "Maximum number of attempts to sign or resign transactions \ + after blockhash expiration. \ + If any transactions sent during the program deploy are still \ + unconfirmed after the initially chosen recent blockhash \ + expires, those transactions will be resigned with a new \ + recent blockhash and resent. Use this setting to adjust \ + the maximum number of transaction signing iterations. Each \ + blockhash is valid for about 60 seconds, which means using \ + the default value of 5 will lead to sending transactions \ + for at least 5 minutes or until all transactions are confirmed,\ + whichever comes first.", + ), + ) .arg(compute_unit_price_arg()), ) .subcommand( @@ -606,6 +648,7 @@ pub fn parse_program_subcommand( default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; let compute_unit_price = value_of(matches, "compute_unit_price"); + let max_sign_attempts = value_of(matches, "max_sign_attempts").unwrap(); CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::Deploy { @@ -623,6 +666,7 @@ pub fn parse_program_subcommand( allow_excessive_balance: matches.is_present("allow_excessive_balance"), skip_fee_check, compute_unit_price, + max_sign_attempts, }), signers: signer_info.signers, } @@ -696,6 +740,7 @@ pub fn parse_program_subcommand( default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?; let compute_unit_price = value_of(matches, "compute_unit_price"); + let max_sign_attempts = value_of(matches, "max_sign_attempts").unwrap(); CliCommandInfo { command: CliCommand::Program(ProgramCliCommand::WriteBuffer { @@ -709,6 +754,7 @@ pub fn parse_program_subcommand( max_len, skip_fee_check, compute_unit_price, + max_sign_attempts, }), signers: signer_info.signers, } @@ -902,6 +948,7 @@ pub fn process_program_subcommand( allow_excessive_balance, skip_fee_check, compute_unit_price, + max_sign_attempts, } => process_program_deploy( rpc_client, config, @@ -917,6 +964,7 @@ pub fn process_program_subcommand( *allow_excessive_balance, *skip_fee_check, *compute_unit_price, + *max_sign_attempts, ), ProgramCliCommand::Upgrade { fee_payer_signer_index, @@ -946,6 +994,7 @@ pub fn process_program_subcommand( max_len, skip_fee_check, compute_unit_price, + max_sign_attempts, } => process_write_buffer( rpc_client, config, @@ -957,6 +1006,7 @@ pub fn process_program_subcommand( *max_len, *skip_fee_check, *compute_unit_price, + *max_sign_attempts, ), ProgramCliCommand::SetBufferAuthority { buffer_pubkey, @@ -1074,6 +1124,7 @@ fn process_program_deploy( allow_excessive_balance: bool, skip_fee_check: bool, compute_unit_price: Option, + max_sign_attempts: usize, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let upgrade_authority_signer = config.signers[upgrade_authority_signer_index]; @@ -1214,6 +1265,7 @@ fn process_program_deploy( allow_excessive_balance, skip_fee_check, compute_unit_price, + max_sign_attempts, ) } else { do_process_program_upgrade( @@ -1229,6 +1281,7 @@ fn process_program_deploy( buffer_signer, skip_fee_check, compute_unit_price, + max_sign_attempts, ) }; if result.is_ok() && is_final { @@ -1374,6 +1427,7 @@ fn process_write_buffer( max_len: Option, skip_fee_check: bool, compute_unit_price: Option, + max_sign_attempts: usize, ) -> ProcessResult { let fee_payer_signer = config.signers[fee_payer_signer_index]; let buffer_authority = config.signers[buffer_authority_signer_index]; @@ -1440,6 +1494,7 @@ fn process_write_buffer( true, skip_fee_check, compute_unit_price, + max_sign_attempts, ); if result.is_err() && buffer_signer_index.is_none() && buffer_signer.is_some() { report_ephemeral_mnemonic(words, mnemonic); @@ -2166,6 +2221,7 @@ fn do_process_program_write_and_deploy( allow_excessive_balance: bool, skip_fee_check: bool, compute_unit_price: Option, + max_sign_attempts: usize, ) -> ProcessResult { let blockhash = rpc_client.get_latest_blockhash()?; @@ -2304,6 +2360,7 @@ fn do_process_program_write_and_deploy( buffer_signer, Some(buffer_authority_signer), program_signers, + max_sign_attempts, )?; if let Some(program_signers) = program_signers { @@ -2333,6 +2390,7 @@ fn do_process_program_upgrade( buffer_signer: Option<&dyn Signer>, skip_fee_check: bool, compute_unit_price: Option, + max_sign_attempts: usize, ) -> ProcessResult { let blockhash = rpc_client.get_latest_blockhash()?; @@ -2450,6 +2508,7 @@ fn do_process_program_upgrade( buffer_signer, Some(upgrade_authority), Some(&[upgrade_authority]), + max_sign_attempts, )?; let program_id = CliProgramId { @@ -2632,6 +2691,7 @@ fn simulate_and_update_compute_unit_limit( )) } +#[allow(clippy::too_many_arguments)] fn send_deploy_messages( rpc_client: Arc, config: &CliConfig, @@ -2642,6 +2702,7 @@ fn send_deploy_messages( initial_signer: Option<&dyn Signer>, write_signer: Option<&dyn Signer>, final_signers: Option<&[&dyn Signer]>, + max_sign_attempts: usize, ) -> Result<(), Box> { if let Some(message) = initial_message { if let Some(initial_signer) = initial_signer { @@ -2729,7 +2790,7 @@ fn send_deploy_messages( &write_messages, &[fee_payer_signer, write_signer], SendAndConfirmConfig { - resign_txs_count: Some(5), + resign_txs_count: Some(max_sign_attempts), with_spinner: true, }, ) @@ -2876,7 +2937,8 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -2905,7 +2967,8 @@ mod tests { max_len: Some(42), allow_excessive_balance: false, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -2936,7 +2999,8 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -2969,7 +3033,8 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -3001,7 +3066,8 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -3036,7 +3102,8 @@ mod tests { max_len: None, allow_excessive_balance: false, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -3067,7 +3134,38 @@ mod tests { max_len: None, skip_fee_check: false, allow_excessive_balance: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, + }), + 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", + "--max-sign-attempts", + "1", + ]); + 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, + allow_excessive_balance: false, + skip_fee_check: false, + compute_unit_price: None, + max_sign_attempts: 1, }), signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -3102,7 +3200,8 @@ mod tests { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -3128,7 +3227,8 @@ mod tests { buffer_authority_signer_index: 0, max_len: Some(42), skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -3157,7 +3257,8 @@ mod tests { buffer_authority_signer_index: 0, max_len: None, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -3189,7 +3290,8 @@ mod tests { buffer_authority_signer_index: 1, max_len: None, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -3226,7 +3328,8 @@ mod tests { buffer_authority_signer_index: 2, max_len: None, skip_fee_check: false, - compute_unit_price: None + compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -3235,6 +3338,33 @@ mod tests { ], } ); + + // specify max sign attempts + let test_command = test_commands.clone().get_matches_from(vec![ + "test", + "program", + "write-buffer", + "/Users/test/program.so", + "--max-sign-attempts", + "10", + ]); + 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: 10, + }), + signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())], + } + ); } #[test] @@ -3762,6 +3892,7 @@ mod tests { allow_excessive_balance: false, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }), signers: vec![&default_keypair], output_format: OutputFormat::JsonCompact, diff --git a/cli/tests/program.rs b/cli/tests/program.rs index 61da71c83d947d..c67f6af37ad176 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -98,6 +98,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -145,6 +146,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); let account1 = rpc_client @@ -201,6 +203,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); let err = process_command(&config).unwrap_err(); assert_eq!( @@ -225,6 +228,7 @@ fn test_cli_program_deploy_non_upgradeable() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap_err(); } @@ -287,6 +291,7 @@ fn test_cli_program_deploy_no_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -315,6 +320,7 @@ fn test_cli_program_deploy_no_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap_err(); } @@ -378,6 +384,7 @@ fn test_cli_program_deploy_with_authority() { max_len: Some(max_len), skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -428,6 +435,7 @@ fn test_cli_program_deploy_with_authority() { max_len: Some(max_len), skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -472,6 +480,7 @@ fn test_cli_program_deploy_with_authority() { max_len: Some(max_len), skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -529,6 +538,7 @@ fn test_cli_program_deploy_with_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); let program_account = rpc_client.get_account(&program_pubkey).unwrap(); @@ -606,6 +616,7 @@ fn test_cli_program_deploy_with_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap_err(); @@ -624,6 +635,7 @@ fn test_cli_program_deploy_with_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -729,6 +741,7 @@ fn test_cli_program_close_program() { max_len: Some(max_len), skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -840,6 +853,7 @@ fn test_cli_program_extend_program() { max_len: None, // Use None to check that it defaults to the max length skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -888,6 +902,7 @@ fn test_cli_program_extend_program() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap_err(); @@ -921,6 +936,7 @@ fn test_cli_program_extend_program() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); } @@ -986,6 +1002,7 @@ fn test_cli_program_write_buffer() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -1023,6 +1040,7 @@ fn test_cli_program_write_buffer() { max_len: Some(max_len), skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1087,6 +1105,7 @@ fn test_cli_program_write_buffer() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1127,6 +1146,7 @@ fn test_cli_program_write_buffer() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); let response = process_command(&config); let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); @@ -1203,6 +1223,7 @@ fn test_cli_program_write_buffer() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; let response = process_command(&config); @@ -1246,6 +1267,7 @@ fn test_cli_program_write_buffer() { max_len: None, //Some(max_len), skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); config.signers = vec![&keypair, &buffer_keypair]; @@ -1262,6 +1284,7 @@ fn test_cli_program_write_buffer() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; let error = process_command(&config).unwrap_err(); @@ -1322,6 +1345,7 @@ fn test_cli_program_set_buffer_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap(); @@ -1375,6 +1399,7 @@ fn test_cli_program_set_buffer_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap_err(); @@ -1421,6 +1446,7 @@ fn test_cli_program_set_buffer_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1478,6 +1504,7 @@ fn test_cli_program_mismatch_buffer_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap(); @@ -1503,6 +1530,7 @@ fn test_cli_program_mismatch_buffer_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap_err(); @@ -1521,6 +1549,7 @@ fn test_cli_program_mismatch_buffer_authority() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); } @@ -1605,6 +1634,7 @@ fn test_cli_program_deploy_with_offline_signing(use_offline_signer_as_fee_payer: 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, }); config.output_format = OutputFormat::JsonCompact; process_command(&config).unwrap(); @@ -1773,6 +1803,7 @@ fn test_cli_program_show() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); @@ -1835,6 +1866,7 @@ fn test_cli_program_show() { max_len: Some(max_len), skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); config.output_format = OutputFormat::JsonCompact; let min_slot = rpc_client.get_slot().unwrap(); @@ -1964,6 +1996,7 @@ fn test_cli_program_dump() { max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(&config).unwrap(); @@ -2008,6 +2041,7 @@ fn create_buffer_with_offline_authority<'a>( max_len: None, skip_fee_check: false, compute_unit_price: None, + max_sign_attempts: 5, }); process_command(config).unwrap(); let buffer_account = rpc_client.get_account(&buffer_signer.pubkey()).unwrap(); @@ -2103,6 +2137,7 @@ fn cli_program_deploy_with_args(compute_unit_price: Option) { max_len: Some(max_len), skip_fee_check: false, compute_unit_price, + max_sign_attempts: 5, }); 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 6fb86f8bef396a..fbed371235266b 100644 --- a/transaction-dos/src/main.rs +++ b/transaction-dos/src/main.rs @@ -248,6 +248,7 @@ fn run_transactions_dos( is_final: true, max_len: None, compute_unit_price: None, + max_sign_attempts: 5, skip_fee_check: true, // skip_fee_check });