Add get_processed_sibling_instruction syscall (#22859)
This commit is contained in:
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user