Skip to content

Commit 7491134

Browse files
authored
Feature - Adds UpgradeableLoaderInstruction::Migrate (anza-xyz#4661) (anza-xyz#4992)
* Adds UpgradeableLoaderInstruction::Migrate. * Adds test_loader_v3_to_v4_migration.
1 parent cfaf849 commit 7491134

File tree

6 files changed

+500
-9
lines changed

6 files changed

+500
-9
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

programs/bpf_loader/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ solana-hash = { workspace = true }
2929
solana-instruction = { workspace = true }
3030
solana-keccak-hasher = { workspace = true }
3131
solana-loader-v3-interface = { workspace = true, features = ["serde"] }
32-
solana-loader-v4-interface = { workspace = true, optional = true }
32+
solana-loader-v4-interface = { workspace = true, features = ["bincode"] }
3333
solana-log-collector = { workspace = true }
3434
solana-measure = { workspace = true }
3535
solana-packet = { workspace = true }
@@ -92,4 +92,4 @@ shuttle-test = [
9292
"solana-program-runtime/shuttle-test",
9393
"solana-sbpf/shuttle-test"
9494
]
95-
svm-internal = ["dep:solana-loader-v4-interface"]
95+
svm-internal = []

programs/bpf_loader/src/lib.rs

+179-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use {
1313
solana_compute_budget::compute_budget::MAX_INSTRUCTION_STACK_DEPTH,
1414
solana_feature_set::{
1515
bpf_account_data_direct_mapping, disable_new_loader_v3_deployments,
16-
enable_bpf_loader_set_authority_checked_ix, remove_accounts_executable_flag_checks,
16+
enable_bpf_loader_set_authority_checked_ix, enable_loader_v4,
17+
remove_accounts_executable_flag_checks,
1718
},
1819
solana_instruction::{error::InstructionError, AccountMeta},
1920
solana_loader_v3_interface::{
@@ -43,7 +44,10 @@ use {
4344
verifier::RequisiteVerifier,
4445
vm::{ContextObject, EbpfVm},
4546
},
46-
solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, native_loader},
47+
solana_sdk_ids::{
48+
bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4, native_loader,
49+
system_program,
50+
},
4751
solana_system_interface::{instruction as system_instruction, MAX_PERMITTED_DATA_LENGTH},
4852
solana_transaction_context::{IndexOfAccount, InstructionContext, TransactionContext},
4953
solana_type_overrides::sync::{atomic::Ordering, Arc},
@@ -393,6 +397,10 @@ declare_builtin_function!(
393397
}
394398
);
395399

400+
mod migration_authority {
401+
solana_pubkey::declare_id!("3Scf35jMNk2xXBD6areNjgMtXgp5ZspDhms8vdcbzC42");
402+
}
403+
396404
#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
397405
pub(crate) fn process_instruction_inner(
398406
invoke_context: &mut InvokeContext,
@@ -1337,7 +1345,174 @@ fn process_loader_upgradeable_instruction(
13371345
);
13381346
}
13391347
UpgradeableLoaderInstruction::Migrate => {
1340-
return Err(InstructionError::InvalidInstructionData);
1348+
if !invoke_context
1349+
.get_feature_set()
1350+
.is_active(&enable_loader_v4::id())
1351+
{
1352+
return Err(InstructionError::InvalidInstructionData);
1353+
}
1354+
1355+
instruction_context.check_number_of_instruction_accounts(3)?;
1356+
let programdata_address = *transaction_context.get_key_of_account_at_index(
1357+
instruction_context.get_index_of_instruction_account_in_transaction(0)?,
1358+
)?;
1359+
let program_address = *transaction_context.get_key_of_account_at_index(
1360+
instruction_context.get_index_of_instruction_account_in_transaction(1)?,
1361+
)?;
1362+
let provided_authority_address = *transaction_context.get_key_of_account_at_index(
1363+
instruction_context.get_index_of_instruction_account_in_transaction(2)?,
1364+
)?;
1365+
let clock_slot = invoke_context
1366+
.get_sysvar_cache()
1367+
.get_clock()
1368+
.map(|clock| clock.slot)?;
1369+
1370+
// Verify ProgramData account
1371+
let programdata =
1372+
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
1373+
if !programdata.is_writable() {
1374+
ic_logger_msg!(log_collector, "ProgramData account not writeable");
1375+
return Err(InstructionError::InvalidArgument);
1376+
}
1377+
let (program_len, upgrade_authority_address) =
1378+
if let Ok(UpgradeableLoaderState::ProgramData {
1379+
slot,
1380+
upgrade_authority_address,
1381+
}) = programdata.get_state()
1382+
{
1383+
if clock_slot == slot {
1384+
ic_logger_msg!(log_collector, "Program was deployed in this block already");
1385+
return Err(InstructionError::InvalidArgument);
1386+
}
1387+
(
1388+
programdata
1389+
.get_data()
1390+
.len()
1391+
.saturating_sub(UpgradeableLoaderState::size_of_programdata_metadata()),
1392+
upgrade_authority_address,
1393+
)
1394+
} else {
1395+
(0, None)
1396+
};
1397+
let programdata_funds = programdata.get_lamports();
1398+
drop(programdata);
1399+
1400+
// Verify authority signature
1401+
if !migration_authority::check_id(&provided_authority_address)
1402+
&& provided_authority_address
1403+
!= upgrade_authority_address.unwrap_or(program_address)
1404+
{
1405+
ic_logger_msg!(log_collector, "Incorrect migration authority provided");
1406+
return Err(InstructionError::IncorrectAuthority);
1407+
}
1408+
if !instruction_context.is_instruction_account_signer(2)? {
1409+
ic_logger_msg!(log_collector, "Migration authority did not sign");
1410+
return Err(InstructionError::MissingRequiredSignature);
1411+
}
1412+
1413+
// Verify Program account
1414+
let mut program =
1415+
instruction_context.try_borrow_instruction_account(transaction_context, 1)?;
1416+
if !program.is_writable() {
1417+
ic_logger_msg!(log_collector, "Program account not writeable");
1418+
return Err(InstructionError::InvalidArgument);
1419+
}
1420+
if program.get_owner() != program_id {
1421+
ic_logger_msg!(log_collector, "Program account not owned by loader");
1422+
return Err(InstructionError::IncorrectProgramId);
1423+
}
1424+
if let UpgradeableLoaderState::Program {
1425+
programdata_address: stored_programdata_address,
1426+
} = program.get_state()?
1427+
{
1428+
if programdata_address != stored_programdata_address {
1429+
ic_logger_msg!(log_collector, "Program and ProgramData account mismatch");
1430+
return Err(InstructionError::InvalidArgument);
1431+
}
1432+
} else {
1433+
ic_logger_msg!(log_collector, "Invalid Program account");
1434+
return Err(InstructionError::InvalidAccountData);
1435+
}
1436+
program.set_data_from_slice(&[])?;
1437+
program.checked_add_lamports(programdata_funds)?;
1438+
if program_len == 0 {
1439+
program.set_owner(&system_program::id().to_bytes())?;
1440+
} else {
1441+
program.set_owner(&loader_v4::id().to_bytes())?;
1442+
}
1443+
drop(program);
1444+
1445+
let mut programdata =
1446+
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
1447+
programdata.set_lamports(0)?;
1448+
drop(programdata);
1449+
1450+
if program_len > 0 {
1451+
invoke_context.native_invoke(
1452+
solana_loader_v4_interface::instruction::set_program_length(
1453+
&program_address,
1454+
&provided_authority_address,
1455+
program_len as u32,
1456+
&program_address,
1457+
)
1458+
.into(),
1459+
&[],
1460+
)?;
1461+
1462+
invoke_context.native_invoke(
1463+
solana_loader_v4_interface::instruction::copy(
1464+
&program_address,
1465+
&provided_authority_address,
1466+
&programdata_address,
1467+
0,
1468+
0,
1469+
program_len as u32,
1470+
)
1471+
.into(),
1472+
&[],
1473+
)?;
1474+
1475+
invoke_context.native_invoke(
1476+
solana_loader_v4_interface::instruction::deploy(
1477+
&program_address,
1478+
&provided_authority_address,
1479+
)
1480+
.into(),
1481+
&[],
1482+
)?;
1483+
1484+
if upgrade_authority_address.is_none() {
1485+
invoke_context.native_invoke(
1486+
solana_loader_v4_interface::instruction::finalize(
1487+
&program_address,
1488+
&provided_authority_address,
1489+
&program_address,
1490+
)
1491+
.into(),
1492+
&[],
1493+
)?;
1494+
} else if migration_authority::check_id(&provided_authority_address) {
1495+
invoke_context.native_invoke(
1496+
solana_loader_v4_interface::instruction::transfer_authority(
1497+
&program_address,
1498+
&provided_authority_address,
1499+
&upgrade_authority_address.unwrap(),
1500+
)
1501+
.into(),
1502+
&[],
1503+
)?;
1504+
}
1505+
}
1506+
1507+
let transaction_context = &invoke_context.transaction_context;
1508+
let instruction_context = transaction_context.get_current_instruction_context()?;
1509+
let mut programdata =
1510+
instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
1511+
programdata.set_data_from_slice(&[])?;
1512+
programdata.set_owner(&system_program::id().to_bytes())?;
1513+
drop(programdata);
1514+
1515+
ic_logger_msg!(log_collector, "Migrated program {:?}", &program_address);
13411516
}
13421517
}
13431518

@@ -1672,7 +1847,7 @@ mod tests {
16721847
},
16731848
solana_pubkey::Pubkey,
16741849
solana_rent::Rent,
1675-
solana_sdk_ids::{system_program, sysvar},
1850+
solana_sdk_ids::sysvar,
16761851
std::{fs::File, io::Read, ops::Range, sync::atomic::AtomicU64},
16771852
};
16781853

runtime/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ rand0-7 = { package = "rand", version = "0.7" }
110110
rand_chacha = { workspace = true }
111111
solana-accounts-db = { workspace = true, features = ["dev-context-only-utils"] }
112112
solana-builtins = { workspace = true, features = ["dev-context-only-utils"] }
113+
solana-loader-v3-interface = { workspace = true }
113114
solana-logger = { workspace = true }
114115
# See order-crates-for-publishing.py for using this unusual `path = "."`
115116
solana-runtime = { path = ".", features = ["dev-context-only-utils"] }

0 commit comments

Comments
 (0)