Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ libssz-types = "0.2"
# Build-time version info
vergen-git2 = { version = "9", features = ["rustc"] }

rayon = "1.11"
rand = "0.9"
rocksdb = "0.24"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
Expand Down
1 change: 1 addition & 0 deletions crates/blockchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ spawned-concurrency.workspace = true

tokio.workspace = true

rayon.workspace = true
thiserror.workspace = true
tracing.workspace = true

Expand Down
76 changes: 47 additions & 29 deletions crates/blockchain/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1164,40 +1164,58 @@ fn verify_signatures(
let validators = &state.validators;
let num_validators = validators.len() as u64;

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

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

// Collect public keys with bounds check in a single pass
let public_keys: Vec<_> = validator_indices(&attestation.aggregation_bits)
.map(|vid| {
if vid >= num_validators {
return Err(StoreError::InvalidValidatorIndex);
let slot: u32 = attestation.data.slot.try_into().expect("slot exceeds u32");
let message = attestation.data.hash_tree_root();

let public_keys: Vec<_> = validator_indices(&attestation.aggregation_bits)
.map(|vid| {
if vid >= num_validators {
return Err(StoreError::InvalidValidatorIndex);
}
validators[vid as usize]
.get_pubkey()
.map_err(|_| StoreError::PubkeyDecodingFailed(vid))
})
.collect::<Result<_, _>>()?;

Ok((&aggregated_proof.proof_data, public_keys, message, slot))
})
.collect::<Result<_, StoreError>>()?;

// Run expensive signature verification in parallel.
// into_par_iter() moves each tuple, avoiding a clone of public_keys.
use rayon::prelude::*;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Import inside function body

use rayon::prelude::* is placed mid-function. The codebase convention is to keep imports at the top of the file or at least at the top of the function; placing it inline between a comment and the expression it enables makes it easy to miss. Consider moving it to the top of the file alongside the other use statements.

Suggested change
use rayon::prelude::*;
use rayon::prelude::*;
// Run expensive signature verification in parallel
verification_inputs
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/blockchain/src/store.rs
Line: 1198

Comment:
**Import inside function body**

`use rayon::prelude::*` is placed mid-function. The codebase convention is to keep imports at the top of the file or at least at the top of the function; placing it inline between a comment and the expression it enables makes it easy to miss. Consider moving it to the top of the file alongside the other `use` statements.

```suggestion
    use rayon::prelude::*;
    // Run expensive signature verification in parallel
    verification_inputs
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

verification_inputs.into_par_iter().try_for_each(
|(proof_data, public_keys, message, slot)| {
let result = {
let _timing = metrics::time_pq_sig_aggregated_signatures_verification();
verify_aggregated_signature(proof_data, public_keys, &message, slot)
};
match result {
Ok(()) => {
metrics::inc_pq_sig_aggregated_signatures_valid();
Ok(())
}
Err(e) => {
metrics::inc_pq_sig_aggregated_signatures_invalid();
Err(StoreError::AggregateVerificationFailed(e))
}
validators[vid as usize]
.get_pubkey()
.map_err(|_| StoreError::PubkeyDecodingFailed(vid))
})
.collect::<Result<_, _>>()?;

let verification_result = {
let _timing = metrics::time_pq_sig_aggregated_signatures_verification();
verify_aggregated_signature(&aggregated_proof.proof_data, public_keys, &message, slot)
};
match verification_result {
Ok(()) => metrics::inc_pq_sig_aggregated_signatures_valid(),
Err(e) => {
metrics::inc_pq_sig_aggregated_signatures_invalid();
return Err(StoreError::AggregateVerificationFailed(e));
}
}
}
},
)?;

let aggregated_elapsed = aggregated_start.elapsed();

let proposer_start = std::time::Instant::now();
Expand Down
Loading