hotshot_new_protocol/
cutover.rs1use std::{collections::BTreeMap, sync::Arc};
12
13use async_broadcast::InactiveReceiver;
14use futures::StreamExt;
15use hotshot::{traits::NodeImplementation, types::SystemContextHandle};
16use hotshot_types::{
17 data::Leaf2,
18 event::{Event, EventType},
19 message::UpgradeLock,
20 traits::{metrics::Gauge, node_implementation::NodeType},
21};
22use versions::NEW_PROTOCOL_VERSION;
23
24use crate::{client::ClientApi, consensus::PreCutoverSeed};
25
26pub async fn extract_pre_cutover_seed<T, I>(
29 handle: &SystemContextHandle<T, I>,
30) -> Option<PreCutoverSeed<T>>
31where
32 T: NodeType,
33 I: NodeImplementation<T>,
34{
35 let cutover_view = match handle.hotshot.upgrade_lock.decided_upgrade_cert() {
36 Some(cert) => cert.data.new_version_first_view,
37 None => {
38 tracing::warn!("no decided upgrade certificate; aborting seed extraction");
39 return None;
40 },
41 };
42
43 let consensus_arc = handle.hotshot.consensus();
44 let consensus = consensus_arc.read().await;
45 let decided_anchor = consensus.decided_leaf();
46 let decided_view = decided_anchor.view_number();
47
48 let high_qc = consensus.high_qc().clone();
49 let saved = consensus.saved_leaves();
50
51 let mut undecided: Vec<Leaf2<T>> = saved
55 .values()
56 .filter(|leaf| leaf.view_number() > decided_view)
57 .cloned()
58 .collect();
59 undecided.sort_by_key(|leaf| leaf.view_number());
60
61 let mut validated_states = BTreeMap::new();
62 if let Some(state) = consensus.state(decided_view) {
63 validated_states.insert(decided_view, state.clone());
64 } else {
65 tracing::warn!(%decided_view, "no validated state for decided anchor");
66 }
67 for leaf in &undecided {
68 let view = leaf.view_number();
69 if let Some(state) = consensus.state(view) {
70 validated_states.insert(view, state.clone());
71 } else {
72 tracing::warn!(%view, "no validated state for undecided leaf");
73 }
74 }
75
76 Some(PreCutoverSeed {
77 decided_anchor,
78 undecided,
79 high_qc: Some(high_qc),
80 validated_states,
81 cutover_view,
82 })
83}
84
85fn cutover_decided<T: NodeType>(upgrade_lock: &UpgradeLock<T>) -> bool {
89 upgrade_lock
90 .decided_upgrade_cert()
91 .is_some_and(|cert| cert.data.new_version >= NEW_PROTOCOL_VERSION)
92}
93
94pub async fn forward_legacy_timeout_votes<T: NodeType>(
97 legacy_event_rx: InactiveReceiver<Event<T>>,
98 client_api: ClientApi<T>,
99 upgrade_lock: UpgradeLock<T>,
100 queue_len: Option<Arc<dyn Gauge>>,
101) {
102 let mut rx = legacy_event_rx.activate_cloned();
103 while let Some(event) = rx.next().await {
104 if let Some(m) = &queue_len {
105 m.set(rx.len())
106 }
107 if let EventType::LegacyTimeoutVoteEmitted { vote } = event.event
108 && cutover_decided(&upgrade_lock)
109 && let Err(err) = client_api.try_submit_legacy_timeout_vote(vote)
110 {
111 tracing::warn!(%err, "failed to forward legacy TimeoutVote2 to new-protocol coordinator");
112 }
113 }
114}
115
116pub async fn forward_legacy_high_qc<T: NodeType>(
120 legacy_event_rx: InactiveReceiver<Event<T>>,
121 client_api: ClientApi<T>,
122 upgrade_lock: UpgradeLock<T>,
123) {
124 let mut rx = legacy_event_rx.activate_cloned();
125 while let Some(event) = rx.next().await {
126 if let EventType::LegacyHighQcFormed { qc } = event.event
127 && cutover_decided(&upgrade_lock)
128 && let Err(err) = client_api.try_submit_legacy_high_qc(qc)
129 {
130 tracing::warn!(%err, "failed to forward legacy high QC to new-protocol coordinator");
131 }
132 }
133}