Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
60f2eaa
instruction-padding: Add a new program for padding instructions (#3608)
joncinque Oct 19, 2022
88c6235
Update repo to `edition = "2021"` (#3900)
joncinque Dec 13, 2022
8a5f9e0
chore: update maintainer references (#4008)
softalchemy Jan 31, 2023
7c7a28a
build(deps): bump num_enum from 0.5.7 to 0.5.9 (#4020)
dependabot[bot] Feb 2, 2023
144c3da
docs: Clarify audit status of all programs, no S word (#4046)
joncinque Feb 13, 2023
3f84f5b
build(deps): bump num_enum from 0.5.9 to 0.6.1 (#4156)
dependabot[bot] May 4, 2023
2f632b1
Update Solana to 1.16.1 and Rust to 1.69 (#4592)
joncinque Jun 26, 2023
958523f
Upgrade to solana 1.16.3 (#4679)
samkim-crypto Jul 13, 2023
9ca18ef
build(deps): bump num_enum from 0.6.1 to 0.7.0 (#5003)
dependabot[bot] Aug 14, 2023
67abb2d
Bump repo to 1.16.13 (#5324)
joncinque Sep 21, 2023
b365a58
chore: Bump Solana crates to 1.16.16 (#5494)
joncinque Oct 11, 2023
0df789f
ci: Bump repo to Solana 1.17 (#5575)
joncinque Oct 19, 2023
92f4bf8
build(deps): bump num_enum from 0.7.0 to 0.7.1 (#5687)
dependabot[bot] Oct 28, 2023
f8f6bf2
rustfmt: use entrypoint full path
Nov 8, 2023
978f5e7
repo: Update to 1.17.6 (#5863)
joncinque Nov 29, 2023
8d7dc54
build(deps): bump num_enum from 0.7.1 to 0.7.2 (#6072)
dependabot[bot] Jan 8, 2024
be4bd16
token 2022: upgrade sdk to 1.17.13 (#6147)
0x0ece Jan 18, 2024
c44b1fd
Update solana dependency version to allow 2.0.0 (#6182)
willhickey Jan 26, 2024
82a8b5b
Upgrade to solana 1.17.17 (#6189)
samkim-crypto Feb 1, 2024
f0421d0
Bump SPL crate versions (#6221)
willhickey Feb 5, 2024
5090db3
Upgrade to Solana 1.18.2 (#6278)
samkim-crypto Feb 29, 2024
31bf313
Bump solana version to 1.18.11 (#6624)
samkim-crypto Apr 24, 2024
925d210
deps: Upgrade to Solana v2 (#6908)
joncinque Jun 25, 2024
506c202
instruction-padding: Bump to 0.2.0 for Solana v2 support (#6912)
joncinque Jun 25, 2024
8239e2f
ci: Bump crates to Solana 2.0.3 (#7047)
joncinque Jul 25, 2024
b35c89c
build(deps): bump num_enum from 0.7.2 to 0.7.3 (#7071)
dependabot[bot] Jul 30, 2024
6917ddc
CI: Update to Solana v2.1 crates (#7416)
joncinque Oct 31, 2024
f96249c
instruction-padding: Remove solana-program dependency (#7436)
joncinque Nov 1, 2024
e33f82b
Publish spl-instruction-padding v0.3.0
github-actions[bot] Nov 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "spl-instruction-padding"
version = "0.3.0"
description = "Solana Program Library Instruction Padding Program"
authors = ["Solana Labs Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2021"

[features]
no-entrypoint = []
test-sbf = []

[dependencies]
num_enum = "0.7.3"
solana-account-info = "2.1.0"
solana-cpi = "2.1.0"
solana-instruction = { version = "2.1.0", features = ["std"] }
solana-program-entrypoint = "2.1.0"
solana-program-error = "2.1.0"
solana-pubkey = "2.1.0"

[dev-dependencies]
solana-program = "2.1.0"
solana-program-test = "2.1.0"
solana-sdk = "2.1.0"
static_assertions = "1.1.0"

[lib]
crate-type = ["cdylib", "lib"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
29 changes: 29 additions & 0 deletions program/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Instruction Pad Program

A program for padding instructions with additional data or accounts, to be used
for testing larger transactions, either more instruction data, or more accounts.

The main use-case is with solana-bench-tps, where we can see the impact of larger
transactions through TPS numbers. With that data, we can develop a fair fee model
for large transactions.

It operates with two instructions: no-op and wrap.

* No-op: simply an instruction with as much data and as many accounts as desired,
of which none will be used for processing.
* Wrap: before the padding data and accounts, accepts a real instruction and
required accounts, and performs a CPI into the program specified by the instruction

Both of these modes add the general overhead of calling a BPF program, and
the wrap mode adds the CPI overhead.

Because of the overhead, it's best to use the instruction padding program with
all large transaction tests, and comparing TPS numbers between:

* using the program with no padding
* using the program with data and account padding

## Audit

The repository [README](https://github.com/solana-labs/solana-program-library#audits)
contains information about program audits.
16 changes: 16 additions & 0 deletions program/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Program entrypoint

#![cfg(not(feature = "no-entrypoint"))]

use {
solana_account_info::AccountInfo, solana_program_error::ProgramResult, solana_pubkey::Pubkey,
};

solana_program_entrypoint::entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
crate::processor::process(program_id, accounts, instruction_data)
}
169 changes: 169 additions & 0 deletions program/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//! Instruction creators for large instructions

use {
num_enum::{IntoPrimitive, TryFromPrimitive},
solana_instruction::{AccountMeta, Instruction},
solana_program_error::ProgramError,
solana_pubkey::Pubkey,
std::{convert::TryInto, mem::size_of},
};

const MAX_CPI_ACCOUNT_INFOS: usize = 128;
const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024;

#[cfg(test)]
static_assertions::const_assert_eq!(
MAX_CPI_ACCOUNT_INFOS,
solana_program::syscalls::MAX_CPI_ACCOUNT_INFOS
);
#[cfg(test)]
static_assertions::const_assert_eq!(
MAX_CPI_INSTRUCTION_DATA_LEN,
solana_program::syscalls::MAX_CPI_INSTRUCTION_DATA_LEN
);

/// Instructions supported by the padding program, which takes in additional
/// account data or accounts and does nothing with them. It's meant for testing
/// larger transactions with bench-tps.
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum PadInstruction {
/// Does no work, but accepts a large amount of data and accounts
Noop,
/// Wraps the provided instruction, calling the provided program via CPI
///
/// Accounts expected by this instruction:
///
/// * All accounts required for the inner instruction
/// * The program invoked by the inner instruction
/// * Additional padding accounts
///
/// Data expected by this instruction:
/// * WrapData
Wrap,
}

/// Data wrapping any inner instruction
pub struct WrapData<'a> {
/// Number of accounts required by the inner instruction
pub num_accounts: u32,
/// the size of the inner instruction data
pub instruction_size: u32,
/// actual inner instruction data
pub instruction_data: &'a [u8],
// additional padding bytes come after, not captured in this struct
}

const U32_BYTES: usize = 4;
fn unpack_u32(input: &[u8]) -> Result<(u32, &[u8]), ProgramError> {
let value = input
.get(..U32_BYTES)
.and_then(|slice| slice.try_into().ok())
.map(u32::from_le_bytes)
.ok_or(ProgramError::InvalidInstructionData)?;
Ok((value, &input[U32_BYTES..]))
}

impl<'a> WrapData<'a> {
/// Unpacks instruction data
pub fn unpack(data: &'a [u8]) -> Result<Self, ProgramError> {
let (num_accounts, rest) = unpack_u32(data)?;
let (instruction_size, rest) = unpack_u32(rest)?;

let (instruction_data, _rest) = rest.split_at(instruction_size as usize);
Ok(Self {
num_accounts,
instruction_size,
instruction_data,
})
}
}

pub fn noop(
program_id: Pubkey,
padding_accounts: Vec<AccountMeta>,
padding_data: u32,
) -> Result<Instruction, ProgramError> {
let total_data_size = size_of::<u8>().saturating_add(padding_data as usize);
// crude, but can find a potential issue right away
if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
return Err(ProgramError::InvalidInstructionData);
}
let mut data = Vec::with_capacity(total_data_size);
data.push(PadInstruction::Noop.into());
for i in 0..padding_data {
data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
}

let num_accounts = padding_accounts.len().saturating_add(1);
if num_accounts > MAX_CPI_ACCOUNT_INFOS {
return Err(ProgramError::InvalidAccountData);
}
let mut accounts = Vec::with_capacity(num_accounts);
accounts.extend(padding_accounts);

Ok(Instruction {
program_id,
accounts,
data,
})
}

pub fn wrap_instruction(
program_id: Pubkey,
instruction: Instruction,
padding_accounts: Vec<AccountMeta>,
padding_data: u32,
) -> Result<Instruction, ProgramError> {
let total_data_size = size_of::<u8>()
.saturating_add(size_of::<u32>())
.saturating_add(size_of::<u32>())
.saturating_add(instruction.data.len())
.saturating_add(padding_data as usize);
// crude, but can find a potential issue right away
if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
return Err(ProgramError::InvalidInstructionData);
}
let mut data = Vec::with_capacity(total_data_size);
data.push(PadInstruction::Wrap.into());
let num_accounts: u32 = instruction
.accounts
.len()
.try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?;
data.extend(num_accounts.to_le_bytes().iter());

let data_size: u32 = instruction
.data
.len()
.try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?;
data.extend(data_size.to_le_bytes().iter());
data.extend(instruction.data);
for i in 0..padding_data {
data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
}

// The format for account data goes:
// * accounts required for the CPI
// * program account to call into
// * additional accounts may be included as padding or to test loading / locks
let num_accounts = instruction
.accounts
.len()
.saturating_add(1)
.saturating_add(padding_accounts.len());
if num_accounts > MAX_CPI_ACCOUNT_INFOS {
return Err(ProgramError::InvalidAccountData);
}
let mut accounts = Vec::with_capacity(num_accounts);
accounts.extend(instruction.accounts);
accounts.push(AccountMeta::new_readonly(instruction.program_id, false));
accounts.extend(padding_accounts);

Ok(Instruction {
program_id,
accounts,
data,
})
}
9 changes: 9 additions & 0 deletions program/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mod entrypoint;
pub mod instruction;
pub mod processor;

pub use {
solana_account_info, solana_cpi, solana_instruction, solana_program_entrypoint,
solana_program_error, solana_pubkey,
};
solana_pubkey::declare_id!("iXpADd6AW1k5FaaXum5qHbSqyd7TtoN6AD7suVa83MF");
54 changes: 54 additions & 0 deletions program/src/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use {
crate::instruction::{PadInstruction, WrapData},
solana_account_info::AccountInfo,
solana_cpi::invoke,
solana_instruction::{AccountMeta, Instruction},
solana_program_error::{ProgramError, ProgramResult},
solana_pubkey::Pubkey,
std::convert::TryInto,
};

pub fn process(
_program_id: &Pubkey,
account_infos: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let (tag, rest) = instruction_data
.split_first()
.ok_or(ProgramError::InvalidInstructionData)?;
match (*tag)
.try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?
{
PadInstruction::Noop => Ok(()),
PadInstruction::Wrap => {
let WrapData {
num_accounts,
instruction_size,
instruction_data,
} = WrapData::unpack(rest)?;
let mut data = Vec::with_capacity(instruction_size as usize);
data.extend_from_slice(instruction_data);

let program_id = *account_infos[num_accounts as usize].key;

let accounts = account_infos
.iter()
.take(num_accounts as usize)
.map(|a| AccountMeta {
pubkey: *a.key,
is_signer: a.is_signer,
is_writable: a.is_writable,
})
.collect::<Vec<_>>();

let instruction = Instruction {
program_id,
accounts,
data,
};

invoke(&instruction, &account_infos[..num_accounts as usize])
}
}
}
38 changes: 38 additions & 0 deletions program/tests/noop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#![cfg(feature = "test-sbf")]

use {
solana_program_test::{processor, tokio, ProgramTest},
solana_sdk::{
instruction::AccountMeta, pubkey::Pubkey, signature::Signer, transaction::Transaction,
},
spl_instruction_padding::{instruction::noop, processor::process},
};

#[tokio::test]
async fn success_with_noop() {
let program_id = Pubkey::new_unique();
let program_test = ProgramTest::new("spl_instruction_padding", program_id, processor!(process));

let context = program_test.start_with_context().await;

let padding_accounts = vec![
AccountMeta::new_readonly(Pubkey::new_unique(), false),
AccountMeta::new_readonly(Pubkey::new_unique(), false),
AccountMeta::new_readonly(Pubkey::new_unique(), false),
];

let padding_data = 800;

let transaction = Transaction::new_signed_with_payer(
&[noop(program_id, padding_accounts, padding_data).unwrap()],
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);

context
.banks_client
.process_transaction(transaction)
.await
.unwrap();
}
Loading