1use std::{
2 collections::{BTreeMap, HashMap},
3 sync::Arc,
4};
5
6use committable::{Commitment, Committable};
7use hotshot::traits::{BlockPayload, ValidatedState};
8use hotshot_types::{
9 data::{BlockNumber, EpochNumber, Leaf2, VidCommitment, ViewNumber},
10 message::UpgradeLock,
11 traits::{
12 block_contents::{BlockHeader, BuilderFee},
13 metrics::Histogram,
14 node_implementation::NodeType,
15 },
16 utils::BuilderCommitment,
17 vote::HasViewNumber,
18};
19use tokio::task::{AbortHandle, JoinSet};
20use tracing::{error, warn};
21
22use crate::{
23 coordinator::metrics::{Measurement, finish_measurement, ignore_measurement},
24 helpers::proposal_commitment,
25 message::Proposal,
26};
27
28pub struct UpdateLeaf<T: NodeType> {
29 pub view: ViewNumber,
30 pub leaf: Leaf2<T>,
31 pub state: Arc<T::ValidatedState>,
32 pub delta: Option<Delta<T>>,
33}
34
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct StateRequest<T: NodeType> {
37 pub view: ViewNumber,
38 pub parent_view: ViewNumber,
39 pub epoch: EpochNumber,
40 pub block: BlockNumber,
41 pub proposal: Proposal<T>,
42 pub parent_commitment: Commitment<Leaf2<T>>,
43 pub payload_size: u32,
44}
45
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub struct HeaderRequest<T: NodeType> {
48 pub view: ViewNumber,
49 pub epoch: EpochNumber,
50 pub parent_proposal: Proposal<T>,
51 pub payload_commitment: VidCommitment,
52 pub builder_commitment: BuilderCommitment,
53 pub metadata: <T::BlockPayload as BlockPayload<T>>::Metadata,
54 pub builder_fee: BuilderFee<T>,
55}
56
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub struct StateResponse<T: NodeType> {
59 pub view: ViewNumber,
60 pub commitment: Commitment<Leaf2<T>>,
61 pub state: Arc<T::ValidatedState>,
62 pub delta: Option<Delta<T>>,
63}
64
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct HeaderResponse<T: NodeType> {
67 pub view: ViewNumber,
68 pub epoch: EpochNumber,
69 pub parent_proposal: Proposal<T>,
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
73#[allow(clippy::large_enum_variant)]
74pub enum StateManagerOutput<T: NodeType> {
75 State {
76 response: StateResponse<T>,
77 validated: bool,
78 },
79 Header {
80 response: HeaderResponse<T>,
81 header: Option<T::BlockHeader>,
82 },
83}
84
85type Delta<T> = Arc<<<T as NodeType>::ValidatedState as ValidatedState<T>>::Delta>;
86
87#[derive(Clone)]
88pub struct StateEntry<T: NodeType> {
89 pub state: Arc<T::ValidatedState>,
90 pub delta: Option<Delta<T>>,
91 pub leaf: Leaf2<T>,
92}
93
94pub struct StateManager<T: NodeType> {
95 instance: Arc<T::InstanceState>,
96 validated_states: BTreeMap<Commitment<Leaf2<T>>, StateEntry<T>>,
97 state_requests: HashMap<Commitment<Leaf2<T>>, (AbortHandle, ViewNumber)>,
98 header_requests: HashMap<(ViewNumber, Commitment<Leaf2<T>>), AbortHandle>,
99 pending_requests: HashMap<Commitment<Leaf2<T>>, Vec<Pending<T>>>,
100 upgrade_lock: UpgradeLock<T>,
101 tasks: JoinSet<Completed<T>>,
102 validate_duration_metric: Option<Arc<dyn Histogram>>,
103 update_leaf_duration_metric: Option<Arc<dyn Histogram>>,
104}
105
106enum Pending<T: NodeType> {
107 State(StateRequest<T>),
108 Header(HeaderRequest<T>),
109}
110
111impl<T: NodeType> Pending<T> {
112 fn view(&self) -> ViewNumber {
113 match self {
114 Pending::State(r) => r.view,
115 Pending::Header(r) => r.view,
116 }
117 }
118}
119
120enum Completed<T: NodeType> {
121 State {
122 response: StateResponse<T>,
123 leaf: Option<Leaf2<T>>,
124 },
125 Header {
126 response: HeaderResponse<T>,
127 header: Option<T::BlockHeader>,
128 },
129}
130
131impl<T: NodeType> StateManager<T> {
132 pub fn new(instance: Arc<T::InstanceState>, upgrade_lock: UpgradeLock<T>) -> Self {
133 Self {
134 instance,
135 validated_states: BTreeMap::new(),
136 state_requests: HashMap::new(),
137 header_requests: HashMap::new(),
138 pending_requests: HashMap::new(),
139 upgrade_lock,
140 tasks: JoinSet::new(),
141 validate_duration_metric: None,
142 update_leaf_duration_metric: None,
143 }
144 }
145
146 pub fn with_metrics(
147 mut self,
148 validate: Option<Arc<dyn Histogram>>,
149 update_leaf: Option<Arc<dyn Histogram>>,
150 ) -> Self {
151 self.validate_duration_metric = validate;
152 self.update_leaf_duration_metric = update_leaf;
153 self
154 }
155
156 pub fn get_state(&self, view: ViewNumber) -> Option<&StateEntry<T>> {
158 self.validated_states
159 .iter()
160 .find(|(_, entry)| entry.leaf.view_number() == view)
161 .map(|(_, entry)| entry)
162 }
163
164 pub fn get_leaf(&self, view: ViewNumber) -> Option<Leaf2<T>> {
166 self.validated_states
167 .iter()
168 .find(|(_, entry)| entry.leaf.view_number() == view)
169 .map(|(_, entry)| entry.leaf.clone())
170 }
171
172 pub fn seed_state(&mut self, view: ViewNumber, state: Arc<T::ValidatedState>, leaf: Leaf2<T>) {
173 self.insert_state(view, state, None, leaf);
174 }
175
176 pub(crate) fn seed_from_header(&mut self, proposal: Proposal<T>) {
179 self.insert_empty_state(proposal);
180 }
181
182 pub fn request_state(&mut self, request: StateRequest<T>) {
183 let commitment = proposal_commitment(&request.proposal);
184 if self.state_requests.contains_key(&commitment) {
185 return;
186 }
187
188 if self.state_requests.contains_key(&request.parent_commitment) {
189 self.pending_requests
190 .entry(request.parent_commitment)
191 .or_default()
192 .push(Pending::State(request));
193 return;
194 }
195
196 let Some(parent_entry) = self
197 .validated_states
198 .get(&request.parent_commitment)
199 .cloned()
200 else {
201 warn!(
202 view = %request.view,
203 parent_view = %request.parent_view,
204 epoch = %request.epoch,
205 block = %request.block,
206 parent_commitment = %request.parent_commitment,
207 "parent state unavailable; deferring state validation (from_header stub inserted). \
208 If this persists, the node cannot vote until the parent state is recovered."
209 );
210 self.insert_empty_state(request.proposal);
211 self.start_pending(commitment);
212 return;
213 };
214
215 let instance = self.instance.clone();
216 let header = request.proposal.block_header.clone();
217 let view = request.view;
218 let payload_size = request.payload_size;
219
220 let Ok(upgrade_lock) = self.upgrade_lock.version(view) else {
221 error!(%view, "unsupported version");
222 return;
223 };
224
225 let duration_metric = self.validate_duration_metric.clone();
226 let handle = self.tasks.spawn(async move {
227 let measurement = duration_metric.map(Measurement::start);
228 let result = parent_entry
229 .state
230 .validate_and_apply_header(
231 &instance,
232 &parent_entry.leaf,
233 &header,
234 payload_size,
235 upgrade_lock,
236 *view,
237 )
238 .await;
239 match result {
240 Ok((state, delta)) => {
241 finish_measurement(measurement);
242 Completed::State {
243 response: StateResponse {
244 view,
245 commitment,
246 state: Arc::new(state),
247 delta: Some(Arc::new(delta)),
248 },
249 leaf: Some(request.proposal.into()),
250 }
251 },
252 Err(err) => {
253 ignore_measurement(measurement);
254 warn!(%err, "state validation failed");
255 Completed::State {
256 response: StateResponse {
257 view,
258 commitment,
259 state: Arc::new(T::ValidatedState::from_header(&header)),
260 delta: None,
261 },
262 leaf: None,
263 }
264 },
265 }
266 });
267
268 self.state_requests.insert(commitment, (handle, view));
269 }
270
271 pub fn request_header(&mut self, request: HeaderRequest<T>) {
272 let parent_commitment = proposal_commitment(&request.parent_proposal);
273 if self
274 .header_requests
275 .contains_key(&(request.view, parent_commitment))
276 {
277 return;
278 }
279
280 if self.state_requests.contains_key(&parent_commitment) {
281 self.pending_requests
282 .entry(parent_commitment)
283 .or_default()
284 .push(Pending::Header(request));
285 return;
286 }
287
288 let Some(parent_entry) = self.validated_states.get(&parent_commitment).cloned() else {
289 self.pending_requests
293 .entry(parent_commitment)
294 .or_default()
295 .push(Pending::Header(request));
296 return;
297 };
298
299 let instance = self.instance.clone();
300 let view = request.view;
301 let epoch = request.epoch;
302 let parent_proposal = request.parent_proposal;
303
304 let Ok(version) = self.upgrade_lock.version(view) else {
305 error!(%view, "unsupported version");
306 return;
307 };
308
309 let handle = self.tasks.spawn(async move {
310 let result = T::BlockHeader::new(
311 &parent_entry.state,
312 &instance,
313 &parent_entry.leaf,
314 request.payload_commitment,
315 request.builder_commitment,
316 request.metadata,
317 request.builder_fee,
318 version,
319 *view,
320 )
321 .await;
322 match result {
323 Ok(header) => Completed::Header {
324 response: HeaderResponse {
325 view,
326 epoch,
327 parent_proposal,
328 },
329 header: Some(header),
330 },
331 Err(err) => {
332 warn!(%err, "header creation failed");
333 Completed::Header {
334 response: HeaderResponse {
335 view,
336 epoch,
337 parent_proposal,
338 },
339 header: None,
340 }
341 },
342 }
343 });
344
345 self.header_requests
346 .insert((view, parent_commitment), handle);
347 }
348
349 pub fn update_state(&mut self, update: UpdateLeaf<T>) {
351 let UpdateLeaf {
352 view,
353 leaf,
354 state,
355 delta,
356 } = update;
357 let commitment = leaf.commit();
358 self.insert_state(view, state, delta, leaf);
359 if let Some((task, _)) = self.state_requests.remove(&commitment) {
360 task.abort();
361 }
362 self.start_pending(commitment);
363 }
364
365 pub async fn next(&mut self) -> Option<StateManagerOutput<T>> {
367 loop {
368 match self.tasks.join_next().await {
369 Some(Ok(result)) => match result {
370 Completed::State {
371 response,
372 leaf: leaf2,
373 } => {
374 if self.state_requests.remove(&response.commitment).is_none() {
375 continue;
376 }
377 if let Some(leaf) = leaf2 {
378 let measurement = self
379 .update_leaf_duration_metric
380 .clone()
381 .map(Measurement::start);
382 self.insert_state(
383 response.view,
384 response.state.clone(),
385 response.delta.clone(),
386 leaf,
387 );
388 finish_measurement(measurement);
389 self.start_pending(response.commitment);
390 return Some(StateManagerOutput::State {
391 response,
392 validated: true,
393 });
394 } else {
395 self.pending_requests.remove(&response.commitment);
396 return Some(StateManagerOutput::State {
397 response,
398 validated: false,
399 });
400 }
401 },
402 Completed::Header { response, header } => {
403 let key = (
404 response.view,
405 proposal_commitment(&response.parent_proposal),
406 );
407 if self.header_requests.remove(&key).is_none() {
408 continue;
409 }
410 return Some(StateManagerOutput::Header { response, header });
411 },
412 },
413 Some(Err(err)) => {
414 if err.is_panic() {
415 error!(%err, "task panicked");
416 }
417 },
418 None => return None,
419 }
420 }
421 }
422
423 pub fn gc(&mut self, view_number: ViewNumber) {
424 self.validated_states
425 .retain(|_, entry| entry.leaf.view_number() >= view_number);
426
427 for (task, view) in self.state_requests.values() {
428 if *view < view_number {
429 task.abort();
430 }
431 }
432
433 self.state_requests
434 .retain(|_, (_, view)| *view >= view_number);
435
436 self.header_requests.retain(|(view, _), handle| {
437 let keep = *view >= view_number;
438 if !keep {
439 handle.abort();
440 }
441 keep
442 });
443
444 self.pending_requests.retain(|_, pending| {
445 pending.retain(|p| p.view() >= view_number);
446 !pending.is_empty()
447 });
448 }
449
450 fn start_pending(&mut self, finished_commitment: Commitment<Leaf2<T>>) {
451 let Some(pending) = self.pending_requests.remove(&finished_commitment) else {
452 return;
453 };
454 for p in pending {
455 match p {
456 Pending::State(r) => self.request_state(r),
457 Pending::Header(r) => self.request_header(r),
458 }
459 }
460 }
461
462 fn insert_state(
469 &mut self,
470 view: ViewNumber,
471 state: Arc<T::ValidatedState>,
472 delta: Option<Delta<T>>,
473 leaf: Leaf2<T>,
474 ) {
475 if let Some(existing) = self.validated_states.get(&leaf.commit())
476 && existing.delta.is_some()
477 && delta.is_none()
478 {
479 warn!(
480 ?view,
481 "Skipping state update to not override a state with a delta"
482 );
483 return;
484 }
485 self.validated_states
486 .insert(leaf.commit(), StateEntry { state, delta, leaf });
487 }
488
489 fn insert_empty_state(&mut self, proposal: Proposal<T>) {
490 let state = T::ValidatedState::from_header(&proposal.block_header);
491 self.insert_state(
492 proposal.view_number(),
493 Arc::new(state),
494 None,
495 proposal.into(),
496 );
497 }
498
499 #[cfg(test)]
500 pub(crate) fn validated_contains_view(&self, v: ViewNumber) -> bool {
501 self.validated_states
502 .iter()
503 .any(|(_, entry)| entry.leaf.view_number() == v)
504 }
505
506 #[cfg(test)]
507 pub(crate) fn pending_contains_commitment(&self, c: &Commitment<Leaf2<T>>) -> bool {
508 self.pending_requests.contains_key(c)
509 }
510}