Skip to main content

hotshot_types/
initializer.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7//! What consensus starts from: the anchor leaf, the certificates and proposals persisted before a
8//! restart, and the epoch roots and DRB results that seed membership.
9
10use std::{collections::BTreeMap, sync::Arc};
11
12use versions::Upgrade;
13
14use crate::{
15    data::{EpochNumber, Leaf2, QuorumProposalWrapper, ViewNumber},
16    drb::DrbResult,
17    epoch_membership::EpochMembershipCoordinator,
18    error::HotShotError,
19    message::Proposal,
20    simple_certificate::{
21        LightClientStateUpdateCertificateV2, QuorumCertificate2, UpgradeCertificate,
22    },
23    traits::{election::Membership, node_implementation::NodeType, states::ValidatedState},
24};
25
26#[derive(Clone, Debug, PartialEq)]
27pub struct InitializerEpochInfo<TYPES: NodeType> {
28    pub epoch: EpochNumber,
29    pub drb_result: DrbResult,
30    // pub stake_table: Option<StakeTable>, // TODO: Figure out how to connect this up
31    pub block_header: Option<TYPES::BlockHeader>,
32}
33
34/// The anchor leaf with its validated state, as supplied to [`HotShotInitializer::load`]. The
35/// state may be omitted, in which case a sparse state is constructed from the leaf's header.
36pub type InitializerAnchor<TYPES> = (
37    Leaf2<TYPES>,
38    Option<Arc<<TYPES as NodeType>::ValidatedState>>,
39);
40
41/// initializer struct for creating starting block
42#[derive(Clone, Debug)]
43pub struct HotShotInitializer<TYPES: NodeType> {
44    /// Instance-level state.
45    instance_state: TYPES::InstanceState,
46
47    /// Epoch height
48    epoch_height: u64,
49
50    /// the anchor leaf for the hotshot initializer
51    anchor_leaf: Leaf2<TYPES>,
52
53    /// ValidatedState for the anchor leaf
54    anchor_state: Arc<TYPES::ValidatedState>,
55
56    /// Starting view number that should be equivalent to the view the node shut down with last.
57    start_view: ViewNumber,
58
59    /// The view we last performed an action in.  An action is proposing or voting for
60    /// either the quorum or DA.
61    last_actioned_view: ViewNumber,
62
63    /// Highest QC that was seen, for genesis it's the genesis QC.  It should be for a view greater
64    /// than `inner`s view number for the non genesis case because we must have seen higher QCs
65    /// to decide on the leaf.
66    high_qc: QuorumCertificate2<TYPES>,
67
68    /// Proposals we have sent out to provide to others for catchup
69    saved_proposals: BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>>,
70
71    /// Previously decided upgrade certificate; this is necessary if an upgrade has happened and we are not restarting with the new version
72    decided_upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
73
74    /// The last formed light client state update certificate if there's any
75    state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
76
77    /// Saved epoch information. This must be sorted ascending by epoch.
78    start_epoch_info: Vec<InitializerEpochInfo<TYPES>>,
79}
80
81impl<TYPES: NodeType> HotShotInitializer<TYPES> {
82    /// initialize from genesis
83    /// # Errors
84    /// If we are unable to apply the genesis block to the default state
85    pub async fn from_genesis(
86        instance_state: TYPES::InstanceState,
87        epoch_height: u64,
88        start_epoch_info: Vec<InitializerEpochInfo<TYPES>>,
89        upgrade: Upgrade,
90    ) -> Result<Self, HotShotError<TYPES>> {
91        let (validated_state, _state_delta) = TYPES::ValidatedState::genesis(&instance_state);
92        let high_qc = QuorumCertificate2::genesis(&validated_state, &instance_state, upgrade).await;
93
94        Ok(Self {
95            anchor_leaf: Leaf2::genesis(&validated_state, &instance_state, upgrade.base).await,
96            anchor_state: Arc::new(validated_state),
97            start_view: ViewNumber::new(0),
98            last_actioned_view: ViewNumber::new(0),
99            saved_proposals: BTreeMap::new(),
100            high_qc,
101            decided_upgrade_certificate: None,
102            instance_state,
103            epoch_height,
104            state_cert: None,
105            start_epoch_info,
106        })
107    }
108
109    /// Create a `HotShotInitializer` from the given information.
110    ///
111    /// The anchor's validated state may be supplied by callers that have it (e.g. a full genesis
112    /// state); when absent, a sparse state is constructed from the anchor leaf's header.
113    #[allow(clippy::too_many_arguments)]
114    pub fn load(
115        instance_state: TYPES::InstanceState,
116        epoch_height: u64,
117        start_epoch_info: Vec<InitializerEpochInfo<TYPES>>,
118        (anchor_leaf, anchor_state): InitializerAnchor<TYPES>,
119        start_view: ViewNumber,
120        high_qc: QuorumCertificate2<TYPES>,
121        last_actioned_view: ViewNumber,
122        saved_proposals: BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>>,
123        decided_upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
124        state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
125    ) -> Self {
126        let anchor_state = anchor_state.unwrap_or_else(|| {
127            Arc::new(TYPES::ValidatedState::from_header(
128                anchor_leaf.block_header(),
129            ))
130        });
131
132        Self {
133            instance_state,
134            epoch_height,
135            anchor_leaf,
136            anchor_state,
137            high_qc,
138            start_view,
139            last_actioned_view,
140            saved_proposals,
141            decided_upgrade_certificate,
142            state_cert,
143            start_epoch_info,
144        }
145    }
146
147    /// Instance-level state.
148    pub fn instance_state(&self) -> &TYPES::InstanceState {
149        &self.instance_state
150    }
151
152    /// Epoch height.
153    pub fn epoch_height(&self) -> u64 {
154        self.epoch_height
155    }
156
157    /// The anchor (last decided) leaf.
158    pub fn anchor_leaf(&self) -> &Leaf2<TYPES> {
159        &self.anchor_leaf
160    }
161
162    /// ValidatedState for the anchor leaf.
163    pub fn anchor_state(&self) -> &Arc<TYPES::ValidatedState> {
164        &self.anchor_state
165    }
166
167    /// The view to start consensus from.
168    pub fn start_view(&self) -> ViewNumber {
169        self.start_view
170    }
171
172    /// The view we last proposed or voted in.
173    pub fn last_actioned_view(&self) -> ViewNumber {
174        self.last_actioned_view
175    }
176
177    /// Highest QC that was seen.
178    pub fn high_qc(&self) -> &QuorumCertificate2<TYPES> {
179        &self.high_qc
180    }
181
182    /// Proposals we have sent out to provide to others for catchup.
183    pub fn saved_proposals(
184        &self,
185    ) -> &BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>> {
186        &self.saved_proposals
187    }
188
189    /// The upgrade certificate consensus has decided, if there is one.
190    pub fn decided_upgrade_certificate(&self) -> Option<&UpgradeCertificate<TYPES>> {
191        self.decided_upgrade_certificate.as_ref()
192    }
193
194    /// The last formed light client state update certificate, if there is any.
195    pub fn state_cert(&self) -> Option<&LightClientStateUpdateCertificateV2<TYPES>> {
196        self.state_cert.as_ref()
197    }
198
199    /// The epoch roots and DRB results persistence holds.
200    pub fn start_epoch_info(&self) -> &[InitializerEpochInfo<TYPES>] {
201        &self.start_epoch_info
202    }
203}
204
205/// Seed `coordinator` with the epoch roots and DRB results in `start_epoch_info`.
206pub async fn load_start_epoch_info<TYPES: NodeType>(
207    coordinator: &EpochMembershipCoordinator<TYPES>,
208    start_epoch_info: &[InitializerEpochInfo<TYPES>],
209) {
210    let membership = coordinator.membership();
211    let mut sorted_epoch_info = start_epoch_info.to_vec();
212    sorted_epoch_info.sort_by_key(|info| info.epoch);
213    for epoch_info in sorted_epoch_info {
214        if let Some(block_header) = &epoch_info.block_header {
215            tracing::warn!("Calling add_epoch_root for epoch {}", epoch_info.epoch);
216
217            coordinator
218                .add_epoch_root(block_header.clone())
219                .await
220                .unwrap_or_else(|err| {
221                    // REVIEW NOTE: Should we panic here? a failure here seems like it should be fatal
222                    tracing::error!(
223                        "Failed to add epoch root for epoch {}: {err}",
224                        epoch_info.epoch
225                    );
226                });
227        }
228    }
229
230    for epoch_info in start_epoch_info {
231        tracing::warn!("Calling add_drb_result for epoch {}", epoch_info.epoch);
232        membership.add_drb_result(epoch_info.epoch, epoch_info.drb_result);
233    }
234}