Upgradeable loader (#13689)
This commit is contained in:
@ -16,6 +16,7 @@ use rayon::slice::ParallelSliceMut;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
account_utils::StateMut,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
clock::{Epoch, Slot},
|
||||
feature_set::{self, FeatureSet},
|
||||
fee_calculator::{FeeCalculator, FeeConfig},
|
||||
@ -236,7 +237,7 @@ impl Accounts {
|
||||
let mut program_id = *program_id;
|
||||
loop {
|
||||
if native_loader::check_id(&program_id) {
|
||||
// at the root of the chain, ready to dispatch
|
||||
// At the root of the chain, ready to dispatch
|
||||
break;
|
||||
}
|
||||
|
||||
@ -262,8 +263,31 @@ impl Accounts {
|
||||
return Err(TransactionError::InvalidProgramForExecution);
|
||||
}
|
||||
|
||||
// add loader to chain
|
||||
// Add loader to chain
|
||||
let program_owner = program.owner;
|
||||
|
||||
if bpf_loader_upgradeable::check_id(&program_owner) {
|
||||
// The upgradeable loader requires the derived ProgramData account
|
||||
if let Ok(UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
}) = program.state()
|
||||
{
|
||||
if let Some(program) = self
|
||||
.accounts_db
|
||||
.load(ancestors, &programdata_address)
|
||||
.map(|(account, _)| account)
|
||||
{
|
||||
accounts.insert(0, (programdata_address, program));
|
||||
} else {
|
||||
error_counters.account_not_found += 1;
|
||||
return Err(TransactionError::ProgramAccountNotFound);
|
||||
}
|
||||
} else {
|
||||
error_counters.invalid_program_for_execution += 1;
|
||||
return Err(TransactionError::InvalidProgramForExecution);
|
||||
}
|
||||
}
|
||||
|
||||
accounts.insert(0, (program_id, program));
|
||||
program_id = program_owner;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use serde::Serialize;
|
||||
use solana_sdk::{
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
client::Client,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
loader_instruction,
|
||||
@ -53,6 +54,132 @@ pub fn load_program<T: Client>(
|
||||
program_pubkey
|
||||
}
|
||||
|
||||
pub fn load_buffer_account<T: Client>(
|
||||
bank_client: &T,
|
||||
from_keypair: &Keypair,
|
||||
program: &[u8],
|
||||
) -> Pubkey {
|
||||
let buffer_keypair = Keypair::new();
|
||||
let buffer_pubkey = buffer_keypair.pubkey();
|
||||
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
&[from_keypair, &buffer_keypair],
|
||||
Message::new(
|
||||
&bpf_loader_upgradeable::create_buffer(
|
||||
&from_keypair.pubkey(),
|
||||
&buffer_pubkey,
|
||||
1.max(
|
||||
bank_client
|
||||
.get_minimum_balance_for_rent_exemption(program.len())
|
||||
.unwrap(),
|
||||
),
|
||||
program.len(),
|
||||
)
|
||||
.unwrap(),
|
||||
Some(&from_keypair.pubkey()),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let chunk_size = 256; // Size of chunk just needs to fit into tx
|
||||
let mut offset = 0;
|
||||
for chunk in program.chunks(chunk_size) {
|
||||
let message = Message::new(
|
||||
&[bpf_loader_upgradeable::write(
|
||||
&buffer_pubkey,
|
||||
offset,
|
||||
chunk.to_vec(),
|
||||
)],
|
||||
Some(&from_keypair.pubkey()),
|
||||
);
|
||||
bank_client
|
||||
.send_and_confirm_message(&[from_keypair, &buffer_keypair], message)
|
||||
.unwrap();
|
||||
offset += chunk_size as u32;
|
||||
}
|
||||
buffer_keypair.pubkey()
|
||||
}
|
||||
|
||||
pub fn load_upgradeable_program<T: Client>(
|
||||
bank_client: &T,
|
||||
from_keypair: &Keypair,
|
||||
program: Vec<u8>,
|
||||
) -> (Pubkey, Keypair) {
|
||||
let executable_keypair = Keypair::new();
|
||||
let program_pubkey = executable_keypair.pubkey();
|
||||
let authority_keypair = Keypair::new();
|
||||
let authority_pubkey = authority_keypair.pubkey();
|
||||
|
||||
let buffer_pubkey = load_buffer_account(bank_client, &from_keypair, &program);
|
||||
|
||||
let message = Message::new(
|
||||
&bpf_loader_upgradeable::deploy_with_max_program_len(
|
||||
&from_keypair.pubkey(),
|
||||
&program_pubkey,
|
||||
&buffer_pubkey,
|
||||
Some(&authority_pubkey),
|
||||
1.max(
|
||||
bank_client
|
||||
.get_minimum_balance_for_rent_exemption(
|
||||
UpgradeableLoaderState::program_len().unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
program.len() * 2,
|
||||
)
|
||||
.unwrap(),
|
||||
Some(&from_keypair.pubkey()),
|
||||
);
|
||||
bank_client
|
||||
.send_and_confirm_message(&[from_keypair, &executable_keypair], message)
|
||||
.unwrap();
|
||||
|
||||
(executable_keypair.pubkey(), authority_keypair)
|
||||
}
|
||||
|
||||
pub fn upgrade_program<T: Client>(
|
||||
bank_client: &T,
|
||||
from_keypair: &Keypair,
|
||||
program_pubkey: &Pubkey,
|
||||
buffer_pubkey: &Pubkey,
|
||||
authority_keypair: &Keypair,
|
||||
spill_pubkey: &Pubkey,
|
||||
) {
|
||||
let message = Message::new(
|
||||
&[bpf_loader_upgradeable::upgrade(
|
||||
&program_pubkey,
|
||||
&buffer_pubkey,
|
||||
&authority_keypair.pubkey(),
|
||||
&spill_pubkey,
|
||||
)],
|
||||
Some(&from_keypair.pubkey()),
|
||||
);
|
||||
bank_client
|
||||
.send_and_confirm_message(&[from_keypair, &authority_keypair], message)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn set_upgrade_authority<T: Client>(
|
||||
bank_client: &T,
|
||||
from_keypair: &Keypair,
|
||||
program_pubkey: &Pubkey,
|
||||
current_authority_keypair: &Keypair,
|
||||
new_authority_pubkey: Option<&Pubkey>,
|
||||
) {
|
||||
let message = Message::new(
|
||||
&[bpf_loader_upgradeable::set_authority(
|
||||
program_pubkey,
|
||||
¤t_authority_keypair.pubkey(),
|
||||
new_authority_pubkey,
|
||||
)],
|
||||
Some(&from_keypair.pubkey()),
|
||||
);
|
||||
bank_client
|
||||
.send_and_confirm_message(&[from_keypair, ¤t_authority_keypair], message)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Return an Instruction that invokes `program_id` with `data` and required
|
||||
// a signature from `from_pubkey`.
|
||||
pub fn create_invoke_instruction<T: Serialize>(
|
||||
|
@ -590,6 +590,74 @@ impl MessageProcessor {
|
||||
Ok((message, id, index))
|
||||
}
|
||||
|
||||
/// Entrypoint for a cross-program invocation from a native program
|
||||
pub fn native_invoke(
|
||||
invoke_context: &mut dyn InvokeContext,
|
||||
instruction: Instruction,
|
||||
keyed_accounts: &[&KeyedAccount],
|
||||
signers_seeds: &[&[&[u8]]],
|
||||
) -> Result<(), InstructionError> {
|
||||
let caller_program_id = invoke_context.get_caller()?;
|
||||
|
||||
// Translate and verify caller's data
|
||||
|
||||
let signers = signers_seeds
|
||||
.iter()
|
||||
.map(|seeds| Pubkey::create_program_address(&seeds, caller_program_id))
|
||||
.collect::<Result<Vec<_>, solana_sdk::pubkey::PubkeyError>>()?;
|
||||
let (message, callee_program_id, callee_program_id_index) =
|
||||
Self::create_message(&instruction, &keyed_accounts, &signers)?;
|
||||
let mut accounts = vec![];
|
||||
let mut account_refs = vec![];
|
||||
'root: for account_key in message.account_keys.iter() {
|
||||
for keyed_account in keyed_accounts {
|
||||
if account_key == keyed_account.unsigned_key() {
|
||||
accounts.push(Rc::new(keyed_account.account.clone()));
|
||||
account_refs.push(keyed_account);
|
||||
continue 'root;
|
||||
}
|
||||
}
|
||||
return Err(InstructionError::MissingAccount);
|
||||
}
|
||||
|
||||
// Process instruction
|
||||
|
||||
invoke_context.record_instruction(&instruction);
|
||||
let program_account = (**accounts
|
||||
.get(callee_program_id_index)
|
||||
.ok_or(InstructionError::MissingAccount)?)
|
||||
.clone();
|
||||
if !program_account.borrow().executable {
|
||||
return Err(InstructionError::AccountNotExecutable);
|
||||
}
|
||||
let executable_accounts = vec![(callee_program_id, program_account)];
|
||||
|
||||
MessageProcessor::process_cross_program_instruction(
|
||||
&message,
|
||||
&executable_accounts,
|
||||
&accounts,
|
||||
invoke_context,
|
||||
)?;
|
||||
|
||||
// Copy results back to caller
|
||||
|
||||
for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() {
|
||||
let account = account.borrow();
|
||||
if message.is_writable(i) && !account.executable {
|
||||
account_ref.try_account_ref_mut()?.lamports = account.lamports;
|
||||
account_ref.try_account_ref_mut()?.owner = account.owner;
|
||||
if account_ref.data_len()? != account.data.len() && account_ref.data_len()? != 0 {
|
||||
// Only support for `CreateAccount` at this time.
|
||||
// Need a way to limit total realloc size across multiple CPI calls
|
||||
return Err(InstructionError::InvalidRealloc);
|
||||
}
|
||||
account_ref.try_account_ref_mut()?.data = account.data.clone();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a cross-program instruction
|
||||
/// This method calls the instruction's program entrypoint function
|
||||
pub fn process_cross_program_instruction(
|
||||
|
Reference in New Issue
Block a user