Skip to content

Commit d8e7366

Browse files
authored
Merge pull request #3019 from ProvableHQ/fix/restore-pending-blocks
[Feat] Restore PendingBlocks API
2 parents 8c93c36 + 6c684ae commit d8e7366

File tree

5 files changed

+485
-26
lines changed

5 files changed

+485
-26
lines changed

.circleci/semver-checks.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#! /bin/bash
22

3+
set -x
4+
5+
BASELINE_REV=acd55ad100550 # UPDATE ME ON NECESSARY BREAKING CHANGES
6+
37
# Ensure that the command is installed.
48
cargo install [email protected] --locked
59

6-
BASELINE_REV=dec54170ce # UPDATE ME ON NECESSARY BREAKING CHANGES
10+
# Ensure we can find the baseline revision
11+
git fetch --unshallow || true
712

8-
# Exclude CLI as it has been removed
9-
cargo semver-checks --workspace --default-features \
10-
--exclude=snarkvm-cli --baseline-rev $BASELINE_REV
13+
cargo semver-checks --workspace --default-features --baseline-rev $BASELINE_REV

ledger/src/check_next_block.rs

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,112 @@ use crate::narwhal::BatchHeader;
1919

2020
use anyhow::{Context, bail};
2121

22+
/// Wrapper for a block that has a valid subDAG, but where the block header,
23+
/// solutions, and transmissions have not been verified yet.
24+
///
25+
/// This type is created by `Ledger::check_block_subdag` and consumed by `Ledger::check_block_content`.
26+
#[derive(Clone, PartialEq, Eq)]
27+
pub struct PendingBlock<N: Network>(Block<N>);
28+
29+
impl<N: Network> Deref for PendingBlock<N> {
30+
type Target = Block<N>;
31+
32+
fn deref(&self) -> &Block<N> {
33+
&self.0
34+
}
35+
}
36+
2237
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
23-
/// Checks the given block is valid next block.
38+
/// Checks that the subDAG in a given block is valid, but does not fully verify the block.
2439
///
25-
/// # Panics
26-
/// This function panics if called from an async context.
27-
pub fn check_next_block<R: CryptoRng + Rng>(&self, block: &Block<N>, rng: &mut R) -> Result<()> {
28-
let height = block.height();
29-
let latest_block = self.latest_block();
40+
/// # Arguments
41+
/// * `block` - The block to check.
42+
/// * `pending_block` - A sequence of blocks between the block to check and the current height of the ledger.
43+
///
44+
/// # Notes
45+
/// * This does *not* check that the header of the block is correct or execute/verify any of the transmissions contained within it.
46+
///
47+
/// * In most cases, you want to use [`Self::check_next_block`] instead to perform a full verification.
48+
///
49+
/// * This will reject any blocks with a height <= the current height and any blocks with a height >= the current height + GC.
50+
/// For the former, a valid block already exists and,
51+
/// for the latter, the comittte is still unknown.
52+
pub fn check_block_subdag(&self, block: Block<N>, pending_blocks: &[PendingBlock<N>]) -> Result<PendingBlock<N>> {
53+
self.check_block_subdag_inner(&block, pending_blocks)?;
54+
Ok(PendingBlock(block))
55+
}
56+
57+
fn check_block_subdag_inner(&self, block: &Block<N>, pending_blocks: &[PendingBlock<N>]) -> Result<()> {
58+
// First check that the heights and hashes of the pending block sequence and of the new block are correct.
59+
// The hash checks should be redundant, but we perform them out of extra caution.
60+
let mut expected_height = self.latest_height() + 1;
61+
for pending in pending_blocks {
62+
if pending.height() != expected_height {
63+
bail!(
64+
"Pending block has invalid height. Expected {expected_height}, but got {actual}.",
65+
actual = pending.height()
66+
);
67+
}
3068

31-
// Check that this is actually the next block.
32-
if height != latest_block.height() + 1 {
33-
bail!("Block height is {height}, but expected {}", latest_block.height() + 1);
69+
if self.contains_block_hash(&pending.hash())? {
70+
bail!("Hash for pending block '{}' already exists in the ledger", block.hash())
71+
}
72+
73+
expected_height += 1;
3474
}
3575

36-
// Ensure the block hash does not already exist.
3776
if self.contains_block_hash(&block.hash())? {
3877
bail!("Block hash '{}' already exists in the ledger", block.hash())
3978
}
4079

80+
if block.height() != expected_height {
81+
bail!("Block has invalid height. Expected {expected_height}, but got {}.", block.height());
82+
}
83+
84+
// Ensure the certificates in the block subdag have met quorum requirements.
85+
self.check_block_subdag_quorum(block)?;
86+
87+
// Determine if the block subdag is correctly constructed and is not a combination of multiple subdags.
88+
self.check_block_subdag_atomicity(block)?;
89+
90+
// Ensure that all leaves of the subdag point to valid batches in other subdags/blocks.
91+
self.check_block_subdag_leaves(block, pending_blocks)?;
92+
93+
Ok(())
94+
}
95+
96+
/// Checks the given block is a valid next block with regard to the current state/height of the Ledger.
97+
///
98+
/// # Panics
99+
/// This function panics if called from an async context.
100+
pub fn check_next_block<R: CryptoRng + Rng>(&self, block: &Block<N>, rng: &mut R) -> Result<()> {
101+
self.check_block_subdag_inner(block, &[])?;
102+
self.check_block_content_inner(block, rng)?;
103+
104+
Ok(())
105+
}
106+
107+
/// Takes a pending block and performs the remaining checks to full verify it.
108+
///
109+
/// # Arguments
110+
/// This takes a [`PendingBlock`] as input, which is the output of a successful call to [`Self::check_block_subdag`].
111+
/// The latter already verified the block's DAG and certificate signatures.
112+
///
113+
/// # Return Value
114+
/// This returns a [`Block`] on success representing the fully verified block.
115+
///
116+
/// # Panics
117+
/// This function panics if called from an async context.
118+
pub fn check_block_content<R: CryptoRng + Rng>(&self, block: PendingBlock<N>, rng: &mut R) -> Result<Block<N>> {
119+
self.check_block_content_inner(&block.0, rng)?;
120+
Ok(block.0)
121+
}
122+
123+
/// # Panics
124+
/// This function panics if called from an async context.
125+
fn check_block_content_inner<R: CryptoRng + Rng>(&self, block: &Block<N>, rng: &mut R) -> Result<()> {
126+
let latest_block = self.latest_block();
127+
41128
// Ensure the solutions do not already exist.
42129
for solution_id in block.solutions().solution_ids() {
43130
if self.contains_solution_id(solution_id)? {
@@ -115,15 +202,6 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
115202
}
116203
}
117204

118-
// Ensure the certificates in the block subdag have met quorum requirements.
119-
self.check_block_subdag_quorum(block)?;
120-
121-
// Determine if the block subdag is correctly constructed and is not a combination of multiple subdags.
122-
self.check_block_subdag_atomicity(block)?;
123-
124-
// Ensure that all leaves of the subdag point to valid batches in other subdags/blocks.
125-
self.check_block_subdag_leaves(block)?;
126-
127205
// Ensure that each existing solution ID from the block exists in the ledger.
128206
for existing_solution_id in expected_existing_solution_ids {
129207
if !self.contains_solution_id(&existing_solution_id)? {
@@ -145,12 +223,21 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
145223
///
146224
/// This does not verify that the batches are signed correctly or that the edges are valid
147225
/// (only point to the previous round), as those checks already happened when the node received the batch.
148-
fn check_block_subdag_leaves(&self, block: &Block<N>) -> Result<()> {
226+
fn check_block_subdag_leaves(&self, block: &Block<N>, previous_blocks: &[PendingBlock<N>]) -> Result<()> {
149227
// Check if the block has a subdag.
150228
let Authority::Quorum(subdag) = block.authority() else {
151229
return Ok(());
152230
};
153231

232+
let previous_certs: HashSet<_> = previous_blocks
233+
.iter()
234+
.filter_map(|block| match block.authority() {
235+
Authority::Quorum(subdag) => Some(subdag.certificate_ids()),
236+
Authority::Beacon(_) => None,
237+
})
238+
.flatten()
239+
.collect();
240+
154241
// Store the IDs of all certificates in this subDAG.
155242
// This allows determining which edges point to other subDAGs/blocks.
156243
let subdag_certs: HashSet<_> = subdag.certificate_ids().collect();
@@ -173,7 +260,7 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
173260
}
174261

175262
// Ensure that the certificate is associated with a previous block.
176-
if !self.vm.block_store().contains_block_for_certificate(prev_id)? {
263+
if !previous_certs.contains(prev_id) && !self.vm.block_store().contains_block_for_certificate(prev_id)? {
177264
bail!(
178265
"Batch(es) in the block point(s) to a certificate {prev_id} in round {prev_round} that is not associated with a previous block"
179266
)
@@ -184,6 +271,8 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
184271
}
185272

186273
/// Check that the certificates in the block subdag have met quorum requirements.
274+
///
275+
/// Called by [`Self::check_block_subdag`]
187276
fn check_block_subdag_quorum(&self, block: &Block<N>) -> Result<()> {
188277
// Check if the block has a subdag.
189278
let subdag = match block.authority() {
@@ -226,6 +315,8 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
226315
}
227316

228317
/// Checks that the block subdag can not be split into multiple valid subdags.
318+
///
319+
/// Called by [`Self::check_block_subdag`]
229320
fn check_block_subdag_atomicity(&self, block: &Block<N>) -> Result<()> {
230321
// Returns `true` if there is a path from the previous certificate to the current certificate.
231322
fn is_linked<N: Network>(

ledger/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ pub use helpers::*;
3737

3838
pub use crate::block::*;
3939

40-
mod advance;
4140
mod check_next_block;
41+
pub use check_next_block::PendingBlock;
42+
43+
mod advance;
4244
mod check_transaction_basic;
4345
mod contains;
4446
mod find;

0 commit comments

Comments
 (0)