Skip to content

Commit 0f9981c

Browse files
authored
program: Port history from SPL (#3)
1 parent 4bc502f commit 0f9981c

File tree

8 files changed

+411
-0
lines changed

8 files changed

+411
-0
lines changed

program/Cargo.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
name = "spl-instruction-padding"
3+
version = "0.3.0"
4+
description = "Solana Program Library Instruction Padding Program"
5+
authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
homepage = "https://solana.com/"
9+
edition = "2021"
10+
11+
[features]
12+
no-entrypoint = []
13+
test-sbf = []
14+
15+
[dependencies]
16+
num_enum = "0.7.3"
17+
solana-account-info = "2.1.0"
18+
solana-cpi = "2.1.0"
19+
solana-instruction = { version = "2.1.0", features = ["std"] }
20+
solana-program-entrypoint = "2.1.0"
21+
solana-program-error = "2.1.0"
22+
solana-pubkey = "2.1.0"
23+
24+
[dev-dependencies]
25+
solana-program = "2.1.0"
26+
solana-program-test = "2.1.0"
27+
solana-sdk = "2.1.0"
28+
static_assertions = "1.1.0"
29+
30+
[lib]
31+
crate-type = ["cdylib", "lib"]
32+
33+
[package.metadata.docs.rs]
34+
targets = ["x86_64-unknown-linux-gnu"]

program/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Instruction Pad Program
2+
3+
A program for padding instructions with additional data or accounts, to be used
4+
for testing larger transactions, either more instruction data, or more accounts.
5+
6+
The main use-case is with solana-bench-tps, where we can see the impact of larger
7+
transactions through TPS numbers. With that data, we can develop a fair fee model
8+
for large transactions.
9+
10+
It operates with two instructions: no-op and wrap.
11+
12+
* No-op: simply an instruction with as much data and as many accounts as desired,
13+
of which none will be used for processing.
14+
* Wrap: before the padding data and accounts, accepts a real instruction and
15+
required accounts, and performs a CPI into the program specified by the instruction
16+
17+
Both of these modes add the general overhead of calling a BPF program, and
18+
the wrap mode adds the CPI overhead.
19+
20+
Because of the overhead, it's best to use the instruction padding program with
21+
all large transaction tests, and comparing TPS numbers between:
22+
23+
* using the program with no padding
24+
* using the program with data and account padding
25+
26+
## Audit
27+
28+
The repository [README](https://github.com/solana-labs/solana-program-library#audits)
29+
contains information about program audits.

program/src/entrypoint.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! Program entrypoint
2+
3+
#![cfg(not(feature = "no-entrypoint"))]
4+
5+
use {
6+
solana_account_info::AccountInfo, solana_program_error::ProgramResult, solana_pubkey::Pubkey,
7+
};
8+
9+
solana_program_entrypoint::entrypoint!(process_instruction);
10+
fn process_instruction(
11+
program_id: &Pubkey,
12+
accounts: &[AccountInfo],
13+
instruction_data: &[u8],
14+
) -> ProgramResult {
15+
crate::processor::process(program_id, accounts, instruction_data)
16+
}

program/src/instruction.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//! Instruction creators for large instructions
2+
3+
use {
4+
num_enum::{IntoPrimitive, TryFromPrimitive},
5+
solana_instruction::{AccountMeta, Instruction},
6+
solana_program_error::ProgramError,
7+
solana_pubkey::Pubkey,
8+
std::{convert::TryInto, mem::size_of},
9+
};
10+
11+
const MAX_CPI_ACCOUNT_INFOS: usize = 128;
12+
const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024;
13+
14+
#[cfg(test)]
15+
static_assertions::const_assert_eq!(
16+
MAX_CPI_ACCOUNT_INFOS,
17+
solana_program::syscalls::MAX_CPI_ACCOUNT_INFOS
18+
);
19+
#[cfg(test)]
20+
static_assertions::const_assert_eq!(
21+
MAX_CPI_INSTRUCTION_DATA_LEN,
22+
solana_program::syscalls::MAX_CPI_INSTRUCTION_DATA_LEN
23+
);
24+
25+
/// Instructions supported by the padding program, which takes in additional
26+
/// account data or accounts and does nothing with them. It's meant for testing
27+
/// larger transactions with bench-tps.
28+
#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
29+
#[repr(u8)]
30+
pub enum PadInstruction {
31+
/// Does no work, but accepts a large amount of data and accounts
32+
Noop,
33+
/// Wraps the provided instruction, calling the provided program via CPI
34+
///
35+
/// Accounts expected by this instruction:
36+
///
37+
/// * All accounts required for the inner instruction
38+
/// * The program invoked by the inner instruction
39+
/// * Additional padding accounts
40+
///
41+
/// Data expected by this instruction:
42+
/// * WrapData
43+
Wrap,
44+
}
45+
46+
/// Data wrapping any inner instruction
47+
pub struct WrapData<'a> {
48+
/// Number of accounts required by the inner instruction
49+
pub num_accounts: u32,
50+
/// the size of the inner instruction data
51+
pub instruction_size: u32,
52+
/// actual inner instruction data
53+
pub instruction_data: &'a [u8],
54+
// additional padding bytes come after, not captured in this struct
55+
}
56+
57+
const U32_BYTES: usize = 4;
58+
fn unpack_u32(input: &[u8]) -> Result<(u32, &[u8]), ProgramError> {
59+
let value = input
60+
.get(..U32_BYTES)
61+
.and_then(|slice| slice.try_into().ok())
62+
.map(u32::from_le_bytes)
63+
.ok_or(ProgramError::InvalidInstructionData)?;
64+
Ok((value, &input[U32_BYTES..]))
65+
}
66+
67+
impl<'a> WrapData<'a> {
68+
/// Unpacks instruction data
69+
pub fn unpack(data: &'a [u8]) -> Result<Self, ProgramError> {
70+
let (num_accounts, rest) = unpack_u32(data)?;
71+
let (instruction_size, rest) = unpack_u32(rest)?;
72+
73+
let (instruction_data, _rest) = rest.split_at(instruction_size as usize);
74+
Ok(Self {
75+
num_accounts,
76+
instruction_size,
77+
instruction_data,
78+
})
79+
}
80+
}
81+
82+
pub fn noop(
83+
program_id: Pubkey,
84+
padding_accounts: Vec<AccountMeta>,
85+
padding_data: u32,
86+
) -> Result<Instruction, ProgramError> {
87+
let total_data_size = size_of::<u8>().saturating_add(padding_data as usize);
88+
// crude, but can find a potential issue right away
89+
if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
90+
return Err(ProgramError::InvalidInstructionData);
91+
}
92+
let mut data = Vec::with_capacity(total_data_size);
93+
data.push(PadInstruction::Noop.into());
94+
for i in 0..padding_data {
95+
data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
96+
}
97+
98+
let num_accounts = padding_accounts.len().saturating_add(1);
99+
if num_accounts > MAX_CPI_ACCOUNT_INFOS {
100+
return Err(ProgramError::InvalidAccountData);
101+
}
102+
let mut accounts = Vec::with_capacity(num_accounts);
103+
accounts.extend(padding_accounts);
104+
105+
Ok(Instruction {
106+
program_id,
107+
accounts,
108+
data,
109+
})
110+
}
111+
112+
pub fn wrap_instruction(
113+
program_id: Pubkey,
114+
instruction: Instruction,
115+
padding_accounts: Vec<AccountMeta>,
116+
padding_data: u32,
117+
) -> Result<Instruction, ProgramError> {
118+
let total_data_size = size_of::<u8>()
119+
.saturating_add(size_of::<u32>())
120+
.saturating_add(size_of::<u32>())
121+
.saturating_add(instruction.data.len())
122+
.saturating_add(padding_data as usize);
123+
// crude, but can find a potential issue right away
124+
if total_data_size > MAX_CPI_INSTRUCTION_DATA_LEN as usize {
125+
return Err(ProgramError::InvalidInstructionData);
126+
}
127+
let mut data = Vec::with_capacity(total_data_size);
128+
data.push(PadInstruction::Wrap.into());
129+
let num_accounts: u32 = instruction
130+
.accounts
131+
.len()
132+
.try_into()
133+
.map_err(|_| ProgramError::InvalidInstructionData)?;
134+
data.extend(num_accounts.to_le_bytes().iter());
135+
136+
let data_size: u32 = instruction
137+
.data
138+
.len()
139+
.try_into()
140+
.map_err(|_| ProgramError::InvalidInstructionData)?;
141+
data.extend(data_size.to_le_bytes().iter());
142+
data.extend(instruction.data);
143+
for i in 0..padding_data {
144+
data.push(i.checked_rem(u8::MAX as u32).unwrap() as u8);
145+
}
146+
147+
// The format for account data goes:
148+
// * accounts required for the CPI
149+
// * program account to call into
150+
// * additional accounts may be included as padding or to test loading / locks
151+
let num_accounts = instruction
152+
.accounts
153+
.len()
154+
.saturating_add(1)
155+
.saturating_add(padding_accounts.len());
156+
if num_accounts > MAX_CPI_ACCOUNT_INFOS {
157+
return Err(ProgramError::InvalidAccountData);
158+
}
159+
let mut accounts = Vec::with_capacity(num_accounts);
160+
accounts.extend(instruction.accounts);
161+
accounts.push(AccountMeta::new_readonly(instruction.program_id, false));
162+
accounts.extend(padding_accounts);
163+
164+
Ok(Instruction {
165+
program_id,
166+
accounts,
167+
data,
168+
})
169+
}

program/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
mod entrypoint;
2+
pub mod instruction;
3+
pub mod processor;
4+
5+
pub use {
6+
solana_account_info, solana_cpi, solana_instruction, solana_program_entrypoint,
7+
solana_program_error, solana_pubkey,
8+
};
9+
solana_pubkey::declare_id!("iXpADd6AW1k5FaaXum5qHbSqyd7TtoN6AD7suVa83MF");

program/src/processor.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use {
2+
crate::instruction::{PadInstruction, WrapData},
3+
solana_account_info::AccountInfo,
4+
solana_cpi::invoke,
5+
solana_instruction::{AccountMeta, Instruction},
6+
solana_program_error::{ProgramError, ProgramResult},
7+
solana_pubkey::Pubkey,
8+
std::convert::TryInto,
9+
};
10+
11+
pub fn process(
12+
_program_id: &Pubkey,
13+
account_infos: &[AccountInfo],
14+
instruction_data: &[u8],
15+
) -> ProgramResult {
16+
let (tag, rest) = instruction_data
17+
.split_first()
18+
.ok_or(ProgramError::InvalidInstructionData)?;
19+
match (*tag)
20+
.try_into()
21+
.map_err(|_| ProgramError::InvalidInstructionData)?
22+
{
23+
PadInstruction::Noop => Ok(()),
24+
PadInstruction::Wrap => {
25+
let WrapData {
26+
num_accounts,
27+
instruction_size,
28+
instruction_data,
29+
} = WrapData::unpack(rest)?;
30+
let mut data = Vec::with_capacity(instruction_size as usize);
31+
data.extend_from_slice(instruction_data);
32+
33+
let program_id = *account_infos[num_accounts as usize].key;
34+
35+
let accounts = account_infos
36+
.iter()
37+
.take(num_accounts as usize)
38+
.map(|a| AccountMeta {
39+
pubkey: *a.key,
40+
is_signer: a.is_signer,
41+
is_writable: a.is_writable,
42+
})
43+
.collect::<Vec<_>>();
44+
45+
let instruction = Instruction {
46+
program_id,
47+
accounts,
48+
data,
49+
};
50+
51+
invoke(&instruction, &account_infos[..num_accounts as usize])
52+
}
53+
}
54+
}

program/tests/noop.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#![cfg(feature = "test-sbf")]
2+
3+
use {
4+
solana_program_test::{processor, tokio, ProgramTest},
5+
solana_sdk::{
6+
instruction::AccountMeta, pubkey::Pubkey, signature::Signer, transaction::Transaction,
7+
},
8+
spl_instruction_padding::{instruction::noop, processor::process},
9+
};
10+
11+
#[tokio::test]
12+
async fn success_with_noop() {
13+
let program_id = Pubkey::new_unique();
14+
let program_test = ProgramTest::new("spl_instruction_padding", program_id, processor!(process));
15+
16+
let context = program_test.start_with_context().await;
17+
18+
let padding_accounts = vec![
19+
AccountMeta::new_readonly(Pubkey::new_unique(), false),
20+
AccountMeta::new_readonly(Pubkey::new_unique(), false),
21+
AccountMeta::new_readonly(Pubkey::new_unique(), false),
22+
];
23+
24+
let padding_data = 800;
25+
26+
let transaction = Transaction::new_signed_with_payer(
27+
&[noop(program_id, padding_accounts, padding_data).unwrap()],
28+
Some(&context.payer.pubkey()),
29+
&[&context.payer],
30+
context.last_blockhash,
31+
);
32+
33+
context
34+
.banks_client
35+
.process_transaction(transaction)
36+
.await
37+
.unwrap();
38+
}

0 commit comments

Comments
 (0)