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

@ -42,6 +42,7 @@ assert_matches = { version = "1.5.0", optional = true }
bincode = "1.3.3"
bytemuck = { version = "1.7.2", features = ["derive"] }
borsh = "0.9.0"
base64 = "0.13"
borsh-derive = "0.9.0"
bs58 = "0.4.0"
bv = { version = "0.11.1", features = ["serde"] }

View File

@ -0,0 +1,41 @@
#pragma once
/**
* @brief Solana return data system calls
**/
#include <sol/types.h>
#include <sol/pubkey.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* Maximum size of return data
*/
#define MAX_RETURN_DATA 1024
/**
* Set the return data
*
* @param bytes byte array to set
* @param bytes_len length of byte array. This may not exceed MAX_RETURN_DATA.
*/
void sol_set_return_data(const uint8_t *bytes, uint64_t bytes_len);
/**
* Get the return data
*
* @param bytes byte buffer
* @param bytes_len maximum length of buffer
* @param program_id the program_id which set the return data. Only set if there was some return data (the function returns non-zero).
* @param result length of return data (may exceed bytes_len if the return data is longer)
*/
uint64_t sol_get_return_data(const uint8_t *bytes, uint64_t bytes_len, SolPubkey *program_id);
#ifdef __cplusplus
}
#endif
/**@}*/

View File

@ -12,6 +12,7 @@
#include <sol/keccak.h>
#include <sol/log.h>
#include <sol/pubkey.h>
#include <sol/return_data.h>
#include <sol/secp256k1.h>
#include <sol/sha.h>
#include <sol/string.h>

View File

@ -1,4 +1,6 @@
use crate::{account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction};
use crate::{
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey,
};
/// Invoke a cross-program instruction
///
@ -35,6 +37,16 @@ pub fn invoke_signed(
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_invoke_signed_rust(
instruction_addr: *const u8,
account_infos_addr: *const u8,
account_infos_len: u64,
signers_seeds_addr: *const u8,
signers_seeds_len: u64,
) -> u64;
}
let result = unsafe {
sol_invoke_signed_rust(
instruction as *const _ as *const u8,
@ -54,13 +66,48 @@ pub fn invoke_signed(
crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds)
}
#[cfg(target_arch = "bpf")]
extern "C" {
fn sol_invoke_signed_rust(
instruction_addr: *const u8,
account_infos_addr: *const u8,
account_infos_len: u64,
signers_seeds_addr: *const u8,
signers_seeds_len: u64,
) -> u64;
/// Maximum size that can be set using sol_set_return_data()
pub const MAX_RETURN_DATA: usize = 1024;
/// Set a program's return data
pub fn set_return_data(data: &[u8]) {
#[cfg(target_arch = "bpf")]
{
extern "C" {
fn sol_set_return_data(data: *const u8, length: u64);
}
unsafe { sol_set_return_data(data.as_ptr(), data.len() as u64) };
}
#[cfg(not(target_arch = "bpf"))]
crate::program_stubs::sol_set_return_data(data)
}
/// Get the return data from invoked program
pub fn get_return_data() -> Option<(Pubkey, Vec<u8>)> {
#[cfg(target_arch = "bpf")]
{
use std::cmp::min;
extern "C" {
fn sol_get_return_data(data: *mut u8, length: u64, program_id: *mut Pubkey) -> u64;
}
let mut buf = [0u8; MAX_RETURN_DATA];
let mut program_id = Pubkey::default();
let size =
unsafe { sol_get_return_data(buf.as_mut_ptr(), buf.len() as u64, &mut program_id) };
if size == 0 {
None
} else {
let size = min(size as usize, MAX_RETURN_DATA);
Some((program_id, buf[..size as usize].to_vec()))
}
}
#[cfg(not(target_arch = "bpf"))]
crate::program_stubs::sol_get_return_data()
}

View File

@ -4,7 +4,7 @@
use crate::{
account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction,
program_error::UNSUPPORTED_SYSVAR,
program_error::UNSUPPORTED_SYSVAR, pubkey::Pubkey,
};
use std::sync::{Arc, RwLock};
@ -80,6 +80,10 @@ pub trait SyscallStubs: Sync + Send {
*val = c;
}
}
fn sol_get_return_data(&self) -> Option<(Pubkey, Vec<u8>)> {
None
}
fn sol_set_return_data(&mut self, _data: &[u8]) {}
}
struct DefaultSyscallStubs {}
@ -153,3 +157,11 @@ pub(crate) fn sol_memset(s: *mut u8, c: u8, n: usize) {
SYSCALL_STUBS.read().unwrap().sol_memset(s, c, n);
}
}
pub(crate) fn sol_get_return_data() -> Option<(Pubkey, Vec<u8>)> {
SYSCALL_STUBS.read().unwrap().sol_get_return_data()
}
pub(crate) fn sol_set_return_data(data: &[u8]) {
SYSCALL_STUBS.write().unwrap().sol_set_return_data(data)
}

View File

@ -70,6 +70,8 @@ pub struct ComputeBudget {
pub sysvar_base_cost: u64,
/// Number of compute units consumed to call secp256k1_recover
pub secp256k1_recover_cost: u64,
/// Number of compute units consumed to do a syscall without any work
pub syscall_base_cost: u64,
/// Optional program heap region size, if `None` then loader default
pub heap_size: Option<usize>,
}
@ -95,6 +97,7 @@ impl ComputeBudget {
cpi_bytes_per_unit: 250, // ~50MB at 200,000 units
sysvar_base_cost: 100,
secp256k1_recover_cost: 25_000,
syscall_base_cost: 100,
heap_size: None,
}
}

View File

@ -207,6 +207,10 @@ pub mod check_seed_length {
solana_sdk::declare_id!("8HYXgkoKGreAMA3MfJkdjbKNVbfZRQP3jqFpa7iqN4v7");
}
pub mod return_data_syscall_enabled {
solana_sdk::declare_id!("BJVXq6NdLC7jCDGjfqJv7M1XHD4Y13VrpDqRF2U7UBcC");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -254,6 +258,7 @@ lazy_static! {
(ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"),
(allow_native_ids::id(), "allow native program ids in program derived addresses"),
(check_seed_length::id(), "Check program address seed lengths"),
(return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall")
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@ -117,6 +117,10 @@ pub trait InvokeContext {
fn get_blockhash(&self) -> &Hash;
/// Get this invocation's `FeeCalculator`
fn get_fee_calculator(&self) -> &FeeCalculator;
/// Set the return data
fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec<u8>)>);
/// Get the return data
fn get_return_data(&self) -> &Option<(Pubkey, Vec<u8>)>;
}
/// Convenience macro to log a message with an `Rc<RefCell<dyn Logger>>`
@ -197,6 +201,8 @@ pub struct BpfComputeBudget {
pub sysvar_base_cost: u64,
/// Number of compute units consumed to call secp256k1_recover
pub secp256k1_recover_cost: u64,
/// Number of compute units consumed to do a syscall without any work
pub syscall_base_cost: u64,
/// Optional program heap region size, if `None` then loader default
pub heap_size: Option<usize>,
}
@ -217,6 +223,7 @@ impl From<ComputeBudget> for BpfComputeBudget {
max_cpi_instruction_size: item.max_cpi_instruction_size,
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
sysvar_base_cost: item.sysvar_base_cost,
syscall_base_cost: item.syscall_base_cost,
secp256k1_recover_cost: item.secp256k1_recover_cost,
heap_size: item.heap_size,
}
@ -240,6 +247,7 @@ impl From<BpfComputeBudget> for ComputeBudget {
cpi_bytes_per_unit: item.cpi_bytes_per_unit,
sysvar_base_cost: item.sysvar_base_cost,
secp256k1_recover_cost: item.secp256k1_recover_cost,
syscall_base_cost: item.syscall_base_cost,
heap_size: item.heap_size,
}
}
@ -313,6 +321,25 @@ pub mod stable_log {
ic_logger_msg!(logger, "Program log: {}", message);
}
/// Log return data as from the program itself. This line will not be present if no return
/// data was set, or if the return data was set to zero length.
///
/// The general form is:
///
/// ```notrust
/// "Program return data: <program-id> <program-generated-data-in-base64>"
/// ```
///
/// That is, any program-generated output is guaranteed to be prefixed by "Program return data: "
pub fn program_return_data(logger: &Rc<RefCell<dyn Logger>>, program_id: &Pubkey, data: &[u8]) {
ic_logger_msg!(
logger,
"Program return data: {} {}",
program_id,
base64::encode(data)
);
}
/// Log successful program execution.
///
/// The general form is:
@ -397,7 +424,9 @@ pub struct MockInvokeContext<'a> {
pub disabled_features: HashSet<Pubkey>,
pub blockhash: Hash,
pub fee_calculator: FeeCalculator,
pub return_data: Option<(Pubkey, Vec<u8>)>,
}
impl<'a> MockInvokeContext<'a> {
pub fn new(keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
let compute_budget = ComputeBudget::default();
@ -415,6 +444,7 @@ impl<'a> MockInvokeContext<'a> {
disabled_features: HashSet::default(),
blockhash: Hash::default(),
fee_calculator: FeeCalculator::default(),
return_data: None,
};
invoke_context
.invoke_stack
@ -543,4 +573,10 @@ impl<'a> InvokeContext for MockInvokeContext<'a> {
fn get_fee_calculator(&self) -> &FeeCalculator {
&self.fee_calculator
}
fn set_return_data(&mut self, return_data: Option<(Pubkey, Vec<u8>)>) {
self.return_data = return_data;
}
fn get_return_data(&self) -> &Option<(Pubkey, Vec<u8>)> {
&self.return_data
}
}