Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parallelize signature verif #3095

Closed
wants to merge 6 commits into from
Closed
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
61 changes: 39 additions & 22 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions massa-protocol-worker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ edition = "2021"
serde_json = "1.0"
tokio = { version = "1.21", features = ["full"] }
tracing = "0.1"
rayon = "1.5"
# custom modules
massa_hash = { path = "../massa-hash" }
massa_logging = { path = "../massa-logging" }
Expand All @@ -20,6 +21,7 @@ massa_protocol_exports = { path = "../massa-protocol-exports" }
massa_serialization = { path = "../massa-serialization" }
massa_storage = { path = "../massa-storage" }
massa_time = { path = "../massa-time" }
massa_signature = { path = "../massa-signature" }

[dev-dependencies]
lazy_static = "1.4"
Expand Down
1 change: 1 addition & 0 deletions massa-protocol-worker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub use protocol_worker::start_protocol_controller;
mod checked_operations;
mod node_info;
mod protocol_network;
mod sig_verifier;

#[cfg(test)]
pub mod tests;
11 changes: 10 additions & 1 deletion massa-protocol-worker/src/protocol_worker.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Copyright (c) 2022 MASSA LABS <[email protected]>

use crate::checked_operations::CheckedOperations;
use crate::sig_verifier::verify_sigs_batch;
use crate::{node_info::NodeInfo, worker_operations_impl::OperationBatchBuffer};

use massa_logging::massa_trace;
Expand All @@ -22,6 +23,7 @@ use massa_protocol_exports::{
ProtocolEventReceiver, ProtocolManagementCommand, ProtocolManager,
};

use massa_models::wrapped::Id;
use massa_storage::Storage;
use massa_time::{MassaTime, TimeError};
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -922,11 +924,18 @@ impl ProtocolWorker {
// Check operation signature only if not already checked.
if self.checked_operations.insert(&operation_id) {
// check signature if the operation wasn't in `checked_operation`
operation.verify_signature()?;
new_operations.insert(operation_id, operation);
};
}

// optimized signature verification
verify_sigs_batch(
&new_operations
.iter()
.map(|(op_id, op)| (*op_id.get_hash(), op.signature, op.creator_public_key))
.collect::<Vec<_>>(),
)?;

// add to known ops
if let Some(node_info) = self.active_nodes.get_mut(source_node_id) {
node_info.insert_known_ops(
Expand Down
29 changes: 29 additions & 0 deletions massa-protocol-worker/src/sig_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2022 MASSA LABS <[email protected]>

//! Optimized batch signature verifier for operations

use massa_hash::Hash;
use massa_protocol_exports::ProtocolError;
use massa_signature::{verify_signature_batch, PublicKey, Signature};
use rayon::prelude::*;

/// Limit for small batch optimization
const SMALL_BATCH_LIMIT: usize = 2;

/// Efficiently verifies a batch of signatures in parallel.
/// Returns an error if at least one of them fails to verify.
pub fn verify_sigs_batch(ops: &[(Hash, Signature, PublicKey)]) -> Result<(), ProtocolError> {
// if it's a small batch, use single-core verification
if ops.len() <= SMALL_BATCH_LIMIT {
return verify_signature_batch(ops).map_err(|_err| ProtocolError::WrongSignature);
}

// otherwise, use parallel batch verif

// compute chunk size for parallelization
let chunk_size = std::cmp::max(1, ops.len() / rayon::current_num_threads());
// process chunks in parallel
ops.par_chunks(chunk_size)
.try_for_each(verify_signature_batch)
.map_err(|_err| ProtocolError::WrongSignature)
}
2 changes: 1 addition & 1 deletion massa-signature/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ edition = "2021"
[dependencies]
bs58 = { version = "0.4", features = ["check"] }
displaydoc = "0.2"
ed25519-dalek = "1.0"
ed25519-dalek = { version = "1.0", features = ["batch"] }
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
nom = "7.1"
Expand Down
4 changes: 2 additions & 2 deletions massa-signature/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ mod signature_impl;

pub use error::MassaSignatureError;
pub use signature_impl::{
KeyPair, PublicKey, PublicKeyDeserializer, Signature, SignatureDeserializer,
PUBLIC_KEY_SIZE_BYTES, SECRET_KEY_BYTES_SIZE, SIGNATURE_SIZE_BYTES,
verify_signature_batch, KeyPair, PublicKey, PublicKeyDeserializer, Signature,
SignatureDeserializer, PUBLIC_KEY_SIZE_BYTES, SECRET_KEY_BYTES_SIZE, SIGNATURE_SIZE_BYTES,
};
41 changes: 40 additions & 1 deletion massa-signature/src/signature_impl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2022 MASSA LABS <[email protected]>

use crate::error::MassaSignatureError;
use ed25519_dalek::{Signer, Verifier};
use ed25519_dalek::{verify_batch, Signer, Verifier};
use massa_hash::Hash;
use massa_serialization::{
DeserializeError, Deserializer, Serializer, U64VarIntDeserializer, U64VarIntSerializer,
Expand Down Expand Up @@ -941,6 +941,45 @@ impl Deserializer<Signature> for SignatureDeserializer {
}
}

/// Verify a batch of signatures on a single core to gain total CPU perf.
/// Every provided triplet `(hash, signature, public_key)` is verified
/// and an error is returned if at least one of them fails.
///
/// # Arguments
/// * `batch`: a slice of triplets `(hash, signature, public_key)`
///
/// # Return value
/// Returns `Ok(())` if all signatures were successfully verified,
/// and `Err(MassaSignatureError::SignatureError(_))` if at least one of them failed.
pub fn verify_signature_batch(
batch: &[(Hash, Signature, PublicKey)],
) -> Result<(), MassaSignatureError> {
// nothing to verify
if batch.is_empty() {
return Ok(());
}

// normal verif is fastest for size 1 batches
if batch.len() == 1 {
let (hash, signature, public_key) = batch[0];
return public_key.verify_signature(&hash, &signature);
}

// otherwise, use batch verif

let mut hashes = Vec::with_capacity(batch.len());
let mut signatures = Vec::with_capacity(batch.len());
let mut public_keys = Vec::with_capacity(batch.len());
batch.iter().for_each(|(hash, signature, public_key)| {
hashes.push(hash.to_bytes().as_slice());
signatures.push(signature.0);
public_keys.push(public_key.0);
});
verify_batch(&hashes, signatures.as_slice(), public_keys.as_slice()).map_err(|err| {
MassaSignatureError::SignatureError(format!("Batch signature verification failed: {}", err))
})
}

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