From 5cb30cf9cc9e0a423f0098c8c5a2ca7fb9be8f87 Mon Sep 17 00:00:00 2001 From: Jon C Date: Wed, 10 Jan 2024 21:12:29 +0100 Subject: [PATCH] cli: Use program length for deployments instead of 2x length (#34730) * cli: Deploy the appropriate length program * Extend the extend-program test for new default * Add CHANGELOG entry * Update docs, and include `extend` * Update CHANGELOG.md Co-authored-by: Tyera --------- Co-authored-by: Tyera --- CHANGELOG.md | 3 + cli/src/program.rs | 8 +-- cli/tests/program.rs | 74 +++++++++++++++++++++-- docs/src/cli/examples/deploy-a-program.md | 20 ++++-- 4 files changed, 88 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ddba7c2ec..028c3bbdaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,9 @@ Release channels have their own copy of this changelog: * Bigtable upload now includes entry summary data for each slot, stored in a new `entries` table * Forbid multiple values for the `--signer` CLI flag, forcing users to specify multiple occurrences of `--signer`, one for each signature + * 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. * 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 72f4ae8451..f4f31b72d0 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -219,7 +219,7 @@ impl ProgramSubCommands for App<'_, '_> { .required(false) .help( "Maximum length of the upgradeable program \ - [default: twice the length of the original deployed program]", + [default: the length of the original deployed program]", ), ) .arg( @@ -300,7 +300,7 @@ impl ProgramSubCommands for App<'_, '_> { .required(false) .help( "Maximum length of the upgradeable program \ - [default: twice the length of the original deployed program]", + [default: the length of the original deployed program]", ), ), ) @@ -1171,10 +1171,8 @@ fn process_program_deploy( ); } len - } else if is_final { - program_len } else { - program_len * 2 + program_len }; let min_rent_exempt_program_data_balance = rpc_client.get_minimum_balance_for_rent_exemption( diff --git a/cli/tests/program.rs b/cli/tests/program.rs index ac937ef0d2..894303b3ab 100644 --- a/cli/tests/program.rs +++ b/cli/tests/program.rs @@ -780,6 +780,12 @@ fn test_cli_program_extend_program() { noop_path.push("noop"); noop_path.set_extension("so"); + let mut noop_large_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + noop_large_path.push("tests"); + noop_large_path.push("fixtures"); + noop_large_path.push("noop_large"); + noop_large_path.set_extension("so"); + let mint_keypair = Keypair::new(); let mint_pubkey = mint_keypair.pubkey(); let faucet_addr = run_local_faucet(mint_keypair, None); @@ -826,7 +832,7 @@ fn test_cli_program_extend_program() { allow_excessive_balance: false, upgrade_authority_signer_index: 1, is_final: false, - max_len: Some(max_len), + max_len: None, // Use None to check that it defaults to the max length skip_fee_check: false, }); config.output_format = OutputFormat::JsonCompact; @@ -837,22 +843,78 @@ fn test_cli_program_extend_program() { &bpf_loader_upgradeable::id(), ); + let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap(); + let expected_len = UpgradeableLoaderState::size_of_programdata(max_len); + assert_eq!(expected_len, programdata_account.data.len()); + // Wait one slot to avoid "Program was deployed in this block already" error wait_n_slots(&rpc_client, 1); - // Extend program - let additional_bytes = 100; + // Extend program for larger program, minus 1 required byte + let mut file = File::open(noop_large_path.to_str().unwrap()).unwrap(); + let mut new_program_data = Vec::new(); + file.read_to_end(&mut new_program_data).unwrap(); + let new_max_len = new_program_data.len(); + let additional_bytes = (new_max_len - max_len) as u32; config.signers = vec![&keypair]; config.command = CliCommand::Program(ProgramCliCommand::ExtendProgram { program_pubkey: program_keypair.pubkey(), - additional_bytes, + additional_bytes: additional_bytes - 1, }); process_command(&config).unwrap(); let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap(); - let expected_len = - UpgradeableLoaderState::size_of_programdata(max_len + additional_bytes as usize); + let expected_len = UpgradeableLoaderState::size_of_programdata(new_max_len - 1); assert_eq!(expected_len, programdata_account.data.len()); + + // Larger program deploy fails because missing 1 byte + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(noop_large_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: None, + program_pubkey: Some(program_keypair.pubkey()), + buffer_signer_index: None, + buffer_pubkey: None, + allow_excessive_balance: false, + upgrade_authority_signer_index: 1, + is_final: false, + max_len: None, + skip_fee_check: false, + }); + process_command(&config).unwrap_err(); + + // Wait one slot to avoid "Program was deployed in this block already" error + wait_n_slots(&rpc_client, 1); + + // Extend 1 last byte + config.signers = vec![&keypair]; + config.command = CliCommand::Program(ProgramCliCommand::ExtendProgram { + program_pubkey: program_keypair.pubkey(), + additional_bytes: 1, + }); + process_command(&config).unwrap(); + + let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap(); + let expected_len = UpgradeableLoaderState::size_of_programdata(new_max_len); + assert_eq!(expected_len, programdata_account.data.len()); + + // Larger program deploy finally succeeds + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::Program(ProgramCliCommand::Deploy { + program_location: Some(noop_large_path.to_str().unwrap().to_string()), + fee_payer_signer_index: 0, + program_signer_index: None, + program_pubkey: Some(program_keypair.pubkey()), + buffer_signer_index: None, + buffer_pubkey: None, + allow_excessive_balance: false, + upgrade_authority_signer_index: 1, + is_final: false, + max_len: None, + skip_fee_check: false, + }); + process_command(&config).unwrap(); } #[test] diff --git a/docs/src/cli/examples/deploy-a-program.md b/docs/src/cli/examples/deploy-a-program.md index b7a62837f7..aca1442414 100644 --- a/docs/src/cli/examples/deploy-a-program.md +++ b/docs/src/cli/examples/deploy-a-program.md @@ -102,17 +102,25 @@ The command looks the same as the deployment command: solana program deploy ``` -By default, programs are deployed to accounts that are twice the size of the -original deployment. Doing so leaves room for program growth in future -redeployments. But, if the initially deployed program is very small and then -later grows substantially, the redeployment may fail. To avoid this, specify a -`max_len` that is at least the size (in bytes) that the program is expected to -become (plus some wiggle room). +By default, programs are deployed to accounts that match the size of the +original program file. But, if the redeployed program is larger, the +redeployment will fail. To avoid this, specify a `max_len` that is at least the +size (in bytes) that the program is expected to become (plus some wiggle room). ```bash solana program deploy --max-len 200000 ``` +### Extend a program + +If a program has already been deployed, and a redeployment goes beyond the +`max_len` of the account, it's possible to extend the program to fit the larger +redeployment: + +```bash +solana program extend +``` + ### Resuming a failed deploy If program deployment fails, there will be a hanging intermediate buffer account