From 5171a3bed75dddc0197e895a204304b49feab6f7 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 20 Feb 2023 13:59:50 -0700 Subject: [PATCH 1/2] Add TestValidator handling for upgradeable programs --- Cargo.lock | 1 + programs/sbf/Cargo.lock | 1 + test-validator/Cargo.toml | 1 + test-validator/src/lib.rs | 59 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 064b800fcc39fb..8d86337fb52a7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6765,6 +6765,7 @@ name = "solana-test-validator" version = "1.16.0" dependencies = [ "base64 0.13.0", + "bincode", "log", "serde_derive", "serde_json", diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index a0e1563ed96507..c2f02614d9b70e 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -6042,6 +6042,7 @@ name = "solana-test-validator" version = "1.16.0" dependencies = [ "base64 0.13.0", + "bincode", "log", "serde_derive", "serde_json", diff --git a/test-validator/Cargo.toml b/test-validator/Cargo.toml index 8acc233edf7632..04c5b6e93cc655 100644 --- a/test-validator/Cargo.toml +++ b/test-validator/Cargo.toml @@ -11,6 +11,7 @@ edition = { workspace = true } [dependencies] base64 = { workspace = true } +bincode = { workspace = true } log = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } diff --git a/test-validator/src/lib.rs b/test-validator/src/lib.rs index 12d2f12f2913d2..f6a8355bd1ad9b 100644 --- a/test-validator/src/lib.rs +++ b/test-validator/src/lib.rs @@ -76,6 +76,14 @@ pub struct ProgramInfo { pub program_path: PathBuf, } +#[derive(Clone)] +pub struct UpgradeableProgramInfo { + pub program_id: Pubkey, + pub loader: Pubkey, + pub upgrade_authority: Pubkey, + pub program_path: PathBuf, +} + #[derive(Debug)] pub struct TestValidatorNodeConfig { gossip_addr: SocketAddr, @@ -111,6 +119,7 @@ pub struct TestValidatorGenesis { no_bpf_jit: bool, accounts: HashMap, programs: Vec, + upgradeable_programs: Vec, ticks_per_slot: Option, epoch_schedule: Option, node_config: TestValidatorNodeConfig, @@ -142,6 +151,7 @@ impl Default for TestValidatorGenesis { no_bpf_jit: bool::default(), accounts: HashMap::::default(), programs: Vec::::default(), + upgradeable_programs: Vec::::default(), ticks_per_slot: Option::::default(), epoch_schedule: Option::::default(), node_config: TestValidatorNodeConfig::default(), @@ -488,6 +498,17 @@ impl TestValidatorGenesis { self } + /// Add a list of upgradeable programs to the test environment. + pub fn add_upgradeable_programs_with_path( + &mut self, + programs: &[UpgradeableProgramInfo], + ) -> &mut Self { + for program in programs { + self.upgradeable_programs.push(program.clone()); + } + self + } + /// Start a test validator with the address of the mint account that will receive tokens /// created at genesis. /// @@ -672,6 +693,44 @@ impl TestValidator { }), ); } + for upgradeable_program in &config.upgradeable_programs { + let data = solana_program_test::read_file(&upgradeable_program.program_path); + let (programdata_address, _) = Pubkey::find_program_address( + &[upgradeable_program.program_id.as_ref()], + &upgradeable_program.loader, + ); + let mut program_data = bincode::serialize(&UpgradeableLoaderState::ProgramData { + slot: 0, + upgrade_authority_address: Some(upgradeable_program.upgrade_authority), + }) + .unwrap(); + program_data.extend_from_slice(&data); + accounts.insert( + programdata_address, + AccountSharedData::from(Account { + lamports: Rent::default().minimum_balance(program_data.len()).max(1), + data: program_data, + owner: upgradeable_program.loader, + executable: true, + rent_epoch: 0, + }), + ); + + let data = bincode::serialize(&UpgradeableLoaderState::Program { + programdata_address, + }) + .unwrap(); + accounts.insert( + upgradeable_program.program_id, + AccountSharedData::from(Account { + lamports: Rent::default().minimum_balance(data.len()).max(1), + data, + owner: upgradeable_program.loader, + executable: true, + rent_epoch: 0, + }), + ); + } let mut genesis_config = create_genesis_config_with_leader_ex( mint_lamports, From 6c5a48db2e693357a6ec4c406c0692defde0ed00 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Mon, 20 Feb 2023 14:25:20 -0700 Subject: [PATCH 2/2] Plumb --upgradeable-program for solana-test-validator --- validator/src/bin/solana-test-validator.rs | 51 ++++++++++++++++++++++ validator/src/cli.rs | 14 ++++++ 2 files changed, 65 insertions(+) diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 5ada0950a066eb..246e84ecc321e5 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -213,6 +213,56 @@ fn main() { } } + let mut upgradeable_programs_to_load = vec![]; + if let Some(values) = matches.values_of("upgradeable_program") { + let values: Vec<&str> = values.collect::>(); + for address_program_upgrade_authority in values.chunks(3) { + match address_program_upgrade_authority { + [address, program, upgrade_authority] => { + let address = address + .parse::() + .or_else(|_| read_keypair_file(address).map(|keypair| keypair.pubkey())) + .unwrap_or_else(|err| { + println!("Error: invalid address {address}: {err}"); + exit(1); + }); + let upgrade_authority_address = if *upgrade_authority == "none" { + Pubkey::default() + } else { + upgrade_authority + .parse::() + .or_else(|_| { + read_keypair_file(upgrade_authority).map(|keypair| keypair.pubkey()) + }) + .unwrap_or_else(|err| { + println!( + "Error: invalid upgrade_authority {upgrade_authority}: {err}" + ); + exit(1); + }) + }; + + let program_path = PathBuf::from(program); + if !program_path.exists() { + println!( + "Error: program file does not exist: {}", + program_path.display() + ); + exit(1); + } + + upgradeable_programs_to_load.push(UpgradeableProgramInfo { + program_id: address, + loader: solana_sdk::bpf_loader_upgradeable::id(), + upgrade_authority: upgrade_authority_address, + program_path, + }); + } + _ => unreachable!(), + } + } + } + let mut accounts_to_load = vec![]; if let Some(values) = matches.values_of("account") { let values: Vec<&str> = values.collect::>(); @@ -409,6 +459,7 @@ fn main() { .bpf_jit(!matches.is_present("no_bpf_jit")) .rpc_port(rpc_port) .add_programs_with_path(&programs_to_load) + .add_upgradeable_programs_with_path(&upgradeable_programs_to_load) .add_accounts_from_json_files(&accounts_to_load) .unwrap_or_else(|e| { println!("Error: add_accounts_from_json_files failed: {e}"); diff --git a/validator/src/cli.rs b/validator/src/cli.rs index 9de9a605e5d294..b1be414b7032d1 100644 --- a/validator/src/cli.rs +++ b/validator/src/cli.rs @@ -2021,6 +2021,20 @@ pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App< First argument can be a pubkey string or path to a keypair", ), ) + .arg( + Arg::with_name("upgradeable_program") + .long("upgradeable-program") + .value_names(&["ADDRESS_OR_KEYPAIR", "SBF_PROGRAM.SO", "UPGRADE_AUTHORITY"]) + .takes_value(true) + .number_of_values(3) + .multiple(true) + .help( + "Add an upgradeable SBF program to the genesis configuration. \ + If the ledger already exists then this parameter is silently ignored. \ + First and third arguments can be a pubkey string or path to a keypair. \ + Upgrade authority set to \"none\" disables upgrades", + ), + ) .arg( Arg::with_name("account") .long("account")