Skip to content

Commit

Permalink
runtime-transaction: get_signature_details (#2847)
Browse files Browse the repository at this point in the history
  • Loading branch information
apfitzge authored Sep 25, 2024
1 parent d22e8e3 commit 105c365
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions programs/sbf/Cargo.lock

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

5 changes: 5 additions & 0 deletions runtime-transaction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ agave-transaction-view = { workspace = true }
log = { workspace = true }
solana-builtins-default-costs = { workspace = true }
solana-compute-budget = { workspace = true }
solana-pubkey = { workspace = true }
solana-sdk = { workspace = true }
solana-svm-transaction = { workspace = true }
thiserror = { workspace = true }
Expand All @@ -35,5 +36,9 @@ targets = ["x86_64-unknown-linux-gnu"]
name = "process_compute_budget_instructions"
harness = false

[[bench]]
name = "get_signature_details"
harness = false

[lints]
workspace = true
132 changes: 132 additions & 0 deletions runtime-transaction/benches/get_signature_details.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use {
criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput},
solana_runtime_transaction::signature_details::get_precompile_signature_details,
solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey},
solana_svm_transaction::instruction::SVMInstruction,
};

fn bench_get_signature_details_empty(c: &mut Criterion) {
let instructions = std::iter::empty();

c.benchmark_group("bench_get_signature_details_empty")
.throughput(Throughput::Elements(1))
.bench_function("0 instructions", |bencher| {
bencher.iter(|| {
let instructions = black_box(instructions.clone());
let _ = get_precompile_signature_details(instructions);
});
});
}

fn bench_get_signature_details_no_sigs_unique(c: &mut Criterion) {
let program_ids = vec![Pubkey::new_unique(); 32];
for num_instructions in [4, 32] {
let instructions = (0..num_instructions)
.map(|i| {
let program_id = &program_ids[i];
(
program_id,
CompiledInstruction {
program_id_index: i as u8,
accounts: vec![],
data: vec![],
},
)
})
.collect::<Vec<_>>();

c.benchmark_group("bench_get_signature_details_no_sigs_unique")
.throughput(Throughput::Elements(1))
.bench_function(format!("{num_instructions} instructions"), |bencher| {
bencher.iter(|| {
let instructions =
black_box(instructions.iter().map(|(program_id, instruction)| {
(*program_id, SVMInstruction::from(instruction))
}));
let _ = get_precompile_signature_details(instructions);
});
});
}
}

fn bench_get_signature_details_packed_sigs(c: &mut Criterion) {
let program_ids = [
solana_sdk::secp256k1_program::id(),
solana_sdk::ed25519_program::id(),
];
for num_instructions in [4, 64] {
let instructions = (0..num_instructions)
.map(|i| {
let index = i % 2;
let program_id = &program_ids[index];
(
program_id,
CompiledInstruction {
program_id_index: index as u8,
accounts: vec![],
data: vec![4], // some dummy number of signatures
},
)
})
.collect::<Vec<_>>();

c.benchmark_group("bench_get_signature_details_packed_sigs")
.throughput(Throughput::Elements(1))
.bench_function(format!("{num_instructions} instructions"), |bencher| {
bencher.iter(|| {
let instructions =
black_box(instructions.iter().map(|(program_id, instruction)| {
(*program_id, SVMInstruction::from(instruction))
}));
let _ = get_precompile_signature_details(instructions);
});
});
}
}

fn bench_get_signature_details_mixed_sigs(c: &mut Criterion) {
let program_ids = [
solana_sdk::secp256k1_program::id(),
solana_sdk::ed25519_program::id(),
]
.into_iter()
.chain((0..6).map(|_| Pubkey::new_unique()))
.collect::<Vec<_>>();
for num_instructions in [4, 64] {
let instructions = (0..num_instructions)
.map(|i| {
let index = i % 8;
let program_id = &program_ids[index];
(
program_id,
CompiledInstruction {
program_id_index: index as u8,
accounts: vec![],
data: vec![4], // some dummy number of signatures
},
)
})
.collect::<Vec<_>>();

c.benchmark_group("bench_get_signature_details_mixed_sigs")
.throughput(Throughput::Elements(1))
.bench_function(format!("{num_instructions} instructions"), |bencher| {
bencher.iter(|| {
let instructions =
black_box(instructions.iter().map(|(program_id, instruction)| {
(*program_id, SVMInstruction::from(instruction))
}));
let _ = get_precompile_signature_details(instructions);
});
});
}
}

criterion_group!(
benches,
bench_get_signature_details_empty,
bench_get_signature_details_no_sigs_unique,
bench_get_signature_details_packed_sigs,
bench_get_signature_details_mixed_sigs
);
criterion_main!(benches);
1 change: 1 addition & 0 deletions runtime-transaction/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ mod compute_budget_instruction_details;
mod compute_budget_program_id_filter;
pub mod instructions_processor;
pub mod runtime_transaction;
pub mod signature_details;
pub mod transaction_meta;
173 changes: 173 additions & 0 deletions runtime-transaction/src/signature_details.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// static account keys has max
use {
agave_transaction_view::static_account_keys_frame::MAX_STATIC_ACCOUNTS_PER_PACKET as FILTER_SIZE,
solana_pubkey::Pubkey, solana_svm_transaction::instruction::SVMInstruction,
};

pub struct PrecompileSignatureDetails {
pub num_secp256k1_instruction_signatures: u64,
pub num_ed25519_instruction_signatures: u64,
}

/// Get transaction signature details.
pub fn get_precompile_signature_details<'a>(
instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
) -> PrecompileSignatureDetails {
let mut filter = SignatureDetailsFilter::new();

// Wrapping arithmetic is safe below because the maximum number of signatures
// per instruction is 255, and the maximum number of instructions per transaction
// is low enough that the sum of all signatures will not overflow a u64.
let mut num_secp256k1_instruction_signatures: u64 = 0;
let mut num_ed25519_instruction_signatures: u64 = 0;
for (program_id, instruction) in instructions {
let program_id_index = instruction.program_id_index;
match filter.is_signature(program_id_index, program_id) {
ProgramIdStatus::NotSignature => {}
ProgramIdStatus::Secp256k1 => {
num_secp256k1_instruction_signatures = num_secp256k1_instruction_signatures
.wrapping_add(get_num_signatures_in_instruction(&instruction));
}
ProgramIdStatus::Ed25519 => {
num_ed25519_instruction_signatures = num_ed25519_instruction_signatures
.wrapping_add(get_num_signatures_in_instruction(&instruction));
}
}
}

PrecompileSignatureDetails {
num_secp256k1_instruction_signatures,
num_ed25519_instruction_signatures,
}
}

#[inline]
fn get_num_signatures_in_instruction(instruction: &SVMInstruction) -> u64 {
u64::from(instruction.data.first().copied().unwrap_or(0))
}

#[derive(Copy, Clone)]
enum ProgramIdStatus {
NotSignature,
Secp256k1,
Ed25519,
}

struct SignatureDetailsFilter {
// array of slots for all possible static and sanitized program_id_index,
// each slot indicates if a program_id_index has not been checked, or is
// already checked with result that can be reused.
flags: [Option<ProgramIdStatus>; FILTER_SIZE as usize],
}

impl SignatureDetailsFilter {
#[inline]
fn new() -> Self {
Self {
flags: [None; FILTER_SIZE as usize],
}
}

#[inline]
fn is_signature(&mut self, index: u8, program_id: &Pubkey) -> ProgramIdStatus {
let flag = &mut self.flags[usize::from(index)];
match flag {
Some(status) => *status,
None => {
*flag = Some(Self::check_program_id(program_id));
*flag.as_ref().unwrap()
}
}
}

#[inline]
fn check_program_id(program_id: &Pubkey) -> ProgramIdStatus {
if program_id == &solana_sdk::secp256k1_program::ID {
ProgramIdStatus::Secp256k1
} else if program_id == &solana_sdk::ed25519_program::ID {
ProgramIdStatus::Ed25519
} else {
ProgramIdStatus::NotSignature
}
}
}

#[cfg(test)]
mod tests {
use super::*;

// simple convenience function so avoid having inconsistent program_id and program_id_index
fn make_instruction<'a>(
program_ids: &'a [Pubkey],
program_id_index: u8,
data: &'a [u8],
) -> (&'a Pubkey, SVMInstruction<'a>) {
(
&program_ids[program_id_index as usize],
SVMInstruction {
program_id_index,
accounts: &[],
data,
},
)
}

#[test]
fn test_get_signature_details_no_instructions() {
let instructions = std::iter::empty();
let signature_details = get_precompile_signature_details(instructions);

assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
}

#[test]
fn test_get_signature_details_no_sigs_unique() {
let program_ids = [Pubkey::new_unique(), Pubkey::new_unique()];
let instructions = [
make_instruction(&program_ids, 0, &[]),
make_instruction(&program_ids, 1, &[]),
];

let signature_details = get_precompile_signature_details(instructions.into_iter());
assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
}

#[test]
fn test_get_signature_details_signatures_mixed() {
let program_ids = [
Pubkey::new_unique(),
solana_sdk::secp256k1_program::ID,
solana_sdk::ed25519_program::ID,
];
let instructions = [
make_instruction(&program_ids, 1, &[5]),
make_instruction(&program_ids, 2, &[3]),
make_instruction(&program_ids, 0, &[]),
make_instruction(&program_ids, 2, &[2]),
make_instruction(&program_ids, 1, &[1]),
make_instruction(&program_ids, 0, &[]),
];

let signature_details = get_precompile_signature_details(instructions.into_iter());
assert_eq!(signature_details.num_secp256k1_instruction_signatures, 6);
assert_eq!(signature_details.num_ed25519_instruction_signatures, 5);
}

#[test]
fn test_get_signature_details_missing_num_signatures() {
let program_ids = [
solana_sdk::secp256k1_program::ID,
solana_sdk::ed25519_program::ID,
];
let instructions = [
make_instruction(&program_ids, 0, &[]),
make_instruction(&program_ids, 1, &[]),
];

let signature_details = get_precompile_signature_details(instructions.into_iter());
assert_eq!(signature_details.num_secp256k1_instruction_signatures, 0);
assert_eq!(signature_details.num_ed25519_instruction_signatures, 0);
}
}
12 changes: 12 additions & 0 deletions sdk/program/src/message/sanitized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,18 @@ pub struct TransactionSignatureDetails {
}

impl TransactionSignatureDetails {
pub fn new(
num_transaction_signatures: u64,
num_secp256k1_instruction_signatures: u64,
num_ed25519_instruction_signatures: u64,
) -> Self {
Self {
num_transaction_signatures,
num_secp256k1_instruction_signatures,
num_ed25519_instruction_signatures,
}
}

/// return total number of signature, treating pre-processor operations as signature
pub(crate) fn total_signatures(&self) -> u64 {
self.num_transaction_signatures
Expand Down

0 comments on commit 105c365

Please sign in to comment.