Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 944bb96

Browse files
authored
instruction-padding: Add a new program for padding instructions (#3608)
1 parent d02d49e commit 944bb96

File tree

11 files changed

+459
-0
lines changed

11 files changed

+459
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Instruction Pad Pull Request
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'instruction-padding/**'
7+
- 'ci/*-version.sh'
8+
- '.github/workflows/pull-request-instruction-padding.yml'
9+
push:
10+
branches: [master]
11+
paths:
12+
- 'instruction-padding/**'
13+
- 'ci/*-version.sh'
14+
- '.github/workflows/pull-request-instruction-padding.yml'
15+
16+
jobs:
17+
cargo-test-sbf:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v2
21+
22+
- name: Set env vars
23+
run: |
24+
source ci/rust-version.sh
25+
echo "RUST_STABLE=$rust_stable" >> $GITHUB_ENV
26+
source ci/solana-version.sh
27+
echo "SOLANA_VERSION=$solana_version" >> $GITHUB_ENV
28+
29+
- uses: actions-rs/toolchain@v1
30+
with:
31+
toolchain: ${{ env.RUST_STABLE }}
32+
override: true
33+
profile: minimal
34+
35+
- uses: actions/cache@v2
36+
with:
37+
path: |
38+
~/.cargo/registry
39+
~/.cargo/git
40+
key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}}
41+
42+
- uses: actions/cache@v2
43+
with:
44+
path: |
45+
~/.cargo/bin/rustfilt
46+
key: cargo-sbf-bins-${{ runner.os }}
47+
48+
- uses: actions/cache@v2
49+
with:
50+
path: ~/.cache/solana
51+
key: solana-${{ env.SOLANA_VERSION }}
52+
53+
- name: Install dependencies
54+
run: |
55+
./ci/install-build-deps.sh
56+
./ci/install-program-deps.sh
57+
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
58+
59+
- name: Build and test
60+
run: ./ci/cargo-test-sbf.sh instruction-padding

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ members = [
1717
"governance/test-sdk",
1818
"governance/tools",
1919
"governance/chat/program",
20+
"instruction-padding/program",
2021
"libraries/math",
2122
"libraries/concurrent-merkle-tree",
2223
"libraries/merkle-tree-reference",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "spl-instruction-padding"
3+
version = "0.1.0"
4+
description = "Solana Program Library Instruction Padding Program"
5+
authors = ["Solana Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
homepage = "https://solana.com/"
9+
edition = "2018"
10+
11+
[features]
12+
no-entrypoint = []
13+
test-sbf = []
14+
15+
[dependencies]
16+
num_enum = "0.5.4"
17+
solana-program = "1.14.4"
18+
19+
[dev-dependencies]
20+
solana-program-test = "1.14.4"
21+
solana-sdk = "1.14.4"
22+
23+
[lib]
24+
crate-type = ["cdylib", "lib"]
25+
26+
[package.metadata.docs.rs]
27+
targets = ["x86_64-unknown-linux-gnu"]

instruction-padding/program/README.md

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

0 commit comments

Comments
 (0)