diff --git a/test-validator/src/lib.rs b/test-validator/src/lib.rs index 1e3b34f25..9f8c055c8 100644 --- a/test-validator/src/lib.rs +++ b/test-validator/src/lib.rs @@ -29,6 +29,7 @@ use { }, solana_sdk::{ account::{Account, AccountSharedData}, + bpf_loader_upgradeable::UpgradeableLoaderState, clock::{Slot, DEFAULT_MS_PER_SLOT}, commitment_config::CommitmentConfig, epoch_schedule::EpochSchedule, @@ -312,6 +313,38 @@ impl TestValidatorGenesis { Ok(self) } + pub fn clone_upgradeable_programs( + &mut self, + addresses: T, + rpc_client: &RpcClient, + ) -> Result<&mut Self, String> + where + T: IntoIterator, + { + let addresses: Vec = addresses.into_iter().collect(); + self.clone_accounts(addresses.clone(), rpc_client, false)?; + + let mut programdata_addresses: HashSet = HashSet::new(); + for address in addresses { + let account = self.accounts.get(&address).unwrap(); + + if let Ok(UpgradeableLoaderState::Program { + programdata_address, + }) = account.deserialize_data() + { + programdata_addresses.insert(programdata_address); + } else { + return Err(format!( + "Failed to read upgradeable program account {address}", + )); + } + } + + self.clone_accounts(programdata_addresses, rpc_client, false)?; + + Ok(self) + } + pub fn add_accounts_from_json_files( &mut self, accounts: &[AccountInfo], diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index e26e2f563..8baa19814 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -248,6 +248,11 @@ fn main() { .map(|v| v.into_iter().collect()) .unwrap_or_default(); + let upgradeable_programs_to_clone: HashSet<_> = + pubkeys_of(&matches, "clone_upgradeable_program") + .map(|v| v.into_iter().collect()) + .unwrap_or_default(); + let warp_slot = if matches.is_present("warp_slot") { Some(match matches.value_of("warp_slot") { Some(_) => value_t_or_exit!(matches, "warp_slot", Slot), @@ -451,6 +456,18 @@ fn main() { } } + if !upgradeable_programs_to_clone.is_empty() { + if let Err(e) = genesis.clone_upgradeable_programs( + upgradeable_programs_to_clone, + cluster_rpc_client + .as_ref() + .expect("bug: --url argument missing?"), + ) { + println!("Error: clone_upgradeable_programs failed: {e}"); + exit(1); + } + } + if let Some(warp_slot) = warp_slot { genesis.warp_slot(warp_slot); } diff --git a/validator/src/cli.rs b/validator/src/cli.rs index da35af1ff..fbea526b6 100644 --- a/validator/src/cli.rs +++ b/validator/src/cli.rs @@ -2162,6 +2162,20 @@ pub fn test_app<'a>(version: &'a str, default_args: &'a DefaultTestArgs) -> App< If the ledger already exists then this parameter is silently ignored", ), ) + .arg( + Arg::with_name("clone_upgradeable_program") + .long("clone-upgradeable-program") + .value_name("ADDRESS") + .takes_value(true) + .validator(is_pubkey_or_keypair) + .multiple(true) + .requires("json_rpc_url") + .help( + "Copy an upgradeable program and its executable data from the cluster \ + referenced by the --url argument the genesis configuration. \ + If the ledger already exists then this parameter is silently ignored", + ), + ) .arg( Arg::with_name("warp_slot") .required(false)