From 9baf2c1ca2d7860f07fb5b033829fc2cb238a1fd Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:43:13 -0800 Subject: [PATCH 1/8] Introduce committee ID --- ledger/committee/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ledger/committee/src/lib.rs b/ledger/committee/src/lib.rs index e198b08e71..d2df067e15 100644 --- a/ledger/committee/src/lib.rs +++ b/ledger/committee/src/lib.rs @@ -83,6 +83,11 @@ impl Committee { // Return the new committee. Ok(Self { starting_round, members, total_stake }) } + + /// Returns the committee ID. + pub fn to_id(&self) -> Result> { + Ok(N::hash_bhp1024(&self.to_bytes_le()?.to_bits_le())?.into()) + } } impl Committee { From 5af43e8e0dc0d3531a834cb92d4b500a0fceb621 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:44:55 -0800 Subject: [PATCH 2/8] Add committee ID to batch header --- ledger/narwhal/batch-header/src/bytes.rs | 9 +++- ledger/narwhal/batch-header/src/lib.rs | 56 +++++++++++++++++--- ledger/narwhal/batch-header/src/serialize.rs | 4 +- ledger/narwhal/batch-header/src/to_id.rs | 4 ++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/ledger/narwhal/batch-header/src/bytes.rs b/ledger/narwhal/batch-header/src/bytes.rs index 37474eb118..ceb455192e 100644 --- a/ledger/narwhal/batch-header/src/bytes.rs +++ b/ledger/narwhal/batch-header/src/bytes.rs @@ -32,6 +32,8 @@ impl FromBytes for BatchHeader { let round = u64::read_le(&mut reader)?; // Read the timestamp. let timestamp = i64::read_le(&mut reader)?; + // Read the committee ID. + let committee_id = Field::read_le(&mut reader)?; // Read the number of transmission IDs. let num_transmission_ids = u32::read_le(&mut reader)?; @@ -69,8 +71,9 @@ impl FromBytes for BatchHeader { let signature = Signature::read_le(&mut reader)?; // Construct the batch. - let batch = Self::from(author, round, timestamp, transmission_ids, previous_certificate_ids, signature) - .map_err(error)?; + let batch = + Self::from(author, round, timestamp, committee_id, transmission_ids, previous_certificate_ids, signature) + .map_err(error)?; // Return the batch. match batch.batch_id == batch_id { @@ -93,6 +96,8 @@ impl ToBytes for BatchHeader { self.round.write_le(&mut writer)?; // Write the timestamp. self.timestamp.write_le(&mut writer)?; + // Write the committee ID. + self.committee_id.write_le(&mut writer)?; // Write the number of transmission IDs. u32::try_from(self.transmission_ids.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?; // Write the transmission IDs. diff --git a/ledger/narwhal/batch-header/src/lib.rs b/ledger/narwhal/batch-header/src/lib.rs index d50ea0f7f3..9fb680f34a 100644 --- a/ledger/narwhal/batch-header/src/lib.rs +++ b/ledger/narwhal/batch-header/src/lib.rs @@ -32,7 +32,7 @@ use narwhal_transmission_id::TransmissionID; #[derive(Clone, PartialEq, Eq)] pub struct BatchHeader { /// The batch ID, defined as the hash of the author, round number, timestamp, transmission IDs, - /// previous batch certificate IDs, and last election certificate IDs. + /// committee ID, previous batch certificate IDs, and last election certificate IDs. batch_id: Field, /// The author of the batch. author: Address, @@ -40,6 +40,8 @@ pub struct BatchHeader { round: u64, /// The timestamp. timestamp: i64, + /// The committee ID. + committee_id: Field, /// The set of `transmission IDs`. transmission_ids: IndexSet>, /// The batch certificate IDs of the previous round. @@ -66,6 +68,7 @@ impl BatchHeader { private_key: &PrivateKey, round: u64, timestamp: i64, + committee_id: Field, transmission_ids: IndexSet>, previous_certificate_ids: IndexSet>, rng: &mut R, @@ -95,11 +98,27 @@ impl BatchHeader { // Retrieve the address. let author = Address::try_from(private_key)?; // Compute the batch ID. - let batch_id = Self::compute_batch_id(author, round, timestamp, &transmission_ids, &previous_certificate_ids)?; + let batch_id = Self::compute_batch_id( + author, + round, + timestamp, + committee_id, + &transmission_ids, + &previous_certificate_ids, + )?; // Sign the preimage. let signature = private_key.sign(&[batch_id], rng)?; // Return the batch header. - Ok(Self { author, batch_id, round, timestamp, transmission_ids, previous_certificate_ids, signature }) + Ok(Self { + author, + batch_id, + round, + timestamp, + committee_id, + transmission_ids, + previous_certificate_ids, + signature, + }) } /// Initializes a new batch header. @@ -107,6 +126,7 @@ impl BatchHeader { author: Address, round: u64, timestamp: i64, + committee_id: Field, transmission_ids: IndexSet>, previous_certificate_ids: IndexSet>, signature: Signature, @@ -134,13 +154,29 @@ impl BatchHeader { ); // Compute the batch ID. - let batch_id = Self::compute_batch_id(author, round, timestamp, &transmission_ids, &previous_certificate_ids)?; + let batch_id = Self::compute_batch_id( + author, + round, + timestamp, + committee_id, + &transmission_ids, + &previous_certificate_ids, + )?; // Verify the signature. if !signature.verify(&author, &[batch_id]) { bail!("Invalid signature for the batch header"); } // Return the batch header. - Ok(Self { author, batch_id, round, timestamp, transmission_ids, previous_certificate_ids, signature }) + Ok(Self { + author, + batch_id, + round, + timestamp, + committee_id, + transmission_ids, + previous_certificate_ids, + signature, + }) } } @@ -165,6 +201,11 @@ impl BatchHeader { self.timestamp } + /// Returns the committee ID. + pub const fn committee_id(&self) -> Field { + self.committee_id + } + /// Returns the transmission IDs. pub const fn transmission_ids(&self) -> &IndexSet> { &self.transmission_ids @@ -228,13 +269,16 @@ pub mod test_helpers { ) -> BatchHeader { // Sample a private key. let private_key = PrivateKey::new(rng).unwrap(); + // Sample the committee ID. + let committee_id = Field::::rand(rng); // Sample transmission IDs. let transmission_ids = narwhal_transmission_id::test_helpers::sample_transmission_ids(rng).into_iter().collect::>(); // Checkpoint the timestamp for the batch. let timestamp = OffsetDateTime::now_utc().unix_timestamp(); // Return the batch header. - BatchHeader::new(&private_key, round, timestamp, transmission_ids, previous_certificate_ids, rng).unwrap() + BatchHeader::new(&private_key, round, timestamp, committee_id, transmission_ids, previous_certificate_ids, rng) + .unwrap() } /// Returns a list of sample batch headers, sampled at random. diff --git a/ledger/narwhal/batch-header/src/serialize.rs b/ledger/narwhal/batch-header/src/serialize.rs index 6acddaead2..6e86f014c8 100644 --- a/ledger/narwhal/batch-header/src/serialize.rs +++ b/ledger/narwhal/batch-header/src/serialize.rs @@ -19,11 +19,12 @@ impl Serialize for BatchHeader { fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { true => { - let mut header = serializer.serialize_struct("BatchHeader", 7)?; + let mut header = serializer.serialize_struct("BatchHeader", 8)?; header.serialize_field("batch_id", &self.batch_id)?; header.serialize_field("author", &self.author)?; header.serialize_field("round", &self.round)?; header.serialize_field("timestamp", &self.timestamp)?; + header.serialize_field("committee_id", &self.committee_id)?; header.serialize_field("transmission_ids", &self.transmission_ids)?; header.serialize_field("previous_certificate_ids", &self.previous_certificate_ids)?; header.serialize_field("signature", &self.signature)?; @@ -47,6 +48,7 @@ impl<'de, N: Network> Deserialize<'de> for BatchHeader { DeserializeExt::take_from_value::(&mut header, "author")?, DeserializeExt::take_from_value::(&mut header, "round")?, DeserializeExt::take_from_value::(&mut header, "timestamp")?, + DeserializeExt::take_from_value::(&mut header, "committee_id")?, DeserializeExt::take_from_value::(&mut header, "transmission_ids")?, DeserializeExt::take_from_value::(&mut header, "previous_certificate_ids")?, DeserializeExt::take_from_value::(&mut header, "signature")?, diff --git a/ledger/narwhal/batch-header/src/to_id.rs b/ledger/narwhal/batch-header/src/to_id.rs index 1d99cbda77..c783d2e4ff 100644 --- a/ledger/narwhal/batch-header/src/to_id.rs +++ b/ledger/narwhal/batch-header/src/to_id.rs @@ -21,6 +21,7 @@ impl BatchHeader { self.author, self.round, self.timestamp, + self.committee_id, &self.transmission_ids, &self.previous_certificate_ids, ) @@ -33,6 +34,7 @@ impl BatchHeader { author: Address, round: u64, timestamp: i64, + committee_id: Field, transmission_ids: &IndexSet>, previous_certificate_ids: &IndexSet>, ) -> Result> { @@ -43,6 +45,8 @@ impl BatchHeader { round.write_le(&mut preimage)?; // Insert the timestamp. timestamp.write_le(&mut preimage)?; + // Insert the committee ID. + committee_id.write_le(&mut preimage)?; // Insert the number of transmissions. u32::try_from(transmission_ids.len())?.write_le(&mut preimage)?; // Insert the transmission IDs. From a25341718d2d3da495bfbe45c2c6117211cdb235 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:58:57 -0800 Subject: [PATCH 3/8] Store the committee ID in the Committee --- ledger/committee/src/bytes.rs | 8 +++++ ledger/committee/src/lib.rs | 15 ++++++---- ledger/committee/src/serialize.rs | 8 ++++- ledger/committee/src/to_id.rs | 50 +++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 ledger/committee/src/to_id.rs diff --git a/ledger/committee/src/bytes.rs b/ledger/committee/src/bytes.rs index 2c3eba9771..52910b99e9 100644 --- a/ledger/committee/src/bytes.rs +++ b/ledger/committee/src/bytes.rs @@ -24,6 +24,8 @@ impl FromBytes for Committee { return Err(error("Invalid committee version")); } + // Read the committee ID. + let id = Field::read_le(&mut reader)?; // Read the starting round. let starting_round = u64::read_le(&mut reader)?; // Read the number of members. @@ -51,6 +53,10 @@ impl FromBytes for Committee { let total_stake = u64::read_le(&mut reader)?; // Construct the committee. let committee = Self::new(starting_round, members).map_err(|e| error(e.to_string()))?; + // Ensure the committee ID matches. + if committee.id() != id { + return Err(error("Invalid committee ID during deserialization")); + } // Ensure the total stake matches. match committee.total_stake() == total_stake { true => Ok(committee), @@ -64,6 +70,8 @@ impl ToBytes for Committee { fn write_le(&self, mut writer: W) -> IoResult<()> { // Write the version. 1u8.write_le(&mut writer)?; + // Write the committee ID. + self.id().write_le(&mut writer)?; // Write the starting round. self.starting_round.write_le(&mut writer)?; // Write the number of members. diff --git a/ledger/committee/src/lib.rs b/ledger/committee/src/lib.rs index d2df067e15..37940efc16 100644 --- a/ledger/committee/src/lib.rs +++ b/ledger/committee/src/lib.rs @@ -18,6 +18,7 @@ mod bytes; mod serialize; mod string; +mod to_id; #[cfg(any(test, feature = "prop-tests"))] pub mod prop_tests; @@ -41,6 +42,8 @@ pub const MAX_DELEGATORS: u32 = 100_000u32; #[derive(Clone, PartialEq, Eq)] pub struct Committee { + /// The committee ID, defined as the hash of the starting round, members, and total stake. + id: Field, /// The starting round number for this committee. starting_round: u64, /// A map of `address` to `(stake, is_open)` state. @@ -78,19 +81,21 @@ impl Committee { ); // Compute the total stake of the committee for this round. let total_stake = Self::compute_total_stake(&members)?; + // Compute the committee ID. + let id = Self::compute_committee_id(starting_round, &members, total_stake)?; #[cfg(feature = "metrics")] metrics::gauge(metrics::committee::TOTAL_STAKE, total_stake as f64); // Return the new committee. - Ok(Self { starting_round, members, total_stake }) + Ok(Self { id, starting_round, members, total_stake }) } +} +impl Committee { /// Returns the committee ID. - pub fn to_id(&self) -> Result> { - Ok(N::hash_bhp1024(&self.to_bytes_le()?.to_bits_le())?.into()) + pub const fn id(&self) -> Field { + self.id } -} -impl Committee { /// Returns the starting round number for this committee. pub const fn starting_round(&self) -> u64 { self.starting_round diff --git a/ledger/committee/src/serialize.rs b/ledger/committee/src/serialize.rs index df121bd75a..3964cf7597 100644 --- a/ledger/committee/src/serialize.rs +++ b/ledger/committee/src/serialize.rs @@ -19,7 +19,8 @@ impl Serialize for Committee { fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { true => { - let mut certificate = serializer.serialize_struct("Committee", 3)?; + let mut certificate = serializer.serialize_struct("Committee", 4)?; + certificate.serialize_field("id", &self.id)?; certificate.serialize_field("starting_round", &self.starting_round)?; certificate.serialize_field("members", &self.members)?; certificate.serialize_field("total_stake", &self.total_stake)?; @@ -36,12 +37,17 @@ impl<'de, N: Network> Deserialize<'de> for Committee { match deserializer.is_human_readable() { true => { let mut value = serde_json::Value::deserialize(deserializer)?; + let id: Field = DeserializeExt::take_from_value::(&mut value, "id")?; let total_stake: u64 = DeserializeExt::take_from_value::(&mut value, "total_stake")?; let committee = Self::new( DeserializeExt::take_from_value::(&mut value, "starting_round")?, DeserializeExt::take_from_value::(&mut value, "members")?, ) .map_err(de::Error::custom)?; + + if committee.id != id { + return Err(de::Error::custom("committee ID mismatch")); + } match committee.total_stake == total_stake { true => Ok(committee), false => Err(de::Error::custom("total stake mismatch")), diff --git a/ledger/committee/src/to_id.rs b/ledger/committee/src/to_id.rs new file mode 100644 index 0000000000..b0a57335d7 --- /dev/null +++ b/ledger/committee/src/to_id.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +impl Committee { + /// Returns the committee ID. + pub fn to_id(&self) -> Result> { + Self::compute_committee_id(self.starting_round, &self.members, self.total_stake) + } +} + +impl Committee { + /// Returns the commmitee ID. + pub fn compute_committee_id( + starting_round: u64, + members: &IndexMap, (u64, bool)>, + total_stake: u64, + ) -> Result> { + let mut preimage = Vec::new(); + // Insert the starting_round. + starting_round.write_le(&mut preimage)?; + // Write the number of members. + u16::try_from(members.len())?.write_le(&mut preimage)?; + // Write the members. + for (address, (stake, is_open)) in members { + // Write the address. + address.write_le(&mut preimage)?; + // Write the stake. + stake.write_le(&mut preimage)?; + // Write the is_open flag. + is_open.write_le(&mut preimage)?; + } + // Insert the total stake. + total_stake.write_le(&mut preimage)?; + // Hash the preimage. + N::hash_bhp1024(&preimage.to_bits_le()) + } +} From 7da117e9ce03ef5f0075c0f2db4e2a6b3a403d8f Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:07:40 -0800 Subject: [PATCH 4/8] Add committee ID checks to block verification --- ledger/block/src/verify.rs | 22 +++++++++++++++++++++ ledger/narwhal/batch-certificate/src/lib.rs | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/ledger/block/src/verify.rs b/ledger/block/src/verify.rs index b7afba54df..c989d62be6 100644 --- a/ledger/block/src/verify.rs +++ b/ledger/block/src/verify.rs @@ -233,6 +233,28 @@ impl Block { Authority::Quorum(subdag) => subdag.timestamp(previous_committee_lookback), }; + // Check that the committee IDs are correct. + if let Authority::Quorum(subdag) = &self.authority { + // Check that the committee ID of the leader certificate is correct. + ensure!( + subdag.leader_certificate().committee_id() == current_committee_lookback.id(), + "Leader certificate has an incorrect committee ID" + ); + + // Check that all all certificates on each round have the same committee ID. + for (round, certificates) in subdag.iter() { + // Check that every certificate for a given round shares the same committee ID. + let expected_committee_id = certificates + .first() + .map(|certificate| certificate.committee_id()) + .ok_or(anyhow!("No certificates found for round {round}"))?; + ensure!( + certificates.iter().all(|certificate| certificate.committee_id() == expected_committee_id), + "All certificates on round {round} do not have the same committee ID", + ); + } + } + // Return success. Ok(( expected_round, diff --git a/ledger/narwhal/batch-certificate/src/lib.rs b/ledger/narwhal/batch-certificate/src/lib.rs index 292bed5366..5255ee9d26 100644 --- a/ledger/narwhal/batch-certificate/src/lib.rs +++ b/ledger/narwhal/batch-certificate/src/lib.rs @@ -121,6 +121,11 @@ impl BatchCertificate { self.batch_header().round() } + /// Returns the committee ID. + pub const fn committee_id(&self) -> Field { + self.batch_header().committee_id() + } + /// Returns the transmission IDs. pub const fn transmission_ids(&self) -> &IndexSet> { self.batch_header().transmission_ids() From ee10658cd589de8af56d7e4162ce11af0bb71804 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sun, 25 Feb 2024 22:36:17 -0800 Subject: [PATCH 5/8] Regenerate genesis block --- parameters/src/mainnet/genesis.rs | 2 +- .../src/mainnet/resources/block.genesis | Bin 15133 -> 15165 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/parameters/src/mainnet/genesis.rs b/parameters/src/mainnet/genesis.rs index c89d73e889..4aa500db6e 100644 --- a/parameters/src/mainnet/genesis.rs +++ b/parameters/src/mainnet/genesis.rs @@ -27,6 +27,6 @@ mod tests { #[test] fn test_genesis_block() { let bytes = GenesisBytes::load_bytes(); - assert_eq!(15133, bytes.len() as u64, "Update me if serialization has changed"); + assert_eq!(15165, bytes.len() as u64, "Update me if serialization has changed"); } } diff --git a/parameters/src/mainnet/resources/block.genesis b/parameters/src/mainnet/resources/block.genesis index 8f1f7992e264cfc7291fb047b4e2fc402db36af0..f7853a6f5b88896b866de741da042ad77700effe 100644 GIT binary patch delta 11406 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

{pybRe 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 W7u&*wnNLDJXF93>i`?%5q5lu1ZwEO5 delta 11377 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 From 182b26389970d4bff6362babaa61359a36b19c6b Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Mon, 26 Feb 2024 01:53:38 -0800 Subject: [PATCH 6/8] Rewrite expectations --- .../tests/expectations/vm/execute_and_finalize/test_rand.out | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out index 43b2ae5b12..eb372e7191 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out @@ -19,14 +19,14 @@ outputs: test_rand.aleo/rand_chacha_check: outputs: - '{"type":"future","id":"5655280628674362392666464396476127281186411758739892961608160465159658100070field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 0field,\n false\n ]\n}"}' - speculate: the execution was accepted + speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: test_rand.aleo/rand_chacha_check: outputs: - '{"type":"future","id":"4722955375376605065432422068493514420613444220803270858286854080465504837613field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 1field,\n true\n ]\n}"}' - speculate: the execution was rejected + speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: From a9df2bcec4a73e9d3c5f1730fca7e10bd4f9f9b2 Mon Sep 17 00:00:00 2001 From: raychu86 <14917648+raychu86@users.noreply.github.com> Date: Sat, 2 Mar 2024 14:41:18 -0800 Subject: [PATCH 7/8] parallelize subdag committee id check --- ledger/block/src/verify.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ledger/block/src/verify.rs b/ledger/block/src/verify.rs index c989d62be6..74a3e86e2e 100644 --- a/ledger/block/src/verify.rs +++ b/ledger/block/src/verify.rs @@ -242,17 +242,18 @@ impl Block { ); // Check that all all certificates on each round have the same committee ID. - for (round, certificates) in subdag.iter() { + cfg_iter!(subdag).try_for_each(|(round, certificates)| { // Check that every certificate for a given round shares the same committee ID. let expected_committee_id = certificates .first() .map(|certificate| certificate.committee_id()) - .ok_or(anyhow!("No certificates found for round {round}"))?; + .ok_or(anyhow!("No certificates found for subdag round {round}"))?; ensure!( - certificates.iter().all(|certificate| certificate.committee_id() == expected_committee_id), - "All certificates on round {round} do not have the same committee ID", + certificates.iter().skip(1).all(|certificate| certificate.committee_id() == expected_committee_id), + "Certificates on round {round} do not all have the same committee ID", ); - } + Ok(()) + })?; } // Return success. From fa3fbacd9f07f2453809eb68e0591145f54745bc Mon Sep 17 00:00:00 2001 From: Howard Wu <9260812+howardwu@users.noreply.github.com> Date: Sat, 2 Mar 2024 14:56:47 -0800 Subject: [PATCH 8/8] nit: reorder items --- ledger/narwhal/batch-certificate/src/lib.rs | 10 +++++----- ledger/narwhal/batch-header/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ledger/narwhal/batch-certificate/src/lib.rs b/ledger/narwhal/batch-certificate/src/lib.rs index 5255ee9d26..a99166c383 100644 --- a/ledger/narwhal/batch-certificate/src/lib.rs +++ b/ledger/narwhal/batch-certificate/src/lib.rs @@ -121,6 +121,11 @@ impl BatchCertificate { self.batch_header().round() } + /// Returns the timestamp of the batch header. + pub fn timestamp(&self) -> i64 { + self.batch_header().timestamp() + } + /// Returns the committee ID. pub const fn committee_id(&self) -> Field { self.batch_header().committee_id() @@ -136,11 +141,6 @@ impl BatchCertificate { self.batch_header().previous_certificate_ids() } - /// Returns the timestamp of the batch header. - pub fn timestamp(&self) -> i64 { - self.batch_header().timestamp() - } - /// Returns the signatures of the batch ID from the committee. pub fn signatures(&self) -> Box>> { Box::new(self.signatures.iter()) diff --git a/ledger/narwhal/batch-header/src/lib.rs b/ledger/narwhal/batch-header/src/lib.rs index 9fb680f34a..ce79fc6c6a 100644 --- a/ledger/narwhal/batch-header/src/lib.rs +++ b/ledger/narwhal/batch-header/src/lib.rs @@ -110,8 +110,8 @@ impl BatchHeader { let signature = private_key.sign(&[batch_id], rng)?; // Return the batch header. Ok(Self { - author, batch_id, + author, round, timestamp, committee_id,