Skip to content

Commit e45113b

Browse files
committed
perf: parallelize attestation signature verification with rayon
Signature verification dominated block processing time (~40-50ms per attestation, sequential). With 36 attestations this meant ~1.4-1.9s per block, causing the target to fall hundreds of slots behind the head. Split into two phases: 1. Prepare inputs sequentially (cheap: bit checks, pubkey lookups) 2. Verify signatures in parallel via rayon's par_iter On a 4-core machine with 36 attestations, expect ~4x speedup (1.4s -> ~350ms).
1 parent 06c4306 commit e45113b

File tree

4 files changed

+47
-29
lines changed

4 files changed

+47
-29
lines changed

Cargo.lock

Lines changed: 1 addition & 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
@@ -66,6 +66,7 @@ libssz-types = "0.2"
6666
# Build-time version info
6767
vergen-git2 = { version = "9", features = ["rustc"] }
6868

69+
rayon = "1.10"
6970
rand = "0.9"
7071
rocksdb = "0.24"
7172
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }

crates/blockchain/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ spawned-concurrency.workspace = true
2323

2424
tokio.workspace = true
2525

26+
rayon.workspace = true
2627
thiserror.workspace = true
2728
tracing.workspace = true
2829

crates/blockchain/src/store.rs

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,40 +1164,55 @@ fn verify_signatures(
11641164
let validators = &state.validators;
11651165
let num_validators = validators.len() as u64;
11661166

1167-
// Verify each attestation's signature proof
1167+
// Verify each attestation's signature proof in parallel
11681168
let aggregated_start = std::time::Instant::now();
1169-
for (attestation, aggregated_proof) in attestations.iter().zip(attestation_signatures) {
1170-
if attestation.aggregation_bits != aggregated_proof.participants {
1171-
return Err(StoreError::ParticipantsMismatch);
1172-
}
11731169

1174-
let slot: u32 = attestation.data.slot.try_into().expect("slot exceeds u32");
1175-
let message = attestation.data.hash_tree_root();
1170+
// Prepare verification inputs sequentially (cheap: bit checks + pubkey lookups)
1171+
let verification_inputs: Vec<_> = attestations
1172+
.iter()
1173+
.zip(attestation_signatures)
1174+
.map(|(attestation, aggregated_proof)| {
1175+
if attestation.aggregation_bits != aggregated_proof.participants {
1176+
return Err(StoreError::ParticipantsMismatch);
1177+
}
11761178

1177-
// Collect public keys with bounds check in a single pass
1178-
let public_keys: Vec<_> = validator_indices(&attestation.aggregation_bits)
1179-
.map(|vid| {
1180-
if vid >= num_validators {
1181-
return Err(StoreError::InvalidValidatorIndex);
1179+
let slot: u32 = attestation.data.slot.try_into().expect("slot exceeds u32");
1180+
let message = attestation.data.hash_tree_root();
1181+
1182+
let public_keys: Vec<_> = validator_indices(&attestation.aggregation_bits)
1183+
.map(|vid| {
1184+
if vid >= num_validators {
1185+
return Err(StoreError::InvalidValidatorIndex);
1186+
}
1187+
validators[vid as usize]
1188+
.get_pubkey()
1189+
.map_err(|_| StoreError::PubkeyDecodingFailed(vid))
1190+
})
1191+
.collect::<Result<_, _>>()?;
1192+
1193+
Ok((&aggregated_proof.proof_data, public_keys, message, slot))
1194+
})
1195+
.collect::<Result<_, StoreError>>()?;
1196+
1197+
// Run expensive signature verification in parallel
1198+
use rayon::prelude::*;
1199+
verification_inputs
1200+
.par_iter()
1201+
.try_for_each(|(proof_data, public_keys, message, slot)| {
1202+
let result =
1203+
verify_aggregated_signature(proof_data, public_keys.clone(), message, *slot);
1204+
match result {
1205+
Ok(()) => {
1206+
metrics::inc_pq_sig_aggregated_signatures_valid();
1207+
Ok(())
1208+
}
1209+
Err(e) => {
1210+
metrics::inc_pq_sig_aggregated_signatures_invalid();
1211+
Err(StoreError::AggregateVerificationFailed(e))
11821212
}
1183-
validators[vid as usize]
1184-
.get_pubkey()
1185-
.map_err(|_| StoreError::PubkeyDecodingFailed(vid))
1186-
})
1187-
.collect::<Result<_, _>>()?;
1188-
1189-
let verification_result = {
1190-
let _timing = metrics::time_pq_sig_aggregated_signatures_verification();
1191-
verify_aggregated_signature(&aggregated_proof.proof_data, public_keys, &message, slot)
1192-
};
1193-
match verification_result {
1194-
Ok(()) => metrics::inc_pq_sig_aggregated_signatures_valid(),
1195-
Err(e) => {
1196-
metrics::inc_pq_sig_aggregated_signatures_invalid();
1197-
return Err(StoreError::AggregateVerificationFailed(e));
11981213
}
1199-
}
1200-
}
1214+
})?;
1215+
12011216
let aggregated_elapsed = aggregated_start.elapsed();
12021217

12031218
let proposer_start = std::time::Instant::now();

0 commit comments

Comments
 (0)