diff --git a/Cargo.lock b/Cargo.lock index bb5e242ac61..bde6c11eaef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,12 +562,13 @@ dependencies = [ [[package]] name = "curve25519-dalek" version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" +source = "git+https://github.com/garious/curve25519-dalek?rev=60efef3553d6bf3d7f3b09b5f97acd54d72529ff#60efef3553d6bf3d7f3b09b5f97acd54d72529ff" dependencies = [ + "borsh", "byteorder", "digest 0.8.1", "rand_core", + "serde", "subtle 2.2.3", "zeroize", ] @@ -575,13 +576,12 @@ dependencies = [ [[package]] name = "curve25519-dalek" version = "2.1.0" -source = "git+https://github.com/garious/curve25519-dalek?rev=60efef3553d6bf3d7f3b09b5f97acd54d72529ff#60efef3553d6bf3d7f3b09b5f97acd54d72529ff" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" dependencies = [ - "borsh", "byteorder", "digest 0.8.1", "rand_core", - "serde", "subtle 2.2.3", "zeroize", ] diff --git a/do.sh b/do.sh index e4d8c0abe4a..97dae41136d 100755 --- a/do.sh +++ b/do.sh @@ -26,16 +26,16 @@ sdkDir=bin/bpf-sdk profile=bpfel-unknown-unknown/release readCargoVariable() { - declare variable="$1" - declare Cargo_toml="$2" + declare variable="$1" + declare Cargo_toml="$2" - while read -r name equals value _; do - if [[ $name = "$variable" && $equals = = ]]; then - echo "${value//\"/}" - return - fi - done < <(cat "$Cargo_toml") - echo "Unable to locate $variable in $Cargo_toml" 1>&2 + while read -r name equals value _; do + if [[ $name = "$variable" && $equals = = ]]; then + echo "${value//\"/}" + return + fi + done < <(cat "$Cargo_toml") + echo "Unable to locate $variable in $Cargo_toml" 1>&2 } perform_action() { @@ -52,18 +52,24 @@ perform_action() { crateName="$(readCargoVariable name "$projectDir/Cargo.toml")" if [[ -f "$projectDir"/Xargo.toml ]]; then - features="--features=program" + features="--features=program" fi case "$1" in build) if [[ -f "$projectDir"/Xargo.toml ]]; then - echo "build $crateName ($projectDir)" - "$sdkDir"/rust/build.sh "$projectDir" + echo "build $crateName ($projectDir)" + "$sdkDir"/rust/build.sh "$projectDir" - so_path="$targetDir/$profile" - so_name="${crateName//\-/_}" - cp "$so_path/${so_name}.so" "$so_path/${so_name}_debug.so" - "$sdkDir"/dependencies/llvm-native/bin/llvm-objcopy --strip-all "$so_path/${so_name}.so" "$so_path/$so_name.so" + so_path="$targetDir/$profile" + so_name="${crateName//\-/_}" + cp "$so_path/${so_name}.so" "$so_path/${so_name}_debug.so" + "$sdkDir"/dependencies/llvm-native/bin/llvm-objcopy --strip-all "$so_path/${so_name}.so" "$so_path/$so_name.so" + if [ -f "$so_path/$so_name.so" ]; then + echo "Built: $so_path/$so_name.so" + else + echo "Error: No dump created" + exit 1 + fi else echo "$projectDir does not contain a program, skipping" fi @@ -197,9 +203,9 @@ if [[ $2 == "all" ]]; then else # Perform operation on requested project if [[ -d $2/program ]]; then - perform_action "$1" "$2/program" "${@:3}" + perform_action "$1" "$2/program" "${@:3}" else - perform_action "$1" "$2" "${@:3}" + perform_action "$1" "$2" "${@:3}" fi fi diff --git a/docs/src/shared-memory.md b/docs/src/shared-memory.md index 9d321f07ec8..e6a3cafd8b3 100644 --- a/docs/src/shared-memory.md +++ b/docs/src/shared-memory.md @@ -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 @@ -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. diff --git a/shared-memory/client/Cargo.lock b/shared-memory/client/Cargo.lock index 19e34266b58..1c3db9c1156 100644 --- a/shared-memory/client/Cargo.lock +++ b/shared-memory/client/Cargo.lock @@ -2028,7 +2028,7 @@ dependencies = [ ] [[package]] -name = "spl_shared_memory_perf_monitor" +name = "spl_shared_memory_client" version = "0.0.1" dependencies = [ "rand", diff --git a/shared-memory/client/tests/shared-memory.rs b/shared-memory/client/tests/shared-memory.rs index 6d09b1c3eb2..95272329bcc 100644 --- a/shared-memory/client/tests/shared-memory.rs +++ b/shared-memory/client/tests/shared-memory.rs @@ -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, ¶meter_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, ¶meter_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(), @@ -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, diff --git a/shared-memory/program/program-id.md b/shared-memory/program/program-id.md index 1333ed77b7e..087ba01b595 100644 --- a/shared-memory/program/program-id.md +++ b/shared-memory/program/program-id.md @@ -1 +1 @@ -TODO +shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL diff --git a/shared-memory/program/src/lib.rs b/shared-memory/program/src/lib.rs index e863af6c7c8..16a5d3bc16e 100644 --- a/shared-memory/program/src/lib.rs +++ b/shared-memory/program/src/lib.rs @@ -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, @@ -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]) { @@ -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 @@ -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, }