Skip to content

Commit

Permalink
Add return data implementation
Browse files Browse the repository at this point in the history
This consists of:
 - syscalls
 - passing return data from invoked to invoker
 - printing to stable log
 - rust and C SDK changes
  • Loading branch information
seanyoung committed Sep 9, 2021
1 parent eade497 commit 132dc2e
Show file tree
Hide file tree
Showing 23 changed files with 495 additions and 36 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions program-runtime/src/instruction_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,9 @@ impl InstructionProcessor {
demote_program_write_locks,
);

// clear the return data
invoke_context.set_return_data(None);

// Invoke callee
invoke_context.push(program_id, &keyed_accounts)?;

Expand Down
1 change: 1 addition & 0 deletions programs/bpf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 53 additions & 19 deletions programs/bpf/c/src/invoke/invoke.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <sol/log.h>
#include <sol/assert.h>
#include <sol/deserialize.h>
#include <sol/return_data.h>

static const uint8_t TEST_SUCCESS = 1;
static const uint8_t TEST_PRIVILEGE_ESCALATION_SIGNER = 2;
Expand All @@ -26,6 +27,7 @@ static const uint8_t TEST_WRITABLE_DEESCALATION_WRITABLE = 14;
static const uint8_t TEST_NESTED_INVOKE_TOO_DEEP = 15;
static const uint8_t TEST_EXECUTABLE_LAMPORTS = 16;
static const uint8_t ADD_LAMPORTS = 17;
static const uint8_t TEST_RETURN_DATA_TOO_LARGE = 18;

static const int MINT_INDEX = 0;
static const int ARGUMENT_INDEX = 1;
Expand Down Expand Up @@ -174,6 +176,32 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
}

sol_log("Test return data");
{
SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}};
uint8_t data[] = { SET_RETURN_DATA };
uint8_t buf[100];

const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};

// set some return data, so that the callee can check it is cleared
sol_set_return_data((uint8_t[]){1, 2, 3, 4}, 4);

sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));

SolPubkey setter;

int64_t ret = sol_get_return_data(data, sizeof(data), &setter);

sol_assert(ret == sizeof(RETURN_DATA_VAL));

sol_assert(sol_memcmp(data, RETURN_DATA_VAL, sizeof(RETURN_DATA_VAL)));
sol_assert(SolPubkey_same(&setter, accounts[INVOKED_PROGRAM_INDEX].key));
}

sol_log("Test create_program_address");
{
uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's',
Expand Down Expand Up @@ -542,27 +570,33 @@ extern uint64_t entrypoint(const uint8_t *input) {
break;
}
case TEST_EXECUTABLE_LAMPORTS: {
sol_log("Test executable lamports");
accounts[ARGUMENT_INDEX].executable = true;
*accounts[ARGUMENT_INDEX].lamports -= 1;
*accounts[DERIVED_KEY1_INDEX].lamports +=1;
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, true, false},
{accounts[DERIVED_KEY1_INDEX].key, true, false},
};
uint8_t data[] = {ADD_LAMPORTS, 0, 0, 0};
SolPubkey program_id;
sol_memcpy(&program_id, params.program_id, sizeof(SolPubkey));
const SolInstruction instruction = {&program_id,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
*accounts[ARGUMENT_INDEX].lamports += 1;
break;
sol_log("Test executable lamports");
accounts[ARGUMENT_INDEX].executable = true;
*accounts[ARGUMENT_INDEX].lamports -= 1;
*accounts[DERIVED_KEY1_INDEX].lamports +=1;
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, true, false},
{accounts[DERIVED_KEY1_INDEX].key, true, false},
};
uint8_t data[] = {ADD_LAMPORTS, 0, 0, 0};
SolPubkey program_id;
sol_memcpy(&program_id, params.program_id, sizeof(SolPubkey));
const SolInstruction instruction = {&program_id,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
*accounts[ARGUMENT_INDEX].lamports += 1;
break;
}
case ADD_LAMPORTS: {
*accounts[0].lamports += 1;
break;
*accounts[0].lamports += 1;
break;
}
case TEST_RETURN_DATA_TOO_LARGE: {
sol_log("Test setting return data too long");
// The actual buffer doesn't matter, just pass null
sol_set_return_data(NULL, 1027);
break;
}

default:
Expand Down
3 changes: 3 additions & 0 deletions programs/bpf/c/src/invoked/instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ const uint8_t VERIFY_PRIVILEGE_DEESCALATION = 8;
const uint8_t VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER = 9;
const uint8_t VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE = 10;
const uint8_t WRITE_ACCOUNT = 11;
const uint8_t SET_RETURN_DATA = 12;

#define RETURN_DATA_VAL "return data test"
8 changes: 8 additions & 0 deletions programs/bpf/c/src/invoked/invoked.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
return ERROR_INVALID_ARGUMENT;
}

// on entry, return data must not be set
sol_assert(sol_get_return_data(NULL, 0, NULL) == -1);

if (params.data_len == 0) {
return SUCCESS;
}
Expand Down Expand Up @@ -91,6 +94,11 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_log("return Ok");
return SUCCESS;
}
case SET_RETURN_DATA: {
sol_set_return_data((const uint8_t*)RETURN_DATA_VAL, sizeof(RETURN_DATA_VAL));
sol_log("set return data");
return SUCCESS;
}
case RETURN_ERROR: {
sol_log("return error");
return 42;
Expand Down
40 changes: 40 additions & 0 deletions programs/bpf/c/src/return_data/return_data.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @brief return data Syscall test
*/
#include <solana_sdk.h>

#define DATA "the quick brown fox jumps over the lazy dog"

extern uint64_t entrypoint(const uint8_t *input) {
uint8_t buf[1024];
SolPubkey me;

// There should be no return data on entry
int64_t ret = sol_get_return_data(NULL, 0, NULL);

sol_assert(ret == -1);

// set some return data
sol_set_return_data((const uint8_t*)DATA, sizeof(DATA));

// ensure the length is correct
ret = sol_get_return_data(NULL, 0, &me);
sol_assert(ret == sizeof(DATA));

// try getting a subset
ret = sol_get_return_data(buf, 4, &me);

sol_assert(ret == sizeof(DATA));

sol_assert(!sol_memcmp(buf, "the ", 4));

// try getting the whole thing
ret = sol_get_return_data(buf, sizeof(buf), &me);

sol_assert(ret == sizeof(DATA));

sol_assert(!sol_memcmp(buf, (const uint8_t*)DATA, sizeof(DATA)));

// done
return SUCCESS;
}
23 changes: 22 additions & 1 deletion programs/bpf/rust/invoke/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use solana_program::{
entrypoint,
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
msg,
program::{invoke, invoke_signed},
program::{get_return_data, invoke, invoke_signed, set_return_data},
program_error::ProgramError,
pubkey::{Pubkey, PubkeyError},
system_instruction,
Expand Down Expand Up @@ -394,6 +394,27 @@ fn process_instruction(
assert_eq!(data[i], i as u8);
}
}

msg!("Test return data via invoked");
{
// this should be cleared on entry, the invoked tests for this
set_return_data(b"x");

let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, false, true)],
vec![SET_RETURN_DATA],
);
let _ = invoke(&instruction, accounts);

assert_eq!(
get_return_data(),
Some((
*accounts[INVOKED_PROGRAM_INDEX].key,
b"Pass this on".to_vec()
))
);
}
}
TEST_PRIVILEGE_ESCALATION_SIGNER => {
msg!("Test privilege escalation signer");
Expand Down
1 change: 1 addition & 0 deletions programs/bpf/rust/invoked/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub const VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER: u8 = 9;
pub const VERIFY_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE: u8 = 10;
pub const WRITE_ACCOUNT: u8 = 11;
pub const CREATE_AND_INIT: u8 = 12;
pub const SET_RETURN_DATA: u8 = 13;

pub fn create_instruction(
program_id: Pubkey,
Expand Down
9 changes: 8 additions & 1 deletion programs/bpf/rust/invoked/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use solana_program::{
bpf_loader, entrypoint,
entrypoint::{ProgramResult, MAX_PERMITTED_DATA_INCREASE},
msg,
program::{invoke, invoke_signed},
program::{get_return_data, invoke, invoke_signed, set_return_data},
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
Expand All @@ -27,6 +27,8 @@ fn process_instruction(
return Ok(());
}

assert_eq!(get_return_data(), None);

match instruction_data[0] {
VERIFY_TRANSLATIONS => {
msg!("verify data translations");
Expand Down Expand Up @@ -286,6 +288,11 @@ fn process_instruction(
}
}
}
SET_RETURN_DATA => {
msg!("Set return data");

set_return_data(b"Pass this on");
}
_ => panic!(),
}

Expand Down
13 changes: 13 additions & 0 deletions programs/bpf/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ fn run_program(
for i in 0..2 {
let mut parameter_bytes = parameter_bytes.clone();
{
invoke_context.set_return_data(None);

let mut vm = create_vm(
&loader_id,
executable.as_ref(),
Expand Down Expand Up @@ -432,6 +434,7 @@ fn test_program_bpf_sanity() {
("noop++", true),
("panic", false),
("relative_call", true),
("return_data", true),
("sanity", true),
("sanity++", true),
("secp256k1_recover", true),
Expand Down Expand Up @@ -756,6 +759,7 @@ fn test_program_bpf_invoke_sanity() {
const TEST_WRITABLE_DEESCALATION_WRITABLE: u8 = 14;
const TEST_NESTED_INVOKE_TOO_DEEP: u8 = 15;
const TEST_EXECUTABLE_LAMPORTS: u8 = 16;
const TEST_RETURN_DATA_TOO_LARGE: u8 = 18;

#[allow(dead_code)]
#[derive(Debug)]
Expand Down Expand Up @@ -878,6 +882,7 @@ fn test_program_bpf_invoke_sanity() {
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
],
Languages::Rust => vec![
system_program::id(),
Expand All @@ -902,6 +907,7 @@ fn test_program_bpf_invoke_sanity() {
invoked_program_id.clone(),
invoked_program_id.clone(),
system_program::id(),
invoked_program_id.clone(),
],
};
assert_eq!(invoked_programs.len(), expected_invoked_programs.len());
Expand Down Expand Up @@ -1030,6 +1036,12 @@ fn test_program_bpf_invoke_sanity() {
&[invoke_program_id.clone()],
);

do_invoke_failure_test_local(
TEST_RETURN_DATA_TOO_LARGE,
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
&[],
);

// Check resulting state

assert_eq!(43, bank.get_balance(&derived_key1));
Expand Down Expand Up @@ -1312,6 +1324,7 @@ fn assert_instruction_count() {
("noop", 5),
("noop++", 5),
("relative_call", 10),
("return_data", 480),
("sanity", 169),
("sanity++", 168),
("secp256k1_recover", 359),
Expand Down
5 changes: 5 additions & 0 deletions programs/bpf_loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 132dc2e

Please sign in to comment.