diff --git a/ledger/block/src/lib.rs b/ledger/block/src/lib.rs index fd9dae34ce..7cbadc0885 100644 --- a/ledger/block/src/lib.rs +++ b/ledger/block/src/lib.rs @@ -125,9 +125,6 @@ impl Block { transactions: Transactions, aborted_transaction_ids: Vec, ) -> Result { - // Ensure the block contains transactions. - ensure!(!transactions.is_empty(), "Cannot create a block with zero transactions"); - // Ensure the number of transactions is within the allowed range. if transactions.len() > Transactions::::MAX_TRANSACTIONS { bail!( diff --git a/ledger/block/src/verify.rs b/ledger/block/src/verify.rs index 1cb990077b..40d681efa9 100644 --- a/ledger/block/src/verify.rs +++ b/ledger/block/src/verify.rs @@ -30,7 +30,7 @@ impl Block { &self, previous_block: &Block, current_state_root: N::StateRoot, - current_committee: &Committee, + current_committee_lookback: &Committee, current_puzzle: &CoinbasePuzzle, current_epoch_challenge: &EpochChallenge, current_timestamp: i64, @@ -41,7 +41,7 @@ impl Block { // Ensure the block authority is correct. let (expected_round, expected_height, expected_timestamp, expected_existing_transaction_ids) = - self.verify_authority(previous_block.round(), previous_block.height(), current_committee)?; + self.verify_authority(previous_block.round(), previous_block.height(), current_committee_lookback)?; // Ensure the block solutions are correct. let ( @@ -138,8 +138,9 @@ impl Block { &self, previous_round: u64, previous_height: u32, - current_committee: &Committee, + current_committee_lookback: &Committee, ) -> Result<(u64, u32, i64, Vec)> { + // Note: Do not remove this. This ensures that all blocks after genesis are quorum blocks. #[cfg(not(any(test, feature = "test")))] ensure!(self.authority.is_quorum(), "The next block must be a quorum block"); @@ -164,12 +165,13 @@ impl Block { subdag.anchor_round() } }; - // Ensure the block round is at least the starting round of the committee. + // Ensure the block round minus the committee lookback range is at least the starting round of the committee lookback. ensure!( - expected_round >= current_committee.starting_round(), - "Block {} has an invalid round (found '{expected_round}', expected at least '{}')", - expected_height, - current_committee.starting_round() + expected_round.saturating_sub(Committee::::COMMITTEE_LOOKBACK_RANGE) + >= current_committee_lookback.starting_round(), + "Block {expected_height} has an invalid round (found '{}', expected at least '{}')", + expected_round.saturating_sub(Committee::::COMMITTEE_LOOKBACK_RANGE), + current_committee_lookback.starting_round() ); // Ensure the block authority is correct. @@ -180,7 +182,7 @@ impl Block { let signer = signature.to_address(); // Ensure the block is signed by a committee member. ensure!( - current_committee.members().contains_key(&signer), + current_committee_lookback.members().contains_key(&signer), "Beacon block {expected_height} has a signer not in the committee (found '{signer}')", ); // Ensure the signature is valid. @@ -193,7 +195,7 @@ impl Block { } Authority::Quorum(subdag) => { // Compute the expected leader. - let expected_leader = current_committee.get_leader(expected_round)?; + let expected_leader = current_committee_lookback.get_leader(expected_round)?; // Ensure the block is authored by the expected leader. ensure!( subdag.leader_address() == expected_leader, @@ -387,9 +389,6 @@ impl Block { fn verify_transactions(&self) -> Result<()> { let height = self.height(); - // Ensure there are transactions. - ensure!(!self.transactions.is_empty(), "Block {height} must contain at least 1 transaction"); - // Ensure the number of transactions is within the allowed range. if self.transactions.len() > Transactions::::MAX_TRANSACTIONS { bail!( diff --git a/ledger/committee/src/lib.rs b/ledger/committee/src/lib.rs index 9ba7843dcd..a933632d89 100644 --- a/ledger/committee/src/lib.rs +++ b/ledger/committee/src/lib.rs @@ -47,6 +47,8 @@ pub struct Committee { } impl Committee { + /// The committee lookback range. + pub const COMMITTEE_LOOKBACK_RANGE: u64 = 50; /// The maximum number of members that may be in a committee. pub const MAX_COMMITTEE_SIZE: u16 = 200; diff --git a/ledger/src/check_next_block.rs b/ledger/src/check_next_block.rs index 4ac3ed0355..53a1817c53 100644 --- a/ledger/src/check_next_block.rs +++ b/ledger/src/check_next_block.rs @@ -84,11 +84,24 @@ impl> Ledger { let ratified_finalize_operations = self.vm.check_speculate(state, block.ratifications(), block.solutions(), block.transactions())?; + // Get the round number for the previous committee. Note, we subtract 2 from odd rounds, + // because committees are updated in even rounds. + let previous_round = match block.round() % 2 == 0 { + true => block.round().saturating_sub(1), + false => block.round().saturating_sub(2), + }; + // Get the committee lookback round. + let committee_lookback_round = previous_round.saturating_sub(Committee::::COMMITTEE_LOOKBACK_RANGE); + // Retrieve the committee lookback. + let committee_lookback = self + .get_committee_for_round(committee_lookback_round)? + .ok_or(anyhow!("Failed to fetch committee for round {committee_lookback_round}"))?; + // Ensure the block is correct. let expected_existing_transaction_ids = block.verify( &self.latest_block(), self.latest_state_root(), - &self.latest_committee()?, + &committee_lookback, self.coinbase_puzzle(), &self.latest_epoch_challenge()?, OffsetDateTime::now_utc().unix_timestamp(),