1use std::ops::Add;
2
3use alloy::primitives::{Address, U256};
4use anyhow::{bail, Context};
5use committable::{Commitment, Committable};
6use either::Either;
7use hotshot_query_service::merklized_state::MerklizedState;
8use hotshot_types::{
9 data::{BlockError, EpochNumber, ViewNumber},
10 traits::{
11 block_contents::BlockHeader, node_implementation::ConsensusTime,
12 signature_key::BuilderSignatureKey, states::StateDelta, ValidatedState as HotShotState,
13 },
14 utils::{epoch_from_block_number, is_ge_epoch_root},
15};
16use itertools::Itertools;
17use jf_merkle_tree_compat::{
18 prelude::{MerkleProof, Sha3Digest, Sha3Node},
19 AppendableMerkleTreeScheme, ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme,
20 LookupResult, MerkleCommitment, MerkleTreeError, MerkleTreeScheme,
21 PersistentUniversalMerkleTreeScheme, UniversalMerkleTreeScheme,
22};
23use num_traits::CheckedSub;
24use serde::{Deserialize, Serialize};
25use thiserror::Error;
26use time::OffsetDateTime;
27use vbs::version::{StaticVersionType, Version};
28
29use super::{
30 fee_info::FeeError, instance_state::NodeState, v0_1::IterableFeeInfo, BlockMerkleCommitment,
31 BlockSize, EpochVersion, FeeMerkleCommitment, L1Client,
32};
33use crate::{
34 traits::StateCatchup,
35 v0::{
36 impls::{distribute_block_reward, StakeTableHash},
37 sparse_mt::{Keccak256Hasher, KeccakNode},
38 },
39 v0_3::{
40 ChainConfig, ResolvableChainConfig, RewardAccountV1, RewardAmount,
41 RewardMerkleCommitmentV1, RewardMerkleTreeV1, REWARD_MERKLE_TREE_V1_HEIGHT,
42 },
43 v0_4::{
44 Delta, RewardAccountV2, RewardMerkleCommitmentV2, RewardMerkleTreeV2,
45 REWARD_MERKLE_TREE_V2_HEIGHT,
46 },
47 BlockMerkleTree, DrbAndHeaderUpgradeVersion, FeeAccount, FeeAmount, FeeInfo, FeeMerkleTree,
48 Header, Leaf2, NsTableValidationError, PayloadByteLen, SeqTypes, UpgradeType,
49 BLOCK_MERKLE_TREE_HEIGHT, FEE_MERKLE_TREE_HEIGHT,
50};
51
52#[allow(dead_code)]
55pub enum StateValidationError {
56 ProposalValidation(ProposalValidationError),
57 BuilderValidation(BuilderValidationError),
58 Fee(FeeError),
59}
60
61#[derive(Error, Debug, Eq, PartialEq)]
63pub enum BuilderValidationError {
64 #[error("Builder signature not found")]
65 SignatureNotFound,
66 #[error("Fee amount out of range: {0}")]
67 FeeAmountOutOfRange(FeeAmount),
68 #[error("Invalid Builder Signature")]
69 InvalidBuilderSignature,
70}
71
72#[derive(Error, Debug, Eq, PartialEq)]
74pub enum ProposalValidationError {
75 #[error("Next stake table hash mismatch: expected={expected:?}, proposal={proposal:?}")]
76 NextStakeTableHashMismatch {
77 expected: StakeTableHash,
78 proposal: StakeTableHash,
79 },
80 #[error("Invalid ChainConfig: expected={expected:?}, proposal={proposal:?}")]
81 InvalidChainConfig {
82 expected: Box<ChainConfig>,
83 proposal: Box<ResolvableChainConfig>,
84 },
85 #[error(
86 "Invalid Payload Size: (max_block_size={max_block_size}, proposed_block_size={block_size})"
87 )]
88 MaxBlockSizeExceeded {
89 max_block_size: BlockSize,
90 block_size: BlockSize,
91 },
92 #[error(
93 "Insufficient Fee: block_size={max_block_size}, base_fee={base_fee}, \
94 proposed_fee={proposed_fee}"
95 )]
96 InsufficientFee {
97 max_block_size: BlockSize,
98 base_fee: FeeAmount,
99 proposed_fee: FeeAmount,
100 },
101 #[error("Invalid Height: parent_height={parent_height}, proposal_height={proposal_height}")]
102 InvalidHeight {
103 parent_height: u64,
104 proposal_height: u64,
105 },
106 #[error("Invalid Block Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
107 InvalidBlockRoot {
108 expected_root: BlockMerkleCommitment,
109 proposal_root: BlockMerkleCommitment,
110 },
111 #[error("Invalid Fee Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
112 InvalidFeeRoot {
113 expected_root: FeeMerkleCommitment,
114 proposal_root: FeeMerkleCommitment,
115 },
116 #[error(
117 "Invalid v1 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
118 )]
119 InvalidV1RewardRoot {
120 expected_root: RewardMerkleCommitmentV1,
121 proposal_root: RewardMerkleCommitmentV1,
122 },
123 #[error(
124 "Invalid v2 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
125 )]
126 InvalidV2RewardRoot {
127 expected_root: RewardMerkleCommitmentV2,
128 proposal_root: RewardMerkleCommitmentV2,
129 },
130 #[error("Invalid namespace table: {0}")]
131 InvalidNsTable(NsTableValidationError),
132 #[error("Some fee amount or their sum total out of range")]
133 SomeFeeAmountOutOfRange,
134 #[error("Invalid timestamp: proposal={proposal_timestamp}, parent={parent_timestamp}")]
135 DecrementingTimestamp {
136 proposal_timestamp: u64,
137 parent_timestamp: u64,
138 },
139 #[error("Timestamp drift too high: proposed:={proposal}, system={system}, diff={diff}")]
140 InvalidTimestampDrift {
141 proposal: u64,
142 system: u64,
143 diff: u64,
144 },
145 #[error(
146 "Inconsistent timestamps on header: timestamp:={timestamp}, \
147 timestamp_millis={timestamp_millis}"
148 )]
149 InconsistentTimestamps {
150 timestamp: u64,
151 timestamp_millis: u64,
152 },
153 #[error("l1_finalized has `None` value")]
154 L1FinalizedNotFound,
155 #[error("l1_finalized height is decreasing: parent={parent:?} proposed={proposed:?}")]
156 L1FinalizedDecrementing {
157 parent: Option<(u64, u64)>,
158 proposed: Option<(u64, u64)>,
159 },
160 #[error("Invalid proposal: l1_head height is decreasing")]
161 DecrementingL1Head,
162 #[error("Builder Validation Error: {0}")]
163 BuilderValidationError(BuilderValidationError),
164 #[error("Invalid proposal: l1 finalized does not match the proposal")]
165 InvalidL1Finalized,
166 #[error("reward root not found")]
167 RewardRootNotFound {},
168 #[error("Next stake table not found")]
169 NextStakeTableNotFound,
170 #[error("Next stake table hash missing")]
171 NextStakeTableHashNotFound,
172 #[error("No Epoch Height")]
173 NoEpochHeight,
174 #[error("No First Epoch Configured")]
175 NoFirstEpoch,
176 #[error("Total rewards mismatch: proposed header has {proposed} but actual is {actual}")]
177 TotalRewardsMismatch {
178 proposed: RewardAmount,
179 actual: RewardAmount,
180 },
181}
182
183impl StateDelta for Delta {}
184
185#[derive(Hash, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
186pub struct ValidatedState {
188 pub block_merkle_tree: BlockMerkleTree,
190 pub fee_merkle_tree: FeeMerkleTree,
192 pub reward_merkle_tree_v1: RewardMerkleTreeV1,
193 pub reward_merkle_tree_v2: RewardMerkleTreeV2,
194 pub chain_config: ResolvableChainConfig,
196}
197
198impl Default for ValidatedState {
199 fn default() -> Self {
200 let block_merkle_tree = BlockMerkleTree::from_elems(
201 Some(BLOCK_MERKLE_TREE_HEIGHT),
202 Vec::<Commitment<Header>>::new(),
203 )
204 .unwrap();
205
206 let fee_merkle_tree = FeeMerkleTree::from_kv_set(
210 FEE_MERKLE_TREE_HEIGHT,
211 Vec::<(FeeAccount, FeeAmount)>::new(),
212 )
213 .unwrap();
214
215 let reward_merkle_tree_v1 = RewardMerkleTreeV1::from_kv_set(
216 REWARD_MERKLE_TREE_V1_HEIGHT,
217 Vec::<(RewardAccountV1, RewardAmount)>::new(),
218 )
219 .unwrap();
220
221 let reward_merkle_tree_v2 = RewardMerkleTreeV2::from_kv_set(
222 REWARD_MERKLE_TREE_V2_HEIGHT,
223 Vec::<(RewardAccountV2, RewardAmount)>::new(),
224 )
225 .unwrap();
226
227 let chain_config = ResolvableChainConfig::from(ChainConfig::default());
228
229 Self {
230 block_merkle_tree,
231 fee_merkle_tree,
232 reward_merkle_tree_v1,
233 reward_merkle_tree_v2,
234 chain_config,
235 }
236 }
237}
238
239impl ValidatedState {
240 pub fn prefund_account(&mut self, account: FeeAccount, amount: FeeAmount) {
242 self.fee_merkle_tree.update(account, amount).unwrap();
243 }
244
245 pub fn balance(&mut self, account: FeeAccount) -> Option<FeeAmount> {
246 match self.fee_merkle_tree.lookup(account) {
247 LookupResult::Ok(balance, _) => Some(*balance),
248 LookupResult::NotFound(_) => Some(0.into()),
249 LookupResult::NotInMemory => None,
250 }
251 }
252
253 pub fn forgotten_accounts(
258 &self,
259 accounts: impl IntoIterator<Item = FeeAccount>,
260 ) -> Vec<FeeAccount> {
261 accounts
262 .into_iter()
263 .unique()
264 .filter(|account| {
265 self.fee_merkle_tree
266 .lookup(*account)
267 .expect_not_in_memory()
268 .is_ok()
269 })
270 .collect()
271 }
272
273 pub fn forgotten_reward_accounts_v2(
274 &self,
275 accounts: impl IntoIterator<Item = RewardAccountV2>,
276 ) -> Vec<RewardAccountV2> {
277 accounts
278 .into_iter()
279 .unique()
280 .filter(|account| {
281 self.reward_merkle_tree_v2
282 .lookup(*account)
283 .expect_not_in_memory()
284 .is_ok()
285 })
286 .collect()
287 }
288
289 pub fn forgotten_reward_accounts_v1(
290 &self,
291 accounts: impl IntoIterator<Item = RewardAccountV1>,
292 ) -> Vec<RewardAccountV1> {
293 accounts
294 .into_iter()
295 .unique()
296 .filter(|account| {
297 self.reward_merkle_tree_v1
298 .lookup(*account)
299 .expect_not_in_memory()
300 .is_ok()
301 })
302 .collect()
303 }
304
305 pub fn need_to_fetch_blocks_mt_frontier(&self) -> bool {
307 let num_leaves = self.block_merkle_tree.num_leaves();
308 if num_leaves == 0 {
309 false
310 } else {
311 self.block_merkle_tree
312 .lookup(num_leaves - 1)
313 .expect_ok()
314 .is_err()
315 }
316 }
317
318 pub fn insert_fee_deposit(
320 &mut self,
321 fee_info: FeeInfo,
322 ) -> anyhow::Result<LookupResult<FeeAmount, (), ()>> {
323 Ok(self
324 .fee_merkle_tree
325 .update_with(fee_info.account, |balance| {
326 Some(balance.cloned().unwrap_or_default().add(fee_info.amount))
327 })?)
328 }
329
330 pub fn apply_proposal(
331 &mut self,
332 delta: &mut Delta,
333 parent_leaf: &Leaf2,
334 l1_deposits: Vec<FeeInfo>,
335 ) {
336 self.block_merkle_tree
338 .push(parent_leaf.block_header().commit())
339 .unwrap();
340
341 for FeeInfo { account, amount } in l1_deposits.iter() {
342 self.fee_merkle_tree
343 .update_with(account, |balance| {
344 Some(balance.cloned().unwrap_or_default().add(*amount))
345 })
346 .expect("update_with succeeds");
347 delta.fees_delta.insert(*account);
348 }
349 }
350
351 pub fn charge_fees(
352 &mut self,
353 delta: &mut Delta,
354 fee_info: Vec<FeeInfo>,
355 recipient: FeeAccount,
356 ) -> Result<(), FeeError> {
357 for fee_info in fee_info {
358 self.charge_fee(fee_info, recipient)?;
359 delta.fees_delta.extend([fee_info.account, recipient]);
360 }
361 Ok(())
362 }
363
364 pub fn charge_fee(&mut self, fee_info: FeeInfo, recipient: FeeAccount) -> Result<(), FeeError> {
366 if fee_info.amount == 0.into() {
367 return Ok(());
368 }
369
370 let fee_state = self.fee_merkle_tree.clone();
371
372 let FeeInfo { account, amount } = fee_info;
374 let mut err = None;
375 let fee_state = fee_state.persistent_update_with(account, |balance| {
376 let balance = balance.copied();
377 let Some(updated) = balance.unwrap_or_default().checked_sub(&amount) else {
378 err = Some(FeeError::InsufficientFunds { balance, amount });
380 return balance;
381 };
382 if updated == FeeAmount::default() {
383 None
386 } else {
387 Some(updated)
389 }
390 })?;
391
392 if let Some(err) = err {
394 return Err(err);
395 }
396
397 let fee_state = fee_state.persistent_update_with(recipient, |balance| {
400 Some(balance.copied().unwrap_or_default() + amount)
401 })?;
402
403 self.fee_merkle_tree = fee_state;
405 Ok(())
406 }
407}
408#[derive(Debug)]
410pub(crate) struct Proposal<'a> {
411 header: &'a Header,
412 block_size: u32,
413}
414
415impl<'a> Proposal<'a> {
416 pub(crate) fn new(header: &'a Header, block_size: u32) -> Self {
417 Self { header, block_size }
418 }
419 fn validate_l1_head(&self, parent_l1_head: u64) -> Result<(), ProposalValidationError> {
422 if self.header.l1_head() < parent_l1_head {
423 return Err(ProposalValidationError::DecrementingL1Head);
424 }
425 Ok(())
426 }
427 fn validate_chain_config(
431 &self,
432 expected_chain_config: &ChainConfig,
433 ) -> Result<(), ProposalValidationError> {
434 let proposed_chain_config = self.header.chain_config();
435 if proposed_chain_config.commit() != expected_chain_config.commit() {
436 return Err(ProposalValidationError::InvalidChainConfig {
437 expected: Box::new(*expected_chain_config),
438 proposal: Box::new(proposed_chain_config),
439 });
440 }
441 Ok(())
442 }
443
444 fn validate_timestamp_non_dec(
446 &self,
447 parent_timestamp: u64,
448 ) -> Result<(), ProposalValidationError> {
449 if self.header.timestamp() < parent_timestamp {
450 return Err(ProposalValidationError::DecrementingTimestamp {
451 proposal_timestamp: self.header.timestamp(),
452 parent_timestamp,
453 });
454 }
455
456 Ok(())
457 }
458
459 fn validate_timestamp_drift(
464 &self,
465 system_time: OffsetDateTime,
466 ) -> Result<(), ProposalValidationError> {
467 let system_timestamp = system_time.unix_timestamp() as u64;
470 let diff = self.header.timestamp().abs_diff(system_timestamp);
471 if diff > 12 {
472 return Err(ProposalValidationError::InvalidTimestampDrift {
473 proposal: self.header.timestamp(),
474 system: system_timestamp,
475 diff,
476 });
477 }
478
479 Ok(())
480 }
481
482 fn validate_timestamp_consistency(&self) -> Result<(), ProposalValidationError> {
484 if self.header.timestamp() != self.header.timestamp_millis() / 1_000 {
485 return Err(ProposalValidationError::InconsistentTimestamps {
486 timestamp: self.header.timestamp(),
487 timestamp_millis: self.header.timestamp_millis(),
488 });
489 }
490
491 Ok(())
492 }
493
494 fn validate_block_merkle_tree(
496 &self,
497 block_merkle_tree_root: BlockMerkleCommitment,
498 ) -> Result<(), ProposalValidationError> {
499 if self.header.block_merkle_tree_root() != block_merkle_tree_root {
500 return Err(ProposalValidationError::InvalidBlockRoot {
501 expected_root: block_merkle_tree_root,
502 proposal_root: self.header.block_merkle_tree_root(),
503 });
504 }
505
506 Ok(())
507 }
508}
509#[derive(Debug)]
513pub(crate) struct ValidatedTransition<'a> {
514 state: ValidatedState,
515 expected_chain_config: ChainConfig,
516 parent: &'a Header,
517 proposal: Proposal<'a>,
518 total_rewards_distributed: Option<RewardAmount>,
519 version: Version,
520 validation_start_time: OffsetDateTime,
521}
522
523impl<'a> ValidatedTransition<'a> {
524 pub(crate) fn new(
525 state: ValidatedState,
526 parent: &'a Header,
527 proposal: Proposal<'a>,
528 total_rewards_distributed: Option<RewardAmount>,
529 version: Version,
530 validation_start_time: OffsetDateTime,
531 ) -> Self {
532 let expected_chain_config = state
533 .chain_config
534 .resolve()
535 .expect("Chain Config not found in validated state");
536 Self {
537 state,
538 expected_chain_config,
539 parent,
540 proposal,
541 total_rewards_distributed,
542 version,
543 validation_start_time,
544 }
545 }
546
547 pub(crate) fn validate(self) -> Result<Self, ProposalValidationError> {
564 self.validate_timestamp()?;
565 self.validate_builder_fee()?;
566 self.validate_height()?;
567 self.validate_chain_config()?;
568 self.validate_block_size()?;
569 self.validate_fee()?;
570 self.validate_fee_merkle_tree()?;
571 self.validate_block_merkle_tree()?;
572 self.validate_reward_merkle_tree()?;
573 self.validate_l1_finalized()?;
574 self.validate_l1_head()?;
575 self.validate_namespace_table()?;
576 self.validate_total_rewards_distributed()?;
577
578 Ok(self)
579 }
580
581 fn validate_l1_finalized(&self) -> Result<(), ProposalValidationError> {
583 let proposed_finalized = self.proposal.header.l1_finalized();
584 let parent_finalized = self.parent.l1_finalized();
585
586 if proposed_finalized < parent_finalized {
587 return Err(ProposalValidationError::L1FinalizedDecrementing {
592 parent: parent_finalized.map(|block| (block.number, block.timestamp.to::<u64>())),
593 proposed: proposed_finalized
594 .map(|block| (block.number, block.timestamp.to::<u64>())),
595 });
596 }
597 Ok(())
598 }
599 async fn wait_for_l1(self, l1_client: &L1Client) -> Result<Self, ProposalValidationError> {
604 self.wait_for_l1_head(l1_client).await;
605 self.wait_for_finalized_block(l1_client).await?;
606 Ok(self)
607 }
608
609 async fn wait_for_l1_head(&self, l1_client: &L1Client) {
612 let _ = l1_client
613 .wait_for_block(self.proposal.header.l1_head())
614 .await;
615 }
616 async fn wait_for_finalized_block(
619 &self,
620 l1_client: &L1Client,
621 ) -> Result<(), ProposalValidationError> {
622 let proposed_finalized = self.proposal.header.l1_finalized();
623
624 if let Some(proposed_finalized) = proposed_finalized {
625 let finalized = l1_client
626 .wait_for_finalized_block(proposed_finalized.number())
627 .await;
628
629 if finalized != proposed_finalized {
630 return Err(ProposalValidationError::InvalidL1Finalized);
631 }
632 }
633
634 Ok(())
635 }
636
637 fn validate_l1_head(&self) -> Result<(), ProposalValidationError> {
639 self.proposal.validate_l1_head(self.parent.l1_head())?;
640 Ok(())
641 }
642 fn validate_builder_fee(&self) -> Result<(), ProposalValidationError> {
645 if let Err(err) = validate_builder_fee(self.proposal.header) {
647 return Err(ProposalValidationError::BuilderValidationError(err));
648 }
649 Ok(())
650 }
651 fn validate_chain_config(&self) -> Result<(), ProposalValidationError> {
653 self.proposal
654 .validate_chain_config(&self.expected_chain_config)?;
655 Ok(())
656 }
657 fn validate_block_size(&self) -> Result<(), ProposalValidationError> {
660 let block_size = self.proposal.block_size as u64;
661 if block_size > *self.expected_chain_config.max_block_size {
662 return Err(ProposalValidationError::MaxBlockSizeExceeded {
663 max_block_size: self.expected_chain_config.max_block_size,
664 block_size: block_size.into(),
665 });
666 }
667 Ok(())
668 }
669 fn validate_fee(&self) -> Result<(), ProposalValidationError> {
672 let Some(amount) = self.proposal.header.fee_info().amount() else {
675 return Err(ProposalValidationError::SomeFeeAmountOutOfRange);
676 };
677
678 if amount < self.expected_chain_config.base_fee * U256::from(self.proposal.block_size) {
679 return Err(ProposalValidationError::InsufficientFee {
680 max_block_size: self.expected_chain_config.max_block_size,
681 base_fee: self.expected_chain_config.base_fee,
682 proposed_fee: amount,
683 });
684 }
685 Ok(())
686 }
687 fn validate_height(&self) -> Result<(), ProposalValidationError> {
689 let parent_header = self.parent;
690 if self.proposal.header.height() != parent_header.height() + 1 {
691 return Err(ProposalValidationError::InvalidHeight {
692 parent_height: parent_header.height(),
693 proposal_height: self.proposal.header.height(),
694 });
695 }
696 Ok(())
697 }
698 fn validate_timestamp(&self) -> Result<(), ProposalValidationError> {
703 self.proposal.validate_timestamp_consistency()?;
704
705 self.proposal
706 .validate_timestamp_non_dec(self.parent.timestamp())?;
707
708 self.proposal
709 .validate_timestamp_drift(self.validation_start_time)?;
710
711 Ok(())
712 }
713 fn validate_block_merkle_tree(&self) -> Result<(), ProposalValidationError> {
716 let block_merkle_tree_root = self.state.block_merkle_tree.commitment();
717 self.proposal
718 .validate_block_merkle_tree(block_merkle_tree_root)?;
719
720 Ok(())
721 }
722
723 fn validate_reward_merkle_tree(&self) -> Result<(), ProposalValidationError> {
726 match self.proposal.header.reward_merkle_tree_root() {
727 Either::Left(proposal_root) => {
728 let expected_root = self.state.reward_merkle_tree_v1.commitment();
729 if proposal_root != expected_root {
730 return Err(ProposalValidationError::InvalidV1RewardRoot {
731 expected_root,
732 proposal_root,
733 });
734 }
735 },
736 Either::Right(proposal_root) => {
737 let expected_root = self.state.reward_merkle_tree_v2.commitment();
738 if proposal_root != expected_root {
739 return Err(ProposalValidationError::InvalidV2RewardRoot {
740 expected_root,
741 proposal_root,
742 });
743 }
744 },
745 }
746
747 Ok(())
748 }
749
750 fn validate_fee_merkle_tree(&self) -> Result<(), ProposalValidationError> {
753 let fee_merkle_tree_root = self.state.fee_merkle_tree.commitment();
754 if self.proposal.header.fee_merkle_tree_root() != fee_merkle_tree_root {
755 return Err(ProposalValidationError::InvalidFeeRoot {
756 expected_root: fee_merkle_tree_root,
757 proposal_root: self.proposal.header.fee_merkle_tree_root(),
758 });
759 }
760
761 Ok(())
762 }
763 fn validate_namespace_table(&self) -> Result<(), ProposalValidationError> {
765 self.proposal
766 .header
767 .ns_table()
768 .validate(&PayloadByteLen(self.proposal.block_size as usize))
770 .map_err(ProposalValidationError::from)
771 }
772
773 fn validate_total_rewards_distributed(&self) -> Result<(), ProposalValidationError> {
776 if self.version >= DrbAndHeaderUpgradeVersion::version() {
777 let Some(actual_total) = self.total_rewards_distributed else {
778 return Err(ProposalValidationError::TotalRewardsMismatch {
780 proposed: self
781 .proposal
782 .header
783 .total_reward_distributed()
784 .unwrap_or_default(),
785 actual: RewardAmount::from(0),
786 });
787 };
788
789 let proposed_total =
790 self.proposal
791 .header
792 .total_reward_distributed()
793 .ok_or_else(|| ProposalValidationError::TotalRewardsMismatch {
794 proposed: RewardAmount::from(0),
795 actual: actual_total,
796 })?;
797
798 if proposed_total != actual_total {
799 return Err(ProposalValidationError::TotalRewardsMismatch {
800 proposed: proposed_total,
801 actual: actual_total,
802 });
803 }
804 }
805 Ok(())
806 }
807}
808
809#[cfg(any(test, feature = "testing"))]
810impl ValidatedState {
811 pub fn forget(&self) -> Self {
812 Self {
813 fee_merkle_tree: FeeMerkleTree::from_commitment(self.fee_merkle_tree.commitment()),
814 block_merkle_tree: BlockMerkleTree::from_commitment(
815 self.block_merkle_tree.commitment(),
816 ),
817 reward_merkle_tree_v2: RewardMerkleTreeV2::from_commitment(
818 self.reward_merkle_tree_v2.commitment(),
819 ),
820 reward_merkle_tree_v1: RewardMerkleTreeV1::from_commitment(
821 self.reward_merkle_tree_v1.commitment(),
822 ),
823 chain_config: ResolvableChainConfig::from(self.chain_config.commit()),
824 }
825 }
826}
827
828impl From<NsTableValidationError> for ProposalValidationError {
829 fn from(err: NsTableValidationError) -> Self {
830 Self::InvalidNsTable(err)
831 }
832}
833
834impl From<ProposalValidationError> for BlockError {
835 fn from(err: ProposalValidationError) -> Self {
836 tracing::error!("Invalid Block Header: {err:#}");
837 BlockError::InvalidBlockHeader(err.to_string())
838 }
839}
840
841impl From<MerkleTreeError> for FeeError {
842 fn from(item: MerkleTreeError) -> Self {
843 Self::MerkleTreeError(item)
844 }
845}
846
847fn validate_builder_fee(proposed_header: &Header) -> Result<(), BuilderValidationError> {
850 for (fee_info, signature) in proposed_header
852 .fee_info()
853 .iter()
854 .zip(proposed_header.builder_signature())
855 {
856 fee_info
858 .amount()
859 .as_u64()
860 .ok_or(BuilderValidationError::FeeAmountOutOfRange(fee_info.amount))?;
861
862 if !fee_info.account().validate_fee_signature(
864 &signature,
865 fee_info.amount().as_u64().unwrap(),
866 proposed_header.metadata(),
867 ) && !fee_info
868 .account()
869 .validate_fee_signature_with_vid_commitment(
870 &signature,
871 fee_info.amount().as_u64().unwrap(),
872 proposed_header.metadata(),
873 &proposed_header.payload_commitment(),
874 )
875 {
876 return Err(BuilderValidationError::InvalidBuilderSignature);
877 }
878 }
879
880 Ok(())
881}
882
883impl ValidatedState {
884 pub async fn apply_header(
890 &self,
891 instance: &NodeState,
892 peers: &impl StateCatchup,
893 parent_leaf: &Leaf2,
894 proposed_header: &Header,
895 version: Version,
896 view_number: ViewNumber,
897 ) -> anyhow::Result<(Self, Delta, Option<RewardAmount>)> {
898 let mut validated_state = self.clone();
901 validated_state.apply_upgrade(instance, version);
902
903 let chain_config = validated_state
906 .get_chain_config(instance, peers, &proposed_header.chain_config())
907 .await?;
908
909 if Some(chain_config) != validated_state.chain_config.resolve() {
910 validated_state.chain_config = chain_config.into();
911 }
912
913 let l1_deposits = get_l1_deposits(
914 instance,
915 proposed_header,
916 parent_leaf,
917 chain_config.fee_contract,
918 )
919 .await;
920
921 let missing_accounts = self.forgotten_accounts(
925 [chain_config.fee_recipient]
926 .into_iter()
927 .chain(proposed_header.fee_info().accounts())
928 .chain(l1_deposits.accounts()),
929 );
930
931 let parent_height = parent_leaf.height();
932 let parent_view = parent_leaf.view_number();
933
934 if self.need_to_fetch_blocks_mt_frontier() {
936 tracing::info!(
937 parent_height,
938 ?parent_view,
939 "fetching block frontier from peers"
940 );
941 peers
942 .remember_blocks_merkle_tree(
943 instance,
944 parent_height,
945 parent_view,
946 &mut validated_state.block_merkle_tree,
947 )
948 .await?;
949 }
950
951 if !missing_accounts.is_empty() {
953 tracing::info!(
954 parent_height,
955 ?parent_view,
956 ?missing_accounts,
957 "fetching missing accounts from peers"
958 );
959
960 let missing_account_proofs = peers
961 .fetch_accounts(
962 instance,
963 parent_height,
964 parent_view,
965 validated_state.fee_merkle_tree.commitment(),
966 missing_accounts,
967 )
968 .await?;
969
970 for proof in missing_account_proofs.iter() {
972 proof
973 .remember(&mut validated_state.fee_merkle_tree)
974 .expect("proof previously verified");
975 }
976 }
977
978 let mut delta = Delta::default();
979 validated_state.apply_proposal(&mut delta, parent_leaf, l1_deposits);
980
981 validated_state.charge_fees(
982 &mut delta,
983 proposed_header.fee_info(),
984 chain_config.fee_recipient,
985 )?;
986
987 let total_rewards_distributed = if version < EpochVersion::version() {
989 None
990 } else if let Some(reward_distributor) = distribute_block_reward(
991 instance,
992 &mut validated_state,
993 parent_leaf,
994 view_number,
995 version,
996 )
997 .await?
998 {
999 reward_distributor
1000 .update_rewards_delta(&mut delta)
1001 .context("failed to update rewards delta")?;
1002
1003 Some(reward_distributor.total_distributed())
1004 } else {
1005 Some(Default::default())
1007 };
1008
1009 Ok((validated_state, delta, total_rewards_distributed))
1010 }
1011
1012 pub(crate) fn apply_upgrade(&mut self, instance: &NodeState, version: Version) {
1014 if version <= instance.current_version {
1016 return;
1017 }
1018
1019 let Some(upgrade) = instance.upgrades.get(&version) else {
1020 return;
1021 };
1022
1023 let cf = match upgrade.upgrade_type {
1024 UpgradeType::Fee { chain_config } => chain_config,
1025 UpgradeType::Epoch { chain_config } => chain_config,
1026 UpgradeType::DrbAndHeader { chain_config } => chain_config,
1027 UpgradeType::Da { chain_config } => chain_config,
1028 };
1029
1030 self.chain_config = cf.into();
1031 }
1032
1033 pub(crate) async fn get_chain_config(
1041 &self,
1042 instance: &NodeState,
1043 peers: &impl StateCatchup,
1044 header_cf: &ResolvableChainConfig,
1045 ) -> anyhow::Result<ChainConfig> {
1046 let state_cf = self.chain_config;
1047
1048 if state_cf.commit() == instance.chain_config.commit() {
1049 return Ok(instance.chain_config);
1050 }
1051
1052 let cf = match (state_cf.resolve(), header_cf.resolve()) {
1053 (Some(cf), _) => cf,
1054 (_, Some(cf)) if cf.commit() == state_cf.commit() => cf,
1055 (_, Some(_)) | (None, None) => peers.fetch_chain_config(state_cf.commit()).await?,
1056 };
1057
1058 Ok(cf)
1059 }
1060}
1061
1062pub async fn get_l1_deposits(
1063 instance: &NodeState,
1064 header: &Header,
1065 parent_leaf: &Leaf2,
1066 fee_contract_address: Option<Address>,
1067) -> Vec<FeeInfo> {
1068 if let (Some(addr), Some(block_info)) = (fee_contract_address, header.l1_finalized()) {
1069 instance
1070 .l1_client
1071 .get_finalized_deposits(
1072 addr,
1073 parent_leaf
1074 .block_header()
1075 .l1_finalized()
1076 .map(|block_info| block_info.number),
1077 block_info.number,
1078 )
1079 .await
1080 } else {
1081 vec![]
1082 }
1083}
1084
1085async fn validate_next_stake_table_hash(
1086 instance: &NodeState,
1087 proposed_header: &Header,
1088) -> Result<(), ProposalValidationError> {
1089 let Some(epoch_height) = instance.epoch_height else {
1090 return Err(ProposalValidationError::NoEpochHeight);
1091 };
1092 if !is_ge_epoch_root(proposed_header.height(), epoch_height) {
1093 return Ok(());
1094 }
1095 let epoch = EpochNumber::new(epoch_from_block_number(
1096 proposed_header.height(),
1097 epoch_height,
1098 ));
1099 let coordinator = instance.coordinator.clone();
1100 let Some(first_epoch) = coordinator.membership().read().await.first_epoch() else {
1101 return Err(ProposalValidationError::NoFirstEpoch);
1102 };
1103
1104 if epoch <= first_epoch + 1 {
1107 return Ok(());
1108 }
1109
1110 let epoch_membership = instance
1111 .coordinator
1112 .stake_table_for_epoch(Some(epoch + 1))
1113 .await
1114 .map_err(|_| ProposalValidationError::NextStakeTableNotFound)?;
1115 let next_stake_table_hash = epoch_membership
1116 .stake_table_hash()
1117 .await
1118 .ok_or(ProposalValidationError::NextStakeTableHashNotFound)?;
1119 let Some(proposed_next_stake_table_hash) = proposed_header.next_stake_table_hash() else {
1120 return Err(ProposalValidationError::NextStakeTableHashNotFound);
1121 };
1122 if next_stake_table_hash != proposed_next_stake_table_hash {
1123 return Err(ProposalValidationError::NextStakeTableHashMismatch {
1124 expected: next_stake_table_hash,
1125 proposal: proposed_next_stake_table_hash,
1126 });
1127 }
1128 Ok(())
1129}
1130
1131impl HotShotState<SeqTypes> for ValidatedState {
1132 type Error = BlockError;
1133 type Instance = NodeState;
1134
1135 type Time = ViewNumber;
1136
1137 type Delta = Delta;
1138 fn on_commit(&self) {}
1139 #[tracing::instrument(
1142 skip_all,
1143 fields(
1144 node_id = instance.node_id,
1145 view = ?parent_leaf.view_number(),
1146 height = parent_leaf.height(),
1147 ),
1148 )]
1149 async fn validate_and_apply_header(
1150 &self,
1151 instance: &Self::Instance,
1152 parent_leaf: &Leaf2,
1153 proposed_header: &Header,
1154 payload_byte_len: u32,
1155 version: Version,
1156 view_number: u64,
1157 ) -> Result<(Self, Self::Delta), Self::Error> {
1158 let validation_start_time = OffsetDateTime::now_utc();
1162
1163 let (validated_state, delta, total_rewards_distributed) = self
1164 .apply_header(
1166 instance,
1167 &instance.state_catchup,
1168 parent_leaf,
1169 proposed_header,
1170 version,
1171 ViewNumber::new(view_number),
1172 )
1173 .await
1174 .map_err(|e| BlockError::FailedHeaderApply(e.to_string()))?;
1175
1176 if version >= DrbAndHeaderUpgradeVersion::version() {
1177 validate_next_stake_table_hash(instance, proposed_header).await?;
1178 }
1179
1180 let validated_state = ValidatedTransition::new(
1182 validated_state,
1183 parent_leaf.block_header(),
1184 Proposal::new(proposed_header, payload_byte_len),
1185 total_rewards_distributed,
1186 version,
1187 validation_start_time,
1188 )
1189 .validate()?
1190 .wait_for_l1(&instance.l1_client)
1191 .await?
1192 .state;
1193
1194 if parent_leaf.view_number().u64().is_multiple_of(10) {
1197 tracing::info!("validated and applied new header");
1198 }
1199 Ok((validated_state, delta))
1200 }
1201 fn from_header(block_header: &Header) -> Self {
1205 let fee_merkle_tree = if block_header.fee_merkle_tree_root().size() == 0 {
1206 FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT)
1209 } else {
1210 FeeMerkleTree::from_commitment(block_header.fee_merkle_tree_root())
1211 };
1212 let block_merkle_tree = if block_header.block_merkle_tree_root().size() == 0 {
1213 BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT)
1216 } else {
1217 BlockMerkleTree::from_commitment(block_header.block_merkle_tree_root())
1218 };
1219
1220 let (reward_merkle_tree_v1, reward_merkle_tree_v2) = match block_header
1221 .reward_merkle_tree_root()
1222 {
1223 Either::Left(reward_tree_v1) => {
1224 let reward_merkle_tree_v2 = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
1225 let reward_merkle_tree_v1 = if reward_tree_v1.size() == 0 {
1226 RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT)
1227 } else {
1228 RewardMerkleTreeV1::from_commitment(reward_tree_v1)
1229 };
1230 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1231 },
1232 Either::Right(reward_tree_v2) => {
1233 let reward_merkle_tree_v1 = RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT);
1234 let reward_merkle_tree_v2 = if reward_tree_v2.size() == 0 {
1235 RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT)
1236 } else {
1237 RewardMerkleTreeV2::from_commitment(reward_tree_v2)
1238 };
1239 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1240 },
1241 };
1242
1243 Self {
1244 fee_merkle_tree,
1245 block_merkle_tree,
1246 reward_merkle_tree_v2,
1247 reward_merkle_tree_v1,
1248 chain_config: block_header.chain_config(),
1249 }
1250 }
1251 fn genesis(instance: &Self::Instance) -> (Self, Self::Delta) {
1253 (instance.genesis_state.clone(), Delta::default())
1254 }
1255}
1256
1257#[cfg(any(test, feature = "testing"))]
1259impl std::fmt::Display for ValidatedState {
1260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1261 write!(f, "{self:#?}")
1262 }
1263}
1264
1265#[cfg(any(test, feature = "testing"))]
1266impl hotshot_types::traits::states::TestableState<SeqTypes> for ValidatedState {
1267 fn create_random_transaction(
1268 _state: Option<&Self>,
1269 rng: &mut dyn rand::RngCore,
1270 _padding: u64,
1271 ) -> crate::Transaction {
1272 crate::Transaction::random(rng)
1273 }
1274}
1275
1276impl MerklizedState<SeqTypes, { Self::ARITY }> for BlockMerkleTree {
1277 type Key = Self::Index;
1278 type Entry = Commitment<Header>;
1279 type T = Sha3Node;
1280 type Commit = Self::Commitment;
1281 type Digest = Sha3Digest;
1282
1283 fn state_type() -> &'static str {
1284 "block_merkle_tree"
1285 }
1286
1287 fn header_state_commitment_field() -> &'static str {
1288 "block_merkle_tree_root"
1289 }
1290
1291 fn tree_height() -> usize {
1292 BLOCK_MERKLE_TREE_HEIGHT
1293 }
1294
1295 fn insert_path(
1296 &mut self,
1297 key: Self::Key,
1298 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1299 ) -> anyhow::Result<()> {
1300 let Some(elem) = proof.elem() else {
1301 bail!("BlockMerkleTree does not support non-membership proofs");
1302 };
1303 self.remember(key, elem, proof)?;
1304 Ok(())
1305 }
1306}
1307
1308impl MerklizedState<SeqTypes, { Self::ARITY }> for FeeMerkleTree {
1309 type Key = Self::Index;
1310 type Entry = Self::Element;
1311 type T = Sha3Node;
1312 type Commit = Self::Commitment;
1313 type Digest = Sha3Digest;
1314
1315 fn state_type() -> &'static str {
1316 "fee_merkle_tree"
1317 }
1318
1319 fn header_state_commitment_field() -> &'static str {
1320 "fee_merkle_tree_root"
1321 }
1322
1323 fn tree_height() -> usize {
1324 FEE_MERKLE_TREE_HEIGHT
1325 }
1326
1327 fn insert_path(
1328 &mut self,
1329 key: Self::Key,
1330 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1331 ) -> anyhow::Result<()> {
1332 match proof.elem() {
1333 Some(elem) => self.remember(key, elem, proof)?,
1334 None => self.non_membership_remember(key, proof)?,
1335 }
1336 Ok(())
1337 }
1338}
1339
1340impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV2 {
1341 type Key = Self::Index;
1342 type Entry = Self::Element;
1343 type T = KeccakNode;
1344 type Commit = Self::Commitment;
1345 type Digest = Keccak256Hasher;
1346
1347 fn state_type() -> &'static str {
1348 "reward_merkle_tree_v2"
1349 }
1350
1351 fn header_state_commitment_field() -> &'static str {
1352 "reward_merkle_tree_root"
1353 }
1354
1355 fn tree_height() -> usize {
1356 REWARD_MERKLE_TREE_V2_HEIGHT
1357 }
1358
1359 fn insert_path(
1360 &mut self,
1361 key: Self::Key,
1362 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1363 ) -> anyhow::Result<()> {
1364 match proof.elem() {
1365 Some(elem) => self.remember(key, elem, proof)?,
1366 None => self.non_membership_remember(key, proof)?,
1367 }
1368 Ok(())
1369 }
1370}
1371
1372impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV1 {
1373 type Key = Self::Index;
1374 type Entry = Self::Element;
1375 type T = Sha3Node;
1376 type Commit = Self::Commitment;
1377 type Digest = Sha3Digest;
1378
1379 fn state_type() -> &'static str {
1380 "reward_merkle_tree"
1381 }
1382
1383 fn header_state_commitment_field() -> &'static str {
1384 "reward_merkle_tree_root"
1385 }
1386
1387 fn tree_height() -> usize {
1388 REWARD_MERKLE_TREE_V1_HEIGHT
1389 }
1390
1391 fn insert_path(
1392 &mut self,
1393 key: Self::Key,
1394 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1395 ) -> anyhow::Result<()> {
1396 match proof.elem() {
1397 Some(elem) => self.remember(key, elem, proof)?,
1398 None => self.non_membership_remember(key, proof)?,
1399 }
1400 Ok(())
1401 }
1402}
1403
1404#[cfg(test)]
1405mod test {
1406 use std::{sync::Arc, time::Duration};
1407
1408 use hotshot::traits::BlockPayload;
1409 use hotshot_query_service::{testing::mocks::MockVersions, Resolvable};
1410 use hotshot_types::{data::ViewNumber, traits::signature_key::BuilderSignatureKey};
1411 use sequencer_utils::ser::FromStringOrInteger;
1412 use tracing::debug;
1413 use vbs::version::StaticVersion;
1414
1415 use super::*;
1416 use crate::{
1417 eth_signature_key::EthKeyPair, mock::MockStateCatchup, v0_1, v0_2, v0_3, v0_4, v0_5,
1418 BlockSize, FeeAccountProof, FeeMerkleProof, FeeVersion, Leaf, MaxSupportedVersion, Payload,
1419 SequencerVersions, TimestampMillis, Transaction,
1420 };
1421
1422 impl Transaction {
1423 async fn into_mock_header(self) -> (Header, u32) {
1424 let instance = NodeState::mock_v2();
1425 let (payload, metadata) =
1426 Payload::from_transactions([self], &instance.genesis_state, &instance)
1427 .await
1428 .unwrap();
1429
1430 let header = Header::genesis::<MockVersions>(&instance, payload.clone(), &metadata);
1431
1432 let header = header.sign();
1433
1434 (header, payload.byte_len().0 as u32)
1435 }
1436 }
1437 impl Header {
1438 fn next(self) -> Self {
1440 let time = OffsetDateTime::now_utc();
1441 let timestamp = time.unix_timestamp() as u64;
1442 let timestamp_millis = TimestampMillis::from_time(&time);
1443
1444 match self {
1445 Header::V1(_) => panic!("You called `Header.next()` on unimplemented version (v1)"),
1446 Header::V2(parent) => Header::V2(v0_2::Header {
1447 height: parent.height + 1,
1448 timestamp,
1449 ..parent.clone()
1450 }),
1451 Header::V3(parent) => Header::V3(v0_3::Header {
1452 height: parent.height + 1,
1453 timestamp,
1454 ..parent.clone()
1455 }),
1456 Header::V4(parent) => Header::V4(v0_4::Header {
1457 height: parent.height + 1,
1458 timestamp,
1459 timestamp_millis,
1460 ..parent.clone()
1461 }),
1462 Header::V5(parent) => Header::V5(v0_5::Header {
1463 height: parent.height + 1,
1464 timestamp,
1465 timestamp_millis,
1466 ..parent.clone()
1467 }),
1468 }
1469 }
1470 fn sign(&self) -> Self {
1472 let key_pair = EthKeyPair::random();
1473 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1474
1475 let sig = FeeAccount::sign_fee(
1476 &key_pair,
1477 fee_info.amount().as_u64().unwrap(),
1478 self.metadata(),
1479 )
1480 .unwrap();
1481
1482 match self {
1483 Header::V1(_) => panic!("You called `Header.sign()` on unimplemented version (v1)"),
1484 Header::V2(header) => Header::V2(v0_2::Header {
1485 fee_info,
1486 builder_signature: Some(sig),
1487 ..header.clone()
1488 }),
1489 Header::V3(header) => Header::V3(v0_3::Header {
1490 fee_info,
1491 builder_signature: Some(sig),
1492 ..header.clone()
1493 }),
1494 Header::V4(header) => Header::V4(v0_4::Header {
1495 fee_info,
1496 builder_signature: Some(sig),
1497 ..header.clone()
1498 }),
1499 Header::V5(header) => Header::V5(v0_5::Header {
1500 fee_info,
1501 builder_signature: Some(sig),
1502 ..header.clone()
1503 }),
1504 }
1505 }
1506
1507 fn invalid_builder_signature(&self) -> Self {
1509 let key_pair = EthKeyPair::random();
1510 let key_pair2 = EthKeyPair::random();
1511 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1512
1513 let sig = FeeAccount::sign_fee(
1514 &key_pair2,
1515 fee_info.amount().as_u64().unwrap(),
1516 self.metadata(),
1517 )
1518 .unwrap();
1519
1520 match self {
1521 Header::V1(_) => panic!(
1522 "You called `Header.invalid_builder_signature()` on unimplemented version (v1)"
1523 ),
1524 Header::V2(parent) => Header::V2(v0_2::Header {
1525 fee_info,
1526 builder_signature: Some(sig),
1527 ..parent.clone()
1528 }),
1529 Header::V3(parent) => Header::V3(v0_3::Header {
1530 fee_info,
1531 builder_signature: Some(sig),
1532 ..parent.clone()
1533 }),
1534 Header::V4(parent) => Header::V4(v0_4::Header {
1535 fee_info,
1536 builder_signature: Some(sig),
1537 ..parent.clone()
1538 }),
1539 Header::V5(parent) => Header::V5(v0_5::Header {
1540 fee_info,
1541 builder_signature: Some(sig),
1542 ..parent.clone()
1543 }),
1544 }
1545 }
1546 }
1547
1548 impl<'a> ValidatedTransition<'a> {
1549 fn mock(instance: NodeState, parent: &'a Header, proposal: Proposal<'a>) -> Self {
1550 let expected_chain_config = instance.chain_config;
1551 let validation_start_time = OffsetDateTime::now_utc();
1552
1553 Self {
1554 state: instance.genesis_state,
1555 expected_chain_config,
1556 parent,
1557 proposal,
1558 total_rewards_distributed: None,
1559 version: Version { major: 0, minor: 1 },
1560 validation_start_time,
1561 }
1562 }
1563 }
1564
1565 #[test_log::test]
1566 fn test_fee_proofs() {
1567 let mut tree = ValidatedState::default().fee_merkle_tree;
1568 let account1 = Address::random();
1569 let account2 = Address::default();
1570 tracing::info!(%account1, %account2);
1571
1572 let balance1 = U256::from(100);
1573 tree.update(FeeAccount(account1), FeeAmount(balance1))
1574 .unwrap();
1575
1576 let (proof1, balance) = FeeAccountProof::prove(&tree, account1).unwrap();
1578 tracing::info!(?proof1, %balance);
1579 assert_eq!(balance, balance1);
1580 assert!(matches!(proof1.proof, FeeMerkleProof::Presence(_)));
1581 assert_eq!(proof1.verify(&tree.commitment()).unwrap(), balance1);
1582
1583 let (proof2, balance) = FeeAccountProof::prove(&tree, account2).unwrap();
1585 tracing::info!(?proof2, %balance);
1586 assert_eq!(balance, U256::ZERO);
1587 assert!(matches!(proof2.proof, FeeMerkleProof::Absence(_)));
1588 assert_eq!(proof2.verify(&tree.commitment()).unwrap(), U256::ZERO);
1589
1590 let mut tree = FeeMerkleTree::from_commitment(tree.commitment());
1592 assert!(FeeAccountProof::prove(&tree, account1).is_none());
1593 assert!(FeeAccountProof::prove(&tree, account2).is_none());
1594 proof1.remember(&mut tree).unwrap();
1596 proof2.remember(&mut tree).unwrap();
1597 FeeAccountProof::prove(&tree, account1).unwrap();
1598 FeeAccountProof::prove(&tree, account2).unwrap();
1599 }
1600
1601 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1602 async fn test_validation_l1_head() {
1603 let tx = Transaction::of_size(10);
1605 let (header, block_size) = tx.into_mock_header().await;
1606
1607 let proposal = Proposal::new(&header, block_size);
1609 ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1612 .validate_l1_head()
1613 .unwrap();
1614
1615 let proposal = Proposal::new(&header, block_size);
1617 let err = proposal.validate_l1_head(u64::MAX).unwrap_err();
1618 assert_eq!(ProposalValidationError::DecrementingL1Head, err);
1619 }
1620
1621 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1622 async fn test_validation_builder_fee() {
1623 let instance = NodeState::mock();
1625 let tx = Transaction::of_size(20);
1626 let (header, block_size) = tx.into_mock_header().await;
1627
1628 let proposal = Proposal::new(&header, block_size);
1630 ValidatedTransition::mock(instance.clone(), &header, proposal)
1631 .validate_builder_fee()
1632 .unwrap();
1633
1634 let header = header.invalid_builder_signature();
1636 let proposal = Proposal::new(&header, block_size);
1637 let err = ValidatedTransition::mock(instance, &header, proposal)
1638 .validate_builder_fee()
1639 .unwrap_err();
1640
1641 tracing::info!(%err, "task failed successfully");
1642 assert_eq!(
1643 ProposalValidationError::BuilderValidationError(
1644 BuilderValidationError::InvalidBuilderSignature
1645 ),
1646 err
1647 );
1648 }
1649
1650 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1651 async fn test_validation_chain_config() {
1652 let instance = NodeState::mock();
1654 let tx = Transaction::of_size(20);
1655 let (header, block_size) = tx.into_mock_header().await;
1656
1657 let proposal = Proposal::new(&header, block_size);
1659 ValidatedTransition::mock(instance.clone(), &header, proposal)
1660 .validate_chain_config()
1661 .unwrap();
1662
1663 let proposal = Proposal::new(&header, block_size);
1665 let expected_chain_config = ChainConfig {
1666 max_block_size: BlockSize(3333),
1667 ..instance.chain_config
1668 };
1669 let err = proposal
1670 .validate_chain_config(&expected_chain_config)
1671 .unwrap_err();
1672
1673 tracing::info!(%err, "task failed successfully");
1674
1675 assert_eq!(
1676 ProposalValidationError::InvalidChainConfig {
1677 expected: Box::new(expected_chain_config),
1678 proposal: Box::new(header.chain_config())
1679 },
1680 err
1681 );
1682 }
1683
1684 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1685 async fn test_validation_max_block_size() {
1686 const MAX_BLOCK_SIZE: usize = 10;
1687
1688 let state = ValidatedState::default();
1690 let expected_chain_config = ChainConfig {
1691 max_block_size: BlockSize::from_integer(MAX_BLOCK_SIZE as u64).unwrap(),
1692 ..state.chain_config.resolve().unwrap()
1693 };
1694 let instance = NodeState::mock().with_chain_config(expected_chain_config);
1695 let tx = Transaction::of_size(20);
1696 let (header, block_size) = tx.into_mock_header().await;
1697
1698 let proposal = Proposal::new(&header, block_size);
1700 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1701 .validate_block_size()
1702 .unwrap_err();
1703
1704 tracing::info!(%err, "task failed successfully");
1705 assert_eq!(
1706 ProposalValidationError::MaxBlockSizeExceeded {
1707 max_block_size: instance.chain_config.max_block_size,
1708 block_size: BlockSize::from_integer(block_size as u64).unwrap()
1709 },
1710 err
1711 );
1712
1713 let proposal = Proposal::new(&header, 1);
1715 ValidatedTransition::mock(instance, &header, proposal)
1716 .validate_block_size()
1717 .unwrap()
1718 }
1719
1720 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1721 async fn test_validation_base_fee() {
1722 let tx = Transaction::of_size(20);
1724 let (header, block_size) = tx.into_mock_header().await;
1725 let state = ValidatedState::default();
1726 let instance = NodeState::mock_v2().with_chain_config(ChainConfig {
1727 base_fee: 1000.into(), ..state.chain_config.resolve().unwrap()
1729 });
1730
1731 let proposal = Proposal::new(&header, block_size);
1732 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1733 .validate_fee()
1734 .unwrap_err();
1735
1736 tracing::info!(%err, "task failed successfully");
1738 assert_eq!(
1739 ProposalValidationError::InsufficientFee {
1740 max_block_size: instance.chain_config.max_block_size,
1741 base_fee: instance.chain_config.base_fee,
1742 proposed_fee: header.fee_info().amount().unwrap()
1743 },
1744 err
1745 );
1746 }
1747
1748 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1749 async fn test_validation_height() {
1750 let instance = NodeState::mock_v2();
1752 let tx = Transaction::of_size(10);
1753 let (parent, block_size) = tx.into_mock_header().await;
1754
1755 let proposal = Proposal::new(&parent, block_size);
1756 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1757 .validate_height()
1758 .unwrap_err();
1759
1760 tracing::info!(%err, "task failed successfully");
1762 assert_eq!(
1763 ProposalValidationError::InvalidHeight {
1764 parent_height: parent.height(),
1765 proposal_height: parent.height()
1766 },
1767 err
1768 );
1769
1770 let mut header = parent.clone();
1772 *header.height_mut() += 1;
1773 let proposal = Proposal::new(&header, block_size);
1774
1775 ValidatedTransition::mock(instance, &parent, proposal)
1776 .validate_height()
1777 .unwrap();
1778 }
1779
1780 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1781 async fn test_validation_timestamp_non_dec() {
1782 let tx = Transaction::of_size(10);
1783 let (parent, block_size) = tx.into_mock_header().await;
1784
1785 let proposal = Proposal::new(&parent, block_size);
1787 let proposal_timestamp = proposal.header.timestamp();
1788 let err = proposal.validate_timestamp_non_dec(u64::MAX).unwrap_err();
1789
1790 tracing::info!(%err, "task failed successfully");
1792 assert_eq!(
1793 ProposalValidationError::DecrementingTimestamp {
1794 proposal_timestamp,
1795 parent_timestamp: u64::MAX,
1796 },
1797 err
1798 );
1799
1800 let proposal = Proposal::new(&parent, block_size);
1802 proposal.validate_timestamp_non_dec(0).unwrap();
1803 }
1804
1805 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1806 async fn test_validation_timestamp_drift() {
1807 let instance = NodeState::mock_v2();
1809 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
1810
1811 let header = parent.clone();
1812 let proposal = Proposal::new(&header, block_size);
1814 let proposal_timestamp = header.timestamp();
1815
1816 let mock_time = OffsetDateTime::now_utc().unix_timestamp() as u64;
1817 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1819 .validate_timestamp()
1820 .unwrap_err();
1821
1822 tracing::info!(%err, "task failed successfully");
1823 assert_eq!(
1824 ProposalValidationError::InvalidTimestampDrift {
1825 proposal: proposal_timestamp,
1826 system: mock_time,
1827 diff: mock_time
1828 },
1829 err
1830 );
1831
1832 let time = OffsetDateTime::now_utc();
1833 let timestamp: u64 = time.unix_timestamp() as u64;
1834 let timestamp_millis = TimestampMillis::from_time(&time).u64();
1835
1836 let mut header = parent.clone();
1837 header.set_timestamp(timestamp - 13, timestamp_millis - 13_000);
1838 let proposal = Proposal::new(&header, block_size);
1839
1840 let err = proposal.validate_timestamp_drift(time).unwrap_err();
1841 tracing::info!(%err, "task failed successfully");
1842 assert_eq!(
1843 ProposalValidationError::InvalidTimestampDrift {
1844 proposal: timestamp - 13,
1845 system: timestamp,
1846 diff: 13
1847 },
1848 err
1849 );
1850
1851 let mut header = parent.clone();
1853 header.set_timestamp(timestamp, timestamp_millis);
1854 let proposal = Proposal::new(&header, block_size);
1855 proposal.validate_timestamp_drift(time).unwrap();
1856
1857 header.set_timestamp(timestamp - 11, timestamp_millis - 11_000);
1858 let proposal = Proposal::new(&header, block_size);
1859 proposal.validate_timestamp_drift(time).unwrap();
1860
1861 header.set_timestamp(timestamp - 12, timestamp_millis - 12_000);
1862 let proposal = Proposal::new(&header, block_size);
1863 proposal.validate_timestamp_drift(time).unwrap();
1864 }
1865
1866 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1867 async fn test_validation_fee_root() {
1868 let instance = NodeState::mock_v2();
1870 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1871
1872 let proposal = Proposal::new(&header, block_size);
1874 ValidatedTransition::mock(instance.clone(), &header, proposal)
1875 .validate_fee_merkle_tree()
1876 .unwrap();
1877
1878 let proposal = Proposal::new(&header, block_size);
1880
1881 let mut fee_merkle_tree = instance.genesis_state.fee_merkle_tree;
1882 fee_merkle_tree
1883 .update_with(FeeAccount::default(), |_| Some(100.into()))
1884 .unwrap();
1885
1886 let err = proposal
1887 .validate_block_merkle_tree(fee_merkle_tree.commitment())
1888 .unwrap_err();
1889
1890 tracing::info!(%err, "task failed successfully");
1891 assert_eq!(
1892 ProposalValidationError::InvalidBlockRoot {
1893 expected_root: fee_merkle_tree.commitment(),
1894 proposal_root: header.block_merkle_tree_root(),
1895 },
1896 err
1897 );
1898 }
1899
1900 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1901 async fn test_validation_block_root() {
1902 let instance = NodeState::mock_v2();
1904 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1905
1906 let proposal = Proposal::new(&header, block_size);
1908 ValidatedTransition::mock(instance.clone(), &header, proposal)
1909 .validate_block_merkle_tree()
1910 .unwrap();
1911
1912 let proposal = Proposal::new(&header, block_size);
1914 let mut block_merkle_tree = instance.genesis_state.block_merkle_tree;
1915 block_merkle_tree.push(header.commitment()).unwrap();
1916 block_merkle_tree
1917 .push(header.clone().next().commitment())
1918 .unwrap();
1919
1920 let err = proposal
1921 .validate_block_merkle_tree(block_merkle_tree.commitment())
1922 .unwrap_err();
1923
1924 tracing::info!(%err, "task failed successfully");
1925 assert_eq!(
1926 ProposalValidationError::InvalidBlockRoot {
1927 expected_root: block_merkle_tree.commitment(),
1928 proposal_root: proposal.header.block_merkle_tree_root(),
1929 },
1930 err
1931 );
1932 }
1933
1934 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1935 async fn test_validation_ns_table() {
1936 use NsTableValidationError::InvalidFinalOffset;
1937 let tx = Transaction::of_size(10);
1939 let (header, block_size) = tx.into_mock_header().await;
1940
1941 let proposal = Proposal::new(&header, block_size);
1943 ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1944 .validate_namespace_table()
1945 .unwrap();
1946
1947 let proposal = Proposal::new(&header, 40);
1949 let err = ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1950 .validate_namespace_table()
1951 .unwrap_err();
1952 tracing::info!(%err, "task failed successfully");
1953 assert_eq!(
1956 ProposalValidationError::InvalidNsTable(InvalidFinalOffset),
1957 err
1958 );
1959 }
1960
1961 #[test_log::test]
1962 fn test_charge_fee() {
1963 let src = FeeAccount::generated_from_seed_indexed([0; 32], 0).0;
1964 let dst = FeeAccount::generated_from_seed_indexed([0; 32], 1).0;
1965 let amt = FeeAmount::from(1);
1966
1967 let fee_info = FeeInfo::new(src, amt);
1968
1969 let new_state = || {
1970 let mut state = ValidatedState::default();
1971 state.prefund_account(src, amt);
1972 state
1973 };
1974
1975 tracing::info!("test successful fee");
1976 let mut state = new_state();
1977 state.charge_fee(fee_info, dst).unwrap();
1978 assert_eq!(state.balance(src), Some(0.into()));
1979 assert_eq!(state.balance(dst), Some(amt));
1980
1981 tracing::info!("test insufficient balance");
1982 let err = state.charge_fee(fee_info, dst).unwrap_err();
1983 assert_eq!(state.balance(src), Some(0.into()));
1984 assert_eq!(state.balance(dst), Some(amt));
1985 assert_eq!(
1986 FeeError::InsufficientFunds {
1987 balance: None,
1988 amount: amt
1989 },
1990 err
1991 );
1992
1993 tracing::info!("test src not in memory");
1994 let mut state = new_state();
1995 state.fee_merkle_tree.forget(src).expect_ok().unwrap();
1996 assert_eq!(
1997 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
1998 state.charge_fee(fee_info, dst).unwrap_err()
1999 );
2000
2001 tracing::info!("test dst not in memory");
2002 let mut state = new_state();
2003 state.prefund_account(dst, amt);
2004 state.fee_merkle_tree.forget(dst).expect_ok().unwrap();
2005 assert_eq!(
2006 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
2007 state.charge_fee(fee_info, dst).unwrap_err()
2008 );
2009 }
2010
2011 #[test]
2012 fn test_fee_amount_serde_json_as_decimal() {
2013 let amt = FeeAmount::from(123);
2014 let serialized = serde_json::to_string(&amt).unwrap();
2015
2016 assert_eq!(serialized, "\"123\"");
2018
2019 let deserialized: FeeAmount = serde_json::from_str(&serialized).unwrap();
2021 assert_eq!(deserialized, amt);
2022 }
2023
2024 #[test]
2025 fn test_fee_amount_from_units() {
2026 for (unit, multiplier) in [
2027 ("wei", 1),
2028 ("gwei", 1_000_000_000),
2029 ("eth", 1_000_000_000_000_000_000),
2030 ] {
2031 let amt: FeeAmount = serde_json::from_str(&format!("\"1 {unit}\"")).unwrap();
2032 assert_eq!(amt, multiplier.into());
2033 }
2034 }
2035
2036 #[test]
2037 fn test_fee_amount_serde_json_from_hex() {
2038 let amt: FeeAmount = serde_json::from_str("\"0x123\"").unwrap();
2041 assert_eq!(amt, FeeAmount::from(0x123));
2042 }
2043
2044 #[test]
2045 fn test_fee_amount_serde_json_from_number() {
2046 let amt: FeeAmount = serde_json::from_str("123").unwrap();
2048 assert_eq!(amt, FeeAmount::from(123));
2049 }
2050
2051 #[test]
2052 fn test_fee_amount_serde_bincode_unchanged() {
2053 let n = ethers_core::types::U256::from(123);
2056 let amt = FeeAmount(U256::from(123));
2057 assert_eq!(
2058 bincode::serialize(&n).unwrap(),
2059 bincode::serialize(&amt).unwrap(),
2060 );
2061 }
2062
2063 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2064 async fn test_validate_builder_fee() {
2065 let max_block_size = 10;
2066
2067 let validated_state = ValidatedState::default();
2068 let instance_state = NodeState::mock().with_chain_config(ChainConfig {
2069 base_fee: 1000.into(), max_block_size: max_block_size.into(),
2071 ..validated_state.chain_config.resolve().unwrap()
2072 });
2073
2074 let parent: Leaf2 =
2075 Leaf::genesis::<MockVersions>(&instance_state.genesis_state, &instance_state)
2076 .await
2077 .into();
2078 let header = parent.block_header().clone();
2079 let metadata = parent.block_header().metadata();
2080
2081 debug!("{:?}", header.version());
2082
2083 let key_pair = EthKeyPair::random();
2084 let account = key_pair.fee_account();
2085
2086 let data = header.fee_info()[0].amount().as_u64().unwrap();
2087 let sig = FeeAccount::sign_builder_message(&key_pair, &data.to_be_bytes()).unwrap();
2088
2089 account
2091 .validate_builder_signature(&sig, &data.to_be_bytes())
2092 .then_some(())
2093 .unwrap();
2094
2095 let sig = FeeAccount::sign_fee(&key_pair, data, metadata).unwrap();
2097
2098 let header = match header {
2099 Header::V1(header) => Header::V1(v0_1::Header {
2100 builder_signature: Some(sig),
2101 fee_info: FeeInfo::new(account, data),
2102 ..header
2103 }),
2104 Header::V2(header) => Header::V2(v0_2::Header {
2105 builder_signature: Some(sig),
2106 fee_info: FeeInfo::new(account, data),
2107 ..header
2108 }),
2109 Header::V3(header) => Header::V3(v0_3::Header {
2110 builder_signature: Some(sig),
2111 fee_info: FeeInfo::new(account, data),
2112 ..header
2113 }),
2114 Header::V4(header) => Header::V4(v0_4::Header {
2115 builder_signature: Some(sig),
2116 fee_info: FeeInfo::new(account, data),
2117 ..header
2118 }),
2119 Header::V5(header) => Header::V5(v0_5::Header {
2120 builder_signature: Some(sig),
2121 fee_info: FeeInfo::new(account, data),
2122 ..header
2123 }),
2124 };
2125
2126 validate_builder_fee(&header).unwrap();
2127 }
2128
2129 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2130 async fn test_validate_total_rewards_distributed() {
2131 let instance = NodeState::mock().with_genesis_version(Version { major: 0, minor: 4 });
2132
2133 let (payload, metadata) =
2134 Payload::from_transactions([], &instance.genesis_state, &instance)
2135 .await
2136 .unwrap();
2137
2138 let header = Header::genesis::<SequencerVersions<StaticVersion<0, 4>, StaticVersion<0, 4>>>(
2139 &instance,
2140 payload.clone(),
2141 &metadata,
2142 );
2143
2144 let validated_state = ValidatedState::default();
2145 let actual_total = RewardAmount::from(1000u64);
2146 let block_size = 100u32;
2147
2148 let proposed_header = match header.clone() {
2149 Header::V4(mut h) => {
2150 h.total_reward_distributed = actual_total;
2151 Header::V4(h)
2152 },
2153 _ => unreachable!("Expected V4 header"),
2154 };
2155
2156 let validation_start_time = OffsetDateTime::now_utc();
2157 let validated_transition = ValidatedTransition::new(
2158 validated_state.clone(),
2159 &header,
2160 Proposal::new(&proposed_header, block_size),
2161 Some(actual_total),
2162 StaticVersion::<0, 4>::version(),
2163 validation_start_time,
2164 );
2165
2166 validated_transition
2167 .validate_total_rewards_distributed()
2168 .unwrap();
2169
2170 let wrong_total = RewardAmount::from(2000u64);
2171 let proposed_header = match header.clone() {
2172 Header::V4(mut h) => {
2173 h.total_reward_distributed = wrong_total;
2174 Header::V4(h)
2175 },
2176 _ => unreachable!("Expected V4 header"),
2177 };
2178
2179 ValidatedTransition::new(
2180 validated_state.clone(),
2181 &header,
2182 Proposal::new(&proposed_header, block_size),
2183 Some(actual_total),
2184 StaticVersion::<0, 4>::version(),
2185 validation_start_time,
2186 )
2187 .validate_total_rewards_distributed()
2188 .unwrap_err();
2189 }
2190
2191 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2192 async fn test_regression_slow_validation_timestamp_drift() {
2193 let instance = NodeState::mock_v2();
2194 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
2195
2196 let validation_start_time = OffsetDateTime::now_utc();
2197 let timestamp = validation_start_time.unix_timestamp() as u64;
2198 let timestamp_millis = TimestampMillis::from_time(&validation_start_time).u64();
2199
2200 let mut header = parent.clone();
2201 header.set_timestamp(timestamp, timestamp_millis);
2202
2203 std::thread::sleep(Duration::from_secs(13));
2204
2205 let proposal_without_fix = Proposal::new(&header, block_size);
2207 let err = ValidatedTransition::new(
2208 instance.genesis_state.clone(),
2209 &parent,
2210 proposal_without_fix,
2211 None,
2212 MaxSupportedVersion::version(),
2213 OffsetDateTime::now_utc(),
2214 )
2215 .validate_timestamp()
2216 .unwrap_err();
2217
2218 assert!(matches!(
2219 err,
2220 ProposalValidationError::InvalidTimestampDrift { .. }
2221 ));
2222
2223 let proposal = Proposal::new(&header, block_size);
2225 ValidatedTransition::new(
2226 instance.genesis_state.clone(),
2227 &parent,
2228 proposal,
2229 None,
2230 MaxSupportedVersion::version(),
2231 validation_start_time,
2232 )
2233 .validate_timestamp()
2234 .unwrap();
2235 }
2236
2237 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2239 async fn test_validate_and_apply_header_slow_catchup_succeeds() {
2240 let mut instance = NodeState::mock_v2();
2242
2243 let mut genesis_state = instance.genesis_state.clone();
2244
2245 genesis_state
2247 .fee_merkle_tree
2248 .update(FeeAccount::default(), FeeAmount::from(1u64))
2249 .unwrap();
2250 instance.genesis_state = genesis_state.clone();
2251
2252 let genesis = Leaf::genesis::<MockVersions>(&genesis_state, &instance).await;
2253 let parent_leaf: Leaf2 = genesis.into();
2254 let parent_header = parent_leaf.block_header().clone();
2255
2256 let mut expected_block_tree = genesis_state.block_merkle_tree.clone();
2257 expected_block_tree.push(parent_header.commit()).unwrap();
2258
2259 let proposed_header = match parent_header {
2260 Header::V2(header) => Header::V2(v0_2::Header {
2261 height: header.height + 1,
2262 timestamp: OffsetDateTime::now_utc().unix_timestamp() as u64,
2263 block_merkle_tree_root: expected_block_tree.commitment(),
2264 chain_config: header.chain_config.commit().into(),
2265 ..header
2266 }),
2267 _ => panic!("Expected V2 header"),
2268 };
2269
2270 let slow_catchup =
2271 MockStateCatchup::from_iter([(ViewNumber::new(0), Arc::new(genesis_state.clone()))])
2272 .with_delay(Duration::from_secs(13));
2273 instance.state_catchup = Arc::new(slow_catchup);
2274
2275 genesis_state
2277 .fee_merkle_tree
2278 .forget(FeeAccount::default())
2279 .expect_ok()
2280 .unwrap();
2281
2282 genesis_state
2283 .validate_and_apply_header(
2284 &instance,
2285 &parent_leaf,
2286 &proposed_header,
2287 0, FeeVersion::version(),
2289 0, )
2291 .await
2292 .unwrap();
2293 }
2294}