From 91381b98b9a62f78ee311e28b2abe1567f4a9f31 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Fri, 5 Jun 2026 18:08:26 +0500 Subject: [PATCH 1/2] seed parent proposal from anchor leaf so leader can propose after restart --- crates/hotshot/new-protocol/bench/src/node.rs | 2 +- crates/hotshot/new-protocol/src/consensus.rs | 23 ++++----- .../hotshot/new-protocol/src/coordinator.rs | 47 ++++++++++--------- .../src/tests/common/coordinator_builder.rs | 2 +- .../new-protocol/src/tests/legacy_cutover.rs | 2 +- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/crates/hotshot/new-protocol/bench/src/node.rs b/crates/hotshot/new-protocol/bench/src/node.rs index 56fe7cc966f..bb7fc80dcce 100644 --- a/crates/hotshot/new-protocol/bench/src/node.rs +++ b/crates/hotshot/new-protocol/bench/src/node.rs @@ -175,7 +175,7 @@ async fn build_coordinator( genesis_state, Leaf2::from(genesis_proposal.clone()), ); - consensus.seed_genesis(genesis_cert1, genesis_proposal); + consensus.seed_parent(genesis_cert1, genesis_proposal); let proposal_validator = ProposalValidator::new(membership.clone(), epoch_height, upgrade_lock.clone()); diff --git a/crates/hotshot/new-protocol/src/consensus.rs b/crates/hotshot/new-protocol/src/consensus.rs index f4b665cba30..210db0e56b4 100644 --- a/crates/hotshot/new-protocol/src/consensus.rs +++ b/crates/hotshot/new-protocol/src/consensus.rs @@ -272,20 +272,17 @@ impl Consensus { } } - /// Seed the genesis state so that the view-1 leader can propose without - /// any external bootstrap injection. - /// - /// Stores a genesis certificate and proposal at view 0, sets the locked - /// certificate, and sets the current epoch. After calling this, a - /// subsequent `apply` that triggers `maybe_propose(view=1)` will find the + /// Seed a parent certificate and proposal so the leader of the *next* view + /// can propose without any external bootstrap injection. + /// Sets the locked certificate and current epoch. After calling this, a + /// subsequent `apply` that triggers `maybe_propose` will find the /// parent cert and proposal it needs. - pub fn seed_genesis(&mut self, genesis_cert1: Certificate1, genesis_proposal: Proposal) { - self.current_epoch = Some(genesis_proposal.epoch); - self.certs - .insert(ViewNumber::genesis(), genesis_cert1.clone()); - self.locked_cert = Some(genesis_cert1); - self.proposals - .insert(ViewNumber::genesis(), genesis_proposal); + pub fn seed_parent(&mut self, cert1: Certificate1, proposal: Proposal) { + let view = cert1.view_number(); + self.current_epoch = Some(proposal.epoch); + self.certs.insert(view, cert1.clone()); + self.locked_cert = Some(cert1); + self.proposals.insert(view, proposal); } /// Apply a [`PreCutoverSeed`] to bridge legacy state into the new diff --git a/crates/hotshot/new-protocol/src/coordinator.rs b/crates/hotshot/new-protocol/src/coordinator.rs index 797d905984e..f41f92617dc 100644 --- a/crates/hotshot/new-protocol/src/coordinator.rs +++ b/crates/hotshot/new-protocol/src/coordinator.rs @@ -134,38 +134,41 @@ where initializer.epoch_height, ); - let genesis_cert1 = initializer.high_qc.clone(); - let genesis_proposal = message::Proposal { - block_header: initializer.anchor_leaf.block_header().clone(), - view_number: ViewNumber::genesis(), - epoch: EpochNumber::genesis(), - justify_qc: genesis_cert1.clone(), + let anchor_leaf = &initializer.anchor_leaf; + let anchor_view = anchor_leaf.view_number(); + let anchor_epoch = anchor_leaf + .epoch(initializer.epoch_height) + .unwrap_or(EpochNumber::genesis()); + let cert1 = initializer.high_qc.clone(); + let parent_proposal = message::Proposal { + block_header: anchor_leaf.block_header().clone(), + view_number: anchor_view, + epoch: anchor_epoch, + justify_qc: anchor_leaf.justify_qc(), next_epoch_justify_qc: None, - upgrade_certificate: None, - view_change_evidence: None, - next_drb_result: None, + upgrade_certificate: anchor_leaf.upgrade_certificate(), + view_change_evidence: anchor_leaf + .view_change_evidence + .clone() + .and_then(|e| match e { + hotshot_types::data::ViewChangeEvidence2::Timeout(tc) => Some(tc), + hotshot_types::data::ViewChangeEvidence2::ViewSync(_) => None, + }), + next_drb_result: anchor_leaf.next_drb_result, state_cert: None, }; + let mut state_manager = StateManager::new( Arc::new(initializer.instance_state.clone()), upgrade_lock.clone(), ); state_manager.seed_state( - initializer.anchor_leaf.view_number(), - initializer.anchor_state.clone(), - initializer.anchor_leaf.clone(), - ); - // The synthetic genesis proposal has a non-null justify_qc (the genesis - // cert1) so the leaf derived from it has a different commitment than - // the anchor leaf produced by `Leaf2::genesis`. `request_header` for - // view 1 looks up the parent state by the *proposal's* leaf - // commitment, so seed the same state under that commitment too. - state_manager.seed_state( - ViewNumber::genesis(), + anchor_view, initializer.anchor_state.clone(), - Leaf2::from(genesis_proposal.clone()), + anchor_leaf.clone(), ); - consensus.seed_genesis(genesis_cert1, genesis_proposal); + consensus.seed_parent(cert1, parent_proposal); + consensus.set_view(anchor_view, anchor_epoch); let lock = upgrade_lock.clone(); Self::builder() diff --git a/crates/hotshot/new-protocol/src/tests/common/coordinator_builder.rs b/crates/hotshot/new-protocol/src/tests/common/coordinator_builder.rs index e2ddbb29691..57becf78ced 100644 --- a/crates/hotshot/new-protocol/src/tests/common/coordinator_builder.rs +++ b/crates/hotshot/new-protocol/src/tests/common/coordinator_builder.rs @@ -118,7 +118,7 @@ pub async fn build_test_coordinator>( genesis_state, Leaf2::from(genesis_proposal.clone()), ); - consensus.seed_genesis(genesis_cert1.clone(), genesis_proposal.clone()); + consensus.seed_parent(genesis_cert1.clone(), genesis_proposal.clone()); if let Some(seed) = pre_cutover_seed { consensus.apply_pre_cutover_seed(seed); diff --git a/crates/hotshot/new-protocol/src/tests/legacy_cutover.rs b/crates/hotshot/new-protocol/src/tests/legacy_cutover.rs index 4c149b130da..b4adc3f4820 100644 --- a/crates/hotshot/new-protocol/src/tests/legacy_cutover.rs +++ b/crates/hotshot/new-protocol/src/tests/legacy_cutover.rs @@ -275,7 +275,7 @@ async fn build_cutover_coordinator( genesis_state, Leaf2::from(genesis_proposal.clone()), ); - consensus.seed_genesis(genesis_cert1, genesis_proposal); + consensus.seed_parent(genesis_cert1, genesis_proposal); let block_builder = BlockBuilder::new( instance.clone(), From ee6f1e542240dc3619472b2843bdc56d7e55af53 Mon Sep 17 00:00:00 2001 From: imabdulbasit Date: Fri, 5 Jun 2026 18:22:08 +0500 Subject: [PATCH 2/2] use proposal view number --- crates/hotshot/new-protocol/src/consensus.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/hotshot/new-protocol/src/consensus.rs b/crates/hotshot/new-protocol/src/consensus.rs index 210db0e56b4..e5d0f2304d7 100644 --- a/crates/hotshot/new-protocol/src/consensus.rs +++ b/crates/hotshot/new-protocol/src/consensus.rs @@ -278,11 +278,10 @@ impl Consensus { /// subsequent `apply` that triggers `maybe_propose` will find the /// parent cert and proposal it needs. pub fn seed_parent(&mut self, cert1: Certificate1, proposal: Proposal) { - let view = cert1.view_number(); self.current_epoch = Some(proposal.epoch); - self.certs.insert(view, cert1.clone()); + self.certs.insert(cert1.view_number(), cert1.clone()); self.locked_cert = Some(cert1); - self.proposals.insert(view, proposal); + self.proposals.insert(proposal.view_number, proposal); } /// Apply a [`PreCutoverSeed`] to bridge legacy state into the new