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