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