|
31 | 31 | return_signers_with_config, CliProgram, CliProgramAccountType, CliProgramAuthority,
|
32 | 32 | CliProgramBuffer, CliProgramId, CliUpgradeableBuffer, CliUpgradeableBuffers,
|
33 | 33 | CliUpgradeableProgram, CliUpgradeableProgramClosed, CliUpgradeableProgramExtended,
|
34 |
| - CliUpgradeablePrograms, ReturnSignersConfig, |
| 34 | + CliUpgradeableProgramMigrated, CliUpgradeablePrograms, ReturnSignersConfig, |
35 | 35 | },
|
36 | 36 | solana_client::{
|
37 | 37 | connection_cache::ConnectionCache,
|
@@ -171,6 +171,11 @@ pub enum ProgramCliCommand {
|
171 | 171 | program_pubkey: Pubkey,
|
172 | 172 | additional_bytes: u32,
|
173 | 173 | },
|
| 174 | + MigrateProgram { |
| 175 | + program_pubkey: Pubkey, |
| 176 | + authority_signer_index: SignerIndex, |
| 177 | + compute_unit_price: Option<u64>, |
| 178 | + }, |
174 | 179 | }
|
175 | 180 |
|
176 | 181 | pub trait ProgramSubCommands {
|
@@ -632,6 +637,33 @@ impl ProgramSubCommands for App<'_, '_> {
|
632 | 637 | data account",
|
633 | 638 | ),
|
634 | 639 | ),
|
| 640 | + ) |
| 641 | + .subcommand( |
| 642 | + SubCommand::with_name("migrate") |
| 643 | + .about( |
| 644 | + "Migrates an upgradeable program to loader-v4", |
| 645 | + ) |
| 646 | + .arg( |
| 647 | + Arg::with_name("program_id") |
| 648 | + .index(1) |
| 649 | + .value_name("PROGRAM_ID") |
| 650 | + .takes_value(true) |
| 651 | + .required(true) |
| 652 | + .validator(is_valid_pubkey) |
| 653 | + .help("Address of the program to extend"), |
| 654 | + ) |
| 655 | + .arg( |
| 656 | + Arg::with_name("authority") |
| 657 | + .long("authority") |
| 658 | + .value_name("AUTHORITY_SIGNER") |
| 659 | + .takes_value(true) |
| 660 | + .validator(is_valid_signer) |
| 661 | + .help( |
| 662 | + "Upgrade authority [default: the default configured \ |
| 663 | + keypair]", |
| 664 | + ), |
| 665 | + ) |
| 666 | + .arg(compute_unit_price_arg()), |
635 | 667 | ),
|
636 | 668 | )
|
637 | 669 | .subcommand(
|
@@ -991,6 +1023,32 @@ pub fn parse_program_subcommand(
|
991 | 1023 | signers: signer_info.signers,
|
992 | 1024 | }
|
993 | 1025 | }
|
| 1026 | + ("migrate", Some(matches)) => { |
| 1027 | + let program_pubkey = pubkey_of(matches, "program_id").unwrap(); |
| 1028 | + |
| 1029 | + let (authority_signer, authority_pubkey) = |
| 1030 | + signer_of(matches, "authority", wallet_manager)?; |
| 1031 | + |
| 1032 | + let signer_info = default_signer.generate_unique_signers( |
| 1033 | + vec![ |
| 1034 | + Some(default_signer.signer_from_path(matches, wallet_manager)?), |
| 1035 | + authority_signer, |
| 1036 | + ], |
| 1037 | + matches, |
| 1038 | + wallet_manager, |
| 1039 | + )?; |
| 1040 | + |
| 1041 | + let compute_unit_price = value_of(matches, "compute_unit_price"); |
| 1042 | + |
| 1043 | + CliCommandInfo { |
| 1044 | + command: CliCommand::Program(ProgramCliCommand::MigrateProgram { |
| 1045 | + program_pubkey, |
| 1046 | + authority_signer_index: signer_info.index_of(authority_pubkey).unwrap(), |
| 1047 | + compute_unit_price, |
| 1048 | + }), |
| 1049 | + signers: signer_info.signers, |
| 1050 | + } |
| 1051 | + } |
994 | 1052 | _ => unreachable!(),
|
995 | 1053 | };
|
996 | 1054 | Ok(response)
|
@@ -1175,6 +1233,17 @@ pub fn process_program_subcommand(
|
1175 | 1233 | program_pubkey,
|
1176 | 1234 | additional_bytes,
|
1177 | 1235 | } => process_extend_program(&rpc_client, config, *program_pubkey, *additional_bytes),
|
| 1236 | + ProgramCliCommand::MigrateProgram { |
| 1237 | + program_pubkey, |
| 1238 | + authority_signer_index, |
| 1239 | + compute_unit_price, |
| 1240 | + } => process_migrate_program( |
| 1241 | + &rpc_client, |
| 1242 | + config, |
| 1243 | + *program_pubkey, |
| 1244 | + *authority_signer_index, |
| 1245 | + *compute_unit_price, |
| 1246 | + ), |
1178 | 1247 | }
|
1179 | 1248 | }
|
1180 | 1249 |
|
@@ -2387,6 +2456,100 @@ fn process_extend_program(
|
2387 | 2456 | }))
|
2388 | 2457 | }
|
2389 | 2458 |
|
| 2459 | +fn process_migrate_program( |
| 2460 | + rpc_client: &RpcClient, |
| 2461 | + config: &CliConfig, |
| 2462 | + program_pubkey: Pubkey, |
| 2463 | + authority_signer_index: SignerIndex, |
| 2464 | + compute_unit_price: Option<u64>, |
| 2465 | +) -> ProcessResult { |
| 2466 | + let payer_pubkey = config.signers[0].pubkey(); |
| 2467 | + let authority_signer = config.signers[authority_signer_index]; |
| 2468 | + |
| 2469 | + let program_account = match rpc_client |
| 2470 | + .get_account_with_commitment(&program_pubkey, config.commitment)? |
| 2471 | + .value |
| 2472 | + { |
| 2473 | + Some(program_account) => Ok(program_account), |
| 2474 | + None => Err(format!("Unable to find program {program_pubkey}")), |
| 2475 | + }?; |
| 2476 | + |
| 2477 | + if !bpf_loader_upgradeable::check_id(&program_account.owner) { |
| 2478 | + return Err(format!("Account {program_pubkey} is not an upgradeable program").into()); |
| 2479 | + } |
| 2480 | + |
| 2481 | + let Ok(UpgradeableLoaderState::Program { |
| 2482 | + programdata_address: programdata_pubkey, |
| 2483 | + }) = program_account.state() |
| 2484 | + else { |
| 2485 | + return Err(format!("Account {program_pubkey} is not an upgradeable program").into()); |
| 2486 | + }; |
| 2487 | + |
| 2488 | + let Some(programdata_account) = rpc_client |
| 2489 | + .get_account_with_commitment(&programdata_pubkey, config.commitment)? |
| 2490 | + .value |
| 2491 | + else { |
| 2492 | + return Err(format!("Program {program_pubkey} is closed").into()); |
| 2493 | + }; |
| 2494 | + |
| 2495 | + let upgrade_authority_address = match programdata_account.state() { |
| 2496 | + Ok(UpgradeableLoaderState::ProgramData { |
| 2497 | + slot: _slot, |
| 2498 | + upgrade_authority_address, |
| 2499 | + }) => upgrade_authority_address, |
| 2500 | + _ => None, |
| 2501 | + }; |
| 2502 | + |
| 2503 | + if authority_signer.pubkey() != upgrade_authority_address.unwrap_or(program_pubkey) { |
| 2504 | + return Err(format!( |
| 2505 | + "Upgrade authority {:?} does not match {:?}", |
| 2506 | + upgrade_authority_address, |
| 2507 | + Some(authority_signer.pubkey()) |
| 2508 | + ) |
| 2509 | + .into()); |
| 2510 | + } |
| 2511 | + |
| 2512 | + let blockhash = rpc_client.get_latest_blockhash()?; |
| 2513 | + let mut message = Message::new( |
| 2514 | + &vec![bpf_loader_upgradeable::migrate_program( |
| 2515 | + &programdata_pubkey, |
| 2516 | + &program_pubkey, |
| 2517 | + &authority_signer.pubkey(), |
| 2518 | + )] |
| 2519 | + .with_compute_unit_config(&ComputeUnitConfig { |
| 2520 | + compute_unit_price, |
| 2521 | + compute_unit_limit: ComputeUnitLimit::Simulated, |
| 2522 | + }), |
| 2523 | + Some(&payer_pubkey), |
| 2524 | + ); |
| 2525 | + simulate_and_update_compute_unit_limit(&ComputeUnitLimit::Simulated, rpc_client, &mut message)?; |
| 2526 | + |
| 2527 | + let mut tx = Transaction::new_unsigned(message); |
| 2528 | + tx.try_sign(&[config.signers[0], config.signers[1]], blockhash)?; |
| 2529 | + let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config( |
| 2530 | + &tx, |
| 2531 | + config.commitment, |
| 2532 | + config.send_transaction_config, |
| 2533 | + ); |
| 2534 | + if let Err(err) = result { |
| 2535 | + if let ClientErrorKind::TransactionError(TransactionError::InstructionError( |
| 2536 | + _, |
| 2537 | + InstructionError::InvalidInstructionData, |
| 2538 | + )) = err.kind() |
| 2539 | + { |
| 2540 | + return Err("Migrating a program is not supported by the cluster".into()); |
| 2541 | + } else { |
| 2542 | + return Err(format!("Migrate program failed: {err}").into()); |
| 2543 | + } |
| 2544 | + } |
| 2545 | + |
| 2546 | + Ok(config |
| 2547 | + .output_format |
| 2548 | + .formatted_string(&CliUpgradeableProgramMigrated { |
| 2549 | + program_id: program_pubkey.to_string(), |
| 2550 | + })) |
| 2551 | +} |
| 2552 | + |
2390 | 2553 | pub fn calculate_max_chunk_size<F>(create_msg: &F) -> usize
|
2391 | 2554 | where
|
2392 | 2555 | F: Fn(u32, Vec<u8>) -> Message,
|
@@ -4255,6 +4418,46 @@ mod tests {
|
4255 | 4418 | );
|
4256 | 4419 | }
|
4257 | 4420 |
|
| 4421 | + #[test] |
| 4422 | + fn test_cli_parse_migrate_program() { |
| 4423 | + let test_commands = get_clap_app("test", "desc", "version"); |
| 4424 | + |
| 4425 | + let default_keypair = Keypair::new(); |
| 4426 | + let keypair_file = make_tmp_path("keypair_file"); |
| 4427 | + write_keypair_file(&default_keypair, &keypair_file).unwrap(); |
| 4428 | + let default_signer = DefaultSigner::new("", &keypair_file); |
| 4429 | + |
| 4430 | + let program_pubkey = Pubkey::new_unique(); |
| 4431 | + let authority_keypair = Keypair::new(); |
| 4432 | + let authority_keypair_file = make_tmp_path("authority_keypair_file"); |
| 4433 | + write_keypair_file(&authority_keypair, &authority_keypair_file).unwrap(); |
| 4434 | + |
| 4435 | + let test_command = test_commands.clone().get_matches_from(vec![ |
| 4436 | + "test", |
| 4437 | + "program", |
| 4438 | + "migrate", |
| 4439 | + &program_pubkey.to_string(), |
| 4440 | + "--authority", |
| 4441 | + &authority_keypair_file.to_string(), |
| 4442 | + "--with-compute-unit-price", |
| 4443 | + "1", |
| 4444 | + ]); |
| 4445 | + assert_eq!( |
| 4446 | + parse_command(&test_command, &default_signer, &mut None).unwrap(), |
| 4447 | + CliCommandInfo { |
| 4448 | + command: CliCommand::Program(ProgramCliCommand::MigrateProgram { |
| 4449 | + program_pubkey, |
| 4450 | + authority_signer_index: 1, |
| 4451 | + compute_unit_price: Some(1), |
| 4452 | + }), |
| 4453 | + signers: vec![ |
| 4454 | + Box::new(read_keypair_file(&keypair_file).unwrap()), |
| 4455 | + Box::new(read_keypair_file(&authority_keypair_file).unwrap()), |
| 4456 | + ], |
| 4457 | + } |
| 4458 | + ); |
| 4459 | + } |
| 4460 | + |
4258 | 4461 | #[test]
|
4259 | 4462 | fn test_cli_keypair_file() {
|
4260 | 4463 | solana_logger::setup();
|
|
0 commit comments