Add return data implementation
This consists of:
- syscalls
- passing return data from invoked to invoker
- printing to stable log
- rust and C SDK changes
(cherry picked from commit 53b47b87b2
)
This commit is contained in:
@@ -41,6 +41,7 @@ full = [
|
||||
assert_matches = { version = "1.5.0", optional = true }
|
||||
bincode = "1.3.3"
|
||||
borsh = "0.9.0"
|
||||
base64 = "0.13"
|
||||
borsh-derive = "0.9.0"
|
||||
bs58 = "0.4.0"
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
|
@@ -716,6 +716,29 @@ void sol_panic_(const char *file, uint64_t len, uint64_t line, uint64_t column)
|
||||
}
|
||||
#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
|
||||
|
@@ -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.
|
||||
///
|
||||
@@ -70,6 +72,16 @@ pub fn invoke_signed_unchecked(
|
||||
) -> ProgramResult {
|
||||
#[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,
|
||||
@@ -89,13 +101,48 @@ pub fn invoke_signed_unchecked(
|
||||
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()
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -231,6 +231,10 @@ pub mod remove_native_loader {
|
||||
solana_sdk::declare_id!("HTTgmruMYRZEntyL3EdCDdnS6e4D5wRq1FA7kQsb66qq");
|
||||
}
|
||||
|
||||
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> = [
|
||||
@@ -289,6 +293,7 @@ lazy_static! {
|
||||
(tx_wide_compute_cap::id(), "Transaction wide compute cap"),
|
||||
(gate_large_block::id(), "validator checks block cost against max limit in realtime, reject if exceeds."),
|
||||
(remove_native_loader::id(), "Remove support for the native loader"),
|
||||
(return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall")
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
@@ -100,6 +100,10 @@ pub trait InvokeContext {
|
||||
);
|
||||
/// Get sysvar data
|
||||
fn get_sysvar_data(&self, id: &Pubkey) -> Option<Rc<Vec<u8>>>;
|
||||
/// 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>>`
|
||||
@@ -181,9 +185,12 @@ 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>,
|
||||
}
|
||||
|
||||
impl Default for BpfComputeBudget {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -206,6 +213,7 @@ impl BpfComputeBudget {
|
||||
max_cpi_instruction_size: 1280, // IPv6 Min MTU size
|
||||
cpi_bytes_per_unit: 250, // ~50MB at 200,000 units
|
||||
sysvar_base_cost: 100,
|
||||
syscall_base_cost: 100,
|
||||
secp256k1_recover_cost: 25_000,
|
||||
heap_size: None,
|
||||
}
|
||||
@@ -261,6 +269,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:
|
||||
@@ -335,7 +362,9 @@ pub struct MockInvokeContext<'a> {
|
||||
pub accounts: Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>,
|
||||
pub sysvars: Vec<(Pubkey, Option<Rc<Vec<u8>>>)>,
|
||||
pub disabled_features: HashSet<Pubkey>,
|
||||
pub return_data: Option<(Pubkey, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl<'a> MockInvokeContext<'a> {
|
||||
pub fn new(keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
|
||||
let bpf_compute_budget = BpfComputeBudget::default();
|
||||
@@ -350,6 +379,7 @@ impl<'a> MockInvokeContext<'a> {
|
||||
accounts: vec![],
|
||||
sysvars: vec![],
|
||||
disabled_features: HashSet::default(),
|
||||
return_data: None,
|
||||
};
|
||||
invoke_context
|
||||
.invoke_stack
|
||||
@@ -467,4 +497,10 @@ impl<'a> InvokeContext for MockInvokeContext<'a> {
|
||||
.iter()
|
||||
.find_map(|(key, sysvar)| if id == key { sysvar.clone() } else { None })
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user