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