1use std::{
2 collections::{BTreeMap, HashMap},
3 sync::Arc,
4};
5
6use committable::{Commitment, Committable};
7use hotshot_types::{
8 data::{BlockNumber, EpochNumber, Leaf2, VidCommitment, ViewNumber},
9 message::UpgradeLock,
10 traits::{
11 BlockPayload, ValidatedState,
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::{debug, error, warn};
21
22use crate::{
23 coordinator::metrics::{Measurement, finish_measurement, ignore_measurement},
24 helpers::proposal_commitment,
25 message::Proposal,
26};
27
28const STATE_GC_MARGIN: u64 = 8;
30
31pub struct UpdateLeaf<T: NodeType> {
32 pub view: ViewNumber,
33 pub leaf: Leaf2<T>,
34 pub state: Arc<T::ValidatedState>,
35 pub delta: Option<Delta<T>>,
36}
37
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub struct StateRequest<T: NodeType> {
40 pub view: ViewNumber,
41 pub parent_view: ViewNumber,
42 pub epoch: EpochNumber,
43 pub block: BlockNumber,
44 pub proposal: Proposal<T>,
45 pub parent_commitment: Commitment<Leaf2<T>>,
46 pub payload_size: Option<u32>,
47}
48
49#[derive(Clone, Debug, Eq, PartialEq)]
50pub struct HeaderRequest<T: NodeType> {
51 pub view: ViewNumber,
52 pub epoch: EpochNumber,
53 pub parent_proposal: Proposal<T>,
54 pub payload_commitment: VidCommitment,
55 pub builder_commitment: BuilderCommitment,
56 pub metadata: <T::BlockPayload as BlockPayload<T>>::Metadata,
57 pub builder_fee: BuilderFee<T>,
58}
59
60#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct StateResponse<T: NodeType> {
62 pub view: ViewNumber,
63 pub commitment: Commitment<Leaf2<T>>,
64 pub state: Arc<T::ValidatedState>,
65 pub delta: Option<Delta<T>>,
66}
67
68#[derive(Clone, Debug, Eq, PartialEq)]
69pub struct HeaderResponse<T: NodeType> {
70 pub view: ViewNumber,
71 pub epoch: EpochNumber,
72 pub parent_proposal: Proposal<T>,
73}
74
75#[derive(Clone, Debug, Eq, PartialEq)]
76#[allow(clippy::large_enum_variant)]
77pub enum StateManagerOutput<T: NodeType> {
78 State {
79 response: StateResponse<T>,
80 validated: bool,
81 },
82 Header {
83 response: HeaderResponse<T>,
84 header: Option<T::BlockHeader>,
85 },
86}
87
88type Delta<T> = Arc<<<T as NodeType>::ValidatedState as ValidatedState<T>>::Delta>;
89
90#[derive(Clone)]
91pub struct StateEntry<T: NodeType> {
92 pub state: Arc<T::ValidatedState>,
93 pub delta: Option<Delta<T>>,
94 pub leaf: Leaf2<T>,
95}
96
97pub struct StateManager<T: NodeType> {
98 instance: Arc<T::InstanceState>,
99 validated_states: BTreeMap<Commitment<Leaf2<T>>, StateEntry<T>>,
100 state_requests: HashMap<Commitment<Leaf2<T>>, InFlight<T>>,
101 header_requests: HashMap<(ViewNumber, Commitment<Leaf2<T>>), AbortHandle>,
102 pending_requests: HashMap<Commitment<Leaf2<T>>, Vec<Pending<T>>>,
103 decided_in_flight: Option<Leaf2<T>>,
106 upgrade_lock: UpgradeLock<T>,
107 tasks: JoinSet<Completed<T>>,
108 validate_duration_metric: Option<Arc<dyn Histogram>>,
109 update_leaf_duration_metric: Option<Arc<dyn Histogram>>,
110}
111
112struct InFlight<T: NodeType> {
114 handle: AbortHandle,
115 view: ViewNumber,
116 proposal: Proposal<T>,
117}
118
119enum Pending<T: NodeType> {
120 State(StateRequest<T>),
121 Header(HeaderRequest<T>),
122}
123
124impl<T: NodeType> Pending<T> {
125 fn view(&self) -> ViewNumber {
126 match self {
127 Pending::State(r) => r.view,
128 Pending::Header(r) => r.view,
129 }
130 }
131}
132
133enum Completed<T: NodeType> {
134 State {
135 response: StateResponse<T>,
136 leaf: Option<Leaf2<T>>,
137 },
138 Header {
139 response: HeaderResponse<T>,
140 header: Option<T::BlockHeader>,
141 },
142}
143
144impl<T: NodeType> StateManager<T> {
145 pub fn new(instance: Arc<T::InstanceState>, upgrade_lock: UpgradeLock<T>) -> Self {
146 Self {
147 instance,
148 validated_states: BTreeMap::new(),
149 state_requests: HashMap::new(),
150 header_requests: HashMap::new(),
151 pending_requests: HashMap::new(),
152 decided_in_flight: None,
153 upgrade_lock,
154 tasks: JoinSet::new(),
155 validate_duration_metric: None,
156 update_leaf_duration_metric: None,
157 }
158 }
159
160 pub fn with_metrics(
161 mut self,
162 validate: Option<Arc<dyn Histogram>>,
163 update_leaf: Option<Arc<dyn Histogram>>,
164 ) -> Self {
165 self.validate_duration_metric = validate;
166 self.update_leaf_duration_metric = update_leaf;
167 self
168 }
169
170 pub fn get_state(&self, view: ViewNumber) -> Option<&StateEntry<T>> {
174 self.validated_states
175 .iter()
176 .find(|(_, entry)| entry.leaf.view_number() == view)
177 .map(|(_, entry)| entry)
178 }
179
180 pub fn get_leaf(&self, view: ViewNumber) -> Option<Leaf2<T>> {
182 self.validated_states
183 .iter()
184 .find(|(_, entry)| entry.leaf.view_number() == view)
185 .map(|(_, entry)| entry.leaf.clone())
186 }
187
188 pub fn seed_state(&mut self, view: ViewNumber, state: Arc<T::ValidatedState>, leaf: Leaf2<T>) {
189 self.insert_state(view, state, None, leaf);
190 }
191
192 pub(crate) fn seed_from_header(&mut self, proposal: Proposal<T>) {
199 let commitment = proposal_commitment(&proposal);
200 if !self.validated_states.contains_key(&commitment) {
201 self.insert_empty_state(proposal);
202 }
203 self.start_pending(commitment);
204 }
205
206 pub(crate) fn seed_decided(&mut self, leaf: Leaf2<T>) {
210 let commitment = leaf.commit();
211 if self.validated_states.contains_key(&commitment) {
212 return;
213 }
214 if self.state_requests.contains_key(&commitment) {
215 let newest = self
216 .decided_in_flight
217 .as_ref()
218 .is_none_or(|held| held.view_number() <= leaf.view_number());
219 if newest {
220 self.decided_in_flight = Some(leaf);
221 }
222 return;
223 }
224 let state = T::ValidatedState::from_header(leaf.block_header());
225 self.insert_state(leaf.view_number(), Arc::new(state), None, leaf);
226 self.start_pending(commitment);
227 }
228
229 pub fn request_state(&mut self, request: StateRequest<T>) {
230 let commitment = proposal_commitment(&request.proposal);
231 if self.state_requests.contains_key(&commitment) {
232 return;
233 }
234
235 if self.state_requests.contains_key(&request.parent_commitment) {
236 self.pending_requests
237 .entry(request.parent_commitment)
238 .or_default()
239 .push(Pending::State(request));
240 return;
241 }
242
243 let Some(parent_entry) = self
244 .validated_states
245 .get(&request.parent_commitment)
246 .cloned()
247 else {
248 warn!(
249 view = %request.view,
250 parent_view = %request.parent_view,
251 epoch = %request.epoch,
252 block = %request.block,
253 parent_commitment = %request.parent_commitment,
254 "parent state unavailable; queued on parent for retry. \
255 If this persists, the parent state never arrived and the node cannot vote."
256 );
257 let queued = self
258 .pending_requests
259 .entry(request.parent_commitment)
260 .or_default();
261 if !queued
262 .iter()
263 .any(|p| matches!(p, Pending::State(r) if r.view == request.view))
264 {
265 queued.push(Pending::State(request));
266 }
267 return;
268 };
269
270 let instance = self.instance.clone();
271 let header = request.proposal.block_header.clone();
272 let proposal = request.proposal.clone();
273 let view = request.view;
274 let payload_size = request.payload_size;
275
276 let Ok(upgrade_lock) = self.upgrade_lock.version(view) else {
277 error!(%view, "unsupported version");
278 return;
279 };
280
281 let duration_metric = self.validate_duration_metric.clone();
282 let handle = self.tasks.spawn(async move {
283 let measurement = duration_metric.map(Measurement::start);
284 let result = parent_entry
285 .state
286 .validate_and_apply_header(
287 &instance,
288 &parent_entry.leaf,
289 &header,
290 payload_size,
291 upgrade_lock,
292 *view,
293 )
294 .await;
295 match result {
296 Ok((state, delta)) => {
297 finish_measurement(measurement);
298 Completed::State {
299 response: StateResponse {
300 view,
301 commitment,
302 state: Arc::new(state),
303 delta: Some(Arc::new(delta)),
304 },
305 leaf: Some(request.proposal.into()),
306 }
307 },
308 Err(err) => {
309 ignore_measurement(measurement);
310 warn!(%err, "state validation failed");
311 Completed::State {
312 response: StateResponse {
313 view,
314 commitment,
315 state: Arc::new(T::ValidatedState::from_header(&header)),
316 delta: None,
317 },
318 leaf: None,
319 }
320 },
321 }
322 });
323
324 self.state_requests.insert(
325 commitment,
326 InFlight {
327 handle,
328 view,
329 proposal,
330 },
331 );
332 }
333
334 pub fn request_header(&mut self, request: HeaderRequest<T>) {
335 let parent_commitment = proposal_commitment(&request.parent_proposal);
336 if self
337 .header_requests
338 .contains_key(&(request.view, parent_commitment))
339 {
340 return;
341 }
342
343 if self.state_requests.contains_key(&parent_commitment) {
344 self.pending_requests
345 .entry(parent_commitment)
346 .or_default()
347 .push(Pending::Header(request));
348 return;
349 }
350
351 let Some(parent_entry) = self.validated_states.get(&parent_commitment).cloned() else {
352 self.pending_requests
356 .entry(parent_commitment)
357 .or_default()
358 .push(Pending::Header(request));
359 return;
360 };
361
362 let instance = self.instance.clone();
363 let view = request.view;
364 let epoch = request.epoch;
365 let parent_proposal = request.parent_proposal;
366
367 let Ok(version) = self.upgrade_lock.version(view) else {
368 error!(%view, "unsupported version");
369 return;
370 };
371
372 let handle = self.tasks.spawn(async move {
373 let result = T::BlockHeader::new(
374 &parent_entry.state,
375 &instance,
376 &parent_entry.leaf,
377 request.payload_commitment,
378 request.builder_commitment,
379 request.metadata,
380 request.builder_fee,
381 version,
382 *view,
383 )
384 .await;
385 match result {
386 Ok(header) => Completed::Header {
387 response: HeaderResponse {
388 view,
389 epoch,
390 parent_proposal,
391 },
392 header: Some(header),
393 },
394 Err(err) => {
395 warn!(%err, "header creation failed");
396 Completed::Header {
397 response: HeaderResponse {
398 view,
399 epoch,
400 parent_proposal,
401 },
402 header: None,
403 }
404 },
405 }
406 });
407
408 self.header_requests
409 .insert((view, parent_commitment), handle);
410 }
411
412 pub fn update_state(&mut self, update: UpdateLeaf<T>) {
417 let UpdateLeaf {
418 view,
419 leaf,
420 state,
421 delta,
422 } = update;
423 let commitment = leaf.commit();
424 if delta.is_none() {
425 if self.validated_states.contains_key(&commitment) {
426 self.insert_state(view, state, delta, leaf);
427 } else {
428 debug!(%view, %commitment, "dropping delta-less update: no entry for leaf");
429 }
430 return;
431 }
432 self.insert_state(view, state, delta, leaf);
433 if let Some(in_flight) = self.state_requests.remove(&commitment) {
434 in_flight.handle.abort();
435 }
436 self.start_pending(commitment);
437 }
438
439 pub async fn next(&mut self) -> Option<StateManagerOutput<T>> {
441 loop {
442 match self.tasks.join_next().await {
443 Some(Ok(result)) => match result {
444 Completed::State {
445 response,
446 leaf: leaf2,
447 } => {
448 if self.state_requests.remove(&response.commitment).is_none() {
449 continue;
450 }
451 let decided = self
452 .decided_in_flight
453 .take_if(|leaf| leaf.commit() == response.commitment);
454 if let Some(leaf) = leaf2 {
455 let measurement = self
456 .update_leaf_duration_metric
457 .clone()
458 .map(Measurement::start);
459 self.insert_state(
460 response.view,
461 response.state.clone(),
462 response.delta.clone(),
463 leaf,
464 );
465 finish_measurement(measurement);
466 self.start_pending(response.commitment);
467 return Some(StateManagerOutput::State {
468 response,
469 validated: true,
470 });
471 } else {
472 if self.validated_states.contains_key(&response.commitment) {
473 self.start_pending(response.commitment);
474 } else if let Some(leaf) = decided {
475 self.seed_decided(leaf);
476 }
477 return Some(StateManagerOutput::State {
478 response,
479 validated: false,
480 });
481 }
482 },
483 Completed::Header { response, header } => {
484 let key = (
485 response.view,
486 proposal_commitment(&response.parent_proposal),
487 );
488 if self.header_requests.remove(&key).is_none() {
489 continue;
490 }
491 return Some(StateManagerOutput::Header { response, header });
492 },
493 },
494 Some(Err(err)) => {
495 if err.is_panic() {
496 error!(%err, "task panicked");
497 }
498 },
499 None => return None,
500 }
501 }
502 }
503
504 pub fn gc(&mut self, view_number: ViewNumber) {
508 let margin_view = ViewNumber::new(view_number.saturating_sub(STATE_GC_MARGIN));
509 self.validated_states.retain(|_, entry| {
510 let view = entry.leaf.view_number();
511 view >= view_number || (entry.delta.is_some() && view >= margin_view)
512 });
513
514 self.decided_in_flight
515 .take_if(|leaf| leaf.view_number() < view_number);
516
517 self.header_requests.retain(|(view, _), handle| {
518 let keep = *view >= view_number;
519 if !keep {
520 handle.abort();
521 }
522 keep
523 });
524
525 self.pending_requests.retain(|_, pending| {
526 pending.retain(|p| p.view() >= view_number);
527 !pending.is_empty()
528 });
529
530 let stale: Vec<_> = self
531 .state_requests
532 .extract_if(|_, in_flight| in_flight.view < view_number)
533 .collect();
534 for (commitment, in_flight) in stale {
535 in_flight.handle.abort();
536 if self.pending_requests.contains_key(&commitment) {
537 self.seed_from_header(in_flight.proposal);
538 }
539 }
540 }
541
542 fn start_pending(&mut self, finished_commitment: Commitment<Leaf2<T>>) {
543 let Some(pending) = self.pending_requests.remove(&finished_commitment) else {
544 return;
545 };
546 for p in pending {
547 match p {
548 Pending::State(r) => self.request_state(r),
549 Pending::Header(r) => self.request_header(r),
550 }
551 }
552 }
553
554 fn insert_state(
561 &mut self,
562 view: ViewNumber,
563 state: Arc<T::ValidatedState>,
564 delta: Option<Delta<T>>,
565 leaf: Leaf2<T>,
566 ) {
567 if let Some(existing) = self.validated_states.get(&leaf.commit())
568 && existing.delta.is_some()
569 && delta.is_none()
570 {
571 warn!(
572 ?view,
573 "Skipping state update to not override a state with a delta"
574 );
575 return;
576 }
577 self.validated_states
578 .insert(leaf.commit(), StateEntry { state, delta, leaf });
579 }
580
581 fn insert_empty_state(&mut self, proposal: Proposal<T>) {
582 let state = T::ValidatedState::from_header(&proposal.block_header);
583 self.insert_state(
584 proposal.view_number(),
585 Arc::new(state),
586 None,
587 proposal.into(),
588 );
589 }
590
591 #[cfg(test)]
592 pub(crate) fn validated_contains_view(&self, v: ViewNumber) -> bool {
593 self.validated_states
594 .iter()
595 .any(|(_, entry)| entry.leaf.view_number() == v)
596 }
597}