Refactor: Remove KeyedAccount from program runtime (#22226)

* Makes error handling in BorrowedAccount optional.
Adds BorrowedAccount ::get_rent_epoch().
Exposes InstructionContext::get_index_in_transaction().
Turns accounts and account_keys into pinned boxed slices.

* Introduces "unsafe" to InvokeContext::push().

* Turns &TransactionContext into &mut TransactionContext in InvokeContext.

* Push and pop InstructionContext in InvokeContext.
Makes test_process_cross_program and test_native_invoke symmetric.
Removes the borrow check from test_invoke_context_verify.

* Removes keyed_accounts from prepare_instruction()

* Removes usage of invoke_stack.

* Removes keyed_accounts from program-test.

* Removes caller_write_privileges.

* Removes keyed_accounts from BPF parameter (de-)serialization.
This commit is contained in:
Alexander Meißner
2022-01-03 23:30:56 +01:00
committed by GitHub
parent 672fed04cb
commit 73e6038986
18 changed files with 847 additions and 805 deletions

View File

@ -6,7 +6,10 @@ use crate::{
lamports::LamportsError,
pubkey::Pubkey,
};
use std::cell::{RefCell, RefMut};
use std::{
cell::{RefCell, RefMut},
pin::Pin,
};
pub type TransactionAccount = (Pubkey, AccountSharedData);
@ -23,8 +26,8 @@ pub struct InstructionAccount {
/// This context is valid for the entire duration of a transaction being processed.
#[derive(Debug)]
pub struct TransactionContext {
account_keys: Vec<Pubkey>,
accounts: Vec<RefCell<AccountSharedData>>,
account_keys: Pin<Box<[Pubkey]>>,
accounts: Pin<Box<[RefCell<AccountSharedData>]>>,
instruction_context_capacity: usize,
instruction_context_stack: Vec<InstructionContext>,
return_data: (Pubkey, Vec<u8>),
@ -36,13 +39,14 @@ impl TransactionContext {
transaction_accounts: Vec<TransactionAccount>,
instruction_context_capacity: usize,
) -> Self {
let (account_keys, accounts) = transaction_accounts
.into_iter()
.map(|(key, account)| (key, RefCell::new(account)))
.unzip();
let (account_keys, accounts): (Vec<Pubkey>, Vec<RefCell<AccountSharedData>>) =
transaction_accounts
.into_iter()
.map(|(key, account)| (key, RefCell::new(account)))
.unzip();
Self {
account_keys,
accounts,
account_keys: Pin::new(account_keys.into_boxed_slice()),
accounts: Pin::new(accounts.into_boxed_slice()),
instruction_context_capacity,
instruction_context_stack: Vec::with_capacity(instruction_context_capacity),
return_data: (Pubkey::default(), Vec::new()),
@ -51,10 +55,10 @@ impl TransactionContext {
/// Used by the bank in the runtime to write back the processed accounts
pub fn deconstruct(self) -> Vec<TransactionAccount> {
self.account_keys
Vec::from(Pin::into_inner(self.account_keys))
.into_iter()
.zip(
self.accounts
Vec::from(Pin::into_inner(self.accounts))
.into_iter()
.map(|account| account.into_inner()),
)
@ -66,8 +70,7 @@ impl TransactionContext {
if !self.instruction_context_stack.is_empty() {
return Err(InstructionError::CallDepth);
}
Ok(self
.accounts
Ok(Vec::from(Pin::into_inner(self.accounts))
.into_iter()
.map(|account| account.into_inner())
.collect())
@ -243,21 +246,29 @@ impl InstructionContext {
.map(|index| index.saturating_add(self.program_accounts.len()))
}
/// Translates the given instruction wide index into a transaction wide index
pub fn get_index_in_transaction(
&self,
index_in_instruction: usize,
) -> Result<usize, InstructionError> {
if index_in_instruction < self.program_accounts.len() {
Ok(self.program_accounts[index_in_instruction])
} else if index_in_instruction < self.get_number_of_accounts() {
Ok(self.instruction_accounts
[index_in_instruction.saturating_sub(self.program_accounts.len())]
.index_in_transaction)
} else {
Err(InstructionError::NotEnoughAccountKeys)
}
}
/// Tries to borrow an account from this Instruction
pub fn try_borrow_account<'a, 'b: 'a>(
&'a self,
transaction_context: &'b TransactionContext,
index_in_instruction: usize,
) -> Result<BorrowedAccount<'a>, InstructionError> {
let index_in_transaction = if index_in_instruction < self.program_accounts.len() {
self.program_accounts[index_in_instruction]
} else if index_in_instruction < self.get_number_of_accounts() {
self.instruction_accounts
[index_in_instruction.saturating_sub(self.program_accounts.len())]
.index_in_transaction
} else {
return Err(InstructionError::NotEnoughAccountKeys);
};
let index_in_transaction = self.get_index_in_transaction(index_in_instruction)?;
if index_in_transaction >= transaction_context.accounts.len() {
return Err(InstructionError::MissingAccount);
}
@ -331,12 +342,13 @@ impl<'a> BorrowedAccount<'a> {
}
/// Assignes the owner of this account (transaction wide)
pub fn set_owner(&mut self, pubkey: Pubkey) -> Result<(), InstructionError> {
if !self.is_writable() {
return Err(InstructionError::Immutable);
pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
self.account.copy_into_owner_from_slice(pubkey);
if self.is_writable() {
Ok(())
} else {
Err(InstructionError::Immutable)
}
self.account.set_owner(pubkey);
Ok(())
}
/// Returns the number of lamports of this account (transaction wide)
@ -346,11 +358,12 @@ impl<'a> BorrowedAccount<'a> {
/// Overwrites the number of lamports of this account (transaction wide)
pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
if !self.is_writable() {
return Err(InstructionError::Immutable);
}
self.account.set_lamports(lamports);
Ok(())
if self.is_writable() {
Ok(())
} else {
Err(InstructionError::Immutable)
}
}
/// Adds lamports to this account (transaction wide)
@ -376,26 +389,23 @@ impl<'a> BorrowedAccount<'a> {
self.account.data()
}
/// Returns a writable slice of the account data (transaction wide)
pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
if !self.is_writable() {
return Err(InstructionError::Immutable);
/// Overwrites the account data and size (transaction wide)
pub fn set_data(&mut self, data: &[u8]) -> Result<(), InstructionError> {
if data.len() == self.account.data().len() {
self.account.data_as_mut_slice().copy_from_slice(data);
} else {
self.account.set_data_from_slice(data);
}
if self.is_writable() {
Ok(())
} else {
Err(InstructionError::Immutable)
}
Ok(self.account.data_as_mut_slice())
}
/// Guarded alternative to `get_data_mut()?.copy_from_slice()` which checks if the account size matches
pub fn copy_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
if !self.is_writable() {
return Err(InstructionError::Immutable);
}
let account_data = self.account.data_as_mut_slice();
if data.len() != account_data.len() {
return Err(InstructionError::AccountDataSizeChanged);
}
account_data.copy_from_slice(data);
Ok(())
}
/*pub fn realloc(&self, new_len: usize, zero_init: bool) {
// TODO
}*/
/// Deserializes the account data into a state
pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
@ -406,22 +416,20 @@ impl<'a> BorrowedAccount<'a> {
/// Serializes a state into the account data
pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
if !self.is_writable() {
return Err(InstructionError::Immutable);
}
let data = self.account.data_as_mut_slice();
let serialized_size =
bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
if serialized_size > data.len() as u64 {
return Err(InstructionError::AccountDataTooSmall);
}
bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)
bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
if self.is_writable() {
Ok(())
} else {
Err(InstructionError::Immutable)
}
}
/*pub fn realloc(&self, new_len: usize, zero_init: bool) {
// TODO
}*/
/// Returns whether this account is executable (transaction wide)
pub fn is_executable(&self) -> bool {
self.account.executable()
@ -429,11 +437,17 @@ impl<'a> BorrowedAccount<'a> {
/// Configures whether this account is executable (transaction wide)
pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
if !self.is_writable() {
return Err(InstructionError::Immutable);
}
self.account.set_executable(is_executable);
Ok(())
if self.is_writable() {
Ok(())
} else {
Err(InstructionError::Immutable)
}
}
/// Returns the rent epoch of this account (transaction wide)
pub fn get_rent_epoch(&self) -> u64 {
self.account.rent_epoch()
}
/// Returns whether this account is a signer (instruction wide)