Skip to content

Commit 14ae250

Browse files
committed
Feature - UpgradeableLoaderInstruction::ExtendProgramChecked (#6131)
* Bumps solana-loader-v3-interface version. * Adds the feature. * Adds feature gating logic. * Adjusts transaction-status parser. * Adjusts CLI manual extend and auto extend. * Adds authority_signer_index to ProgramCliCommand::ExtendProgramChecked. * Adjusts solana-bpf-loader-program-tests. (cherry picked from commit 94d70cd)
1 parent 98c6de1 commit 14ae250

File tree

18 files changed

+602
-245
lines changed

18 files changed

+602
-245
lines changed

Cargo.lock

Lines changed: 25 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ solana-last-restart-slot = "2.2.1"
446446
solana-lattice-hash = { path = "lattice-hash", version = "=2.2.16" }
447447
solana-ledger = { path = "ledger", version = "=2.2.16" }
448448
solana-loader-v2-interface = "2.2.1"
449-
solana-loader-v3-interface = "3.0.0"
449+
solana-loader-v3-interface = "4.0.1"
450450
solana-loader-v4-interface = "2.2.1"
451451
solana-loader-v4-program = { path = "programs/loader-v4", version = "=2.2.16" }
452452
solana-local-cluster = { path = "local-cluster", version = "=2.2.16" }

cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ solana-feature-gate-interface = "=2.2.2"
5151
solana-hash = "=2.2.1"
5252
solana-instruction = "=2.2.1"
5353
solana-keypair = "=2.2.1"
54-
solana-loader-v3-interface = "=3.0.0"
54+
solana-loader-v3-interface = { version = "=4.0.1", features = ["bincode"] }
5555
solana-loader-v4-interface = "=2.2.1"
5656
solana-loader-v4-program = { workspace = true }
5757
solana-logger = "=2.3.1"

cli/src/program.rs

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ pub enum ProgramCliCommand {
168168
use_lamports_unit: bool,
169169
bypass_warning: bool,
170170
},
171-
ExtendProgram {
171+
ExtendProgramChecked {
172172
program_pubkey: Pubkey,
173+
authority_signer_index: SignerIndex,
173174
additional_bytes: u32,
174175
},
175176
MigrateProgram {
@@ -1008,17 +1009,22 @@ pub fn parse_program_subcommand(
10081009
let program_pubkey = pubkey_of(matches, "program_id").unwrap();
10091010
let additional_bytes = value_of(matches, "additional_bytes").unwrap();
10101011

1012+
let (authority_signer, authority_pubkey) =
1013+
signer_of(matches, "authority", wallet_manager)?;
1014+
10111015
let signer_info = default_signer.generate_unique_signers(
1012-
vec![Some(
1013-
default_signer.signer_from_path(matches, wallet_manager)?,
1014-
)],
1016+
vec![
1017+
Some(default_signer.signer_from_path(matches, wallet_manager)?),
1018+
authority_signer,
1019+
],
10151020
matches,
10161021
wallet_manager,
10171022
)?;
10181023

10191024
CliCommandInfo {
1020-
command: CliCommand::Program(ProgramCliCommand::ExtendProgram {
1025+
command: CliCommand::Program(ProgramCliCommand::ExtendProgramChecked {
10211026
program_pubkey,
1027+
authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(),
10221028
additional_bytes,
10231029
}),
10241030
signers: signer_info.signers,
@@ -1230,10 +1236,17 @@ pub fn process_program_subcommand(
12301236
*use_lamports_unit,
12311237
*bypass_warning,
12321238
),
1233-
ProgramCliCommand::ExtendProgram {
1239+
ProgramCliCommand::ExtendProgramChecked {
12341240
program_pubkey,
1241+
authority_signer_index,
12351242
additional_bytes,
1236-
} => process_extend_program(&rpc_client, config, *program_pubkey, *additional_bytes),
1243+
} => process_extend_program(
1244+
&rpc_client,
1245+
config,
1246+
*program_pubkey,
1247+
*authority_signer_index,
1248+
*additional_bytes,
1249+
),
12371250
ProgramCliCommand::MigrateProgram {
12381251
program_pubkey,
12391252
authority_signer_index,
@@ -2365,9 +2378,11 @@ fn process_extend_program(
23652378
rpc_client: &RpcClient,
23662379
config: &CliConfig,
23672380
program_pubkey: Pubkey,
2381+
authority_signer_index: SignerIndex,
23682382
additional_bytes: u32,
23692383
) -> ProcessResult {
23702384
let payer_pubkey = config.signers[0].pubkey();
2385+
let authority_signer = config.signers[authority_signer_index];
23712386

23722387
if additional_bytes == 0 {
23732388
return Err("Additional bytes must be greater than zero".into());
@@ -2410,23 +2425,39 @@ fn process_extend_program(
24102425
_ => Err(format!("Program {program_pubkey} is closed")),
24112426
}?;
24122427

2413-
match upgrade_authority_address {
2414-
None => Err(format!("Program {program_pubkey} is not upgradeable")),
2415-
_ => Ok(()),
2416-
}?;
2428+
let upgrade_authority_address = upgrade_authority_address
2429+
.ok_or_else(|| format!("Program {program_pubkey} is not upgradeable"))?;
24172430

2418-
let blockhash = rpc_client.get_latest_blockhash()?;
2431+
if authority_signer.pubkey() != upgrade_authority_address {
2432+
return Err(format!(
2433+
"Upgrade authority {} does not match {}",
2434+
upgrade_authority_address,
2435+
authority_signer.pubkey(),
2436+
)
2437+
.into());
2438+
}
24192439

2420-
let mut tx = Transaction::new_unsigned(Message::new(
2421-
&[loader_v3_instruction::extend_program(
2422-
&program_pubkey,
2423-
Some(&payer_pubkey),
2424-
additional_bytes,
2425-
)],
2426-
Some(&payer_pubkey),
2427-
));
2440+
let blockhash = rpc_client.get_latest_blockhash()?;
2441+
let feature_set = fetch_feature_set(rpc_client)?;
2442+
2443+
let instruction =
2444+
if feature_set.is_active(&agave_feature_set::enable_extend_program_checked::id()) {
2445+
loader_v3_instruction::extend_program_checked(
2446+
&program_pubkey,
2447+
&upgrade_authority_address,
2448+
Some(&payer_pubkey),
2449+
additional_bytes,
2450+
)
2451+
} else {
2452+
loader_v3_instruction::extend_program(
2453+
&program_pubkey,
2454+
Some(&payer_pubkey),
2455+
additional_bytes,
2456+
)
2457+
};
2458+
let mut tx = Transaction::new_unsigned(Message::new(&[instruction], Some(&payer_pubkey)));
24282459

2429-
tx.try_sign(&[config.signers[0]], blockhash)?;
2460+
tx.try_sign(&[config.signers[0], authority_signer], blockhash)?;
24302461
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
24312462
&tx,
24322463
config.commitment,
@@ -2971,6 +3002,17 @@ fn extend_program_data_if_needed(
29713002
return Ok(());
29723003
};
29733004

3005+
let upgrade_authority_address = match program_data_account.state() {
3006+
Ok(UpgradeableLoaderState::ProgramData {
3007+
slot: _,
3008+
upgrade_authority_address,
3009+
}) => Ok(upgrade_authority_address),
3010+
_ => Err(format!("Program {program_id} is closed")),
3011+
}?;
3012+
3013+
let upgrade_authority_address = upgrade_authority_address
3014+
.ok_or_else(|| format!("Program {program_id} is not upgradeable"))?;
3015+
29743016
let required_len = UpgradeableLoaderState::size_of_programdata(program_len);
29753017
let max_permitted_data_length = usize::try_from(MAX_PERMITTED_DATA_LENGTH).unwrap();
29763018
if required_len > max_permitted_data_length {
@@ -2992,11 +3034,20 @@ fn extend_program_data_if_needed(
29923034

29933035
let additional_bytes =
29943036
u32::try_from(additional_bytes).expect("`u32` is big enough to hold an account size");
2995-
initial_instructions.push(loader_v3_instruction::extend_program(
2996-
program_id,
2997-
Some(fee_payer),
2998-
additional_bytes,
2999-
));
3037+
3038+
let feature_set = fetch_feature_set(rpc_client)?;
3039+
let instruction =
3040+
if feature_set.is_active(&agave_feature_set::enable_extend_program_checked::id()) {
3041+
loader_v3_instruction::extend_program_checked(
3042+
program_id,
3043+
&upgrade_authority_address,
3044+
Some(fee_payer),
3045+
additional_bytes,
3046+
)
3047+
} else {
3048+
loader_v3_instruction::extend_program(program_id, Some(fee_payer), additional_bytes)
3049+
};
3050+
initial_instructions.push(instruction);
30003051

30013052
Ok(())
30023053
}
@@ -3091,7 +3142,12 @@ fn send_deploy_messages(
30913142
// account to sign the transaction. One (transfer) only requires the fee-payer signature.
30923143
// This check is to ensure signing does not fail on a KeypairPubkeyMismatch error from an
30933144
// extraneous signature.
3094-
if message.header.num_required_signatures == 2 {
3145+
if message.header.num_required_signatures == 3 {
3146+
initial_transaction.try_sign(
3147+
&[fee_payer_signer, initial_signer, write_signer.unwrap()],
3148+
blockhash,
3149+
)?;
3150+
} else if message.header.num_required_signatures == 2 {
30953151
initial_transaction.try_sign(&[fee_payer_signer, initial_signer], blockhash)?;
30963152
} else {
30973153
initial_transaction.try_sign(&[fee_payer_signer], blockhash)?;
@@ -4401,8 +4457,9 @@ mod tests {
44014457
assert_eq!(
44024458
parse_command(&test_command, &default_signer, &mut None).unwrap(),
44034459
CliCommandInfo {
4404-
command: CliCommand::Program(ProgramCliCommand::ExtendProgram {
4460+
command: CliCommand::Program(ProgramCliCommand::ExtendProgramChecked {
44054461
program_pubkey,
4462+
authority_signer_index: 0,
44064463
additional_bytes
44074464
}),
44084465
signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],

cli/tests/program.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,9 +1444,10 @@ fn test_cli_program_extend_program() {
14441444
file.read_to_end(&mut new_program_data).unwrap();
14451445
let new_max_len = new_program_data.len();
14461446
let additional_bytes = (new_max_len - max_len) as u32;
1447-
config.signers = vec![&keypair];
1448-
config.command = CliCommand::Program(ProgramCliCommand::ExtendProgram {
1447+
config.signers = vec![&keypair, &upgrade_authority];
1448+
config.command = CliCommand::Program(ProgramCliCommand::ExtendProgramChecked {
14491449
program_pubkey: program_keypair.pubkey(),
1450+
authority_signer_index: 1,
14501451
additional_bytes: additional_bytes - 1,
14511452
});
14521453
process_command(&config).unwrap();
@@ -1492,9 +1493,10 @@ fn test_cli_program_extend_program() {
14921493
wait_n_slots(&rpc_client, 1);
14931494

14941495
// Extend 1 last byte
1495-
config.signers = vec![&keypair];
1496-
config.command = CliCommand::Program(ProgramCliCommand::ExtendProgram {
1496+
config.signers = vec![&keypair, &upgrade_authority];
1497+
config.command = CliCommand::Program(ProgramCliCommand::ExtendProgramChecked {
14971498
program_pubkey: program_keypair.pubkey(),
1499+
authority_signer_index: 1,
14981500
additional_bytes: 1,
14991501
});
15001502
process_command(&config).unwrap();

feature-set/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,10 @@ pub mod mask_out_rent_epoch_in_vm_serialization {
10171017
solana_pubkey::declare_id!("RENtePQcDLrAbxAsP3k8dwVcnNYQ466hi2uKvALjnXx");
10181018
}
10191019

1020+
pub mod enable_extend_program_checked {
1021+
solana_pubkey::declare_id!("97QCmR4QtfeQsAti9srfHFk5uMRFP95CvXG8EGr615HM");
1022+
}
1023+
10201024
pub static FEATURE_NAMES: LazyLock<AHashMap<Pubkey, &'static str>> = LazyLock::new(|| {
10211025
[
10221026
(secp256k1_program_enabled::id(), "secp256k1 program"),
@@ -1247,6 +1251,7 @@ pub static FEATURE_NAMES: LazyLock<AHashMap<Pubkey, &'static str>> = LazyLock::n
12471251
(disable_partitioned_rent_collection::id(), "SIMD-0175: Disable partitioned rent collection"),
12481252
(raise_block_limits_to_60m::id(), "Raise block limit to 60M SIMD-0256"),
12491253
(mask_out_rent_epoch_in_vm_serialization::id(), "SIMD-0267: Sets rent_epoch to a constant in the VM"),
1254+
(enable_extend_program_checked::id(), "Enable ExtendProgramChecked instruction"),
12501255
/*************** ADD NEW FEATURES HERE ***************/
12511256
]
12521257
.iter()

genesis/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ solana-clap-utils = { workspace = true }
2323
solana-cli-config = { workspace = true }
2424
solana-entry = { workspace = true }
2525
solana-ledger = { workspace = true }
26+
solana-loader-v3-interface = "=4.0.1"
2627
solana-logger = "=2.3.1"
2728
solana-rpc-client = { workspace = true }
2829
solana-rpc-client-api = { workspace = true }

programs/bpf-loader-tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ edition = { workspace = true }
1515
assert_matches = { workspace = true }
1616
bincode = { workspace = true }
1717
solana-bpf-loader-program = { workspace = true }
18+
solana-loader-v3-interface = { workspace = true, features = ["bincode"] }
1819
solana-program-test = { workspace = true }
1920
solana-sdk = { workspace = true }
2021

programs/bpf-loader-tests/tests/common.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#![allow(dead_code)]
22

33
use {
4+
solana_loader_v3_interface::state::UpgradeableLoaderState,
45
solana_program_test::*,
56
solana_sdk::{
67
account::AccountSharedData,
78
account_utils::StateMut,
8-
bpf_loader_upgradeable::{id, UpgradeableLoaderState},
9+
bpf_loader_upgradeable::id,
910
instruction::{Instruction, InstructionError},
1011
pubkey::Pubkey,
1112
signature::{Keypair, Signer},

0 commit comments

Comments
 (0)