Skip to content

Commit

Permalink
add offset and reduce shared object size
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmay committed Oct 14, 2020
1 parent 27f1f00 commit f5d8c97
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 35 deletions.
16 changes: 8 additions & 8 deletions Cargo.lock

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

18 changes: 9 additions & 9 deletions docs/src/shared-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
title: Shared memory Program
---

A simple program and highly optimized program that writes the instruction data
into the provided account's data
A simple program and highly optimized program that writes instruction data into
the provided account's data

## Background

Expand All @@ -20,13 +20,13 @@ The Shared memory Program's source is available on

## Interface

The shared memory program expects a single account, owned by the shared memory program. The account's data
must be large enough to hold the entire instruction data.
The Shared memory program expects one account and writes instruction data into
the account's data. The first 8 bytes of the instruction data contain the
little-endian offset into the account data. The rest of the instruction data is
written into the account data starting at that offset.

## Operational overview

The Shared memory program directly writes all the instruction data into the
provided account's data. It is useful for returning data from cross-program
invoked programs to the invoker. Because the account does not need to be signed
it is not reliable to use this program to pass data between programs from
different transactions.
This program is useful for returning data from cross-program invoked programs to
the invoker. Because the account does not need to be signed it is not reliable
to use this program to pass data between programs from different transactions.
2 changes: 1 addition & 1 deletion shared-memory/client/Cargo.lock

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

57 changes: 49 additions & 8 deletions shared-memory/client/tests/shared-memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,34 +70,56 @@ fn run_program(

#[test]
fn assert_instruction_count() {
const OFFSET: usize = 51;
const NUM_TO_SHARE: usize = 500;
let program_id = Pubkey::new_rand();
let shared_key = Pubkey::new_rand();
let shared_account = Account::new_ref(u64::MAX, NUM_TO_SHARE * 2, &program_id);
let shared_account = Account::new_ref(u64::MAX, OFFSET + NUM_TO_SHARE * 2, &program_id);

// Send some data to share
let parameter_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
let data = vec![42; NUM_TO_SHARE];
let share_count = run_program(&program_id, &parameter_accounts[..], &data).unwrap();

const BASELINE_COUNT: u64 = 1464; // 94 if NUM_TO_SHARE is 8
let content = vec![42; NUM_TO_SHARE];
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
instruction_data.extend_from_slice(&content);
let share_count = run_program(&program_id, &parameter_accounts[..], &instruction_data).unwrap();
const BASELINE_COUNT: u64 = 1474; // 113 if NUM_TO_SHARE is 8
println!(
"BPF instructions executed {:?} (expected {:?})",
share_count, BASELINE_COUNT
);
assert_eq!(
&shared_account.borrow().data[OFFSET..OFFSET + NUM_TO_SHARE],
content
);
assert!(share_count <= BASELINE_COUNT);
assert_eq!(&shared_account.borrow().data[..data.len()], data);
}

#[test]
fn test_share_data() {
const OFFSET: usize = 51;
const NUM_TO_SHARE: usize = 500;
let program_id = Pubkey::new(&[0; 32]);
let shared_key = Pubkey::new_rand();
let shared_account = Account::new_ref(u64::MAX, NUM_TO_SHARE * 2, &program_id);
let instruction_data = vec![42; NUM_TO_SHARE];

// success
let content = vec![42; NUM_TO_SHARE];
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
instruction_data.extend_from_slice(&content);
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
let mut input = serialize_parameters(
&bpf_loader::id(),
&program_id,
&keyed_accounts,
&instruction_data,
)
.unwrap();
assert_eq!(unsafe { entrypoint(input.as_mut_ptr()) }, SUCCESS);

// success zero offset
let content = vec![42; NUM_TO_SHARE];
let mut instruction_data = 0_usize.to_le_bytes().to_vec();
instruction_data.extend_from_slice(&content);
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
let mut input = serialize_parameters(
&bpf_loader::id(),
Expand Down Expand Up @@ -135,7 +157,26 @@ fn test_share_data() {

// account data too small
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
let instruction_data = vec![42; NUM_TO_SHARE * 10];
let content = vec![42; NUM_TO_SHARE * 10];
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
instruction_data.extend_from_slice(&content);
let mut input = serialize_parameters(
&bpf_loader::id(),
&program_id,
&keyed_accounts,
&instruction_data,
)
.unwrap();
assert_eq!(
unsafe { entrypoint(input.as_mut_ptr()) },
u64::from(ProgramError::AccountDataTooSmall)
);

// offset too large
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
let content = vec![42; NUM_TO_SHARE];
let mut instruction_data = (OFFSET * 10).to_le_bytes().to_vec();
instruction_data.extend_from_slice(&content);
let mut input = serialize_parameters(
&bpf_loader::id(),
&program_id,
Expand Down
2 changes: 1 addition & 1 deletion shared-memory/program/program-id.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
TODO
shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL
30 changes: 22 additions & 8 deletions shared-memory/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// This program is highly optimized for its particular use case and does not
// implement the typical `process_instruction` entrypoint.

extern crate solana_sdk;
use arrayref::{array_refs, mut_array_refs};
use solana_sdk::{
entrypoint::MAX_PERMITTED_DATA_INCREASE, entrypoint::SUCCESS, program_error::ProgramError,
Expand All @@ -18,7 +19,7 @@ use std::{
slice::{from_raw_parts, from_raw_parts_mut},
};

// TODO solana_sdk::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
solana_sdk::declare_id!("shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL");

/// A more efficient `copy_from_slice` implementation.
fn fast_copy(mut src: &[u8], mut dst: &mut [u8]) {
Expand All @@ -31,7 +32,9 @@ fn fast_copy(mut src: &[u8], mut dst: &mut [u8]) {
src = src_rem;
dst = dst_rem;
}
dst.copy_from_slice(src);
unsafe {
std::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), src.len());
}
}

/// Deserializes only the particular input parameters that the shared memory
Expand Down Expand Up @@ -76,21 +79,32 @@ unsafe fn deserialize_input_parametes<'a>(input: *mut u8) -> Result<(&'a mut [u8
Ok((account_data, instruction_data))
}

/// This program expects one account and writes instruction data into the
/// account's data. The first 8 bytes of the instruction data contain the
/// little-endian offset into the account data. The rest of the instruction
/// data is written into the account data starting at that offset.
///
/// This program uses the raw Solana runtime's entrypoint which takes a pointer
/// to serialized input parameters. For more information about the format of
/// the serialized input parameters see `solana_sdk::entrypoint::deserialize`
///
/// This program expects one account and writes all the instruction data into
/// the account's data starting at offset 0.
/// # Safety
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
match deserialize_input_parametes(input) {
Ok((account_data, instruction_data)) => {
match account_data.get_mut(..instruction_data.len()) {
None => return ProgramError::AccountDataTooSmall.into(),
Some(data) => fast_copy(instruction_data, data),
};
if instruction_data.len() < 8 {
return ProgramError::AccountDataTooSmall.into();
}
#[allow(clippy::ptr_offset_with_cast)]
let (offset, content) = array_refs![instruction_data, 8; ..;];
let offset = usize::from_le_bytes(*offset);
if account_data.len() < offset + content.len() {
return ProgramError::AccountDataTooSmall.into();
}
let data_ptr = account_data.as_mut_ptr() as usize;
let data = from_raw_parts_mut((data_ptr + offset) as *mut u8, content.len());
fast_copy(content, data);
}
Err(err) => return err,
}
Expand Down

0 comments on commit f5d8c97

Please sign in to comment.