1use std::ops::Add;
2
3use alloy::primitives::{Address, U256};
4use anyhow::{Context, bail};
5use committable::{Commitment, Committable};
6use either::Either;
7use hotshot_query_service_types::merklized_state::MerklizedState;
8use hotshot_types::{
9 data::{BlockError, EpochNumber, ViewNumber},
10 traits::{
11 ValidatedState as HotShotState, block_contents::BlockHeader,
12 signature_key::BuilderSignatureKey, states::StateDelta,
13 },
14 utils::{epoch_from_block_number, is_ge_epoch_root},
15};
16use itertools::Itertools;
17use jf_merkle_tree_compat::{
18 AppendableMerkleTreeScheme, ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme,
19 LookupResult, MerkleCommitment, MerkleTreeError, MerkleTreeScheme,
20 PersistentUniversalMerkleTreeScheme, UniversalMerkleTreeScheme,
21 prelude::{MerkleProof, Sha3Digest, Sha3Node},
22};
23use num_traits::CheckedSub;
24use serde::{Deserialize, Serialize};
25use thiserror::Error;
26use time::OffsetDateTime;
27use vbs::version::Version;
28use versions::{
29 DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION, NEW_PROTOCOL_VERSION,
30};
31
32use super::{
33 BlockMerkleCommitment, BlockSize, FeeMerkleCommitment, L1Client, fee_info::FeeError,
34 instance_state::NodeState, v0_1::IterableFeeInfo,
35};
36use crate::{
37 BLOCK_MERKLE_TREE_HEIGHT, BlockMerkleTree, FEE_MERKLE_TREE_HEIGHT, FeeAccount, FeeAmount,
38 FeeInfo, FeeMerkleTree, Header, Leaf2, NsTableValidationError, PayloadByteLen, SeqTypes,
39 UpgradeType,
40 traits::StateCatchup,
41 v0::{
42 impls::{StakeTableHash, distribute_block_reward},
43 sparse_mt::{Keccak256Hasher, KeccakNode},
44 },
45 v0_3::{
46 ChainConfig, REWARD_MERKLE_TREE_V1_HEIGHT, ResolvableChainConfig, RewardAccountV1,
47 RewardAmount, RewardMerkleCommitmentV1, RewardMerkleTreeV1,
48 },
49 v0_4::{
50 Delta, REWARD_MERKLE_TREE_V2_HEIGHT, RewardAccountV2, RewardMerkleCommitmentV2,
51 RewardMerkleTreeV2,
52 },
53 v0_5::LeaderCounts,
54};
55
56#[allow(dead_code)]
59pub enum StateValidationError {
60 ProposalValidation(ProposalValidationError),
61 BuilderValidation(BuilderValidationError),
62 Fee(FeeError),
63}
64
65#[derive(Error, Debug, Eq, PartialEq)]
67pub enum BuilderValidationError {
68 #[error("Builder signature not found")]
69 SignatureNotFound,
70 #[error("Fee amount out of range: {0}")]
71 FeeAmountOutOfRange(FeeAmount),
72 #[error("Invalid Builder Signature")]
73 InvalidBuilderSignature,
74}
75
76#[derive(Error, Debug, Eq, PartialEq)]
78pub enum ProposalValidationError {
79 #[error("Next stake table hash mismatch: expected={expected:?}, proposal={proposal:?}")]
80 NextStakeTableHashMismatch {
81 expected: StakeTableHash,
82 proposal: StakeTableHash,
83 },
84 #[error("Invalid ChainConfig: expected={expected:?}, proposal={proposal:?}")]
85 InvalidChainConfig {
86 expected: Box<ChainConfig>,
87 proposal: Box<ResolvableChainConfig>,
88 },
89 #[error(
90 "Invalid Payload Size: (max_block_size={max_block_size}, proposed_block_size={block_size})"
91 )]
92 MaxBlockSizeExceeded {
93 max_block_size: BlockSize,
94 block_size: BlockSize,
95 },
96 #[error(
97 "Insufficient Fee: block_size={max_block_size}, base_fee={base_fee}, \
98 proposed_fee={proposed_fee}"
99 )]
100 InsufficientFee {
101 max_block_size: BlockSize,
102 base_fee: FeeAmount,
103 proposed_fee: FeeAmount,
104 },
105 #[error("Invalid Height: parent_height={parent_height}, proposal_height={proposal_height}")]
106 InvalidHeight {
107 parent_height: u64,
108 proposal_height: u64,
109 },
110 #[error("Invalid Block Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
111 InvalidBlockRoot {
112 expected_root: BlockMerkleCommitment,
113 proposal_root: BlockMerkleCommitment,
114 },
115 #[error("Invalid Fee Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
116 InvalidFeeRoot {
117 expected_root: FeeMerkleCommitment,
118 proposal_root: FeeMerkleCommitment,
119 },
120 #[error("Invalid v1 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
121 InvalidV1RewardRoot {
122 expected_root: RewardMerkleCommitmentV1,
123 proposal_root: RewardMerkleCommitmentV1,
124 },
125 #[error("Invalid v2 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
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 #[error("Leader counts missing in V6 header")]
184 LeaderCountsMissing,
185 #[error("Leader index missing for V6 validation")]
186 LeaderIndexMissing,
187 #[error("Leader counts should reset at epoch start but didn't")]
188 LeaderCountsNotReset,
189 #[error("Invalid leader counts: expected {expected:?}, proposed {proposed:?}")]
190 InvalidLeaderCounts {
191 expected: Box<LeaderCounts>,
192 proposed: Box<LeaderCounts>,
193 },
194}
195
196impl StateDelta for Delta {}
197
198#[derive(Hash, Clone, Deserialize, Serialize, PartialEq, Eq)]
199pub struct ValidatedState {
201 pub block_merkle_tree: BlockMerkleTree,
203 pub fee_merkle_tree: FeeMerkleTree,
205 pub reward_merkle_tree_v1: RewardMerkleTreeV1,
206 pub reward_merkle_tree_v2: RewardMerkleTreeV2,
207 pub chain_config: ResolvableChainConfig,
209}
210
211impl std::fmt::Debug for ValidatedState {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("ValidatedState")
214 .field("block_merkle_tree", &self.block_merkle_tree.commitment())
215 .field("fee_merkle_tree", &self.fee_merkle_tree.commitment())
216 .field(
217 "reward_merkle_tree_v1",
218 &self.reward_merkle_tree_v1.commitment(),
219 )
220 .field(
221 "reward_merkle_tree_v2",
222 &self.reward_merkle_tree_v2.commitment(),
223 )
224 .field("chain_config", &self.chain_config)
225 .finish()
226 }
227}
228
229impl Default for ValidatedState {
230 fn default() -> Self {
231 let block_merkle_tree = BlockMerkleTree::from_elems(
232 Some(BLOCK_MERKLE_TREE_HEIGHT),
233 Vec::<Commitment<Header>>::new(),
234 )
235 .unwrap();
236
237 let fee_merkle_tree = FeeMerkleTree::from_kv_set(
241 FEE_MERKLE_TREE_HEIGHT,
242 Vec::<(FeeAccount, FeeAmount)>::new(),
243 )
244 .unwrap();
245
246 let reward_merkle_tree_v1 = RewardMerkleTreeV1::from_kv_set(
247 REWARD_MERKLE_TREE_V1_HEIGHT,
248 Vec::<(RewardAccountV1, RewardAmount)>::new(),
249 )
250 .unwrap();
251
252 let reward_merkle_tree_v2 = RewardMerkleTreeV2::from_kv_set(
253 REWARD_MERKLE_TREE_V2_HEIGHT,
254 Vec::<(RewardAccountV2, RewardAmount)>::new(),
255 )
256 .unwrap();
257
258 let chain_config = ResolvableChainConfig::from(ChainConfig::default());
259
260 Self {
261 block_merkle_tree,
262 fee_merkle_tree,
263 reward_merkle_tree_v1,
264 reward_merkle_tree_v2,
265 chain_config,
266 }
267 }
268}
269
270impl ValidatedState {
271 pub fn prefund_account(&mut self, account: FeeAccount, amount: FeeAmount) {
273 self.fee_merkle_tree.update(account, amount).unwrap();
274 }
275
276 pub fn balance(&mut self, account: FeeAccount) -> Option<FeeAmount> {
277 match self.fee_merkle_tree.lookup(account) {
278 LookupResult::Ok(balance, _) => Some(balance),
279 LookupResult::NotFound(_) => Some(0.into()),
280 LookupResult::NotInMemory => None,
281 }
282 }
283
284 pub fn forgotten_accounts(
289 &self,
290 accounts: impl IntoIterator<Item = FeeAccount>,
291 ) -> Vec<FeeAccount> {
292 accounts
293 .into_iter()
294 .unique()
295 .filter(|account| {
296 self.fee_merkle_tree
297 .lookup(*account)
298 .expect_not_in_memory()
299 .is_ok()
300 })
301 .collect()
302 }
303
304 pub fn forgotten_reward_accounts_v2(
305 &self,
306 accounts: impl IntoIterator<Item = RewardAccountV2>,
307 ) -> Vec<RewardAccountV2> {
308 accounts
309 .into_iter()
310 .filter(|account| {
311 self.reward_merkle_tree_v2
312 .lookup(*account)
313 .expect_not_in_memory()
314 .is_ok()
315 })
316 .collect()
317 }
318
319 pub fn forgotten_reward_accounts_v1(
320 &self,
321 accounts: impl IntoIterator<Item = RewardAccountV1>,
322 ) -> Vec<RewardAccountV1> {
323 accounts
324 .into_iter()
325 .unique()
326 .filter(|account| {
327 self.reward_merkle_tree_v1
328 .lookup(*account)
329 .expect_not_in_memory()
330 .is_ok()
331 })
332 .collect()
333 }
334
335 pub fn need_to_fetch_blocks_mt_frontier(&self) -> bool {
337 let num_leaves = self.block_merkle_tree.num_leaves();
338 if num_leaves == 0 {
339 false
340 } else {
341 self.block_merkle_tree
342 .lookup(num_leaves - 1)
343 .expect_ok()
344 .is_err()
345 }
346 }
347
348 pub fn insert_fee_deposit(
350 &mut self,
351 fee_info: FeeInfo,
352 ) -> anyhow::Result<LookupResult<FeeAmount, (), ()>> {
353 Ok(self
354 .fee_merkle_tree
355 .update_with(fee_info.account, |balance| {
356 Some(balance.cloned().unwrap_or_default().add(fee_info.amount))
357 })?)
358 }
359
360 pub fn apply_proposal(
361 &mut self,
362 delta: &mut Delta,
363 parent_leaf: &Leaf2,
364 l1_deposits: Vec<FeeInfo>,
365 ) {
366 self.block_merkle_tree
368 .push(parent_leaf.block_header().commit())
369 .unwrap();
370
371 for FeeInfo { account, amount } in l1_deposits.iter() {
372 self.fee_merkle_tree
373 .update_with(account, |balance| {
374 Some(balance.cloned().unwrap_or_default().add(*amount))
375 })
376 .expect("update_with succeeds");
377 delta.fees_delta.insert(*account);
378 }
379 }
380
381 pub fn charge_fees(
382 &mut self,
383 delta: &mut Delta,
384 fee_info: Vec<FeeInfo>,
385 recipient: FeeAccount,
386 ) -> Result<(), FeeError> {
387 for fee_info in fee_info {
388 self.charge_fee(fee_info, recipient)?;
389 delta.fees_delta.extend([fee_info.account, recipient]);
390 }
391 Ok(())
392 }
393
394 pub fn charge_fee(&mut self, fee_info: FeeInfo, recipient: FeeAccount) -> Result<(), FeeError> {
396 if fee_info.amount == 0.into() {
397 return Ok(());
398 }
399
400 let fee_state = self.fee_merkle_tree.clone();
401
402 let FeeInfo { account, amount } = fee_info;
404 let mut err = None;
405 let fee_state = fee_state.persistent_update_with(account, |balance| {
406 let balance = balance.copied();
407 let Some(updated) = balance.unwrap_or_default().checked_sub(&amount) else {
408 err = Some(FeeError::InsufficientFunds { balance, amount });
410 return balance;
411 };
412 if updated == FeeAmount::default() {
413 None
416 } else {
417 Some(updated)
419 }
420 })?;
421
422 if let Some(err) = err {
424 return Err(err);
425 }
426
427 let fee_state = fee_state.persistent_update_with(recipient, |balance| {
430 Some(balance.copied().unwrap_or_default() + amount)
431 })?;
432
433 self.fee_merkle_tree = fee_state;
435 Ok(())
436 }
437}
438#[derive(Debug)]
440pub(crate) struct Proposal<'a> {
441 header: &'a Header,
442 block_size: u32,
443}
444
445impl<'a> Proposal<'a> {
446 pub(crate) fn new(header: &'a Header, block_size: u32) -> Self {
447 Self { header, block_size }
448 }
449 fn validate_l1_head(&self, parent_l1_head: u64) -> Result<(), ProposalValidationError> {
452 if self.header.l1_head() < parent_l1_head {
453 return Err(ProposalValidationError::DecrementingL1Head);
454 }
455 Ok(())
456 }
457 fn validate_chain_config(
461 &self,
462 expected_chain_config: &ChainConfig,
463 ) -> Result<(), ProposalValidationError> {
464 let proposed_chain_config = self.header.chain_config();
465 if proposed_chain_config.commit() != expected_chain_config.commit() {
466 return Err(ProposalValidationError::InvalidChainConfig {
467 expected: Box::new(*expected_chain_config),
468 proposal: Box::new(proposed_chain_config),
469 });
470 }
471 Ok(())
472 }
473
474 fn validate_timestamp_non_dec(
476 &self,
477 parent_timestamp: u64,
478 ) -> Result<(), ProposalValidationError> {
479 if self.header.timestamp() < parent_timestamp {
480 return Err(ProposalValidationError::DecrementingTimestamp {
481 proposal_timestamp: self.header.timestamp(),
482 parent_timestamp,
483 });
484 }
485
486 Ok(())
487 }
488
489 fn validate_timestamp_drift(
494 &self,
495 system_time: OffsetDateTime,
496 ) -> Result<(), ProposalValidationError> {
497 let system_timestamp = system_time.unix_timestamp() as u64;
500 let diff = self.header.timestamp().abs_diff(system_timestamp);
501 if diff > 12 {
502 return Err(ProposalValidationError::InvalidTimestampDrift {
503 proposal: self.header.timestamp(),
504 system: system_timestamp,
505 diff,
506 });
507 }
508
509 Ok(())
510 }
511
512 fn validate_timestamp_consistency(&self) -> Result<(), ProposalValidationError> {
514 if self.header.timestamp() != self.header.timestamp_millis() / 1_000 {
515 return Err(ProposalValidationError::InconsistentTimestamps {
516 timestamp: self.header.timestamp(),
517 timestamp_millis: self.header.timestamp_millis(),
518 });
519 }
520
521 Ok(())
522 }
523
524 fn validate_block_merkle_tree(
526 &self,
527 block_merkle_tree_root: BlockMerkleCommitment,
528 ) -> Result<(), ProposalValidationError> {
529 if self.header.block_merkle_tree_root() != block_merkle_tree_root {
530 return Err(ProposalValidationError::InvalidBlockRoot {
531 expected_root: block_merkle_tree_root,
532 proposal_root: self.header.block_merkle_tree_root(),
533 });
534 }
535
536 Ok(())
537 }
538}
539#[derive(Debug)]
543pub(crate) struct ValidatedTransition<'a> {
544 state: ValidatedState,
545 expected_chain_config: ChainConfig,
546 parent: &'a Header,
547 proposal: Proposal<'a>,
548 total_rewards_distributed: Option<RewardAmount>,
549 version: Version,
550 validation_start_time: OffsetDateTime,
551 epoch_height: Option<u64>,
552 leader_index: Option<usize>,
553}
554
555impl<'a> ValidatedTransition<'a> {
556 #[allow(clippy::too_many_arguments)]
557 pub(crate) fn new(
558 state: ValidatedState,
559 parent: &'a Header,
560 proposal: Proposal<'a>,
561 total_rewards_distributed: Option<RewardAmount>,
562 version: Version,
563 validation_start_time: OffsetDateTime,
564 epoch_height: Option<u64>,
565 leader_index: Option<usize>,
566 ) -> Self {
567 let expected_chain_config = state
568 .chain_config
569 .resolve()
570 .expect("Chain Config not found in validated state");
571 Self {
572 state,
573 expected_chain_config,
574 parent,
575 proposal,
576 total_rewards_distributed,
577 version,
578 validation_start_time,
579 epoch_height,
580 leader_index,
581 }
582 }
583
584 pub(crate) fn validate(self) -> Result<Self, ProposalValidationError> {
601 self.validate_timestamp()?;
602 self.validate_builder_fee()?;
603 self.validate_height()?;
604 self.validate_chain_config()?;
605 self.validate_block_size()?;
606 self.validate_fee()?;
607 self.validate_fee_merkle_tree()?;
608 self.validate_block_merkle_tree()?;
609 self.validate_reward_merkle_tree()?;
610 self.validate_l1_finalized()?;
611 self.validate_l1_head()?;
612 self.validate_namespace_table()?;
613 self.validate_total_rewards_distributed()?;
614 self.validate_leader_counts()?;
615
616 Ok(self)
617 }
618
619 fn validate_l1_finalized(&self) -> Result<(), ProposalValidationError> {
621 let proposed_finalized = self.proposal.header.l1_finalized();
622 let parent_finalized = self.parent.l1_finalized();
623
624 if proposed_finalized < parent_finalized {
625 return Err(ProposalValidationError::L1FinalizedDecrementing {
630 parent: parent_finalized.map(|block| (block.number, block.timestamp.to::<u64>())),
631 proposed: proposed_finalized
632 .map(|block| (block.number, block.timestamp.to::<u64>())),
633 });
634 }
635 Ok(())
636 }
637 async fn wait_for_l1(self, l1_client: &L1Client) -> Result<Self, ProposalValidationError> {
642 self.wait_for_l1_head(l1_client).await;
643 self.wait_for_finalized_block(l1_client).await?;
644 Ok(self)
645 }
646
647 async fn wait_for_l1_head(&self, l1_client: &L1Client) {
650 let _ = l1_client
651 .wait_for_block(self.proposal.header.l1_head())
652 .await;
653 }
654 async fn wait_for_finalized_block(
657 &self,
658 l1_client: &L1Client,
659 ) -> Result<(), ProposalValidationError> {
660 let proposed_finalized = self.proposal.header.l1_finalized();
661
662 if let Some(proposed_finalized) = proposed_finalized {
663 let finalized = l1_client
664 .wait_for_finalized_block(proposed_finalized.number())
665 .await;
666
667 if finalized != proposed_finalized {
668 return Err(ProposalValidationError::InvalidL1Finalized);
669 }
670 }
671
672 Ok(())
673 }
674
675 fn validate_l1_head(&self) -> Result<(), ProposalValidationError> {
677 self.proposal.validate_l1_head(self.parent.l1_head())?;
678 Ok(())
679 }
680 fn validate_builder_fee(&self) -> Result<(), ProposalValidationError> {
683 if let Err(err) = validate_builder_fee(self.proposal.header) {
685 return Err(ProposalValidationError::BuilderValidationError(err));
686 }
687 Ok(())
688 }
689 fn validate_chain_config(&self) -> Result<(), ProposalValidationError> {
691 self.proposal
692 .validate_chain_config(&self.expected_chain_config)?;
693 Ok(())
694 }
695 fn validate_block_size(&self) -> Result<(), ProposalValidationError> {
698 let block_size = self.proposal.block_size as u64;
699 if block_size > *self.expected_chain_config.max_block_size {
700 return Err(ProposalValidationError::MaxBlockSizeExceeded {
701 max_block_size: self.expected_chain_config.max_block_size,
702 block_size: block_size.into(),
703 });
704 }
705 Ok(())
706 }
707 fn validate_fee(&self) -> Result<(), ProposalValidationError> {
710 let Some(amount) = self.proposal.header.fee_info().amount() else {
713 return Err(ProposalValidationError::SomeFeeAmountOutOfRange);
714 };
715
716 if amount < self.expected_chain_config.base_fee * U256::from(self.proposal.block_size) {
717 return Err(ProposalValidationError::InsufficientFee {
718 max_block_size: self.expected_chain_config.max_block_size,
719 base_fee: self.expected_chain_config.base_fee,
720 proposed_fee: amount,
721 });
722 }
723 Ok(())
724 }
725 fn validate_height(&self) -> Result<(), ProposalValidationError> {
727 let parent_header = self.parent;
728 if self.proposal.header.height() != parent_header.height() + 1 {
729 return Err(ProposalValidationError::InvalidHeight {
730 parent_height: parent_header.height(),
731 proposal_height: self.proposal.header.height(),
732 });
733 }
734 Ok(())
735 }
736 fn validate_timestamp(&self) -> Result<(), ProposalValidationError> {
741 self.proposal.validate_timestamp_consistency()?;
742
743 self.proposal
744 .validate_timestamp_non_dec(self.parent.timestamp())?;
745
746 self.proposal
747 .validate_timestamp_drift(self.validation_start_time)?;
748
749 Ok(())
750 }
751 fn validate_block_merkle_tree(&self) -> Result<(), ProposalValidationError> {
754 let block_merkle_tree_root = self.state.block_merkle_tree.commitment();
755 self.proposal
756 .validate_block_merkle_tree(block_merkle_tree_root)?;
757
758 Ok(())
759 }
760
761 fn validate_reward_merkle_tree(&self) -> Result<(), ProposalValidationError> {
764 match self.proposal.header.reward_merkle_tree_root() {
765 Either::Left(proposal_root) => {
766 let expected_root = self.state.reward_merkle_tree_v1.commitment();
767 if proposal_root != expected_root {
768 return Err(ProposalValidationError::InvalidV1RewardRoot {
769 expected_root,
770 proposal_root,
771 });
772 }
773 },
774 Either::Right(proposal_root) => {
775 let expected_root = self.state.reward_merkle_tree_v2.commitment();
776 if proposal_root != expected_root {
777 return Err(ProposalValidationError::InvalidV2RewardRoot {
778 expected_root,
779 proposal_root,
780 });
781 }
782 },
783 }
784
785 Ok(())
786 }
787
788 fn validate_fee_merkle_tree(&self) -> Result<(), ProposalValidationError> {
791 let fee_merkle_tree_root = self.state.fee_merkle_tree.commitment();
792 if self.proposal.header.fee_merkle_tree_root() != fee_merkle_tree_root {
793 return Err(ProposalValidationError::InvalidFeeRoot {
794 expected_root: fee_merkle_tree_root,
795 proposal_root: self.proposal.header.fee_merkle_tree_root(),
796 });
797 }
798
799 Ok(())
800 }
801 fn validate_namespace_table(&self) -> Result<(), ProposalValidationError> {
803 self.proposal
804 .header
805 .ns_table()
806 .validate(&PayloadByteLen(self.proposal.block_size as usize))
808 .map_err(ProposalValidationError::from)
809 }
810
811 fn validate_total_rewards_distributed(&self) -> Result<(), ProposalValidationError> {
814 if self.version >= DRB_AND_HEADER_UPGRADE_VERSION {
815 let Some(actual_total) = self.total_rewards_distributed else {
816 return Err(ProposalValidationError::TotalRewardsMismatch {
818 proposed: self
819 .proposal
820 .header
821 .total_reward_distributed()
822 .unwrap_or_default(),
823 actual: RewardAmount::from(0),
824 });
825 };
826
827 let proposed_total =
828 self.proposal
829 .header
830 .total_reward_distributed()
831 .ok_or_else(|| ProposalValidationError::TotalRewardsMismatch {
832 proposed: RewardAmount::from(0),
833 actual: actual_total,
834 })?;
835
836 if proposed_total != actual_total {
837 return Err(ProposalValidationError::TotalRewardsMismatch {
838 proposed: proposed_total,
839 actual: actual_total,
840 });
841 }
842 }
843 Ok(())
844 }
845
846 fn validate_leader_counts(&self) -> Result<(), ProposalValidationError> {
851 if self.version < EPOCH_REWARD_VERSION {
852 return Ok(());
853 }
854
855 let Some(epoch_height) = self.epoch_height else {
856 return Err(ProposalValidationError::NoEpochHeight);
857 };
858
859 let Some(leader_index) = self.leader_index else {
860 return Err(ProposalValidationError::LeaderIndexMissing);
861 };
862
863 let proposed_counts = self
864 .proposal
865 .header
866 .leader_counts()
867 .ok_or(ProposalValidationError::LeaderCountsMissing)?;
868
869 let proposed_height = self.proposal.header.height();
870
871 let expected_counts = Header::calculate_leader_counts(
872 self.parent,
873 proposed_height,
874 leader_index,
875 epoch_height,
876 );
877
878 if proposed_counts != &expected_counts {
879 return Err(ProposalValidationError::InvalidLeaderCounts {
880 expected: Box::new(expected_counts),
881 proposed: Box::new(*proposed_counts),
882 });
883 }
884
885 Ok(())
886 }
887}
888
889#[cfg(any(test, feature = "testing"))]
890impl ValidatedState {
891 pub fn forget(&self) -> Self {
892 Self {
893 fee_merkle_tree: FeeMerkleTree::from_commitment(self.fee_merkle_tree.commitment()),
894 block_merkle_tree: BlockMerkleTree::from_commitment(
895 self.block_merkle_tree.commitment(),
896 ),
897 reward_merkle_tree_v2: RewardMerkleTreeV2::from_commitment(
898 self.reward_merkle_tree_v2.commitment(),
899 ),
900 reward_merkle_tree_v1: RewardMerkleTreeV1::from_commitment(
901 self.reward_merkle_tree_v1.commitment(),
902 ),
903 chain_config: ResolvableChainConfig::from(self.chain_config.commit()),
904 }
905 }
906}
907
908impl From<NsTableValidationError> for ProposalValidationError {
909 fn from(err: NsTableValidationError) -> Self {
910 Self::InvalidNsTable(err)
911 }
912}
913
914impl From<ProposalValidationError> for BlockError {
915 fn from(err: ProposalValidationError) -> Self {
916 tracing::error!("Invalid Block Header: {err:#}");
917 BlockError::InvalidBlockHeader(err.to_string())
918 }
919}
920
921impl From<MerkleTreeError> for FeeError {
922 fn from(item: MerkleTreeError) -> Self {
923 Self::MerkleTreeError(item)
924 }
925}
926
927fn validate_builder_fee(proposed_header: &Header) -> Result<(), BuilderValidationError> {
930 for (fee_info, signature) in proposed_header
932 .fee_info()
933 .iter()
934 .zip(proposed_header.builder_signature())
935 {
936 fee_info
938 .amount()
939 .as_u64()
940 .ok_or(BuilderValidationError::FeeAmountOutOfRange(fee_info.amount))?;
941
942 if !fee_info.account().validate_fee_signature(
944 &signature,
945 fee_info.amount().as_u64().unwrap(),
946 proposed_header.metadata(),
947 ) && !fee_info
948 .account()
949 .validate_fee_signature_with_vid_commitment(
950 &signature,
951 fee_info.amount().as_u64().unwrap(),
952 proposed_header.metadata(),
953 &proposed_header.payload_commitment(),
954 )
955 {
956 return Err(BuilderValidationError::InvalidBuilderSignature);
957 }
958 }
959
960 Ok(())
961}
962
963impl ValidatedState {
964 pub async fn apply_header(
970 &self,
971 instance: &NodeState,
972 peers: &impl StateCatchup,
973 parent_leaf: &Leaf2,
974 proposed_header: &Header,
975 version: Version,
976 view_number: ViewNumber,
977 ) -> anyhow::Result<(Self, Delta, Option<RewardAmount>)> {
978 let mut validated_state = self.clone();
981 validated_state.apply_upgrade(instance, version);
982
983 let chain_config = validated_state
986 .get_chain_config(instance, peers, &proposed_header.chain_config())
987 .await?;
988
989 if Some(chain_config) != validated_state.chain_config.resolve() {
990 validated_state.chain_config = chain_config.into();
991 }
992
993 let l1_deposits = get_l1_deposits(
994 instance,
995 proposed_header,
996 parent_leaf,
997 chain_config.fee_contract,
998 )
999 .await;
1000
1001 let missing_accounts = self.forgotten_accounts(
1005 [chain_config.fee_recipient]
1006 .into_iter()
1007 .chain(proposed_header.fee_info().accounts())
1008 .chain(l1_deposits.accounts()),
1009 );
1010
1011 let parent_height = parent_leaf.height();
1012 let parent_view = parent_leaf.view_number();
1013
1014 if self.need_to_fetch_blocks_mt_frontier() {
1016 tracing::info!(
1017 parent_height,
1018 ?parent_view,
1019 "fetching block frontier from peers"
1020 );
1021 peers
1022 .remember_blocks_merkle_tree(
1023 instance,
1024 parent_height,
1025 parent_view,
1026 &mut validated_state.block_merkle_tree,
1027 )
1028 .await?;
1029 }
1030
1031 if !missing_accounts.is_empty() {
1033 tracing::info!(
1034 parent_height,
1035 ?parent_view,
1036 ?missing_accounts,
1037 "fetching missing accounts from peers"
1038 );
1039
1040 let missing_account_proofs = peers
1041 .fetch_accounts(
1042 instance,
1043 parent_height,
1044 parent_view,
1045 validated_state.fee_merkle_tree.commitment(),
1046 missing_accounts,
1047 )
1048 .await?;
1049
1050 for proof in missing_account_proofs.iter() {
1052 proof
1053 .remember(&mut validated_state.fee_merkle_tree)
1054 .expect("proof previously verified");
1055 }
1056 }
1057
1058 let mut delta = Delta::default();
1059 validated_state.apply_proposal(&mut delta, parent_leaf, l1_deposits);
1060
1061 if version < NEW_PROTOCOL_VERSION {
1063 validated_state.charge_fees(
1064 &mut delta,
1065 proposed_header.fee_info(),
1066 chain_config.fee_recipient,
1067 )?;
1068 }
1069
1070 let total_rewards_distributed = if version < EPOCH_VERSION {
1072 None
1073 } else if version >= EPOCH_REWARD_VERSION {
1074 let parent_header = parent_leaf.block_header();
1075 let epoch_height = instance
1076 .epoch_height
1077 .context("epoch height not in instance state for V6")?;
1078 let leader_index = Header::get_leader_index(
1079 version,
1080 proposed_header.height(),
1081 view_number.u64(),
1082 instance,
1083 )
1084 .await?
1085 .context("leader index not found for V6")?;
1086 let leader_counts = Header::calculate_leader_counts(
1087 parent_header,
1088 proposed_header.height(),
1089 leader_index,
1090 epoch_height,
1091 );
1092 let (epoch_rewards_applied, changed_accounts) = Header::handle_epoch_rewards(
1093 proposed_header.height(),
1094 &leader_counts,
1095 instance,
1096 &mut validated_state,
1097 proposed_header.reward_merkle_tree_root().right(),
1098 )
1099 .await?;
1100
1101 delta.rewards_delta.extend(changed_accounts);
1102
1103 let parent_total = parent_leaf
1105 .block_header()
1106 .total_reward_distributed()
1107 .unwrap_or_default();
1108 Some(RewardAmount(parent_total.0 + epoch_rewards_applied.0))
1109 } else if let Some(reward_distributor) = distribute_block_reward(
1110 instance,
1111 &mut validated_state,
1112 parent_leaf,
1113 view_number,
1114 version,
1115 )
1116 .await?
1117 {
1118 reward_distributor
1119 .update_rewards_delta(&mut delta)
1120 .context("failed to update rewards delta")?;
1121
1122 Some(reward_distributor.total_distributed())
1123 } else {
1124 Some(Default::default())
1126 };
1127
1128 Ok((validated_state, delta, total_rewards_distributed))
1129 }
1130
1131 pub(crate) fn apply_upgrade(&mut self, instance: &NodeState, version: Version) {
1133 if version <= instance.current_version {
1135 return;
1136 }
1137
1138 let Some(upgrade) = instance.upgrades.get(&version) else {
1139 return;
1140 };
1141
1142 let cf = match upgrade.upgrade_type {
1143 UpgradeType::Fee { chain_config } => chain_config,
1144 UpgradeType::Epoch { chain_config } => chain_config,
1145 UpgradeType::DrbAndHeader { chain_config } => chain_config,
1146 UpgradeType::Da { chain_config } => chain_config,
1147 UpgradeType::EpochReward { chain_config } => chain_config,
1148 };
1149
1150 self.chain_config = cf.into();
1151 }
1152
1153 pub(crate) async fn get_chain_config(
1161 &self,
1162 instance: &NodeState,
1163 peers: &impl StateCatchup,
1164 header_cf: &ResolvableChainConfig,
1165 ) -> anyhow::Result<ChainConfig> {
1166 let state_cf = self.chain_config;
1167
1168 if state_cf.commit() == instance.chain_config.commit() {
1169 return Ok(instance.chain_config);
1170 }
1171
1172 let cf = match (state_cf.resolve(), header_cf.resolve()) {
1173 (Some(cf), _) => cf,
1174 (_, Some(cf)) if cf.commit() == state_cf.commit() => cf,
1175 (_, Some(_)) | (None, None) => peers.fetch_chain_config(state_cf.commit()).await?,
1176 };
1177
1178 Ok(cf)
1179 }
1180}
1181
1182pub async fn get_l1_deposits(
1183 instance: &NodeState,
1184 header: &Header,
1185 parent_leaf: &Leaf2,
1186 fee_contract_address: Option<Address>,
1187) -> Vec<FeeInfo> {
1188 if let (Some(addr), Some(block_info)) = (fee_contract_address, header.l1_finalized()) {
1189 instance
1190 .l1_client
1191 .get_finalized_deposits(
1192 addr,
1193 parent_leaf
1194 .block_header()
1195 .l1_finalized()
1196 .map(|block_info| block_info.number),
1197 block_info.number,
1198 )
1199 .await
1200 } else {
1201 vec![]
1202 }
1203}
1204
1205async fn validate_next_stake_table_hash(
1206 instance: &NodeState,
1207 proposed_header: &Header,
1208) -> Result<(), ProposalValidationError> {
1209 let Some(epoch_height) = instance.epoch_height else {
1210 return Err(ProposalValidationError::NoEpochHeight);
1211 };
1212 if !is_ge_epoch_root(proposed_header.height(), epoch_height) {
1213 return Ok(());
1214 }
1215 let epoch = EpochNumber::new(epoch_from_block_number(
1216 proposed_header.height(),
1217 epoch_height,
1218 ));
1219 let coordinator = instance.coordinator.clone();
1220 let Some(first_epoch) = coordinator.membership().first_epoch() else {
1221 return Err(ProposalValidationError::NoFirstEpoch);
1222 };
1223
1224 let Some(proposed_next_stake_table_hash) = proposed_header.next_stake_table_hash() else {
1226 if epoch <= first_epoch {
1227 return Ok(());
1228 } else {
1229 return Err(ProposalValidationError::NextStakeTableHashNotNone);
1230 }
1231 };
1232
1233 let epoch_membership = instance
1234 .coordinator
1235 .stake_table_for_epoch(Some(epoch + 1))
1236 .map_err(|_| ProposalValidationError::NextStakeTableNotFound)?;
1237 let next_stake_table_hash = epoch_membership
1238 .stake_table_hash()
1239 .ok_or(ProposalValidationError::NextStakeTableHashNotFound)?;
1240 if next_stake_table_hash != proposed_next_stake_table_hash {
1241 return Err(ProposalValidationError::NextStakeTableHashMismatch {
1242 expected: next_stake_table_hash,
1243 proposal: proposed_next_stake_table_hash,
1244 });
1245 }
1246 Ok(())
1247}
1248
1249impl HotShotState<SeqTypes> for ValidatedState {
1250 type Error = BlockError;
1251 type Instance = NodeState;
1252
1253 type Delta = Delta;
1254 fn on_commit(&self) {}
1255 #[tracing::instrument(
1258 skip_all,
1259 fields(
1260 node_id = instance.node_id,
1261 view = ?parent_leaf.view_number(),
1262 height = parent_leaf.height(),
1263 ),
1264 )]
1265 async fn validate_and_apply_header(
1266 &self,
1267 instance: &Self::Instance,
1268 parent_leaf: &Leaf2,
1269 proposed_header: &Header,
1270 payload_byte_len: u32,
1271 version: Version,
1272 view_number: u64,
1273 ) -> Result<(Self, Self::Delta), Self::Error> {
1274 let validation_start_time = OffsetDateTime::now_utc();
1278
1279 let (validated_state, delta, total_rewards_distributed) = self
1280 .apply_header(
1282 instance,
1283 &instance.state_catchup,
1284 parent_leaf,
1285 proposed_header,
1286 version,
1287 ViewNumber::new(view_number),
1288 )
1289 .await
1290 .map_err(|e| BlockError::FailedHeaderApply(e.to_string()))?;
1291
1292 if version >= DRB_AND_HEADER_UPGRADE_VERSION {
1293 validate_next_stake_table_hash(instance, proposed_header).await?;
1294 }
1295
1296 let leader_index =
1298 Header::get_leader_index(version, proposed_header.height(), view_number, instance)
1299 .await
1300 .map_err(|e| BlockError::InvalidBlockHeader(e.to_string()))?;
1301
1302 let validated_state = ValidatedTransition::new(
1304 validated_state,
1305 parent_leaf.block_header(),
1306 Proposal::new(proposed_header, payload_byte_len),
1307 total_rewards_distributed,
1308 version,
1309 validation_start_time,
1310 instance.epoch_height,
1311 leader_index,
1312 )
1313 .validate()?
1314 .wait_for_l1(&instance.l1_client)
1315 .await?
1316 .state;
1317
1318 if parent_leaf.view_number().u64().is_multiple_of(10) {
1321 tracing::info!("validated and applied new header");
1322 }
1323 Ok((validated_state, delta))
1324 }
1325 fn from_header(block_header: &Header) -> Self {
1329 let fee_merkle_tree = if block_header.fee_merkle_tree_root().size() == 0 {
1330 FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT)
1333 } else {
1334 FeeMerkleTree::from_commitment(block_header.fee_merkle_tree_root())
1335 };
1336 let block_merkle_tree = if block_header.block_merkle_tree_root().size() == 0 {
1337 BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT)
1340 } else {
1341 BlockMerkleTree::from_commitment(block_header.block_merkle_tree_root())
1342 };
1343
1344 let (reward_merkle_tree_v1, reward_merkle_tree_v2) = match block_header
1345 .reward_merkle_tree_root()
1346 {
1347 Either::Left(reward_tree_v1) => {
1348 let reward_merkle_tree_v2 = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
1349 let reward_merkle_tree_v1 = if reward_tree_v1.size() == 0 {
1350 RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT)
1351 } else {
1352 RewardMerkleTreeV1::from_commitment(reward_tree_v1)
1353 };
1354 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1355 },
1356 Either::Right(reward_tree_v2) => {
1357 let reward_merkle_tree_v1 = RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT);
1358 let reward_merkle_tree_v2 = if reward_tree_v2.size() == 0 {
1359 RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT)
1360 } else {
1361 RewardMerkleTreeV2::from_commitment(reward_tree_v2)
1362 };
1363 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1364 },
1365 };
1366
1367 Self {
1368 fee_merkle_tree,
1369 block_merkle_tree,
1370 reward_merkle_tree_v2,
1371 reward_merkle_tree_v1,
1372 chain_config: block_header.chain_config(),
1373 }
1374 }
1375 fn genesis(instance: &Self::Instance) -> (Self, Self::Delta) {
1377 (instance.genesis_state.clone(), Delta::default())
1378 }
1379}
1380
1381#[cfg(any(test, feature = "testing"))]
1383impl std::fmt::Display for ValidatedState {
1384 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1385 write!(f, "{self:#?}")
1386 }
1387}
1388
1389#[cfg(any(test, feature = "testing"))]
1390impl hotshot_types::traits::states::TestableState<SeqTypes> for ValidatedState {
1391 fn create_random_transaction(
1392 _state: Option<&Self>,
1393 rng: &mut dyn rand::RngCore,
1394 _padding: u64,
1395 ) -> crate::Transaction {
1396 crate::Transaction::random(rng)
1397 }
1398}
1399
1400impl MerklizedState<SeqTypes, { Self::ARITY }> for BlockMerkleTree {
1401 type Key = Self::Index;
1402 type Entry = Commitment<Header>;
1403 type T = Sha3Node;
1404 type Commit = Self::Commitment;
1405 type Digest = Sha3Digest;
1406
1407 fn state_type() -> &'static str {
1408 "block_merkle_tree"
1409 }
1410
1411 fn header_state_commitment_field() -> &'static str {
1412 "block_merkle_tree_root"
1413 }
1414
1415 fn tree_height() -> usize {
1416 BLOCK_MERKLE_TREE_HEIGHT
1417 }
1418
1419 fn insert_path(
1420 &mut self,
1421 key: Self::Key,
1422 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1423 ) -> anyhow::Result<()> {
1424 let Some(elem) = proof.elem() else {
1425 bail!("BlockMerkleTree does not support non-membership proofs");
1426 };
1427 self.remember(key, elem, proof)?;
1428 Ok(())
1429 }
1430}
1431
1432impl MerklizedState<SeqTypes, { Self::ARITY }> for FeeMerkleTree {
1433 type Key = Self::Index;
1434 type Entry = Self::Element;
1435 type T = Sha3Node;
1436 type Commit = Self::Commitment;
1437 type Digest = Sha3Digest;
1438
1439 fn state_type() -> &'static str {
1440 "fee_merkle_tree"
1441 }
1442
1443 fn header_state_commitment_field() -> &'static str {
1444 "fee_merkle_tree_root"
1445 }
1446
1447 fn tree_height() -> usize {
1448 FEE_MERKLE_TREE_HEIGHT
1449 }
1450
1451 fn insert_path(
1452 &mut self,
1453 key: Self::Key,
1454 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1455 ) -> anyhow::Result<()> {
1456 match proof.elem() {
1457 Some(elem) => self.remember(key, elem, proof)?,
1458 None => self.non_membership_remember(key, proof)?,
1459 }
1460 Ok(())
1461 }
1462}
1463
1464impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV2 {
1465 type Key = Self::Index;
1466 type Entry = Self::Element;
1467 type T = KeccakNode;
1468 type Commit = Self::Commitment;
1469 type Digest = Keccak256Hasher;
1470
1471 fn state_type() -> &'static str {
1472 "reward_merkle_tree_v2"
1473 }
1474
1475 fn header_state_commitment_field() -> &'static str {
1476 "reward_merkle_tree_root"
1477 }
1478
1479 fn tree_height() -> usize {
1480 REWARD_MERKLE_TREE_V2_HEIGHT
1481 }
1482
1483 fn insert_path(
1484 &mut self,
1485 key: Self::Key,
1486 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1487 ) -> anyhow::Result<()> {
1488 match proof.elem() {
1489 Some(elem) => self.remember(key, elem, proof)?,
1490 None => self.non_membership_remember(key, proof)?,
1491 }
1492 Ok(())
1493 }
1494}
1495
1496impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV1 {
1497 type Key = Self::Index;
1498 type Entry = Self::Element;
1499 type T = Sha3Node;
1500 type Commit = Self::Commitment;
1501 type Digest = Sha3Digest;
1502
1503 fn state_type() -> &'static str {
1504 "reward_merkle_tree"
1505 }
1506
1507 fn header_state_commitment_field() -> &'static str {
1508 "reward_merkle_tree_root"
1509 }
1510
1511 fn tree_height() -> usize {
1512 REWARD_MERKLE_TREE_V1_HEIGHT
1513 }
1514
1515 fn insert_path(
1516 &mut self,
1517 key: Self::Key,
1518 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1519 ) -> anyhow::Result<()> {
1520 match proof.elem() {
1521 Some(elem) => self.remember(key, elem, proof)?,
1522 None => self.non_membership_remember(key, proof)?,
1523 }
1524 Ok(())
1525 }
1526}
1527
1528#[cfg(test)]
1529mod test {
1530 use std::{sync::Arc, time::Duration};
1531
1532 use espresso_utils::ser::FromStringOrInteger;
1533 use hotshot::traits::BlockPayload;
1534 use hotshot_example_types::node_types::TEST_VERSIONS;
1535 use hotshot_query_service::{Resolvable, testing::mocks::MOCK_UPGRADE};
1536 use hotshot_types::{data::ViewNumber, traits::signature_key::BuilderSignatureKey};
1537 use tracing::debug;
1538 use versions::{FEE_VERSION, MAX_SUPPORTED_VERSION, version};
1539
1540 use super::*;
1541 use crate::{
1542 BlockSize, FeeAccountProof, FeeMerkleProof, Leaf, Payload, TimestampMillis, Transaction,
1543 eth_signature_key::EthKeyPair, mock::MockStateCatchup, v0_1, v0_2, v0_3, v0_4, v0_5, v0_6,
1544 };
1545
1546 impl Transaction {
1547 async fn into_mock_header(self) -> (Header, u32) {
1548 let instance = NodeState::mock_v2();
1549 let (payload, metadata) =
1550 Payload::from_transactions([self], &instance.genesis_state, &instance)
1551 .await
1552 .unwrap();
1553
1554 let header = Header::genesis(&instance, payload.clone(), &metadata, MOCK_UPGRADE.base);
1555
1556 let header = header.sign();
1557
1558 (header, payload.byte_len().0 as u32)
1559 }
1560 }
1561 impl Header {
1562 fn next(self) -> Self {
1564 let time = OffsetDateTime::now_utc();
1565 let timestamp = time.unix_timestamp() as u64;
1566 let timestamp_millis = TimestampMillis::from_time(&time);
1567
1568 match self {
1569 Header::V1(_) => panic!("You called `Header.next()` on unimplemented version (v1)"),
1570 Header::V2(parent) => Header::V2(v0_2::Header {
1571 height: parent.height + 1,
1572 timestamp,
1573 ..parent.clone()
1574 }),
1575 Header::V3(parent) => Header::V3(v0_3::Header {
1576 height: parent.height + 1,
1577 timestamp,
1578 ..parent.clone()
1579 }),
1580 Header::V4(parent) => Header::V4(v0_4::Header {
1581 height: parent.height + 1,
1582 timestamp,
1583 timestamp_millis,
1584 ..parent.clone()
1585 }),
1586 Header::V5(parent) => Header::V5(v0_5::Header {
1587 height: parent.height + 1,
1588 timestamp,
1589 timestamp_millis,
1590 ..parent.clone()
1591 }),
1592 Header::V6(parent) => Header::V6(v0_6::Header {
1593 height: parent.height + 1,
1594 timestamp,
1595 timestamp_millis,
1596 ..parent.clone()
1597 }),
1598 }
1599 }
1600 fn sign(&self) -> Self {
1602 let key_pair = EthKeyPair::random();
1603 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1604
1605 let sig = FeeAccount::sign_fee(
1606 &key_pair,
1607 fee_info.amount().as_u64().unwrap(),
1608 self.metadata(),
1609 )
1610 .unwrap();
1611
1612 match self {
1613 Header::V1(_) => panic!("You called `Header.sign()` on unimplemented version (v1)"),
1614 Header::V2(header) => Header::V2(v0_2::Header {
1615 fee_info,
1616 builder_signature: Some(sig),
1617 ..header.clone()
1618 }),
1619 Header::V3(header) => Header::V3(v0_3::Header {
1620 fee_info,
1621 builder_signature: Some(sig),
1622 ..header.clone()
1623 }),
1624 Header::V4(header) => Header::V4(v0_4::Header {
1625 fee_info,
1626 builder_signature: Some(sig),
1627 ..header.clone()
1628 }),
1629 Header::V5(header) => Header::V5(v0_5::Header {
1630 fee_info,
1631 builder_signature: Some(sig),
1632 ..header.clone()
1633 }),
1634 Header::V6(header) => Header::V6(v0_6::Header {
1635 fee_info,
1636 builder_signature: Some(sig),
1637 ..header.clone()
1638 }),
1639 }
1640 }
1641
1642 fn invalid_builder_signature(&self) -> Self {
1644 let key_pair = EthKeyPair::random();
1645 let key_pair2 = EthKeyPair::random();
1646 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1647
1648 let sig = FeeAccount::sign_fee(
1649 &key_pair2,
1650 fee_info.amount().as_u64().unwrap(),
1651 self.metadata(),
1652 )
1653 .unwrap();
1654
1655 match self {
1656 Header::V1(_) => panic!(
1657 "You called `Header.invalid_builder_signature()` on unimplemented version (v1)"
1658 ),
1659 Header::V2(parent) => Header::V2(v0_2::Header {
1660 fee_info,
1661 builder_signature: Some(sig),
1662 ..parent.clone()
1663 }),
1664 Header::V3(parent) => Header::V3(v0_3::Header {
1665 fee_info,
1666 builder_signature: Some(sig),
1667 ..parent.clone()
1668 }),
1669 Header::V4(parent) => Header::V4(v0_4::Header {
1670 fee_info,
1671 builder_signature: Some(sig),
1672 ..parent.clone()
1673 }),
1674 Header::V5(parent) => Header::V5(v0_5::Header {
1675 fee_info,
1676 builder_signature: Some(sig),
1677 ..parent.clone()
1678 }),
1679 Header::V6(parent) => Header::V6(v0_6::Header {
1680 fee_info,
1681 builder_signature: Some(sig),
1682 ..parent.clone()
1683 }),
1684 }
1685 }
1686 }
1687
1688 impl<'a> ValidatedTransition<'a> {
1689 fn mock(instance: NodeState, parent: &'a Header, proposal: Proposal<'a>) -> Self {
1690 let expected_chain_config = instance.chain_config;
1691 let validation_start_time = OffsetDateTime::now_utc();
1692
1693 Self {
1694 state: instance.genesis_state,
1695 expected_chain_config,
1696 parent,
1697 proposal,
1698 total_rewards_distributed: None,
1699 epoch_height: instance.epoch_height,
1700 version: version(0, 1),
1701 validation_start_time,
1702 leader_index: None,
1703 }
1704 }
1705 }
1706
1707 #[test_log::test]
1708 fn test_fee_proofs() {
1709 let mut tree = ValidatedState::default().fee_merkle_tree;
1710 let account1 = Address::random();
1711 let account2 = Address::default();
1712 tracing::info!(%account1, %account2);
1713
1714 let balance1 = U256::from(100);
1715 tree.update(FeeAccount(account1), FeeAmount(balance1))
1716 .unwrap();
1717
1718 let (proof1, balance) = FeeAccountProof::prove(&tree, account1).unwrap();
1720 tracing::info!(?proof1, %balance);
1721 assert_eq!(balance, balance1);
1722 assert!(matches!(proof1.proof, FeeMerkleProof::Presence(_)));
1723 assert_eq!(proof1.verify(&tree.commitment()).unwrap(), balance1);
1724
1725 let (proof2, balance) = FeeAccountProof::prove(&tree, account2).unwrap();
1727 tracing::info!(?proof2, %balance);
1728 assert_eq!(balance, U256::ZERO);
1729 assert!(matches!(proof2.proof, FeeMerkleProof::Absence(_)));
1730 assert_eq!(proof2.verify(&tree.commitment()).unwrap(), U256::ZERO);
1731
1732 let mut tree = FeeMerkleTree::from_commitment(tree.commitment());
1734 assert!(FeeAccountProof::prove(&tree, account1).is_none());
1735 assert!(FeeAccountProof::prove(&tree, account2).is_none());
1736 proof1.remember(&mut tree).unwrap();
1738 proof2.remember(&mut tree).unwrap();
1739 FeeAccountProof::prove(&tree, account1).unwrap();
1740 FeeAccountProof::prove(&tree, account2).unwrap();
1741 }
1742
1743 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1744 async fn test_validation_l1_head() {
1745 let tx = Transaction::of_size(10);
1747 let (header, block_size) = tx.into_mock_header().await;
1748
1749 let proposal = Proposal::new(&header, block_size);
1751 ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1754 .validate_l1_head()
1755 .unwrap();
1756
1757 let proposal = Proposal::new(&header, block_size);
1759 let err = proposal.validate_l1_head(u64::MAX).unwrap_err();
1760 assert_eq!(ProposalValidationError::DecrementingL1Head, err);
1761 }
1762
1763 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1764 async fn test_validation_builder_fee() {
1765 let instance = NodeState::mock();
1767 let tx = Transaction::of_size(20);
1768 let (header, block_size) = tx.into_mock_header().await;
1769
1770 let proposal = Proposal::new(&header, block_size);
1772 ValidatedTransition::mock(instance.clone(), &header, proposal)
1773 .validate_builder_fee()
1774 .unwrap();
1775
1776 let header = header.invalid_builder_signature();
1778 let proposal = Proposal::new(&header, block_size);
1779 let err = ValidatedTransition::mock(instance, &header, proposal)
1780 .validate_builder_fee()
1781 .unwrap_err();
1782
1783 tracing::info!(%err, "task failed successfully");
1784 assert_eq!(
1785 ProposalValidationError::BuilderValidationError(
1786 BuilderValidationError::InvalidBuilderSignature
1787 ),
1788 err
1789 );
1790 }
1791
1792 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1793 async fn test_validation_chain_config() {
1794 let instance = NodeState::mock();
1796 let tx = Transaction::of_size(20);
1797 let (header, block_size) = tx.into_mock_header().await;
1798
1799 let proposal = Proposal::new(&header, block_size);
1801 ValidatedTransition::mock(instance.clone(), &header, proposal)
1802 .validate_chain_config()
1803 .unwrap();
1804
1805 let proposal = Proposal::new(&header, block_size);
1807 let expected_chain_config = ChainConfig {
1808 max_block_size: BlockSize(3333),
1809 ..instance.chain_config
1810 };
1811 let err = proposal
1812 .validate_chain_config(&expected_chain_config)
1813 .unwrap_err();
1814
1815 tracing::info!(%err, "task failed successfully");
1816
1817 assert_eq!(
1818 ProposalValidationError::InvalidChainConfig {
1819 expected: Box::new(expected_chain_config),
1820 proposal: Box::new(header.chain_config())
1821 },
1822 err
1823 );
1824 }
1825
1826 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1827 async fn test_validation_max_block_size() {
1828 const MAX_BLOCK_SIZE: usize = 10;
1829
1830 let state = ValidatedState::default();
1832 let expected_chain_config = ChainConfig {
1833 max_block_size: BlockSize::from_integer(MAX_BLOCK_SIZE as u64).unwrap(),
1834 ..state.chain_config.resolve().unwrap()
1835 };
1836 let instance = NodeState::mock().with_chain_config(expected_chain_config);
1837 let tx = Transaction::of_size(20);
1838 let (header, block_size) = tx.into_mock_header().await;
1839
1840 let proposal = Proposal::new(&header, block_size);
1842 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1843 .validate_block_size()
1844 .unwrap_err();
1845
1846 tracing::info!(%err, "task failed successfully");
1847 assert_eq!(
1848 ProposalValidationError::MaxBlockSizeExceeded {
1849 max_block_size: instance.chain_config.max_block_size,
1850 block_size: BlockSize::from_integer(block_size as u64).unwrap()
1851 },
1852 err
1853 );
1854
1855 let proposal = Proposal::new(&header, 1);
1857 ValidatedTransition::mock(instance, &header, proposal)
1858 .validate_block_size()
1859 .unwrap()
1860 }
1861
1862 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1863 async fn test_validation_base_fee() {
1864 let tx = Transaction::of_size(20);
1866 let (header, block_size) = tx.into_mock_header().await;
1867 let state = ValidatedState::default();
1868 let instance = NodeState::mock_v2().with_chain_config(ChainConfig {
1869 base_fee: 1000.into(), ..state.chain_config.resolve().unwrap()
1871 });
1872
1873 let proposal = Proposal::new(&header, block_size);
1874 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1875 .validate_fee()
1876 .unwrap_err();
1877
1878 tracing::info!(%err, "task failed successfully");
1880 assert_eq!(
1881 ProposalValidationError::InsufficientFee {
1882 max_block_size: instance.chain_config.max_block_size,
1883 base_fee: instance.chain_config.base_fee,
1884 proposed_fee: header.fee_info().amount().unwrap()
1885 },
1886 err
1887 );
1888 }
1889
1890 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1891 async fn test_validation_height() {
1892 let instance = NodeState::mock_v2();
1894 let tx = Transaction::of_size(10);
1895 let (parent, block_size) = tx.into_mock_header().await;
1896
1897 let proposal = Proposal::new(&parent, block_size);
1898 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1899 .validate_height()
1900 .unwrap_err();
1901
1902 tracing::info!(%err, "task failed successfully");
1904 assert_eq!(
1905 ProposalValidationError::InvalidHeight {
1906 parent_height: parent.height(),
1907 proposal_height: parent.height()
1908 },
1909 err
1910 );
1911
1912 let mut header = parent.clone();
1914 *header.height_mut() += 1;
1915 let proposal = Proposal::new(&header, block_size);
1916
1917 ValidatedTransition::mock(instance, &parent, proposal)
1918 .validate_height()
1919 .unwrap();
1920 }
1921
1922 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1923 async fn test_validation_timestamp_non_dec() {
1924 let tx = Transaction::of_size(10);
1925 let (parent, block_size) = tx.into_mock_header().await;
1926
1927 let proposal = Proposal::new(&parent, block_size);
1929 let proposal_timestamp = proposal.header.timestamp();
1930 let err = proposal.validate_timestamp_non_dec(u64::MAX).unwrap_err();
1931
1932 tracing::info!(%err, "task failed successfully");
1934 assert_eq!(
1935 ProposalValidationError::DecrementingTimestamp {
1936 proposal_timestamp,
1937 parent_timestamp: u64::MAX,
1938 },
1939 err
1940 );
1941
1942 let proposal = Proposal::new(&parent, block_size);
1944 proposal.validate_timestamp_non_dec(0).unwrap();
1945 }
1946
1947 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1948 async fn test_validation_timestamp_drift() {
1949 let instance = NodeState::mock_v2();
1951 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
1952
1953 let header = parent.clone();
1954 let proposal = Proposal::new(&header, block_size);
1956 let proposal_timestamp = header.timestamp();
1957
1958 let mock_time = OffsetDateTime::now_utc().unix_timestamp() as u64;
1959 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1961 .validate_timestamp()
1962 .unwrap_err();
1963
1964 tracing::info!(%err, "task failed successfully");
1965 assert_eq!(
1966 ProposalValidationError::InvalidTimestampDrift {
1967 proposal: proposal_timestamp,
1968 system: mock_time,
1969 diff: mock_time
1970 },
1971 err
1972 );
1973
1974 let time = OffsetDateTime::now_utc();
1975 let timestamp: u64 = time.unix_timestamp() as u64;
1976 let timestamp_millis = TimestampMillis::from_time(&time).u64();
1977
1978 let mut header = parent.clone();
1979 header.set_timestamp(timestamp - 13, timestamp_millis - 13_000);
1980 let proposal = Proposal::new(&header, block_size);
1981
1982 let err = proposal.validate_timestamp_drift(time).unwrap_err();
1983 tracing::info!(%err, "task failed successfully");
1984 assert_eq!(
1985 ProposalValidationError::InvalidTimestampDrift {
1986 proposal: timestamp - 13,
1987 system: timestamp,
1988 diff: 13
1989 },
1990 err
1991 );
1992
1993 let mut header = parent.clone();
1995 header.set_timestamp(timestamp, timestamp_millis);
1996 let proposal = Proposal::new(&header, block_size);
1997 proposal.validate_timestamp_drift(time).unwrap();
1998
1999 header.set_timestamp(timestamp - 11, timestamp_millis - 11_000);
2000 let proposal = Proposal::new(&header, block_size);
2001 proposal.validate_timestamp_drift(time).unwrap();
2002
2003 header.set_timestamp(timestamp - 12, timestamp_millis - 12_000);
2004 let proposal = Proposal::new(&header, block_size);
2005 proposal.validate_timestamp_drift(time).unwrap();
2006 }
2007
2008 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2009 async fn test_validation_fee_root() {
2010 let instance = NodeState::mock_v2();
2012 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
2013
2014 let proposal = Proposal::new(&header, block_size);
2016 ValidatedTransition::mock(instance.clone(), &header, proposal)
2017 .validate_fee_merkle_tree()
2018 .unwrap();
2019
2020 let proposal = Proposal::new(&header, block_size);
2022
2023 let mut fee_merkle_tree = instance.genesis_state.fee_merkle_tree;
2024 fee_merkle_tree
2025 .update_with(FeeAccount::default(), |_| Some(100.into()))
2026 .unwrap();
2027
2028 let err = proposal
2029 .validate_block_merkle_tree(fee_merkle_tree.commitment())
2030 .unwrap_err();
2031
2032 tracing::info!(%err, "task failed successfully");
2033 assert_eq!(
2034 ProposalValidationError::InvalidBlockRoot {
2035 expected_root: fee_merkle_tree.commitment(),
2036 proposal_root: header.block_merkle_tree_root(),
2037 },
2038 err
2039 );
2040 }
2041
2042 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2043 async fn test_validation_block_root() {
2044 let instance = NodeState::mock_v2();
2046 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
2047
2048 let proposal = Proposal::new(&header, block_size);
2050 ValidatedTransition::mock(instance.clone(), &header, proposal)
2051 .validate_block_merkle_tree()
2052 .unwrap();
2053
2054 let proposal = Proposal::new(&header, block_size);
2056 let mut block_merkle_tree = instance.genesis_state.block_merkle_tree;
2057 block_merkle_tree.push(header.commitment()).unwrap();
2058 block_merkle_tree
2059 .push(header.clone().next().commitment())
2060 .unwrap();
2061
2062 let err = proposal
2063 .validate_block_merkle_tree(block_merkle_tree.commitment())
2064 .unwrap_err();
2065
2066 tracing::info!(%err, "task failed successfully");
2067 assert_eq!(
2068 ProposalValidationError::InvalidBlockRoot {
2069 expected_root: block_merkle_tree.commitment(),
2070 proposal_root: proposal.header.block_merkle_tree_root(),
2071 },
2072 err
2073 );
2074 }
2075
2076 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2077 async fn test_validation_ns_table() {
2078 use NsTableValidationError::InvalidFinalOffset;
2079 let tx = Transaction::of_size(10);
2081 let (header, block_size) = tx.into_mock_header().await;
2082
2083 let proposal = Proposal::new(&header, block_size);
2085 ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
2086 .validate_namespace_table()
2087 .unwrap();
2088
2089 let proposal = Proposal::new(&header, 40);
2091 let err = ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
2092 .validate_namespace_table()
2093 .unwrap_err();
2094 tracing::info!(%err, "task failed successfully");
2095 assert_eq!(
2098 ProposalValidationError::InvalidNsTable(InvalidFinalOffset),
2099 err
2100 );
2101 }
2102
2103 #[test_log::test]
2104 fn test_charge_fee() {
2105 let src = FeeAccount::generated_from_seed_indexed([0; 32], 0).0;
2106 let dst = FeeAccount::generated_from_seed_indexed([0; 32], 1).0;
2107 let amt = FeeAmount::from(1);
2108
2109 let fee_info = FeeInfo::new(src, amt);
2110
2111 let new_state = || {
2112 let mut state = ValidatedState::default();
2113 state.prefund_account(src, amt);
2114 state
2115 };
2116
2117 tracing::info!("test successful fee");
2118 let mut state = new_state();
2119 state.charge_fee(fee_info, dst).unwrap();
2120 assert_eq!(state.balance(src), Some(0.into()));
2121 assert_eq!(state.balance(dst), Some(amt));
2122
2123 tracing::info!("test insufficient balance");
2124 let err = state.charge_fee(fee_info, dst).unwrap_err();
2125 assert_eq!(state.balance(src), Some(0.into()));
2126 assert_eq!(state.balance(dst), Some(amt));
2127 assert_eq!(
2128 FeeError::InsufficientFunds {
2129 balance: None,
2130 amount: amt
2131 },
2132 err
2133 );
2134
2135 tracing::info!("test src not in memory");
2136 let mut state = new_state();
2137 state.fee_merkle_tree.forget(src).expect_ok().unwrap();
2138 assert_eq!(
2139 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
2140 state.charge_fee(fee_info, dst).unwrap_err()
2141 );
2142
2143 tracing::info!("test dst not in memory");
2144 let mut state = new_state();
2145 state.prefund_account(dst, amt);
2146 state.fee_merkle_tree.forget(dst).expect_ok().unwrap();
2147 assert_eq!(
2148 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
2149 state.charge_fee(fee_info, dst).unwrap_err()
2150 );
2151 }
2152
2153 #[test]
2154 fn test_fee_amount_serde_json_as_decimal() {
2155 let amt = FeeAmount::from(123);
2156 let serialized = serde_json::to_string(&amt).unwrap();
2157
2158 assert_eq!(serialized, "\"123\"");
2160
2161 let deserialized: FeeAmount = serde_json::from_str(&serialized).unwrap();
2163 assert_eq!(deserialized, amt);
2164 }
2165
2166 #[test]
2167 fn test_fee_amount_from_units() {
2168 for (unit, multiplier) in [
2169 ("wei", 1),
2170 ("gwei", 1_000_000_000),
2171 ("eth", 1_000_000_000_000_000_000),
2172 ] {
2173 let amt: FeeAmount = serde_json::from_str(&format!("\"1 {unit}\"")).unwrap();
2174 assert_eq!(amt, multiplier.into());
2175 }
2176 }
2177
2178 #[test]
2179 fn test_fee_amount_serde_json_from_hex() {
2180 let amt: FeeAmount = serde_json::from_str("\"0x123\"").unwrap();
2183 assert_eq!(amt, FeeAmount::from(0x123));
2184 }
2185
2186 #[test]
2187 fn test_fee_amount_serde_json_from_number() {
2188 let amt: FeeAmount = serde_json::from_str("123").unwrap();
2190 assert_eq!(amt, FeeAmount::from(123));
2191 }
2192
2193 #[test]
2194 fn test_fee_amount_serde_bincode_unchanged() {
2195 let n = ethers_core::types::U256::from(123);
2198 let amt = FeeAmount(U256::from(123));
2199 assert_eq!(
2200 bincode::serialize(&n).unwrap(),
2201 bincode::serialize(&amt).unwrap(),
2202 );
2203 }
2204
2205 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2206 async fn test_validate_builder_fee() {
2207 let max_block_size = 10;
2208
2209 let validated_state = ValidatedState::default();
2210 let instance_state = NodeState::mock().with_chain_config(ChainConfig {
2211 base_fee: 1000.into(), max_block_size: max_block_size.into(),
2213 ..validated_state.chain_config.resolve().unwrap()
2214 });
2215
2216 let parent: Leaf2 = Leaf::genesis(
2217 &instance_state.genesis_state,
2218 &instance_state,
2219 MOCK_UPGRADE.base,
2220 )
2221 .await
2222 .into();
2223 let header = parent.block_header().clone();
2224 let metadata = parent.block_header().metadata();
2225
2226 debug!("{:?}", header.version());
2227
2228 let key_pair = EthKeyPair::random();
2229 let account = key_pair.fee_account();
2230
2231 let data = header.fee_info()[0].amount().as_u64().unwrap();
2232 let sig = FeeAccount::sign_builder_message(&key_pair, &data.to_be_bytes()).unwrap();
2233
2234 account
2236 .validate_builder_signature(&sig, &data.to_be_bytes())
2237 .then_some(())
2238 .unwrap();
2239
2240 let sig = FeeAccount::sign_fee(&key_pair, data, metadata).unwrap();
2242
2243 let header = match header {
2244 Header::V1(header) => Header::V1(v0_1::Header {
2245 builder_signature: Some(sig),
2246 fee_info: FeeInfo::new(account, data),
2247 ..header
2248 }),
2249 Header::V2(header) => Header::V2(v0_2::Header {
2250 builder_signature: Some(sig),
2251 fee_info: FeeInfo::new(account, data),
2252 ..header
2253 }),
2254 Header::V3(header) => Header::V3(v0_3::Header {
2255 builder_signature: Some(sig),
2256 fee_info: FeeInfo::new(account, data),
2257 ..header
2258 }),
2259 Header::V4(header) => Header::V4(v0_4::Header {
2260 builder_signature: Some(sig),
2261 fee_info: FeeInfo::new(account, data),
2262 ..header
2263 }),
2264 Header::V5(header) => Header::V5(v0_5::Header {
2265 builder_signature: Some(sig),
2266 fee_info: FeeInfo::new(account, data),
2267 ..header
2268 }),
2269 Header::V6(header) => Header::V6(v0_6::Header {
2270 builder_signature: Some(sig),
2271 fee_info: FeeInfo::new(account, data),
2272 ..header
2273 }),
2274 };
2275
2276 validate_builder_fee(&header).unwrap();
2277 }
2278
2279 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2280 async fn test_validate_total_rewards_distributed() {
2281 let instance = NodeState::mock().with_genesis_version(version(0, 4));
2282
2283 let (payload, metadata) =
2284 Payload::from_transactions([], &instance.genesis_state, &instance)
2285 .await
2286 .unwrap();
2287
2288 let header = Header::genesis(
2289 &instance,
2290 payload.clone(),
2291 &metadata,
2292 TEST_VERSIONS.da_committee.base,
2293 );
2294
2295 let validated_state = ValidatedState::default();
2296 let actual_total = RewardAmount::from(1000u64);
2297 let block_size = 100u32;
2298
2299 let proposed_header = match header.clone() {
2300 Header::V4(mut h) => {
2301 h.total_reward_distributed = actual_total;
2302 Header::V4(h)
2303 },
2304 _ => unreachable!("Expected V4 header"),
2305 };
2306
2307 let validation_start_time = OffsetDateTime::now_utc();
2308 let validated_transition = ValidatedTransition::new(
2309 validated_state.clone(),
2310 &header,
2311 Proposal::new(&proposed_header, block_size),
2312 Some(actual_total),
2313 version(0, 4),
2314 validation_start_time,
2315 None, None,
2317 );
2318
2319 validated_transition
2320 .validate_total_rewards_distributed()
2321 .unwrap();
2322
2323 let wrong_total = RewardAmount::from(2000u64);
2324 let proposed_header = match header.clone() {
2325 Header::V4(mut h) => {
2326 h.total_reward_distributed = wrong_total;
2327 Header::V4(h)
2328 },
2329 _ => unreachable!("Expected V4 header"),
2330 };
2331
2332 ValidatedTransition::new(
2333 validated_state.clone(),
2334 &header,
2335 Proposal::new(&proposed_header, block_size),
2336 Some(actual_total),
2337 version(0, 4),
2338 validation_start_time,
2339 None, None,
2341 )
2342 .validate_total_rewards_distributed()
2343 .unwrap_err();
2344 }
2345
2346 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2347 async fn test_regression_slow_validation_timestamp_drift() {
2348 let instance = NodeState::mock_v2();
2349 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
2350
2351 let validation_start_time = OffsetDateTime::now_utc();
2352 let timestamp = validation_start_time.unix_timestamp() as u64;
2353 let timestamp_millis = TimestampMillis::from_time(&validation_start_time).u64();
2354
2355 let mut header = parent.clone();
2356 header.set_timestamp(timestamp, timestamp_millis);
2357
2358 std::thread::sleep(Duration::from_secs(13));
2359
2360 let proposal_without_fix = Proposal::new(&header, block_size);
2362 let err = ValidatedTransition::new(
2363 instance.genesis_state.clone(),
2364 &parent,
2365 proposal_without_fix,
2366 None,
2367 MAX_SUPPORTED_VERSION,
2368 OffsetDateTime::now_utc(),
2369 None, None,
2371 )
2372 .validate_timestamp()
2373 .unwrap_err();
2374
2375 assert!(matches!(
2376 err,
2377 ProposalValidationError::InvalidTimestampDrift { .. }
2378 ));
2379
2380 let proposal = Proposal::new(&header, block_size);
2382 ValidatedTransition::new(
2383 instance.genesis_state.clone(),
2384 &parent,
2385 proposal,
2386 None,
2387 MAX_SUPPORTED_VERSION,
2388 validation_start_time,
2389 None, None,
2391 )
2392 .validate_timestamp()
2393 .unwrap();
2394 }
2395
2396 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2398 async fn test_validate_and_apply_header_slow_catchup_succeeds() {
2399 let mut instance = NodeState::mock_v2();
2401
2402 let mut genesis_state = instance.genesis_state.clone();
2403
2404 genesis_state
2406 .fee_merkle_tree
2407 .update(FeeAccount::default(), FeeAmount::from(1u64))
2408 .unwrap();
2409 instance.genesis_state = genesis_state.clone();
2410
2411 let genesis = Leaf::genesis(&genesis_state, &instance, MOCK_UPGRADE.base).await;
2412 let parent_leaf: Leaf2 = genesis.into();
2413 let parent_header = parent_leaf.block_header().clone();
2414
2415 let mut expected_block_tree = genesis_state.block_merkle_tree.clone();
2416 expected_block_tree.push(parent_header.commit()).unwrap();
2417
2418 let proposed_header = match parent_header {
2419 Header::V2(header) => Header::V2(v0_2::Header {
2420 height: header.height + 1,
2421 timestamp: OffsetDateTime::now_utc().unix_timestamp() as u64,
2422 block_merkle_tree_root: expected_block_tree.commitment(),
2423 chain_config: header.chain_config.commit().into(),
2424 ..header
2425 }),
2426 _ => panic!("Expected V2 header"),
2427 };
2428
2429 let slow_catchup =
2430 MockStateCatchup::from_iter([(ViewNumber::new(0), Arc::new(genesis_state.clone()))])
2431 .with_delay(Duration::from_secs(13));
2432 instance.state_catchup = Arc::new(slow_catchup);
2433
2434 genesis_state
2436 .fee_merkle_tree
2437 .forget(FeeAccount::default())
2438 .expect_ok()
2439 .unwrap();
2440
2441 genesis_state
2442 .validate_and_apply_header(
2443 &instance,
2444 &parent_leaf,
2445 &proposed_header,
2446 0, FEE_VERSION,
2448 0, )
2450 .await
2451 .unwrap();
2452 }
2453}