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::Version;
28use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_VERSION};
29
30use super::{
31 fee_info::FeeError, instance_state::NodeState, v0_1::IterableFeeInfo, BlockMerkleCommitment,
32 BlockSize, FeeMerkleCommitment, L1Client,
33};
34use crate::{
35 traits::StateCatchup,
36 v0::{
37 impls::{distribute_block_reward, StakeTableHash},
38 sparse_mt::{Keccak256Hasher, KeccakNode},
39 },
40 v0_3::{
41 ChainConfig, ResolvableChainConfig, RewardAccountV1, RewardAmount,
42 RewardMerkleCommitmentV1, RewardMerkleTreeV1, REWARD_MERKLE_TREE_V1_HEIGHT,
43 },
44 v0_4::{
45 Delta, RewardAccountV2, RewardMerkleCommitmentV2, RewardMerkleTreeV2,
46 REWARD_MERKLE_TREE_V2_HEIGHT,
47 },
48 BlockMerkleTree, FeeAccount, FeeAmount, FeeInfo, FeeMerkleTree, Header, Leaf2,
49 NsTableValidationError, PayloadByteLen, SeqTypes, UpgradeType, BLOCK_MERKLE_TREE_HEIGHT,
50 FEE_MERKLE_TREE_HEIGHT,
51};
52
53#[allow(dead_code)]
56pub enum StateValidationError {
57 ProposalValidation(ProposalValidationError),
58 BuilderValidation(BuilderValidationError),
59 Fee(FeeError),
60}
61
62#[derive(Error, Debug, Eq, PartialEq)]
64pub enum BuilderValidationError {
65 #[error("Builder signature not found")]
66 SignatureNotFound,
67 #[error("Fee amount out of range: {0}")]
68 FeeAmountOutOfRange(FeeAmount),
69 #[error("Invalid Builder Signature")]
70 InvalidBuilderSignature,
71}
72
73#[derive(Error, Debug, Eq, PartialEq)]
75pub enum ProposalValidationError {
76 #[error("Next stake table hash mismatch: expected={expected:?}, proposal={proposal:?}")]
77 NextStakeTableHashMismatch {
78 expected: StakeTableHash,
79 proposal: StakeTableHash,
80 },
81 #[error("Invalid ChainConfig: expected={expected:?}, proposal={proposal:?}")]
82 InvalidChainConfig {
83 expected: Box<ChainConfig>,
84 proposal: Box<ResolvableChainConfig>,
85 },
86 #[error(
87 "Invalid Payload Size: (max_block_size={max_block_size}, proposed_block_size={block_size})"
88 )]
89 MaxBlockSizeExceeded {
90 max_block_size: BlockSize,
91 block_size: BlockSize,
92 },
93 #[error(
94 "Insufficient Fee: block_size={max_block_size}, base_fee={base_fee}, \
95 proposed_fee={proposed_fee}"
96 )]
97 InsufficientFee {
98 max_block_size: BlockSize,
99 base_fee: FeeAmount,
100 proposed_fee: FeeAmount,
101 },
102 #[error("Invalid Height: parent_height={parent_height}, proposal_height={proposal_height}")]
103 InvalidHeight {
104 parent_height: u64,
105 proposal_height: u64,
106 },
107 #[error("Invalid Block Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
108 InvalidBlockRoot {
109 expected_root: BlockMerkleCommitment,
110 proposal_root: BlockMerkleCommitment,
111 },
112 #[error("Invalid Fee Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
113 InvalidFeeRoot {
114 expected_root: FeeMerkleCommitment,
115 proposal_root: FeeMerkleCommitment,
116 },
117 #[error(
118 "Invalid v1 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
119 )]
120 InvalidV1RewardRoot {
121 expected_root: RewardMerkleCommitmentV1,
122 proposal_root: RewardMerkleCommitmentV1,
123 },
124 #[error(
125 "Invalid v2 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
126 )]
127 InvalidV2RewardRoot {
128 expected_root: RewardMerkleCommitmentV2,
129 proposal_root: RewardMerkleCommitmentV2,
130 },
131 #[error("Invalid namespace table: {0}")]
132 InvalidNsTable(NsTableValidationError),
133 #[error("Some fee amount or their sum total out of range")]
134 SomeFeeAmountOutOfRange,
135 #[error("Invalid timestamp: proposal={proposal_timestamp}, parent={parent_timestamp}")]
136 DecrementingTimestamp {
137 proposal_timestamp: u64,
138 parent_timestamp: u64,
139 },
140 #[error("Timestamp drift too high: proposed:={proposal}, system={system}, diff={diff}")]
141 InvalidTimestampDrift {
142 proposal: u64,
143 system: u64,
144 diff: u64,
145 },
146 #[error(
147 "Inconsistent timestamps on header: timestamp:={timestamp}, \
148 timestamp_millis={timestamp_millis}"
149 )]
150 InconsistentTimestamps {
151 timestamp: u64,
152 timestamp_millis: u64,
153 },
154 #[error("l1_finalized has `None` value")]
155 L1FinalizedNotFound,
156 #[error("l1_finalized height is decreasing: parent={parent:?} proposed={proposed:?}")]
157 L1FinalizedDecrementing {
158 parent: Option<(u64, u64)>,
159 proposed: Option<(u64, u64)>,
160 },
161 #[error("Invalid proposal: l1_head height is decreasing")]
162 DecrementingL1Head,
163 #[error("Builder Validation Error: {0}")]
164 BuilderValidationError(BuilderValidationError),
165 #[error("Invalid proposal: l1 finalized does not match the proposal")]
166 InvalidL1Finalized,
167 #[error("reward root not found")]
168 RewardRootNotFound {},
169 #[error("Next stake table not found")]
170 NextStakeTableNotFound,
171 #[error("Next stake table hash missing")]
172 NextStakeTableHashNotFound,
173 #[error("Next stake table hash was not `None`")]
174 NextStakeTableHashNotNone,
175 #[error("No Epoch Height")]
176 NoEpochHeight,
177 #[error("No First Epoch Configured")]
178 NoFirstEpoch,
179 #[error("Total rewards mismatch: proposed header has {proposed} but actual is {actual}")]
180 TotalRewardsMismatch {
181 proposed: RewardAmount,
182 actual: RewardAmount,
183 },
184}
185
186impl StateDelta for Delta {}
187
188#[derive(Hash, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
189pub struct ValidatedState {
191 pub block_merkle_tree: BlockMerkleTree,
193 pub fee_merkle_tree: FeeMerkleTree,
195 pub reward_merkle_tree_v1: RewardMerkleTreeV1,
196 pub reward_merkle_tree_v2: RewardMerkleTreeV2,
197 pub chain_config: ResolvableChainConfig,
199}
200
201impl Default for ValidatedState {
202 fn default() -> Self {
203 let block_merkle_tree = BlockMerkleTree::from_elems(
204 Some(BLOCK_MERKLE_TREE_HEIGHT),
205 Vec::<Commitment<Header>>::new(),
206 )
207 .unwrap();
208
209 let fee_merkle_tree = FeeMerkleTree::from_kv_set(
213 FEE_MERKLE_TREE_HEIGHT,
214 Vec::<(FeeAccount, FeeAmount)>::new(),
215 )
216 .unwrap();
217
218 let reward_merkle_tree_v1 = RewardMerkleTreeV1::from_kv_set(
219 REWARD_MERKLE_TREE_V1_HEIGHT,
220 Vec::<(RewardAccountV1, RewardAmount)>::new(),
221 )
222 .unwrap();
223
224 let reward_merkle_tree_v2 = RewardMerkleTreeV2::from_kv_set(
225 REWARD_MERKLE_TREE_V2_HEIGHT,
226 Vec::<(RewardAccountV2, RewardAmount)>::new(),
227 )
228 .unwrap();
229
230 let chain_config = ResolvableChainConfig::from(ChainConfig::default());
231
232 Self {
233 block_merkle_tree,
234 fee_merkle_tree,
235 reward_merkle_tree_v1,
236 reward_merkle_tree_v2,
237 chain_config,
238 }
239 }
240}
241
242impl ValidatedState {
243 pub fn prefund_account(&mut self, account: FeeAccount, amount: FeeAmount) {
245 self.fee_merkle_tree.update(account, amount).unwrap();
246 }
247
248 pub fn balance(&mut self, account: FeeAccount) -> Option<FeeAmount> {
249 match self.fee_merkle_tree.lookup(account) {
250 LookupResult::Ok(balance, _) => Some(*balance),
251 LookupResult::NotFound(_) => Some(0.into()),
252 LookupResult::NotInMemory => None,
253 }
254 }
255
256 pub fn forgotten_accounts(
261 &self,
262 accounts: impl IntoIterator<Item = FeeAccount>,
263 ) -> Vec<FeeAccount> {
264 accounts
265 .into_iter()
266 .unique()
267 .filter(|account| {
268 self.fee_merkle_tree
269 .lookup(*account)
270 .expect_not_in_memory()
271 .is_ok()
272 })
273 .collect()
274 }
275
276 pub fn forgotten_reward_accounts_v2(
277 &self,
278 accounts: impl IntoIterator<Item = RewardAccountV2>,
279 ) -> Vec<RewardAccountV2> {
280 accounts
281 .into_iter()
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 >= DRB_AND_HEADER_UPGRADE_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 < EPOCH_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 >= DRB_AND_HEADER_UPGRADE_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_example_types::node_types::TEST_VERSIONS;
1412 use hotshot_query_service::{testing::mocks::MOCK_UPGRADE, Resolvable};
1413 use hotshot_types::{data::ViewNumber, traits::signature_key::BuilderSignatureKey};
1414 use sequencer_utils::ser::FromStringOrInteger;
1415 use tracing::debug;
1416 use versions::{version, FEE_VERSION, MAX_SUPPORTED_VERSION};
1417
1418 use super::*;
1419 use crate::{
1420 eth_signature_key::EthKeyPair, mock::MockStateCatchup, v0_1, v0_2, v0_3, v0_4, v0_5,
1421 BlockSize, FeeAccountProof, FeeMerkleProof, Leaf, Payload, 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(&instance, payload.clone(), &metadata, MOCK_UPGRADE.base);
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(0, 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 = Leaf::genesis(
2077 &instance_state.genesis_state,
2078 &instance_state,
2079 MOCK_UPGRADE.base,
2080 )
2081 .await
2082 .into();
2083 let header = parent.block_header().clone();
2084 let metadata = parent.block_header().metadata();
2085
2086 debug!("{:?}", header.version());
2087
2088 let key_pair = EthKeyPair::random();
2089 let account = key_pair.fee_account();
2090
2091 let data = header.fee_info()[0].amount().as_u64().unwrap();
2092 let sig = FeeAccount::sign_builder_message(&key_pair, &data.to_be_bytes()).unwrap();
2093
2094 account
2096 .validate_builder_signature(&sig, &data.to_be_bytes())
2097 .then_some(())
2098 .unwrap();
2099
2100 let sig = FeeAccount::sign_fee(&key_pair, data, metadata).unwrap();
2102
2103 let header = match header {
2104 Header::V1(header) => Header::V1(v0_1::Header {
2105 builder_signature: Some(sig),
2106 fee_info: FeeInfo::new(account, data),
2107 ..header
2108 }),
2109 Header::V2(header) => Header::V2(v0_2::Header {
2110 builder_signature: Some(sig),
2111 fee_info: FeeInfo::new(account, data),
2112 ..header
2113 }),
2114 Header::V3(header) => Header::V3(v0_3::Header {
2115 builder_signature: Some(sig),
2116 fee_info: FeeInfo::new(account, data),
2117 ..header
2118 }),
2119 Header::V4(header) => Header::V4(v0_4::Header {
2120 builder_signature: Some(sig),
2121 fee_info: FeeInfo::new(account, data),
2122 ..header
2123 }),
2124 Header::V5(header) => Header::V5(v0_5::Header {
2125 builder_signature: Some(sig),
2126 fee_info: FeeInfo::new(account, data),
2127 ..header
2128 }),
2129 };
2130
2131 validate_builder_fee(&header).unwrap();
2132 }
2133
2134 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2135 async fn test_validate_total_rewards_distributed() {
2136 let instance = NodeState::mock().with_genesis_version(version(0, 4));
2137
2138 let (payload, metadata) =
2139 Payload::from_transactions([], &instance.genesis_state, &instance)
2140 .await
2141 .unwrap();
2142
2143 let header = Header::genesis(
2144 &instance,
2145 payload.clone(),
2146 &metadata,
2147 TEST_VERSIONS.da_committee.base,
2148 );
2149
2150 let validated_state = ValidatedState::default();
2151 let actual_total = RewardAmount::from(1000u64);
2152 let block_size = 100u32;
2153
2154 let proposed_header = match header.clone() {
2155 Header::V4(mut h) => {
2156 h.total_reward_distributed = actual_total;
2157 Header::V4(h)
2158 },
2159 _ => unreachable!("Expected V4 header"),
2160 };
2161
2162 let validation_start_time = OffsetDateTime::now_utc();
2163 let validated_transition = ValidatedTransition::new(
2164 validated_state.clone(),
2165 &header,
2166 Proposal::new(&proposed_header, block_size),
2167 Some(actual_total),
2168 version(0, 4),
2169 validation_start_time,
2170 );
2171
2172 validated_transition
2173 .validate_total_rewards_distributed()
2174 .unwrap();
2175
2176 let wrong_total = RewardAmount::from(2000u64);
2177 let proposed_header = match header.clone() {
2178 Header::V4(mut h) => {
2179 h.total_reward_distributed = wrong_total;
2180 Header::V4(h)
2181 },
2182 _ => unreachable!("Expected V4 header"),
2183 };
2184
2185 ValidatedTransition::new(
2186 validated_state.clone(),
2187 &header,
2188 Proposal::new(&proposed_header, block_size),
2189 Some(actual_total),
2190 version(0, 4),
2191 validation_start_time,
2192 )
2193 .validate_total_rewards_distributed()
2194 .unwrap_err();
2195 }
2196
2197 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2198 async fn test_regression_slow_validation_timestamp_drift() {
2199 let instance = NodeState::mock_v2();
2200 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
2201
2202 let validation_start_time = OffsetDateTime::now_utc();
2203 let timestamp = validation_start_time.unix_timestamp() as u64;
2204 let timestamp_millis = TimestampMillis::from_time(&validation_start_time).u64();
2205
2206 let mut header = parent.clone();
2207 header.set_timestamp(timestamp, timestamp_millis);
2208
2209 std::thread::sleep(Duration::from_secs(13));
2210
2211 let proposal_without_fix = Proposal::new(&header, block_size);
2213 let err = ValidatedTransition::new(
2214 instance.genesis_state.clone(),
2215 &parent,
2216 proposal_without_fix,
2217 None,
2218 MAX_SUPPORTED_VERSION,
2219 OffsetDateTime::now_utc(),
2220 )
2221 .validate_timestamp()
2222 .unwrap_err();
2223
2224 assert!(matches!(
2225 err,
2226 ProposalValidationError::InvalidTimestampDrift { .. }
2227 ));
2228
2229 let proposal = Proposal::new(&header, block_size);
2231 ValidatedTransition::new(
2232 instance.genesis_state.clone(),
2233 &parent,
2234 proposal,
2235 None,
2236 MAX_SUPPORTED_VERSION,
2237 validation_start_time,
2238 )
2239 .validate_timestamp()
2240 .unwrap();
2241 }
2242
2243 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2245 async fn test_validate_and_apply_header_slow_catchup_succeeds() {
2246 let mut instance = NodeState::mock_v2();
2248
2249 let mut genesis_state = instance.genesis_state.clone();
2250
2251 genesis_state
2253 .fee_merkle_tree
2254 .update(FeeAccount::default(), FeeAmount::from(1u64))
2255 .unwrap();
2256 instance.genesis_state = genesis_state.clone();
2257
2258 let genesis = Leaf::genesis(&genesis_state, &instance, MOCK_UPGRADE.base).await;
2259 let parent_leaf: Leaf2 = genesis.into();
2260 let parent_header = parent_leaf.block_header().clone();
2261
2262 let mut expected_block_tree = genesis_state.block_merkle_tree.clone();
2263 expected_block_tree.push(parent_header.commit()).unwrap();
2264
2265 let proposed_header = match parent_header {
2266 Header::V2(header) => Header::V2(v0_2::Header {
2267 height: header.height + 1,
2268 timestamp: OffsetDateTime::now_utc().unix_timestamp() as u64,
2269 block_merkle_tree_root: expected_block_tree.commitment(),
2270 chain_config: header.chain_config.commit().into(),
2271 ..header
2272 }),
2273 _ => panic!("Expected V2 header"),
2274 };
2275
2276 let slow_catchup =
2277 MockStateCatchup::from_iter([(ViewNumber::new(0), Arc::new(genesis_state.clone()))])
2278 .with_delay(Duration::from_secs(13));
2279 instance.state_catchup = Arc::new(slow_catchup);
2280
2281 genesis_state
2283 .fee_merkle_tree
2284 .forget(FeeAccount::default())
2285 .expect_ok()
2286 .unwrap();
2287
2288 genesis_state
2289 .validate_and_apply_header(
2290 &instance,
2291 &parent_leaf,
2292 &proposed_header,
2293 0, FEE_VERSION,
2295 0, )
2297 .await
2298 .unwrap();
2299 }
2300}