From f61e882c7aefefdf8d1ee5c59300d8c340d12f3a Mon Sep 17 00:00:00 2001 From: Arun Koshy Date: Sat, 9 Sep 2023 11:56:53 -0700 Subject: [PATCH] Add CertificateV2 ... Includes new AggregateSignatureState --- crates/sui-core/Cargo.toml | 2 +- crates/sui-core/src/consensus_handler.rs | 14 +- crates/sui-framework/docs/object.md | 39 -- crates/sui-open-rpc/spec/openrpc.json | 1 + crates/sui-protocol-config/src/lib.rs | 8 + .../consensus/benches/process_certificates.rs | 2 +- .../consensus/src/tests/bullshark_tests.rs | 32 +- .../consensus/src/tests/consensus_tests.rs | 10 +- .../consensus/src/tests/randomized_tests.rs | 2 +- .../tests/consensus_integration_tests.rs | 2 +- narwhal/node/src/generate_format.rs | 14 +- narwhal/primary/src/aggregators.rs | 12 +- narwhal/primary/src/certificate_fetcher.rs | 4 +- narwhal/primary/src/certifier.rs | 9 +- narwhal/primary/src/primary.rs | 5 +- narwhal/primary/src/proposer.rs | 4 +- narwhal/primary/src/synchronizer.rs | 17 +- .../src/tests/certificate_fetcher_tests.rs | 12 +- .../primary/src/tests/certificate_tests.rs | 58 ++- narwhal/primary/src/tests/certifier_tests.rs | 15 +- narwhal/primary/src/tests/primary_tests.rs | 57 ++- narwhal/primary/src/tests/proposer_tests.rs | 16 +- .../primary/src/tests/synchronizer_tests.rs | 40 +- narwhal/storage/src/certificate_store.rs | 17 +- narwhal/storage/src/consensus_store.rs | 6 +- narwhal/test-utils/src/lib.rs | 60 ++- narwhal/types/benches/verify_certificate.rs | 4 +- narwhal/types/src/consensus.rs | 31 +- narwhal/types/src/primary.rs | 384 +++++++++++++++++- narwhal/types/tests/primary_tests.rs | 10 +- 30 files changed, 684 insertions(+), 203 deletions(-) diff --git a/crates/sui-core/Cargo.toml b/crates/sui-core/Cargo.toml index 52e6e323a9a934..358fcb3f565ff7 100644 --- a/crates/sui-core/Cargo.toml +++ b/crates/sui-core/Cargo.toml @@ -61,6 +61,7 @@ narwhal-crypto.workspace = true narwhal-executor.workspace = true narwhal-network.workspace = true narwhal-node.workspace = true +narwhal-test-utils.workspace = true narwhal-types.workspace = true narwhal-worker.workspace = true shared-crypto.workspace = true @@ -93,7 +94,6 @@ serde_yaml.workspace = true move-symbol-pool.workspace = true -narwhal-test-utils.workspace = true sui-test-transaction-builder.workspace = true sui-types = { workspace = true, features = ["test-utils"] } diff --git a/crates/sui-core/src/consensus_handler.rs b/crates/sui-core/src/consensus_handler.rs index 91afd5e912e1d9..648743669e0515 100644 --- a/crates/sui-core/src/consensus_handler.rs +++ b/crates/sui-core/src/consensus_handler.rs @@ -19,7 +19,8 @@ use lru::LruCache; use mysten_metrics::{monitored_scope, spawn_monitored_task}; use narwhal_config::Committee; use narwhal_executor::{ExecutionIndices, ExecutionState}; -use narwhal_types::{BatchAPI, CertificateAPI, ConsensusOutput, HeaderAPI}; +use narwhal_test_utils::latest_protocol_version; +use narwhal_types::{BatchAPI, Certificate, CertificateAPI, ConsensusOutput, HeaderAPI}; use serde::{Deserialize, Serialize}; use std::collections::hash_map::DefaultHasher; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -643,7 +644,7 @@ impl SequencedConsensusTransaction { pub fn new_test(transaction: ConsensusTransaction) -> Self { Self { transaction: SequencedConsensusTransactionKind::External(transaction), - certificate: Default::default(), + certificate: Arc::new(Certificate::default(&latest_protocol_version())), certificate_author: AuthorityName::ZERO, consensus_index: Default::default(), } @@ -733,8 +734,13 @@ mod tests { .build() .unwrap(); - let certificate = - Certificate::new_unsigned(&committee, Header::V1(header), vec![]).unwrap(); + let certificate = Certificate::new_unsigned( + latest_protocol_config, + &committee, + Header::V1(header), + vec![], + ) + .unwrap(); certificates.push(certificate); } diff --git a/crates/sui-framework/docs/object.md b/crates/sui-framework/docs/object.md index 6d083f02ba85b5..2eebd7758f97a9 100644 --- a/crates/sui-framework/docs/object.md +++ b/crates/sui-framework/docs/object.md @@ -17,7 +17,6 @@ Sui object identifiers - [Function `id_from_address`](#0x2_object_id_from_address) - [Function `sui_system_state`](#0x2_object_sui_system_state) - [Function `clock`](#0x2_object_clock) -- [Function `authenticator_state`](#0x2_object_authenticator_state) - [Function `uid_as_inner`](#0x2_object_uid_as_inner) - [Function `uid_to_inner`](#0x2_object_uid_to_inner) - [Function `uid_to_bytes`](#0x2_object_uid_to_bytes) @@ -185,16 +184,6 @@ Sender is not @0x0 the system address. - - -The hardcoded ID for the singleton AuthenticatorState Object. - - -
const SUI_AUTHENTICATOR_STATE_ID: address = 7;
-
- - - The hardcoded ID for the singleton Clock Object. @@ -370,34 +359,6 @@ This should only be called once from clock - - - -## Function `authenticator_state` - -Create the UID for the singleton AuthenticatorState object. -This should only be called once from authenticator_state. - - -
public(friend) fun authenticator_state(): object::UID
-
- - - -
-Implementation - - -
public(friend) fun authenticator_state(): UID {
-    UID {
-        id: ID { bytes: SUI_AUTHENTICATOR_STATE_ID }
-    }
-}
-
- - -
diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index e02f9816e44e25..b5a9d918eea8f2 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -1364,6 +1364,7 @@ "loaded_child_object_format_type": false, "loaded_child_objects_fixed": true, "missing_type_is_compatibility_error": true, + "narwhal_certificate_v2": false, "narwhal_new_leader_election_schedule": false, "narwhal_versioned_metadata": false, "no_extraneous_module_bytes": false, diff --git a/crates/sui-protocol-config/src/lib.rs b/crates/sui-protocol-config/src/lib.rs index 1fefad277d10ed..5826fbc4d49f5c 100644 --- a/crates/sui-protocol-config/src/lib.rs +++ b/crates/sui-protocol-config/src/lib.rs @@ -284,6 +284,10 @@ struct FeatureFlags { // If true, use the new child object format type logging #[serde(skip_serializing_if = "is_false")] loaded_child_object_format_type: bool, + + // If true, then use CertificateV2 in narwhal. + #[serde(skip_serializing_if = "is_false")] + narwhal_certificate_v2: bool, } fn is_false(b: &bool) -> bool { @@ -922,6 +926,10 @@ impl ProtocolConfig { pub fn create_authenticator_state_in_genesis(&self) -> bool { self.enable_jwk_consensus_updates() } + + pub fn narwhal_certificate_v2(&self) -> bool { + self.feature_flags.narwhal_certificate_v2 + } } #[cfg(not(msim))] diff --git a/narwhal/consensus/benches/process_certificates.rs b/narwhal/consensus/benches/process_certificates.rs index b8aeef1bc63452..432f23d9abaa3e 100644 --- a/narwhal/consensus/benches/process_certificates.rs +++ b/narwhal/consensus/benches/process_certificates.rs @@ -30,7 +30,7 @@ pub fn process_certificates(c: &mut Criterion) { let rounds: Round = *size; // process certificates for rounds, check we don't grow the dag too much - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); diff --git a/narwhal/consensus/src/tests/bullshark_tests.rs b/narwhal/consensus/src/tests/bullshark_tests.rs index 3c5d378582fad0..71c9d430e73faf 100644 --- a/narwhal/consensus/src/tests/bullshark_tests.rs +++ b/narwhal/consensus/src/tests/bullshark_tests.rs @@ -30,7 +30,7 @@ async fn order_leaders() { let committee = fixture.committee(); // Make certificates for rounds 1 to 7. let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -127,7 +127,7 @@ async fn commit_one_with_leader_schedule_change() { let committee = fixture.committee(); // Make certificates for rounds 1 to 9. let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -188,7 +188,7 @@ async fn not_enough_support_with_leader_schedule_change() { let committee = fixture.committee(); let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -336,7 +336,7 @@ async fn test_long_period_of_asynchrony_for_leader_schedule_change() { let committee = fixture.committee(); let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -455,7 +455,7 @@ async fn commit_one() { let committee = fixture.committee(); // Make certificates for rounds 1 and 2. let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -559,7 +559,7 @@ async fn dead_node() { // remove the last authority - 4 let dead_node = ids.pop().unwrap(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -670,7 +670,7 @@ async fn not_enough_support() { let mut ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); ids.sort(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -870,7 +870,7 @@ async fn missing_leader() { let mut ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); ids.sort(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -986,7 +986,7 @@ async fn committed_round_after_restart() { let epoch = committee.epoch(); // Make certificates for rounds 1 to 11. - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -1092,7 +1092,7 @@ async fn delayed_certificates_are_rejected() { let gc_depth = 10; // Make certificates for rounds 1 to 11. - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -1151,7 +1151,7 @@ async fn submitting_equivocating_certificate_should_error() { let gc_depth = 10; // Make certificates for rounds 1 to 11. - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -1227,7 +1227,7 @@ async fn reset_consensus_scores_on_every_schedule_change() { let gc_depth = 10; // Make certificates for rounds 1 to 50. - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -1341,7 +1341,7 @@ async fn restart_with_new_committee() { tokio::spawn(async move { while rx_primary.recv().await.is_some() {} }); // Make certificates for rounds 1 and 2. - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -1426,7 +1426,7 @@ async fn garbage_collection_basic() { .map(|authority| authority.id()) .collect(); let slow_node = ids[3]; - let genesis = Certificate::genesis(&committee); + let genesis = Certificate::genesis(&latest_protocol_version(), &committee); let slow_nodes = vec![(slow_node, 0.0_f64)]; let (certificates, _round_5_certificates) = test_utils::make_certificates_with_slow_nodes( @@ -1522,7 +1522,7 @@ async fn slow_node() { .map(|authority| authority.id()) .collect(); let slow_node = ids[3]; - let genesis = Certificate::genesis(&committee); + let genesis = Certificate::genesis(&latest_protocol_version(), &committee); let slow_nodes = vec![(slow_node, 0.0_f64)]; let (certificates, round_8_certificates) = test_utils::make_certificates_with_slow_nodes( @@ -1662,7 +1662,7 @@ async fn not_enough_support_and_missing_leaders_and_gc() { let keys_with_dead_node = ids[0..=2].to_vec(); let slow_node = ids[3]; let slow_nodes = vec![(slow_node, 0.0_f64)]; - let genesis = Certificate::genesis(&committee); + let genesis = Certificate::genesis(&latest_protocol_version(), &committee); let (mut certificates, round_2_certificates) = test_utils::make_certificates_with_slow_nodes( &committee, diff --git a/narwhal/consensus/src/tests/consensus_tests.rs b/narwhal/consensus/src/tests/consensus_tests.rs index e4a2953c999cfb..31df9beb3329f5 100644 --- a/narwhal/consensus/src/tests/consensus_tests.rs +++ b/narwhal/consensus/src/tests/consensus_tests.rs @@ -65,7 +65,7 @@ async fn test_consensus_recovery_with_bullshark_with_config(config: ProtocolConf // AND make certificates for rounds 1 to 7 (inclusive) let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -507,7 +507,13 @@ async fn test_leader_schedule_from_store() { scores.add_score(id, score as u64); } - let sub_dag = CommittedSubDag::new(vec![], Certificate::default(), 0, scores, None); + let sub_dag = CommittedSubDag::new( + vec![], + Certificate::default(&latest_protocol_version()), + 0, + scores, + None, + ); store .write_consensus_state(&HashMap::new(), &sub_dag) diff --git a/narwhal/consensus/src/tests/randomized_tests.rs b/narwhal/consensus/src/tests/randomized_tests.rs index ef558f114017ac..c1f235afb471d9 100644 --- a/narwhal/consensus/src/tests/randomized_tests.rs +++ b/narwhal/consensus/src/tests/randomized_tests.rs @@ -288,7 +288,7 @@ fn generate_randomised_dag( .rng(rand) .build(); let committee: Committee = fixture.committee(); - let genesis = Certificate::genesis(&committee); + let genesis = Certificate::genesis(&latest_protocol_version(), &committee); // Create a known DAG let (original_certificates, _last_round) = make_certificates_with_parameters( diff --git a/narwhal/executor/tests/consensus_integration_tests.rs b/narwhal/executor/tests/consensus_integration_tests.rs index 7d7668e091ef61..e6d13a7aedcbf8 100644 --- a/narwhal/executor/tests/consensus_integration_tests.rs +++ b/narwhal/executor/tests/consensus_integration_tests.rs @@ -34,7 +34,7 @@ async fn test_recovery() { // Make certificates for rounds 1 and 2. let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); diff --git a/narwhal/node/src/generate_format.rs b/narwhal/node/src/generate_format.rs index c51f1079ae9d5f..6e9269d35a9744 100644 --- a/narwhal/node/src/generate_format.rs +++ b/narwhal/node/src/generate_format.rs @@ -11,6 +11,7 @@ use mysten_network::Multiaddr; use rand::{prelude::StdRng, SeedableRng}; use serde_reflection::{Registry, Result, Samples, Tracer, TracerConfig}; use std::{fs::File, io::Write}; +use test_utils::latest_protocol_version; use types::{ Batch, BatchDigest, Certificate, CertificateDigest, Header, HeaderDigest, HeaderV1Builder, MetadataV1, VersionedMetadata, WorkerOthersBatchMessage, WorkerOwnBatchMessage, @@ -61,7 +62,8 @@ fn get_registry() -> Result { let committee = committee_builder.build(); - let certificates: Vec = Certificate::genesis(&committee); + let certificates: Vec = + Certificate::genesis(&latest_protocol_version(), &committee); // Find the author id inside the committee let authority = committee.authority_by_key(kp.public()).unwrap(); @@ -83,10 +85,16 @@ fn get_registry() -> Result { .unwrap(); let worker_pk = network_keys[0].public().clone(); - let certificate = - Certificate::new_unsigned(&committee, Header::V1(header.clone()), vec![]).unwrap(); + let certificate = Certificate::new_unsigned( + &latest_protocol_version(), + &committee, + Header::V1(header.clone()), + vec![], + ) + .unwrap(); let signature = keys[0].sign(certificate.digest().as_ref()); let certificate = Certificate::new_unsigned( + &latest_protocol_version(), &committee, Header::V1(header.clone()), vec![(authority.id(), signature)], diff --git a/narwhal/primary/src/aggregators.rs b/narwhal/primary/src/aggregators.rs index 1c435ddbd63e03..ad8bc1293f1af1 100644 --- a/narwhal/primary/src/aggregators.rs +++ b/narwhal/primary/src/aggregators.rs @@ -11,6 +11,7 @@ use crypto::{ use fastcrypto::hash::{Digest, Hash}; use std::collections::HashSet; use std::sync::Arc; +use sui_protocol_config::ProtocolConfig; use tracing::warn; use types::{ ensure, @@ -20,6 +21,7 @@ use types::{ /// Aggregates votes for a particular header into a certificate. pub struct VotesAggregator { + protocol_config: ProtocolConfig, weight: Stake, votes: Vec<(AuthorityIdentifier, Signature)>, used: HashSet, @@ -27,10 +29,11 @@ pub struct VotesAggregator { } impl VotesAggregator { - pub fn new(metrics: Arc) -> Self { + pub fn new(protocol_config: &ProtocolConfig, metrics: Arc) -> Self { metrics.votes_received_last_round.set(0); Self { + protocol_config: protocol_config.clone(), weight: 0, votes: Vec::new(), used: HashSet::new(), @@ -59,7 +62,12 @@ impl VotesAggregator { .votes_received_last_round .set(self.votes.len() as i64); if self.weight >= committee.quorum_threshold() { - let cert = Certificate::new_unverified(committee, header.clone(), self.votes.clone())?; + let cert = Certificate::new_unverified( + &self.protocol_config, + committee, + header.clone(), + self.votes.clone(), + )?; let (_, pks) = cert.signed_by(committee); let certificate_digest: Digest<{ crypto::DIGEST_LENGTH }> = Digest::from(cert.digest()); diff --git a/narwhal/primary/src/certificate_fetcher.rs b/narwhal/primary/src/certificate_fetcher.rs index 24714a5dee01e0..0f8ad274d5770e 100644 --- a/narwhal/primary/src/certificate_fetcher.rs +++ b/narwhal/primary/src/certificate_fetcher.rs @@ -441,13 +441,13 @@ async fn process_certificates_helper( let verify_tasks = all_certificates .chunks(VERIFY_CERTIFICATES_BATCH_SIZE) .map(|certs| { - let certs = certs.to_vec(); + let mut certs = certs.to_vec(); let sync = synchronizer.clone(); let metrics = metrics.clone(); // Use threads dedicated to computation heavy work. spawn_blocking(move || { let now = Instant::now(); - for c in &certs { + for c in &mut certs { sync.sanitize_certificate(c)?; } metrics diff --git a/narwhal/primary/src/certifier.rs b/narwhal/primary/src/certifier.rs index ba0675e0292b7a..5f87299bf095f5 100644 --- a/narwhal/primary/src/certifier.rs +++ b/narwhal/primary/src/certifier.rs @@ -15,6 +15,7 @@ use std::sync::Arc; use std::time::Duration; use storage::CertificateStore; use sui_macros::fail_point_async; +use sui_protocol_config::ProtocolConfig; use tokio::{ sync::oneshot, task::{JoinHandle, JoinSet}, @@ -41,6 +42,7 @@ pub struct Certifier { authority_id: AuthorityIdentifier, /// The committee information. committee: Committee, + protocol_config: ProtocolConfig, /// The persistent storage keyed to certificates. certificate_store: CertificateStore, /// Handles synchronization with other nodes and our workers. @@ -71,6 +73,7 @@ impl Certifier { pub fn spawn( authority_id: AuthorityIdentifier, committee: Committee, + protocol_config: ProtocolConfig, certificate_store: CertificateStore, synchronizer: Arc, signature_service: SignatureService, @@ -84,6 +87,7 @@ impl Certifier { Self { authority_id, committee, + protocol_config, certificate_store, synchronizer, signature_service, @@ -237,6 +241,7 @@ impl Certifier { async fn propose_header( authority_id: AuthorityIdentifier, committee: Committee, + protocol_config: ProtocolConfig, certificate_store: CertificateStore, signature_service: SignatureService, metrics: Arc, @@ -259,7 +264,7 @@ impl Certifier { metrics.proposed_header_round.set(header.round() as i64); // Reset the votes aggregator and sign our own header. - let mut votes_aggregator = VotesAggregator::new(metrics.clone()); + let mut votes_aggregator = VotesAggregator::new(&protocol_config, metrics.clone()); let vote = Vote::new(&header, &authority_id, &signature_service).await; let mut certificate = votes_aggregator.append(vote, &committee, &header)?; @@ -373,10 +378,12 @@ impl Certifier { let signature_service = self.signature_service.clone(); let metrics = self.metrics.clone(); let network = self.network.clone(); + let protocol_config = self.protocol_config.clone(); fail_point_async!("narwhal-delay"); self.propose_header_tasks.spawn(monitored_future!(Self::propose_header( name, committee, + protocol_config, certificate_store, signature_service, metrics, diff --git a/narwhal/primary/src/primary.rs b/narwhal/primary/src/primary.rs index 9568e3a6b43d3f..93d0310868604b 100644 --- a/narwhal/primary/src/primary.rs +++ b/narwhal/primary/src/primary.rs @@ -90,7 +90,7 @@ impl Primary { network_signer: NetworkKeyPair, committee: Committee, worker_cache: WorkerCache, - _protocol_config: ProtocolConfig, + protocol_config: ProtocolConfig, parameters: Parameters, client: NetworkClient, certificate_store: CertificateStore, @@ -167,6 +167,7 @@ impl Primary { let synchronizer = Arc::new(Synchronizer::new( authority.id(), committee.clone(), + protocol_config.clone(), worker_cache.clone(), parameters.gc_depth, client.clone(), @@ -425,6 +426,7 @@ impl Primary { let core_handle = Certifier::spawn( authority.id(), committee.clone(), + protocol_config.clone(), certificate_store.clone(), synchronizer.clone(), signature_service, @@ -453,6 +455,7 @@ impl Primary { let proposer_handle = Proposer::spawn( authority.id(), committee, + &protocol_config, proposer_store, parameters.header_num_of_batches_threshold, parameters.max_header_num_of_batches, diff --git a/narwhal/primary/src/proposer.rs b/narwhal/primary/src/proposer.rs index 024a6d8827970c..211951de2eaf22 100644 --- a/narwhal/primary/src/proposer.rs +++ b/narwhal/primary/src/proposer.rs @@ -10,6 +10,7 @@ use mysten_metrics::spawn_logged_monitored_task; use std::collections::{BTreeMap, VecDeque}; use std::{cmp::Ordering, sync::Arc}; use storage::ProposerStore; +use sui_protocol_config::ProtocolConfig; use tokio::time::{sleep_until, Instant}; use tokio::{ sync::{oneshot, watch}, @@ -106,6 +107,7 @@ impl Proposer { pub fn spawn( authority_id: AuthorityIdentifier, committee: Committee, + protocol_config: &ProtocolConfig, proposer_store: ProposerStore, header_num_of_batches_threshold: usize, max_header_num_of_batches: usize, @@ -121,7 +123,7 @@ impl Proposer { metrics: Arc, leader_schedule: LeaderSchedule, ) -> JoinHandle<()> { - let genesis = Certificate::genesis(&committee); + let genesis = Certificate::genesis(protocol_config, &committee); spawn_logged_monitored_task!( async move { Self { diff --git a/narwhal/primary/src/synchronizer.rs b/narwhal/primary/src/synchronizer.rs index 36368a38315732..19b443322b5601 100644 --- a/narwhal/primary/src/synchronizer.rs +++ b/narwhal/primary/src/synchronizer.rs @@ -26,6 +26,7 @@ use std::{ time::Duration, }; use storage::{CertificateStore, PayloadStore}; +use sui_protocol_config::ProtocolConfig; use tokio::{ sync::{broadcast, oneshot, watch, MutexGuard}, task::JoinSet, @@ -306,6 +307,7 @@ impl Synchronizer { pub fn new( authority_id: AuthorityIdentifier, committee: Committee, + protocol_config: ProtocolConfig, worker_cache: WorkerCache, gc_depth: Round, client: NetworkClient, @@ -319,7 +321,7 @@ impl Synchronizer { primary_channel_metrics: &PrimaryChannelMetrics, ) -> Self { let committee: &Committee = &committee; - let genesis = Self::make_genesis(committee); + let genesis = Self::make_genesis(&protocol_config, committee); let highest_processed_round = certificate_store.highest_round_number(); let highest_created_certificate = certificate_store.last_round(authority_id).unwrap(); let gc_round = rx_consensus_round_updates.borrow().gc_round; @@ -619,8 +621,11 @@ impl Synchronizer { Ok(()) } - fn make_genesis(committee: &Committee) -> HashMap { - Certificate::genesis(committee) + fn make_genesis( + protocol_config: &ProtocolConfig, + committee: &Committee, + ) -> HashMap { + Certificate::genesis(protocol_config, committee) .into_iter() .map(|x| (x.digest(), x)) .collect() @@ -628,7 +633,7 @@ impl Synchronizer { /// Checks if the certificate is valid and can potentially be accepted into the DAG. // TODO: produce a different type after sanitize, e.g. VerifiedCertificate. - pub fn sanitize_certificate(&self, certificate: &Certificate) -> DagResult<()> { + pub fn sanitize_certificate(&self, certificate: &mut Certificate) -> DagResult<()> { ensure!( self.inner.committee.epoch() == certificate.epoch(), DagError::InvalidEpoch { @@ -650,7 +655,7 @@ impl Synchronizer { async fn process_certificate_internal( &self, - certificate: Certificate, + mut certificate: Certificate, sanitize: bool, early_suspend: bool, ) -> DagResult<()> { @@ -676,7 +681,7 @@ impl Synchronizer { } } if sanitize { - self.sanitize_certificate(&certificate)?; + self.sanitize_certificate(&mut certificate)?; } debug!( diff --git a/narwhal/primary/src/tests/certificate_fetcher_tests.rs b/narwhal/primary/src/tests/certificate_fetcher_tests.rs index da441b2a7a29a0..8c25a8683fce4e 100644 --- a/narwhal/primary/src/tests/certificate_fetcher_tests.rs +++ b/narwhal/primary/src/tests/certificate_fetcher_tests.rs @@ -161,6 +161,7 @@ async fn fetch_certificates_basic() { let synchronizer = Arc::new(Synchronizer::new( id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), gc_depth, client, @@ -205,7 +206,8 @@ async fn fetch_certificates_basic() { ); // Generate headers and certificates in successive rounds - let genesis_certs: Vec<_> = Certificate::genesis(&fixture.committee()); + let genesis_certs: Vec<_> = + Certificate::genesis(&latest_protocol_version(), &fixture.committee()); for cert in genesis_certs.iter() { certificate_store .write(cert.clone()) @@ -221,7 +223,11 @@ async fn fetch_certificates_basic() { for i in 0..rounds { let parents: BTreeSet<_> = current_round .into_iter() - .map(|header| fixture.certificate(&header).digest()) + .map(|header| { + fixture + .certificate(&latest_protocol_version(), &header) + .digest() + }) .collect(); (_, current_round) = fixture.headers_round(i, &parents, &latest_protocol_version()); headers.extend(current_round.clone()); @@ -236,7 +242,7 @@ async fn fetch_certificates_basic() { // Create certificates test data. let mut certificates = vec![]; for header in headers.into_iter() { - certificates.push(fixture.certificate(&header)); + certificates.push(fixture.certificate(&latest_protocol_version(), &header)); } assert_eq!(certificates.len(), total_certificates); // note genesis is not included assert_eq!(240, total_certificates); diff --git a/narwhal/primary/src/tests/certificate_tests.rs b/narwhal/primary/src/tests/certificate_tests.rs index 3f860747195ea9..b7917a2e80cef5 100644 --- a/narwhal/primary/src/tests/certificate_tests.rs +++ b/narwhal/primary/src/tests/certificate_tests.rs @@ -11,6 +11,7 @@ use rand::{ SeedableRng, }; use std::num::NonZeroUsize; +use test_utils::latest_protocol_version; use test_utils::CommitteeFixture; use types::{Certificate, Vote, VoteAPI}; @@ -19,11 +20,19 @@ fn test_empty_certificate_verification() { let fixture = CommitteeFixture::builder().build(); let committee = fixture.committee(); - let header = fixture.header(); + let header = fixture.header(&latest_protocol_version()); // You should not be allowed to create a certificate that does not satisfying quorum requirements - assert!(Certificate::new_unverified(&committee, header.clone(), Vec::new()).is_err()); - - let certificate = Certificate::new_unsigned(&committee, header, Vec::new()).unwrap(); + assert!(Certificate::new_unverified( + &latest_protocol_version(), + &committee, + header.clone(), + Vec::new() + ) + .is_err()); + + let mut certificate = + Certificate::new_unsigned(&latest_protocol_version(), &committee, header, Vec::new()) + .unwrap(); assert!(certificate .verify(&committee, &fixture.worker_cache()) .is_err()); @@ -33,7 +42,7 @@ fn test_empty_certificate_verification() { fn test_valid_certificate_verification() { let fixture = CommitteeFixture::builder().build(); let committee = fixture.committee(); - let header = fixture.header(); + let header = fixture.header(&latest_protocol_version()); let mut signatures = Vec::new(); @@ -43,7 +52,9 @@ fn test_valid_certificate_verification() { signatures.push((vote.author(), vote.signature().clone())); } - let certificate = Certificate::new_unverified(&committee, header, signatures).unwrap(); + let mut certificate = + Certificate::new_unverified(&latest_protocol_version(), &committee, header, signatures) + .unwrap(); assert!(certificate .verify(&committee, &fixture.worker_cache()) @@ -54,7 +65,7 @@ fn test_valid_certificate_verification() { fn test_certificate_insufficient_signatures() { let fixture = CommitteeFixture::builder().build(); let committee = fixture.committee(); - let header = fixture.header(); + let header = fixture.header(&latest_protocol_version()); let mut signatures = Vec::new(); @@ -64,9 +75,17 @@ fn test_certificate_insufficient_signatures() { signatures.push((vote.author(), vote.signature().clone())); } - assert!(Certificate::new_unverified(&committee, header.clone(), signatures.clone()).is_err()); + assert!(Certificate::new_unverified( + &latest_protocol_version(), + &committee, + header.clone(), + signatures.clone() + ) + .is_err()); - let certificate = Certificate::new_unsigned(&committee, header, signatures).unwrap(); + let mut certificate = + Certificate::new_unsigned(&latest_protocol_version(), &committee, header, signatures) + .unwrap(); assert!(certificate .verify(&committee, &fixture.worker_cache()) @@ -77,7 +96,7 @@ fn test_certificate_insufficient_signatures() { fn test_certificate_validly_repeated_public_keys() { let fixture = CommitteeFixture::builder().build(); let committee = fixture.committee(); - let header = fixture.header(); + let header = fixture.header(&latest_protocol_version()); let mut signatures = Vec::new(); @@ -89,9 +108,10 @@ fn test_certificate_validly_repeated_public_keys() { signatures.push((vote.author(), vote.signature().clone())); } - let certificate_res = Certificate::new_unverified(&committee, header, signatures); + let certificate_res = + Certificate::new_unverified(&latest_protocol_version(), &committee, header, signatures); assert!(certificate_res.is_ok()); - let certificate = certificate_res.unwrap(); + let mut certificate = certificate_res.unwrap(); assert!(certificate .verify(&committee, &fixture.worker_cache()) @@ -102,7 +122,7 @@ fn test_certificate_validly_repeated_public_keys() { fn test_unknown_signature_in_certificate() { let fixture = CommitteeFixture::builder().build(); let committee = fixture.committee(); - let header = fixture.header(); + let header = fixture.header(&latest_protocol_version()); let mut signatures = Vec::new(); @@ -118,7 +138,13 @@ fn test_unknown_signature_in_certificate() { let vote = Vote::new_with_signer(&header, &malicious_id, &malicious_key); signatures.push((vote.author(), vote.signature().clone())); - assert!(Certificate::new_unverified(&committee, header, signatures).is_err()); + assert!(Certificate::new_unverified( + &latest_protocol_version(), + &committee, + header, + signatures + ) + .is_err()); } proptest::proptest! { @@ -130,7 +156,7 @@ proptest::proptest! { .committee_size(NonZeroUsize::new(committee_size).unwrap()) .build(); let committee = fixture.committee(); - let header = fixture.header(); + let header = fixture.header(&latest_protocol_version()); let mut signatures = Vec::new(); @@ -141,7 +167,7 @@ proptest::proptest! { signatures.push((vote.author(), vote.signature().clone())); } - let certificate = Certificate::new_unverified(&committee, header, signatures).unwrap(); + let mut certificate = Certificate::new_unverified(&latest_protocol_version(), &committee, header, signatures).unwrap(); assert!(certificate .verify(&committee, &fixture.worker_cache()) diff --git a/narwhal/primary/src/tests/certifier_tests.rs b/narwhal/primary/src/tests/certifier_tests.rs index 0871870bac809c..5c7a81394757be 100644 --- a/narwhal/primary/src/tests/certifier_tests.rs +++ b/narwhal/primary/src/tests/certifier_tests.rs @@ -13,6 +13,7 @@ use primary::NUM_SHUTDOWN_RECEIVERS; use prometheus::Registry; use rand::{rngs::StdRng, SeedableRng}; use std::num::NonZeroUsize; +use test_utils::latest_protocol_version; use test_utils::CommitteeFixture; use tokio::sync::watch; use tokio::time::Duration; @@ -44,7 +45,7 @@ async fn propose_header() { let (certificate_store, payload_store) = create_db_stores(); // Create a fake header. - let proposed_header = primary.header(&committee); + let proposed_header = primary.header(&latest_protocol_version(), &committee); // Set up network. let own_address = committee @@ -103,6 +104,7 @@ async fn propose_header() { let synchronizer = Arc::new(Synchronizer::new( id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -119,6 +121,7 @@ async fn propose_header() { let _handle = Certifier::spawn( id, committee.clone(), + latest_protocol_version(), certificate_store.clone(), synchronizer, signature_service, @@ -159,7 +162,7 @@ async fn propose_header_failure() { let (certificate_store, payload_store) = create_db_stores(); // Create a fake header. - let proposed_header = primary.header(&committee); + let proposed_header = primary.header(&latest_protocol_version(), &committee); // Set up network. let own_address = committee @@ -201,6 +204,7 @@ async fn propose_header_failure() { let synchronizer = Arc::new(Synchronizer::new( authority_id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -217,6 +221,7 @@ async fn propose_header_failure() { let _handle = Certifier::spawn( authority_id, committee.clone(), + latest_protocol_version(), certificate_store.clone(), synchronizer, signature_service, @@ -278,7 +283,7 @@ async fn run_vote_aggregator_with_param( let (certificate_store, payload_store) = create_db_stores(); // Create a fake header. - let proposed_header = primary.header(&committee); + let proposed_header = primary.header(&latest_protocol_version(), &committee); // Set up network. let own_address = committee @@ -332,6 +337,7 @@ async fn run_vote_aggregator_with_param( let synchronizer = Arc::new(Synchronizer::new( id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -347,6 +353,7 @@ async fn run_vote_aggregator_with_param( let _handle = Certifier::spawn( id, committee.clone(), + latest_protocol_version(), certificate_store.clone(), synchronizer, signature_service, @@ -401,6 +408,7 @@ async fn shutdown_core() { let synchronizer = Arc::new(Synchronizer::new( id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -431,6 +439,7 @@ async fn shutdown_core() { let handle = Certifier::spawn( id, committee.clone(), + latest_protocol_version(), certificate_store.clone(), synchronizer.clone(), signature_service.clone(), diff --git a/narwhal/primary/src/tests/primary_tests.rs b/narwhal/primary/src/tests/primary_tests.rs index dbe5eca88fc05a..60106767b4a3c2 100644 --- a/narwhal/primary/src/tests/primary_tests.rs +++ b/narwhal/primary/src/tests/primary_tests.rs @@ -25,7 +25,9 @@ use std::{ time::Duration, }; use storage::{NodeStorage, VoteDigestStore}; -use test_utils::{make_optimal_signed_certificates, temp_dir, CommitteeFixture}; +use test_utils::{ + latest_protocol_version, make_optimal_signed_certificates, temp_dir, CommitteeFixture, +}; use tokio::{sync::watch, time::timeout}; use types::{ now, Certificate, CertificateAPI, FetchCertificatesRequest, Header, HeaderAPI, @@ -288,6 +290,7 @@ async fn test_request_vote_has_missing_parents() { let synchronizer = Arc::new(Synchronizer::new( target_id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -315,7 +318,7 @@ async fn test_request_vote_has_missing_parents() { // Make some mock certificates that are parents of our new header. let committee: Committee = fixture.committee(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -338,7 +341,7 @@ async fn test_request_vote_has_missing_parents() { // Create a test header. let test_header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .author(author_id) .round(3) .parents(round_2_certs.iter().map(|c| c.digest()).collect()) @@ -455,6 +458,7 @@ async fn test_request_vote_accept_missing_parents() { let synchronizer = Arc::new(Synchronizer::new( target_id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -482,7 +486,7 @@ async fn test_request_vote_accept_missing_parents() { // Make some mock certificates that are parents of our new header. let committee: Committee = fixture.committee(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -506,7 +510,7 @@ async fn test_request_vote_accept_missing_parents() { // Create a test header. let test_header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .author(author_id) .round(3) .parents(round_2_certs.iter().map(|c| c.digest()).collect()) @@ -610,6 +614,7 @@ async fn test_request_vote_missing_batches() { let synchronizer = Arc::new(Synchronizer::new( authority_id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -640,7 +645,7 @@ async fn test_request_vote_missing_batches() { for primary in fixture.authorities().filter(|a| a.id() != authority_id) { let header = Header::V1( primary - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .with_payload_batch( test_utils::fixture_batch_with_transactions( 10, @@ -653,7 +658,7 @@ async fn test_request_vote_missing_batches() { .unwrap(), ); - let certificate = fixture.certificate(&header); + let certificate = fixture.certificate(&latest_protocol_version(), &header); let digest = certificate.clone().digest(); certificates.insert(digest, certificate.clone()); @@ -664,7 +669,7 @@ async fn test_request_vote_missing_batches() { } let test_header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .round(2) .parents(certificates.keys().cloned().collect()) .with_payload_batch( @@ -755,6 +760,7 @@ async fn test_request_vote_already_voted() { let synchronizer = Arc::new(Synchronizer::new( id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -786,7 +792,7 @@ async fn test_request_vote_already_voted() { for primary in fixture.authorities().filter(|a| a.id() != id) { let header = Header::V1( primary - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .with_payload_batch( test_utils::fixture_batch_with_transactions( 10, @@ -799,7 +805,7 @@ async fn test_request_vote_already_voted() { .unwrap(), ); - let certificate = fixture.certificate(&header); + let certificate = fixture.certificate(&latest_protocol_version(), &header); let digest = certificate.clone().digest(); certificates.insert(digest, certificate.clone()); @@ -831,7 +837,7 @@ async fn test_request_vote_already_voted() { // Verify Handler generates a Vote. let test_header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .round(2) .parents(certificates.keys().cloned().collect()) .with_payload_batch( @@ -883,7 +889,7 @@ async fn test_request_vote_already_voted() { // Verify a different request for the same round receives an error. let test_header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .round(2) .parents(certificates.keys().cloned().collect()) .with_payload_batch( @@ -943,6 +949,7 @@ async fn test_fetch_certificates_handler() { let synchronizer = Arc::new(Synchronizer::new( id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -968,16 +975,21 @@ async fn test_fetch_certificates_handler() { metrics: metrics.clone(), }; - let mut current_round: Vec<_> = Certificate::genesis(&fixture.committee()) - .into_iter() - .map(|cert| cert.header().clone()) - .collect(); + let mut current_round: Vec<_> = + Certificate::genesis(&latest_protocol_version(), &fixture.committee()) + .into_iter() + .map(|cert| cert.header().clone()) + .collect(); let mut headers = vec![]; let total_rounds = 4; for i in 0..total_rounds { let parents: BTreeSet<_> = current_round .into_iter() - .map(|header| fixture.certificate(&header).digest()) + .map(|header| { + fixture + .certificate(&latest_protocol_version(), &header) + .digest() + }) .collect(); (_, current_round) = fixture.headers_round(i, &parents, &test_utils::latest_protocol_version()); @@ -989,7 +1001,7 @@ async fn test_fetch_certificates_handler() { // Create certificates test data. let mut certificates = vec![]; for header in headers.into_iter() { - certificates.push(fixture.certificate(&header)); + certificates.push(fixture.certificate(&latest_protocol_version(), &header)); } assert_eq!(certificates.len(), total_certificates); assert_eq!(16, total_certificates); @@ -1114,6 +1126,7 @@ async fn test_request_vote_created_at_in_future() { let synchronizer = Arc::new(Synchronizer::new( id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -1144,7 +1157,7 @@ async fn test_request_vote_created_at_in_future() { for primary in fixture.authorities().filter(|a| a.id() != id) { let header = Header::V1( primary - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .with_payload_batch( test_utils::fixture_batch_with_transactions( 10, @@ -1157,7 +1170,7 @@ async fn test_request_vote_created_at_in_future() { .unwrap(), ); - let certificate = fixture.certificate(&header); + let certificate = fixture.certificate(&latest_protocol_version(), &header); let digest = certificate.clone().digest(); certificates.insert(digest, certificate.clone()); @@ -1193,7 +1206,7 @@ async fn test_request_vote_created_at_in_future() { let test_header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .round(2) .parents(certificates.keys().cloned().collect()) .with_payload_batch( @@ -1231,7 +1244,7 @@ async fn test_request_vote_created_at_in_future() { let created_at = now() + 500; let test_header = author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .round(2) .parents(certificates.keys().cloned().collect()) .with_payload_batch( diff --git a/narwhal/primary/src/tests/proposer_tests.rs b/narwhal/primary/src/tests/proposer_tests.rs index 0f22430210623c..f34c75437245b8 100644 --- a/narwhal/primary/src/tests/proposer_tests.rs +++ b/narwhal/primary/src/tests/proposer_tests.rs @@ -30,6 +30,7 @@ async fn propose_empty() { let _proposer_handle = Proposer::spawn( name, committee.clone(), + &latest_protocol_version(), ProposerStore::new_for_tests(), /* header_num_of_batches_threshold */ 32, /* max_header_num_of_batches */ 100, @@ -77,6 +78,7 @@ async fn propose_payload_and_repropose_after_n_seconds() { let _proposer_handle = Proposer::spawn( name, committee.clone(), + &latest_protocol_version(), ProposerStore::new_for_tests(), /* header_num_of_batches_threshold */ 1, /* max_header_num_of_batches */ max_num_of_batches, @@ -147,10 +149,10 @@ async fn propose_payload_and_repropose_after_n_seconds() { // AND send some parents to advance the round let parents: Vec<_> = fixture - .headers() + .headers(&latest_protocol_version()) .iter() .take(4) - .map(|h| fixture.certificate(h)) + .map(|h| fixture.certificate(&latest_protocol_version(), h)) .collect(); let result = tx_parents.send((parents, 1, 0)).await; @@ -199,6 +201,7 @@ async fn equivocation_protection() { let proposer_handle = Proposer::spawn( authority_id, committee.clone(), + &latest_protocol_version(), proposer_store.clone(), /* header_num_of_batches_threshold */ 1, /* max_header_num_of_batches */ 10, @@ -238,10 +241,10 @@ async fn equivocation_protection() { // Create and send parents let parents: Vec<_> = fixture - .headers() + .headers(&latest_protocol_version()) .iter() .take(3) - .map(|h| fixture.certificate(h)) + .map(|h| fixture.certificate(&latest_protocol_version(), h)) .collect(); let result = tx_parents.send((parents, 1, 0)).await; @@ -271,6 +274,7 @@ async fn equivocation_protection() { let _proposer_handle = Proposer::spawn( authority_id, committee.clone(), + &latest_protocol_version(), proposer_store, /* header_num_of_batches_threshold */ 1, /* max_header_num_of_batches */ 10, @@ -309,10 +313,10 @@ async fn equivocation_protection() { // Create and send a superset parents, same round but different set from before let parents: Vec<_> = fixture - .headers() + .headers(&latest_protocol_version()) .iter() .take(4) - .map(|h| fixture.certificate(h)) + .map(|h| fixture.certificate(&latest_protocol_version(), h)) .collect(); let result = tx_parents.send((parents, 1, 0)).await; diff --git a/narwhal/primary/src/tests/synchronizer_tests.rs b/narwhal/primary/src/tests/synchronizer_tests.rs index 2ed94ef8bc1cc2..55eafdc2673978 100644 --- a/narwhal/primary/src/tests/synchronizer_tests.rs +++ b/narwhal/primary/src/tests/synchronizer_tests.rs @@ -51,6 +51,7 @@ async fn accept_certificates() { let synchronizer = Arc::new(Synchronizer::new( authority_id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -78,10 +79,10 @@ async fn accept_certificates() { // Send 3 certificates to the Synchronizer. let certificates: Vec<_> = fixture - .headers() + .headers(&latest_protocol_version()) .iter() .take(3) - .map(|h| fixture.certificate(h)) + .map(|h| fixture.certificate(&latest_protocol_version(), h)) .collect(); for cert in certificates.clone() { synchronizer.try_accept_certificate(cert).await.unwrap(); @@ -148,6 +149,7 @@ async fn accept_suspended_certificates() { let synchronizer = Arc::new(Synchronizer::new( authority_id, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -163,7 +165,7 @@ async fn accept_suspended_certificates() { // Make fake certificates. let committee = fixture.committee(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -254,6 +256,7 @@ async fn synchronizer_recover_basic() { let synchronizer = Arc::new(Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -281,10 +284,10 @@ async fn synchronizer_recover_basic() { // Send 3 certificates to Synchronizer. let certificates: Vec<_> = fixture - .headers() + .headers(&latest_protocol_version()) .iter() .take(3) - .map(|h| fixture.certificate(h)) + .map(|h| fixture.certificate(&latest_protocol_version(), h)) .collect(); for cert in certificates.clone() { synchronizer.try_accept_certificate(cert).await.unwrap(); @@ -302,6 +305,7 @@ async fn synchronizer_recover_basic() { let _synchronizer = Arc::new(Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -370,6 +374,7 @@ async fn synchronizer_recover_partial_certs() { let synchronizer = Arc::new(Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -397,10 +402,10 @@ async fn synchronizer_recover_partial_certs() { // Send 1 certificate. let certificates: Vec = fixture - .headers() + .headers(&latest_protocol_version()) .iter() .take(3) - .map(|h| fixture.certificate(h)) + .map(|h| fixture.certificate(&latest_protocol_version(), h)) .collect(); let last_cert = certificates.clone().into_iter().last().unwrap(); synchronizer @@ -420,6 +425,7 @@ async fn synchronizer_recover_partial_certs() { let synchronizer = Arc::new(Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -480,6 +486,7 @@ async fn synchronizer_recover_previous_round() { let synchronizer = Arc::new(Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -506,7 +513,7 @@ async fn synchronizer_recover_previous_round() { client.set_primary_network(network.clone()); // Send 3 certificates from round 1, and 2 certificates from round 2 to Synchronizer. - let genesis_certs = Certificate::genesis(&committee); + let genesis_certs = Certificate::genesis(&latest_protocol_version(), &committee); let genesis = genesis_certs .iter() .map(|x| x.digest()) @@ -549,6 +556,7 @@ async fn synchronizer_recover_previous_round() { let _synchronizer = Arc::new(Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client.clone(), @@ -595,6 +603,7 @@ async fn deliver_certificate_using_store() { let synchronizer = Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -609,7 +618,7 @@ async fn deliver_certificate_using_store() { ); // create some certificates in a complete DAG form - let genesis_certs = Certificate::genesis(&committee); + let genesis_certs = Certificate::genesis(&latest_protocol_version(), &committee); let genesis = genesis_certs .iter() .map(|x| x.digest()) @@ -666,6 +675,7 @@ async fn deliver_certificate_not_found_parents() { let synchronizer = Synchronizer::new( name, fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -680,7 +690,7 @@ async fn deliver_certificate_not_found_parents() { ); // create some certificates in a complete DAG form - let genesis_certs = Certificate::genesis(&committee); + let genesis_certs = Certificate::genesis(&latest_protocol_version(), &committee); let genesis = genesis_certs .iter() .map(|x| x.digest()) @@ -748,6 +758,7 @@ async fn sync_batches_drops_old() { let synchronizer = Arc::new(Synchronizer::new( primary.id(), fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ 50, client, @@ -765,7 +776,7 @@ async fn sync_batches_drops_old() { for _ in 0..3 { let header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .with_payload_batch( test_utils::fixture_batch_with_transactions(10, &latest_protocol_version()), 0, @@ -775,7 +786,7 @@ async fn sync_batches_drops_old() { .unwrap(), ); - let certificate = fixture.certificate(&header); + let certificate = fixture.certificate(&latest_protocol_version(), &header); let digest = certificate.clone().digest(); certificates.insert(digest, certificate.clone()); @@ -786,7 +797,7 @@ async fn sync_batches_drops_old() { } let test_header = Header::V1( author - .header_builder(&fixture.committee()) + .header_builder(&latest_protocol_version(), &fixture.committee()) .round(2) .parents(certificates.keys().cloned().collect()) .with_payload_batch( @@ -834,6 +845,7 @@ async fn gc_suspended_certificates() { let synchronizer = Arc::new(Synchronizer::new( primary.id(), fixture.committee(), + latest_protocol_version(), worker_cache.clone(), /* gc_depth */ GC_DEPTH, client, @@ -849,7 +861,7 @@ async fn gc_suspended_certificates() { // Make 5 rounds of fake certificates. let committee: Committee = fixture.committee(); - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); diff --git a/narwhal/storage/src/certificate_store.rs b/narwhal/storage/src/certificate_store.rs index 3eb9bfaf85732e..a3f1927ab38b5a 100644 --- a/narwhal/storage/src/certificate_store.rs +++ b/narwhal/storage/src/certificate_store.rs @@ -800,23 +800,28 @@ mod test { fn certificates(rounds: u64) -> Vec { let fixture = CommitteeFixture::builder().build(); let committee = fixture.committee(); - let mut current_round: Vec<_> = Certificate::genesis(&committee) - .into_iter() - .map(|cert| cert.header().clone()) - .collect(); + let mut current_round: Vec<_> = + Certificate::genesis(&latest_protocol_version(), &committee) + .into_iter() + .map(|cert| cert.header().clone()) + .collect(); let mut result: Vec = Vec::new(); for i in 0..rounds { let parents: BTreeSet<_> = current_round .iter() - .map(|header| fixture.certificate(header).digest()) + .map(|header| { + fixture + .certificate(&latest_protocol_version(), header) + .digest() + }) .collect(); (_, current_round) = fixture.headers_round(i, &parents, &latest_protocol_version()); result.extend( current_round .iter() - .map(|h| fixture.certificate(h)) + .map(|h| fixture.certificate(&latest_protocol_version(), h)) .collect::>(), ); } diff --git a/narwhal/storage/src/consensus_store.rs b/narwhal/storage/src/consensus_store.rs index e097668c2557c0..33580876119b7d 100644 --- a/narwhal/storage/src/consensus_store.rs +++ b/narwhal/storage/src/consensus_store.rs @@ -192,7 +192,7 @@ mod test { use crate::ConsensusStore; use std::collections::HashMap; use store::Map; - use test_utils::CommitteeFixture; + use test_utils::{latest_protocol_version, CommitteeFixture}; use types::{ Certificate, CommittedSubDag, CommittedSubDagShell, ConsensusCommit, ConsensusCommitV2, ReputationScores, TimestampMs, @@ -209,7 +209,7 @@ mod test { for sequence_number in 0..10 { let sub_dag = CommittedSubDag::new( vec![], - Certificate::default(), + Certificate::default(&latest_protocol_version()), sequence_number, ReputationScores::new(&committee), None, @@ -237,7 +237,7 @@ mod test { let sub_dag = CommittedSubDag::new( vec![], - Certificate::default(), + Certificate::default(&latest_protocol_version()), sequence_number, scores, None, diff --git a/narwhal/test-utils/src/lib.rs b/narwhal/test-utils/src/lib.rs index 88005599f8081a..b684271f9682ee 100644 --- a/narwhal/test-utils/src/lib.rs +++ b/narwhal/test-utils/src/lib.rs @@ -798,7 +798,9 @@ pub fn mock_certificate_with_rand( .payload(fixture_payload_with_rand(1, rand, protocol_config)) .build() .unwrap(); - let certificate = Certificate::new_unsigned(committee, Header::V1(header), Vec::new()).unwrap(); + let certificate = + Certificate::new_unsigned(protocol_config, committee, Header::V1(header), Vec::new()) + .unwrap(); (certificate.digest(), certificate) } @@ -833,7 +835,9 @@ pub fn mock_certificate_with_epoch( .payload(fixture_payload(1, protocol_config)) .build() .unwrap(); - let certificate = Certificate::new_unsigned(committee, Header::V1(header), Vec::new()).unwrap(); + let certificate = + Certificate::new_unsigned(protocol_config, committee, Header::V1(header), Vec::new()) + .unwrap(); (certificate.digest(), certificate) } @@ -855,15 +859,21 @@ pub fn mock_signed_certificate( let header = header_builder.build().unwrap(); - let cert = - Certificate::new_unsigned(committee, Header::V1(header.clone()), Vec::new()).unwrap(); + let cert = Certificate::new_unsigned( + protocol_config, + committee, + Header::V1(header.clone()), + Vec::new(), + ) + .unwrap(); let mut votes = Vec::new(); for (name, signer) in signers { let sig = Signature::new_secure(&to_intent_message(cert.header().digest()), signer); votes.push((*name, sig)) } - let cert = Certificate::new_unverified(committee, Header::V1(header), votes).unwrap(); + let cert = + Certificate::new_unverified(protocol_config, committee, Header::V1(header), votes).unwrap(); (cert.digest(), cert) } @@ -1038,24 +1048,27 @@ impl CommitteeFixture { // pub fn header(&self, author: PublicKey) -> Header { // Currently sign with the last authority - pub fn header(&self) -> Header { - self.authorities.last().unwrap().header(&self.committee()) + pub fn header(&self, protocol_config: &ProtocolConfig) -> Header { + self.authorities + .last() + .unwrap() + .header(protocol_config, &self.committee()) } - pub fn headers(&self) -> Vec
{ + pub fn headers(&self, protocol_config: &ProtocolConfig) -> Vec
{ let committee = self.committee(); self.authorities .iter() - .map(|a| a.header_with_round(&committee, 1)) + .map(|a| a.header_with_round(protocol_config, &committee, 1)) .collect() } - pub fn headers_next_round(&self) -> Vec
{ + pub fn headers_next_round(&self, protocol_config: &ProtocolConfig) -> Vec
{ let committee = self.committee(); self.authorities .iter() - .map(|a| a.header_with_round(&committee, 2)) + .map(|a| a.header_with_round(protocol_config, &committee, 2)) .collect() } @@ -1100,14 +1113,14 @@ impl CommitteeFixture { .collect() } - pub fn certificate(&self, header: &Header) -> Certificate { + pub fn certificate(&self, protocol_config: &ProtocolConfig, header: &Header) -> Certificate { let committee = self.committee(); let votes: Vec<_> = self .votes(header) .into_iter() .map(|x| (x.author(), x.signature().clone())) .collect(); - Certificate::new_unverified(&committee, header.clone(), votes).unwrap() + Certificate::new_unverified(protocol_config, &committee, header.clone(), votes).unwrap() } } @@ -1177,18 +1190,23 @@ impl AuthorityFixture { ) } - pub fn header(&self, committee: &Committee) -> Header { + pub fn header(&self, protocol_config: &ProtocolConfig, committee: &Committee) -> Header { let header = self - .header_builder(committee) + .header_builder(protocol_config, committee) .payload(Default::default()) .build() .unwrap(); Header::V1(header) } - pub fn header_with_round(&self, committee: &Committee, round: Round) -> Header { + pub fn header_with_round( + &self, + protocol_config: &ProtocolConfig, + committee: &Committee, + round: Round, + ) -> Header { let header = self - .header_builder(committee) + .header_builder(protocol_config, committee) .payload(Default::default()) .round(round) .build() @@ -1196,13 +1214,17 @@ impl AuthorityFixture { Header::V1(header) } - pub fn header_builder(&self, committee: &Committee) -> types::HeaderV1Builder { + pub fn header_builder( + &self, + protocol_config: &ProtocolConfig, + committee: &Committee, + ) -> types::HeaderV1Builder { types::HeaderV1Builder::default() .author(self.id()) .round(1) .epoch(committee.epoch()) .parents( - Certificate::genesis(committee) + Certificate::genesis(protocol_config, committee) .iter() .map(|x| x.digest()) .collect(), diff --git a/narwhal/types/benches/verify_certificate.rs b/narwhal/types/benches/verify_certificate.rs index 63115c0dbaf0ec..cb08ce29b3b059 100644 --- a/narwhal/types/benches/verify_certificate.rs +++ b/narwhal/types/benches/verify_certificate.rs @@ -22,7 +22,7 @@ pub fn verify_certificates(c: &mut Criterion) { let ids: Vec<_> = fixture.authorities().map(|a| a.id()).collect(); // process certificates for rounds, check we don't grow the dag too much - let genesis = Certificate::genesis(&committee) + let genesis = Certificate::genesis(&latest_protocol_version(), &committee) .iter() .map(|x| x.digest()) .collect::>(); @@ -44,7 +44,7 @@ pub fn verify_certificates(c: &mut Criterion) { |b, cert| { let worker_cache = fixture.worker_cache(); b.iter(|| { - let _ = cert.verify(&committee, &worker_cache); + let _ = cert.clone().verify(&committee, &worker_cache); }) }, ); diff --git a/narwhal/types/src/consensus.rs b/narwhal/types/src/consensus.rs index 5a6b8037e2e4f4..e61b2cbefcc77e 100644 --- a/narwhal/types/src/consensus.rs +++ b/narwhal/types/src/consensus.rs @@ -39,7 +39,7 @@ impl Hash<{ crypto::DIGEST_LENGTH }> for ConsensusOutput { } } -#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct CommittedSubDag { /// The sequence of committed certificates. pub certificates: Vec, @@ -449,7 +449,7 @@ mod tests { use indexmap::IndexMap; use std::collections::BTreeSet; use std::num::NonZeroUsize; - use test_utils::CommitteeFixture; + use test_utils::{latest_protocol_version, CommitteeFixture}; #[test] fn test_zero_timestamp_in_sub_dag() { @@ -467,8 +467,13 @@ mod tests { .build() .unwrap(); - let certificate = - Certificate::new_unsigned(&committee, Header::V1(header), Vec::new()).unwrap(); + let certificate = Certificate::new_unsigned( + &latest_protocol_version(), + &committee, + Header::V1(header), + Vec::new(), + ) + .unwrap(); // AND we initialise the sub dag via the "restore" way let sub_dag_round = CommittedSubDag { @@ -503,8 +508,13 @@ mod tests { .build() .unwrap(); - let certificate = - Certificate::new_unsigned(&committee, Header::V1(header), Vec::new()).unwrap(); + let certificate = Certificate::new_unsigned( + &latest_protocol_version(), + &committee, + Header::V1(header), + Vec::new(), + ) + .unwrap(); // AND let sub_dag_round_2 = CommittedSubDag::new( @@ -530,8 +540,13 @@ mod tests { .build() .unwrap(); - let certificate = - Certificate::new_unsigned(&committee, Header::V1(header), Vec::new()).unwrap(); + let certificate = Certificate::new_unsigned( + &latest_protocol_version(), + &committee, + Header::V1(header), + Vec::new(), + ) + .unwrap(); // WHEN create the sub dag based on the "previously committed" sub dag. let sub_dag_round_4 = CommittedSubDag::new( diff --git a/narwhal/types/src/primary.rs b/narwhal/types/src/primary.rs index e32ba9658083c2..97d6675f56c308 100644 --- a/narwhal/types/src/primary.rs +++ b/narwhal/types/src/primary.rs @@ -865,78 +865,107 @@ impl PartialEq for Vote { #[enum_dispatch(CertificateAPI)] pub enum Certificate { V1(CertificateV1), -} - -// TODO: Revisit if we should not impl Default for Certificate -impl Default for Certificate { - fn default() -> Self { - Self::V1(CertificateV1::default()) - } + V2(CertificateV2), } impl Certificate { - // TODO: Add version number and match on that - pub fn genesis(committee: &Committee) -> Vec { - CertificateV1::genesis(committee) - .into_iter() - .map(Self::V1) - .collect() + pub fn genesis(protocol_config: &ProtocolConfig, committee: &Committee) -> Vec { + if protocol_config.narwhal_certificate_v2() { + CertificateV2::genesis(committee) + .into_iter() + .map(Self::V2) + .collect() + } else { + CertificateV1::genesis(committee) + .into_iter() + .map(Self::V1) + .collect() + } } pub fn new_unverified( + protocol_config: &ProtocolConfig, committee: &Committee, header: Header, votes: Vec<(AuthorityIdentifier, Signature)>, ) -> DagResult { - CertificateV1::new_unverified(committee, header, votes) + if protocol_config.narwhal_certificate_v2() { + CertificateV2::new_unverified(committee, header, votes) + } else { + CertificateV1::new_unverified(committee, header, votes) + } } pub fn new_unsigned( + protocol_config: &ProtocolConfig, committee: &Committee, header: Header, votes: Vec<(AuthorityIdentifier, Signature)>, ) -> DagResult { - CertificateV1::new_unsigned(committee, header, votes) + if protocol_config.narwhal_certificate_v2() { + CertificateV2::new_unsigned(committee, header, votes) + } else { + CertificateV1::new_unsigned(committee, header, votes) + } } - pub fn new_test_empty(author: AuthorityIdentifier) -> Self { - CertificateV1::new_test_empty(author) + pub fn new_test_empty(protocol_config: &ProtocolConfig, author: AuthorityIdentifier) -> Self { + if protocol_config.narwhal_certificate_v2() { + CertificateV2::new_test_empty(author) + } else { + CertificateV1::new_test_empty(author) + } } /// This function requires that certificate was verified against given committee pub fn signed_authorities(&self, committee: &Committee) -> Vec { match self { Certificate::V1(certificate) => certificate.signed_authorities(committee), + Certificate::V2(certificate) => certificate.signed_authorities(committee), } } pub fn signed_by(&self, committee: &Committee) -> (Stake, Vec) { match self { Certificate::V1(certificate) => certificate.signed_by(committee), + Certificate::V2(certificate) => certificate.signed_by(committee), } } - pub fn verify(&self, committee: &Committee, worker_cache: &WorkerCache) -> DagResult<()> { + pub fn verify(&mut self, committee: &Committee, worker_cache: &WorkerCache) -> DagResult<()> { match self { Certificate::V1(certificate) => certificate.verify(committee, worker_cache), + Certificate::V2(certificate) => certificate.verify(committee, worker_cache), } } pub fn round(&self) -> Round { match self { Certificate::V1(certificate) => certificate.round(), + Certificate::V2(certificate) => certificate.round(), } } pub fn epoch(&self) -> Epoch { match self { Certificate::V1(certificate) => certificate.epoch(), + Certificate::V2(certificate) => certificate.epoch(), } } pub fn origin(&self) -> AuthorityIdentifier { match self { Certificate::V1(certificate) => certificate.origin(), + Certificate::V2(certificate) => certificate.origin(), + } + } + + // Used for testing + pub fn default(protocol_config: &ProtocolConfig) -> Certificate { + if protocol_config.narwhal_certificate_v2() { + Certificate::V2(CertificateV2::default()) + } else { + Certificate::V1(CertificateV1::default()) } } } @@ -947,6 +976,7 @@ impl Hash<{ crypto::DIGEST_LENGTH }> for Certificate { fn digest(&self) -> CertificateDigest { match self { Certificate::V1(data) => data.digest(), + Certificate::V2(data) => data.digest(), } } } @@ -961,6 +991,10 @@ pub trait CertificateAPI { // Used for testing. fn update_header(&mut self, header: Header); fn header_mut(&mut self) -> &mut Header; + + // CertificateV2 + fn aggregate_signature_state(&self) -> &AggregateSignatureState; + fn set_aggregate_signature_state(&mut self, state: AggregateSignatureState); } #[serde_as] @@ -982,6 +1016,14 @@ impl CertificateAPI for CertificateV1 { &self.aggregated_signature } + fn aggregate_signature_state(&self) -> &AggregateSignatureState { + unimplemented!("CertificateV2 field! Use aggregated_signature."); + } + + fn set_aggregate_signature_state(&mut self, _state: AggregateSignatureState) { + unimplemented!("CertificateV2 field! Use aggregated_signature."); + } + fn signed_authorities(&self) -> &roaring::RoaringBitmap { &self.signed_authorities } @@ -1183,6 +1225,278 @@ impl CertificateV1 { } } +// Holds AggregateSignatureBytes but with the added layer to specify if the +// signature was verified via a leader, verified directly, unverified or +// unsigned. This will be used to take advantage of the certificate chain that +// is formed via the DAG by only verifying the leaders of the certificate chain +// when they are fetched from validators during catchup. +#[derive(Clone, Serialize, Deserialize, MallocSizeOf, Debug)] +pub enum AggregateSignatureState { + VerifiedViaLeader(AggregateSignatureBytes), + VerifiedDirectly(AggregateSignatureBytes), + Unverified(AggregateSignatureBytes), + Unsigned(AggregateSignatureBytes), +} + +impl Default for AggregateSignatureState { + fn default() -> Self { + AggregateSignatureState::Unsigned(AggregateSignatureBytes::default()) + } +} + +#[serde_as] +#[derive(Clone, Serialize, Deserialize, Default, MallocSizeOf)] +pub struct CertificateV2 { + pub header: Header, + pub aggregate_signature_state: AggregateSignatureState, + #[serde_as(as = "NarwhalBitmap")] + signed_authorities: roaring::RoaringBitmap, + pub metadata: Metadata, +} + +impl CertificateAPI for CertificateV2 { + fn header(&self) -> &Header { + &self.header + } + + fn aggregated_signature(&self) -> &AggregateSignatureBytes { + match &self.aggregate_signature_state { + AggregateSignatureState::VerifiedViaLeader(bytes) + | AggregateSignatureState::VerifiedDirectly(bytes) + | AggregateSignatureState::Unverified(bytes) + | AggregateSignatureState::Unsigned(bytes) => bytes, + } + } + + fn aggregate_signature_state(&self) -> &AggregateSignatureState { + &self.aggregate_signature_state + } + + fn set_aggregate_signature_state(&mut self, state: AggregateSignatureState) { + self.aggregate_signature_state = state; + } + + fn signed_authorities(&self) -> &roaring::RoaringBitmap { + &self.signed_authorities + } + + fn metadata(&self) -> &Metadata { + &self.metadata + } + + // Used for testing. + fn update_header(&mut self, header: Header) { + self.header = header; + } + + fn header_mut(&mut self) -> &mut Header { + &mut self.header + } +} + +impl CertificateV2 { + pub fn genesis(committee: &Committee) -> Vec { + committee + .authorities() + .map(|authority| Self { + header: Header::V1(HeaderV1 { + author: authority.id(), + epoch: committee.epoch(), + ..Default::default() + }), + ..Self::default() + }) + .collect() + } + + pub fn new_unverified( + committee: &Committee, + header: Header, + votes: Vec<(AuthorityIdentifier, Signature)>, + ) -> DagResult { + Self::new_unsafe(committee, header, votes, true) + } + + pub fn new_unsigned( + committee: &Committee, + header: Header, + votes: Vec<(AuthorityIdentifier, Signature)>, + ) -> DagResult { + Self::new_unsafe(committee, header, votes, false) + } + + pub fn new_test_empty(author: AuthorityIdentifier) -> Certificate { + let header = Header::V1(HeaderV1 { + author, + ..Default::default() + }); + Certificate::V1(CertificateV1 { + header, + ..Default::default() + }) + } + + fn new_unsafe( + committee: &Committee, + header: Header, + votes: Vec<(AuthorityIdentifier, Signature)>, + check_stake: bool, + ) -> DagResult { + let mut votes = votes; + votes.sort_by_key(|(pk, _)| *pk); + let mut votes: VecDeque<_> = votes.into_iter().collect(); + + let mut weight = 0; + let mut sigs = Vec::new(); + + let filtered_votes = committee + .authorities() + .enumerate() + .filter(|(_, authority)| { + if !votes.is_empty() && authority.id() == votes.front().unwrap().0 { + sigs.push(votes.pop_front().unwrap()); + weight += authority.stake(); + // If there are repeats, also remove them + while !votes.is_empty() && votes.front().unwrap() == sigs.last().unwrap() { + votes.pop_front().unwrap(); + } + return true; + } + false + }) + .map(|(index, _)| index as u32); + + let signed_authorities= roaring::RoaringBitmap::from_sorted_iter(filtered_votes) + .map_err(|_| DagError::InvalidBitmap("Failed to convert votes into a bitmap of authority keys. Something is likely very wrong...".to_string()))?; + + // Ensure that all authorities in the set of votes are known + ensure!( + votes.is_empty(), + DagError::UnknownAuthority(votes.front().unwrap().0.to_string()) + ); + + // Ensure that the authorities have enough weight + ensure!( + !check_stake || weight >= committee.quorum_threshold(), + DagError::CertificateRequiresQuorum + ); + + let aggregated_signature = if sigs.is_empty() { + AggregateSignature::default() + } else { + AggregateSignature::aggregate::>( + sigs.iter().map(|(_, sig)| sig).collect(), + ) + .map_err(|_| DagError::InvalidSignature)? + }; + + let aggregate_signature_bytes = AggregateSignatureBytes::from(&aggregated_signature); + + let aggregate_signature_state = if !check_stake { + AggregateSignatureState::Unsigned(aggregate_signature_bytes) + } else { + AggregateSignatureState::Unverified(aggregate_signature_bytes) + }; + + Ok(Certificate::V2(CertificateV2 { + header, + aggregate_signature_state, + signed_authorities, + metadata: Metadata::default(), + })) + } + + /// This function requires that certificate was verified against given committee + pub fn signed_authorities(&self, committee: &Committee) -> Vec { + assert_eq!(committee.epoch(), self.epoch()); + let (_stake, pks) = self.signed_by(committee); + pks + } + + pub fn signed_by(&self, committee: &Committee) -> (Stake, Vec) { + // Ensure the certificate has a quorum. + let mut weight = 0; + + let auth_indexes = self.signed_authorities.iter().collect::>(); + let mut auth_iter = 0; + let pks = committee + .authorities() + .enumerate() + .filter(|(i, authority)| match auth_indexes.get(auth_iter) { + Some(index) if *index == *i as u32 => { + weight += authority.stake(); + auth_iter += 1; + true + } + _ => false, + }) + .map(|(_, authority)| authority.protocol_key().clone()) + .collect(); + (weight, pks) + } + + /// Verifies the validity of the certificate. + /// TODO: Output a different type, similar to Sui VerifiedCertificate. + pub fn verify(&mut self, committee: &Committee, worker_cache: &WorkerCache) -> DagResult<()> { + // Ensure the header is from the correct epoch. + ensure!( + self.epoch() == committee.epoch(), + DagError::InvalidEpoch { + expected: committee.epoch(), + received: self.epoch() + } + ); + + // Genesis certificates are always valid. + if self.round() == 0 && Self::genesis(committee).contains(self) { + return Ok(()); + } + + // Save signature verifications when the header is invalid. + self.header.validate(committee, worker_cache)?; + + let (weight, pks) = self.signed_by(committee); + + ensure!( + weight >= committee.quorum_threshold(), + DagError::CertificateRequiresQuorum + ); + + let aggregrate_signature_bytes = match self.aggregate_signature_state { + AggregateSignatureState::VerifiedViaLeader(ref bytes) => bytes, + AggregateSignatureState::VerifiedDirectly(_) => return Ok(()), + AggregateSignatureState::Unverified(ref bytes) => bytes, + AggregateSignatureState::Unsigned(_) => { + bail!(DagError::CertificateRequiresQuorum); + } + }; + + // Verify the signatures + let certificate_digest: Digest<{ crypto::DIGEST_LENGTH }> = Digest::from(self.digest()); + AggregateSignature::try_from(aggregrate_signature_bytes) + .map_err(|_| DagError::InvalidSignature)? + .verify_secure(&to_intent_message(certificate_digest), &pks[..]) + .map_err(|_| DagError::InvalidSignature)?; + + self.aggregate_signature_state = + AggregateSignatureState::VerifiedDirectly(aggregrate_signature_bytes.clone()); + + Ok(()) + } + + pub fn round(&self) -> Round { + self.header.round() + } + + pub fn epoch(&self) -> Epoch { + self.header.epoch() + } + + pub fn origin(&self) -> AuthorityIdentifier { + self.header.author() + } +} + #[derive( Clone, Copy, @@ -1248,6 +1562,14 @@ impl Hash<{ crypto::DIGEST_LENGTH }> for CertificateV1 { } } +impl Hash<{ crypto::DIGEST_LENGTH }> for CertificateV2 { + type TypedDigest = CertificateDigest; + + fn digest(&self) -> CertificateDigest { + CertificateDigest(self.header.digest().0) + } +} + impl fmt::Debug for Certificate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { @@ -1260,6 +1582,15 @@ impl fmt::Debug for Certificate { data.header.digest(), data.epoch() ), + Certificate::V2(data) => write!( + f, + "{}: C{}({}, {}, E{})", + data.digest(), + data.round(), + data.origin(), + data.header.digest(), + data.epoch() + ), } } } @@ -1268,6 +1599,13 @@ impl PartialEq for Certificate { fn eq(&self, other: &Self) -> bool { match (self, other) { (Certificate::V1(data), Certificate::V1(other_data)) => data.eq(other_data), + (Certificate::V2(data), Certificate::V2(other_data)) => data.eq(other_data), + (Certificate::V1(_), Certificate::V2(_)) => { + unimplemented!("Invalid comparison between CertificateV1 & CertificateV2"); + } + (Certificate::V2(_), Certificate::V1(_)) => { + unimplemented!("Invalid comparison between CertificateV2 & CertificateV1"); + } } } } @@ -1282,6 +1620,16 @@ impl PartialEq for CertificateV1 { } } +impl PartialEq for CertificateV2 { + fn eq(&self, other: &Self) -> bool { + let mut ret = self.header().digest() == other.header().digest(); + ret &= self.round() == other.round(); + ret &= self.epoch() == other.epoch(); + ret &= self.origin() == other.origin(); + ret + } +} + /// Request for broadcasting certificates to peers. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SendCertificateRequest { diff --git a/narwhal/types/tests/primary_tests.rs b/narwhal/types/tests/primary_tests.rs index 9239daf704ef95..972123aa2bbf9d 100644 --- a/narwhal/types/tests/primary_tests.rs +++ b/narwhal/types/tests/primary_tests.rs @@ -9,7 +9,7 @@ use rand::rngs::OsRng; use rand::seq::SliceRandom; use std::collections::BTreeSet; use std::num::NonZeroUsize; -use test_utils::{AuthorityFixture, CommitteeFixture}; +use test_utils::{latest_protocol_version, AuthorityFixture, CommitteeFixture}; #[tokio::test] async fn test_certificate_singers_are_ordered() { @@ -47,7 +47,13 @@ async fn test_certificate_singers_are_ordered() { votes.shuffle(&mut OsRng); // Create a certificate - let certificate = Certificate::new_unverified(&committee, Header::V1(header), votes).unwrap(); + let certificate = Certificate::new_unverified( + &latest_protocol_version(), + &committee, + Header::V1(header), + votes, + ) + .unwrap(); let (stake, signers) = certificate.signed_by(&committee);