Add return data implementation

This consists of:
 - syscalls
 - passing return data from invoked to invoker
 - printing to stable log
 - rust and C SDK changes
This commit is contained in:
Sean Young
2021-09-01 10:14:01 +01:00
parent 2dee098b91
commit 098585234d
23 changed files with 641 additions and 35 deletions

View File

@@ -917,6 +917,11 @@ impl Executor for BpfExecutor {
let trace_string = String::from_utf8(trace_buffer).unwrap();
trace!("BPF Program Instruction Trace:\n{}", trace_string);
}
drop(vm);
let return_data = invoke_context.get_return_data();
if let Some((program_id, return_data)) = return_data {
stable_log::program_return_data(&logger, program_id, return_data);
}
match result {
Ok(status) => {
if status != SUCCESS {

View File

@@ -24,7 +24,7 @@ use solana_sdk::{
allow_native_ids, blake3_syscall_enabled, check_seed_length,
close_upgradeable_program_accounts, demote_program_write_locks, disable_fees_sysvar,
enforce_aligned_host_addrs, libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix,
secp256k1_recover_syscall_enabled,
return_data_syscall_enabled, secp256k1_recover_syscall_enabled,
},
hash::{Hasher, HASH_BYTES},
ic_msg,
@@ -33,6 +33,7 @@ use solana_sdk::{
keyed_account::KeyedAccount,
native_loader,
process_instruction::{self, stable_log, ComputeMeter, InvokeContext, Logger},
program::MAX_RETURN_DATA,
pubkey::{Pubkey, PubkeyError, MAX_SEEDS, MAX_SEED_LEN},
rent::Rent,
secp256k1_recover::{
@@ -43,6 +44,7 @@ use solana_sdk::{
use std::{
alloc::Layout,
cell::{Ref, RefCell, RefMut},
cmp::min,
mem::{align_of, size_of},
rc::Rc,
slice::from_raw_parts_mut,
@@ -62,9 +64,9 @@ pub enum SyscallError {
Abort,
#[error("BPF program Panicked in {0} at {1}:{2}")]
Panic(String, u64, u64),
#[error("cannot borrow invoke context")]
#[error("Cannot borrow invoke context")]
InvokeContextBorrowFailed,
#[error("malformed signer seed: {0}: {1:?}")]
#[error("Malformed signer seed: {0}: {1:?}")]
MalformedSignerSeed(Utf8Error, Vec<u8>),
#[error("Could not create program address with signer seeds: {0}")]
BadSeeds(PubkeyError),
@@ -82,6 +84,8 @@ pub enum SyscallError {
TooManyAccounts,
#[error("Overlapping copy")]
CopyOverlapping,
#[error("Return data too large ({0} > {1})")]
ReturnDataTooLarge(u64, u64),
}
impl From<SyscallError> for EbpfError<BpfError> {
fn from(error: SyscallError) -> Self {
@@ -173,6 +177,14 @@ pub fn register_syscalls(
// Memory allocator
syscall_registry.register_syscall_by_name(b"sol_alloc_free_", SyscallAllocFree::call)?;
// Return data
if invoke_context.is_feature_active(&return_data_syscall_enabled::id()) {
syscall_registry
.register_syscall_by_name(b"sol_set_return_data", SyscallSetReturnData::call)?;
syscall_registry
.register_syscall_by_name(b"sol_get_return_data", SyscallGetReturnData::call)?;
}
Ok(syscall_registry)
}
@@ -353,6 +365,8 @@ pub fn bind_syscall_context_objects<'a>(
let is_fee_sysvar_via_syscall_active =
!invoke_context.is_feature_active(&disable_fees_sysvar::id());
let is_return_data_syscall_active =
invoke_context.is_feature_active(&return_data_syscall_enabled::id());
let invoke_context = Rc::new(RefCell::new(invoke_context));
@@ -386,6 +400,25 @@ pub fn bind_syscall_context_objects<'a>(
None,
)?;
// Return data
bind_feature_gated_syscall_context_object!(
vm,
is_return_data_syscall_active,
Box::new(SyscallSetReturnData {
invoke_context: invoke_context.clone(),
loader_id,
}),
);
bind_feature_gated_syscall_context_object!(
vm,
is_return_data_syscall_active,
Box::new(SyscallGetReturnData {
invoke_context: invoke_context.clone(),
loader_id,
}),
);
// Cross-program invocation syscalls
vm.bind_syscall_context_object(
Box::new(SyscallInvokeSignedC {
@@ -2548,6 +2581,142 @@ fn call<'a>(
Ok(SUCCESS)
}
// Return data handling
pub struct SyscallSetReturnData<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
loader_id: &'a Pubkey,
}
impl<'a> SyscallObject<BpfError> for SyscallSetReturnData<'a> {
fn call(
&mut self,
addr: u64,
len: u64,
_arg3: u64,
_arg4: u64,
_arg5: u64,
memory_mapping: &MemoryMapping,
result: &mut Result<u64, EbpfError<BpfError>>,
) {
let mut invoke_context = question_mark!(
self.invoke_context
.try_borrow_mut()
.map_err(|_| SyscallError::InvokeContextBorrowFailed),
result
);
let budget = invoke_context.get_compute_budget();
question_mark!(
invoke_context
.get_compute_meter()
.consume(len / budget.cpi_bytes_per_unit + budget.syscall_base_cost),
result
);
if len > MAX_RETURN_DATA as u64 {
*result = Err(SyscallError::ReturnDataTooLarge(len, MAX_RETURN_DATA as u64).into());
return;
}
if len == 0 {
invoke_context.set_return_data(None);
} else {
let return_data = question_mark!(
translate_slice::<u8>(memory_mapping, addr, len, self.loader_id, true),
result
);
let program_id = *question_mark!(
invoke_context
.get_caller()
.map_err(SyscallError::InstructionError),
result
);
invoke_context.set_return_data(Some((program_id, return_data.to_vec())));
}
*result = Ok(0);
}
}
pub struct SyscallGetReturnData<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
loader_id: &'a Pubkey,
}
impl<'a> SyscallObject<BpfError> for SyscallGetReturnData<'a> {
fn call(
&mut self,
return_data_addr: u64,
len: u64,
program_id_addr: u64,
_arg4: u64,
_arg5: u64,
memory_mapping: &MemoryMapping,
result: &mut Result<u64, EbpfError<BpfError>>,
) {
let invoke_context = question_mark!(
self.invoke_context
.try_borrow()
.map_err(|_| SyscallError::InvokeContextBorrowFailed),
result
);
let budget = invoke_context.get_compute_budget();
question_mark!(
invoke_context
.get_compute_meter()
.consume(budget.syscall_base_cost),
result
);
if let Some((program_id, return_data)) = invoke_context.get_return_data() {
if len != 0 {
let length = min(return_data.len() as u64, len);
question_mark!(
invoke_context
.get_compute_meter()
.consume((length + size_of::<Pubkey>() as u64) / budget.cpi_bytes_per_unit),
result
);
let return_data_result = question_mark!(
translate_slice_mut::<u8>(
memory_mapping,
return_data_addr,
length,
self.loader_id,
true,
),
result
);
return_data_result.copy_from_slice(&return_data[..length as usize]);
let program_id_result = question_mark!(
translate_slice_mut::<Pubkey>(
memory_mapping,
program_id_addr,
1,
self.loader_id,
true,
),
result
);
program_id_result[0] = *program_id;
}
// Return the actual length, rather the length returned
*result = Ok(return_data.len() as u64);
} else {
*result = Ok(0);
}
}
}
#[cfg(test)]
mod tests {
use super::*;