Add get_processed_sibling_instruction syscall (#22859)

This commit is contained in:
Jack May
2022-02-02 16:45:57 -08:00
committed by GitHub
parent 75563f6c7b
commit ab02dba96f
19 changed files with 898 additions and 61 deletions

View File

@ -528,7 +528,8 @@ pub fn checked_add(a: u64, b: u64) -> Result<u64, InstructionError> {
/// default [`AccountMeta::new`] constructor creates writable accounts, this is
/// a minor hazard: use [`AccountMeta::new_readonly`] to specify that an account
/// is not writable.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[repr(C)]
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct AccountMeta {
/// An account's public key.
pub pubkey: Pubkey,
@ -639,8 +640,16 @@ impl CompiledInstruction {
let data = serialize(data).unwrap();
Self {
program_id_index: program_ids_index,
data,
accounts,
data,
}
}
pub fn new_from_raw_parts(program_id_index: u8, data: Vec<u8>, accounts: Vec<u8>) -> Self {
Self {
program_id_index,
accounts,
data,
}
}
@ -648,3 +657,137 @@ impl CompiledInstruction {
&program_ids[self.program_id_index as usize]
}
}
/// Use to query and convey information about the sibling instruction components
/// when calling the `sol_get_processed_sibling_instruction` syscall.
#[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct ProcessedSiblingInstruction {
/// Length of the instruction data
pub data_len: usize,
/// Number of AccountMeta structures
pub accounts_len: usize,
}
/// Returns a sibling instruction from the processed sibling instruction list.
///
/// The processed sibling instruction list is a reverse-ordered list of
/// successfully processed sibling instructions. For example, given the call flow:
///
/// A
/// B -> C -> D
/// B -> E
/// B -> F
///
/// Then B's processed sibling instruction list is: `[A]`
/// Then F's processed sibling instruction list is: `[E, C]`
pub fn get_processed_sibling_instruction(index: usize) -> Option<Instruction> {
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_get_processed_sibling_instruction(
index: u64,
meta: *mut ProcessedSiblingInstruction,
program_id: *mut Pubkey,
data: *mut u8,
accounts: *mut AccountMeta,
) -> u64;
}
let mut meta = ProcessedSiblingInstruction::default();
let mut program_id = Pubkey::default();
if 1 == unsafe {
sol_get_processed_sibling_instruction(
index as u64,
&mut meta,
&mut program_id,
&mut u8::default(),
&mut AccountMeta::default(),
)
} {
let mut data = Vec::new();
let mut accounts = Vec::new();
data.resize_with(meta.data_len, u8::default);
accounts.resize_with(meta.accounts_len, AccountMeta::default);
let _ = unsafe {
sol_get_processed_sibling_instruction(
index as u64,
&mut meta,
&mut program_id,
data.as_mut_ptr(),
accounts.as_mut_ptr(),
)
};
Some(Instruction::new_with_bytes(program_id, &data, accounts))
} else {
None
}
}
#[cfg(not(target_arch = "bpf"))]
crate::program_stubs::sol_get_processed_sibling_instruction(index)
}
// Stack height when processing transaction-level instructions
pub const TRANSACTION_LEVEL_STACK_HEIGHT: usize = 1;
/// Get the current stack height, transaction-level instructions are height
/// TRANSACTION_LEVEL_STACK_HEIGHT, fist invoked inner instruction is height
/// TRANSACTION_LEVEL_STACK_HEIGHT + 1, etc...
pub fn get_stack_height() -> usize {
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_get_stack_height() -> u64;
}
unsafe { sol_get_stack_height() as usize }
}
#[cfg(not(target_arch = "bpf"))]
{
crate::program_stubs::sol_get_stack_height() as usize
}
}
#[test]
fn test_account_meta_layout() {
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
struct AccountMetaRust {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}
let account_meta_rust = AccountMetaRust::default();
let base_rust_addr = &account_meta_rust as *const _ as u64;
let pubkey_rust_addr = &account_meta_rust.pubkey as *const _ as u64;
let is_signer_rust_addr = &account_meta_rust.is_signer as *const _ as u64;
let is_writable_rust_addr = &account_meta_rust.is_writable as *const _ as u64;
let account_meta_c = AccountMeta::default();
let base_c_addr = &account_meta_c as *const _ as u64;
let pubkey_c_addr = &account_meta_c.pubkey as *const _ as u64;
let is_signer_c_addr = &account_meta_c.is_signer as *const _ as u64;
let is_writable_c_addr = &account_meta_c.is_writable as *const _ as u64;
assert_eq!(
std::mem::size_of::<AccountMetaRust>(),
std::mem::size_of::<AccountMeta>()
);
assert_eq!(
pubkey_rust_addr - base_rust_addr,
pubkey_c_addr - base_c_addr
);
assert_eq!(
is_signer_rust_addr - base_rust_addr,
is_signer_c_addr - base_c_addr
);
assert_eq!(
is_writable_rust_addr - base_rust_addr,
is_writable_c_addr - base_c_addr
);
}

View File

@ -91,6 +91,12 @@ pub trait SyscallStubs: Sync + Send {
fn sol_log_data(&self, fields: &[&[u8]]) {
println!("data: {}", fields.iter().map(base64::encode).join(" "));
}
fn sol_get_processed_sibling_instruction(&self, _index: usize) -> Option<Instruction> {
None
}
fn sol_get_stack_height(&self) -> u64 {
0
}
}
struct DefaultSyscallStubs {}
@ -177,6 +183,17 @@ pub(crate) fn sol_log_data(data: &[&[u8]]) {
SYSCALL_STUBS.read().unwrap().sol_log_data(data)
}
pub(crate) fn sol_get_processed_sibling_instruction(index: usize) -> Option<Instruction> {
SYSCALL_STUBS
.read()
.unwrap()
.sol_get_processed_sibling_instruction(index)
}
pub(crate) fn sol_get_stack_height() -> u64 {
SYSCALL_STUBS.read().unwrap().sol_get_stack_height()
}
/// Check that two regions do not overlap.
///
/// Adapted from libcore, hidden to share with bpf_loader without being part of

View File

@ -311,6 +311,10 @@ pub mod reject_vote_account_close_unless_zero_credit_epoch {
solana_sdk::declare_id!("ALBk3EWdeAg2WAGf6GPDUf1nynyNqCdEVmgouG7rpuCj");
}
pub mod add_get_processed_sibling_instruction_syscall {
solana_sdk::declare_id!("CFK1hRCNy8JJuAAY8Pb2GjLFNdCThS2qwZNe3izzBMgn");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -383,6 +387,7 @@ lazy_static! {
(vote_withdraw_authority_may_change_authorized_voter::id(), "vote account withdraw authority may change the authorized voter #22521"),
(spl_associated_token_account_v1_0_4::id(), "SPL Associated Token Account Program release version 1.0.4, tied to token 3.3.0 #22648"),
(reject_vote_account_close_unless_zero_credit_epoch::id(), "fail vote account withdraw to 0 unless account earned 0 credits in last completed epoch"),
(add_get_processed_sibling_instruction_syscall::id(), "add add_get_processed_sibling_instruction_syscall"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@ -2,7 +2,7 @@
use crate::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
instruction::{CompiledInstruction, InstructionError},
instruction::{InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT},
lamports::LamportsError,
pubkey::Pubkey,
};
@ -32,7 +32,7 @@ pub struct TransactionContext {
instruction_context_capacity: usize,
instruction_context_stack: Vec<InstructionContext>,
number_of_instructions_at_transaction_level: usize,
instruction_trace: Vec<Vec<CompiledInstruction>>,
instruction_trace: InstructionTrace,
return_data: (Pubkey, Vec<u8>),
}
@ -60,7 +60,12 @@ impl TransactionContext {
}
/// Used by the bank in the runtime to write back the processed accounts and recorded instructions
pub fn deconstruct(self) -> (Vec<TransactionAccount>, Vec<Vec<CompiledInstruction>>) {
pub fn deconstruct(
self,
) -> (
Vec<TransactionAccount>,
Vec<Vec<(usize, InstructionContext)>>,
) {
(
Vec::from(Pin::into_inner(self.account_keys))
.into_iter()
@ -126,7 +131,8 @@ impl TransactionContext {
self.instruction_context_capacity
}
/// Gets the level of the next InstructionContext
/// Gets instruction stack height, top-level instructions are height
/// `solana_sdk::instruction::TRANSACTION_LEVEL_STACK_HEIGHT`
pub fn get_instruction_context_stack_height(&self) -> usize {
self.instruction_context_stack.len()
}
@ -151,17 +157,23 @@ impl TransactionContext {
if self.instruction_context_stack.len() >= self.instruction_context_capacity {
return Err(InstructionError::CallDepth);
}
let instruction_context = InstructionContext {
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
};
if self.instruction_context_stack.is_empty() {
debug_assert!(
self.instruction_trace.len() < self.number_of_instructions_at_transaction_level
);
self.instruction_trace.push(Vec::new());
self.instruction_trace.push(vec![(
TRANSACTION_LEVEL_STACK_HEIGHT,
instruction_context.clone(),
)]);
}
self.instruction_context_stack.push(InstructionContext {
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
});
self.instruction_context_stack.push(instruction_context);
Ok(())
}
@ -204,17 +216,32 @@ impl TransactionContext {
}
/// Used by the runtime when a new CPI instruction begins
pub fn record_compiled_instruction(&mut self, instruction: CompiledInstruction) {
pub fn record_instruction(&mut self, stack_height: usize, instruction: InstructionContext) {
if let Some(records) = self.instruction_trace.last_mut() {
records.push(instruction);
records.push((stack_height, instruction));
}
}
/// Returns instruction trace
pub fn get_instruction_trace(&self) -> &InstructionTrace {
&self.instruction_trace
}
}
/// List of (stack height, instruction) for each top-level instruction
pub type InstructionTrace = Vec<Vec<(usize, InstructionContext)>>;
#[derive(Clone, Debug)]
pub struct AccountMeta {
pub index_in_transaction: usize,
pub is_signer: bool,
pub is_writable: bool,
}
/// Loaded instruction shared between runtime and programs.
///
/// This context is valid for the entire duration of a (possibly cross program) instruction being processed.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct InstructionContext {
program_accounts: Vec<usize>,
instruction_accounts: Vec<InstructionAccount>,
@ -222,16 +249,50 @@ pub struct InstructionContext {
}
impl InstructionContext {
/// New
pub fn new(
program_accounts: &[usize],
instruction_accounts: &[InstructionAccount],
instruction_data: &[u8],
) -> Self {
InstructionContext {
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
}
}
/// Number of program accounts
pub fn get_number_of_program_accounts(&self) -> usize {
self.program_accounts.len()
}
/// Get the index of the instruction's program id
pub fn get_program_id_index(&self) -> usize {
self.program_accounts.last().cloned().unwrap_or_default()
}
/// Get the instruction's program id
pub fn get_program_id(&self, transaction_context: &TransactionContext) -> Pubkey {
transaction_context.account_keys[self.program_accounts.last().cloned().unwrap_or_default()]
}
/// Number of accounts in this Instruction (without program accounts)
pub fn get_number_of_instruction_accounts(&self) -> usize {
self.instruction_accounts.len()
}
pub fn get_instruction_accounts_metas(&self) -> Vec<AccountMeta> {
self.instruction_accounts
.iter()
.map(|instruction_account| AccountMeta {
index_in_transaction: instruction_account.index_in_transaction,
is_signer: instruction_account.is_signer,
is_writable: instruction_account.is_writable,
})
.collect()
}
/// Number of accounts in this Instruction
pub fn get_number_of_accounts(&self) -> usize {
self.program_accounts
@ -347,6 +408,28 @@ impl InstructionContext {
}
result
}
/// Returns whether an account is a signer
pub fn is_signer(&self, index_in_instruction: usize) -> bool {
if index_in_instruction < self.program_accounts.len() {
false
} else {
self.instruction_accounts
[index_in_instruction.saturating_sub(self.program_accounts.len())]
.is_signer
}
}
/// Returns whether an account is writable
pub fn is_writable(&self, index_in_instruction: usize) -> bool {
if index_in_instruction < self.program_accounts.len() {
false
} else {
self.instruction_accounts
[index_in_instruction.saturating_sub(self.program_accounts.len())]
.is_writable
}
}
}
/// Shared account borrowed from the TransactionContext and an InstructionContext.