espresso_types/v0/impls/
state.rs

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/// This enum is not used in code but functions as an index of
53/// possible validation errors.
54#[allow(dead_code)]
55pub enum StateValidationError {
56    ProposalValidation(ProposalValidationError),
57    BuilderValidation(BuilderValidationError),
58    Fee(FeeError),
59}
60
61/// Possible builder validation failures
62#[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/// Possible proposal validation failures
73#[derive(Error, Debug, Eq, PartialEq)]
74pub enum ProposalValidationError {
75    #[error("Next stake table hash mismatch: expected={expected:?}, proposal={proposal:?}")]
76    NextStakeTableHashMismatch {
77        expected: StakeTableHash,
78        proposal: StakeTableHash,
79    },
80    #[error("Invalid ChainConfig: expected={expected:?}, proposal={proposal:?}")]
81    InvalidChainConfig {
82        expected: Box<ChainConfig>,
83        proposal: Box<ResolvableChainConfig>,
84    },
85    #[error(
86        "Invalid Payload Size: (max_block_size={max_block_size}, proposed_block_size={block_size})"
87    )]
88    MaxBlockSizeExceeded {
89        max_block_size: BlockSize,
90        block_size: BlockSize,
91    },
92    #[error(
93        "Insufficient Fee: block_size={max_block_size}, base_fee={base_fee}, \
94         proposed_fee={proposed_fee}"
95    )]
96    InsufficientFee {
97        max_block_size: BlockSize,
98        base_fee: FeeAmount,
99        proposed_fee: FeeAmount,
100    },
101    #[error("Invalid Height: parent_height={parent_height}, proposal_height={proposal_height}")]
102    InvalidHeight {
103        parent_height: u64,
104        proposal_height: u64,
105    },
106    #[error("Invalid Block Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
107    InvalidBlockRoot {
108        expected_root: BlockMerkleCommitment,
109        proposal_root: BlockMerkleCommitment,
110    },
111    #[error("Invalid Fee Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
112    InvalidFeeRoot {
113        expected_root: FeeMerkleCommitment,
114        proposal_root: FeeMerkleCommitment,
115    },
116    #[error(
117        "Invalid v1 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
118    )]
119    InvalidV1RewardRoot {
120        expected_root: RewardMerkleCommitmentV1,
121        proposal_root: RewardMerkleCommitmentV1,
122    },
123    #[error(
124        "Invalid v2 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
125    )]
126    InvalidV2RewardRoot {
127        expected_root: RewardMerkleCommitmentV2,
128        proposal_root: RewardMerkleCommitmentV2,
129    },
130    #[error("Invalid namespace table: {0}")]
131    InvalidNsTable(NsTableValidationError),
132    #[error("Some fee amount or their sum total out of range")]
133    SomeFeeAmountOutOfRange,
134    #[error("Invalid timestamp: proposal={proposal_timestamp}, parent={parent_timestamp}")]
135    DecrementingTimestamp {
136        proposal_timestamp: u64,
137        parent_timestamp: u64,
138    },
139    #[error("Timestamp drift too high: proposed:={proposal}, system={system}, diff={diff}")]
140    InvalidTimestampDrift {
141        proposal: u64,
142        system: u64,
143        diff: u64,
144    },
145    #[error(
146        "Inconsistent timestamps on header: timestamp:={timestamp}, \
147         timestamp_millis={timestamp_millis}"
148    )]
149    InconsistentTimestamps {
150        timestamp: u64,
151        timestamp_millis: u64,
152    },
153    #[error("l1_finalized has `None` value")]
154    L1FinalizedNotFound,
155    #[error("l1_finalized height is decreasing: parent={parent:?} proposed={proposed:?}")]
156    L1FinalizedDecrementing {
157        parent: Option<(u64, u64)>,
158        proposed: Option<(u64, u64)>,
159    },
160    #[error("Invalid proposal: l1_head height is decreasing")]
161    DecrementingL1Head,
162    #[error("Builder Validation Error: {0}")]
163    BuilderValidationError(BuilderValidationError),
164    #[error("Invalid proposal: l1 finalized does not match the proposal")]
165    InvalidL1Finalized,
166    #[error("reward root not found")]
167    RewardRootNotFound {},
168    #[error("Next stake table not found")]
169    NextStakeTableNotFound,
170    #[error("Next stake table hash missing")]
171    NextStakeTableHashNotFound,
172    #[error("Next stake table hash was not `None`")]
173    NextStakeTableHashNotNone,
174    #[error("No Epoch Height")]
175    NoEpochHeight,
176    #[error("No First Epoch Configured")]
177    NoFirstEpoch,
178    #[error("Total rewards mismatch: proposed header has {proposed} but actual is {actual}")]
179    TotalRewardsMismatch {
180        proposed: RewardAmount,
181        actual: RewardAmount,
182    },
183}
184
185impl StateDelta for Delta {}
186
187#[derive(Hash, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
188/// State to be validated by replicas.
189pub struct ValidatedState {
190    /// Frontier of [`BlockMerkleTree`]
191    pub block_merkle_tree: BlockMerkleTree,
192    /// Frontier of [`FeeMerkleTree`]
193    pub fee_merkle_tree: FeeMerkleTree,
194    pub reward_merkle_tree_v1: RewardMerkleTreeV1,
195    pub reward_merkle_tree_v2: RewardMerkleTreeV2,
196    /// Configuration [`Header`] proposals will be validated against.
197    pub chain_config: ResolvableChainConfig,
198}
199
200impl Default for ValidatedState {
201    fn default() -> Self {
202        let block_merkle_tree = BlockMerkleTree::from_elems(
203            Some(BLOCK_MERKLE_TREE_HEIGHT),
204            Vec::<Commitment<Header>>::new(),
205        )
206        .unwrap();
207
208        // Words of wisdom from @mrain: "capacity = arity^height"
209        // "For index space 2^160, arity 256 (2^8),
210        // you should set the height as 160/8=20"
211        let fee_merkle_tree = FeeMerkleTree::from_kv_set(
212            FEE_MERKLE_TREE_HEIGHT,
213            Vec::<(FeeAccount, FeeAmount)>::new(),
214        )
215        .unwrap();
216
217        let reward_merkle_tree_v1 = RewardMerkleTreeV1::from_kv_set(
218            REWARD_MERKLE_TREE_V1_HEIGHT,
219            Vec::<(RewardAccountV1, RewardAmount)>::new(),
220        )
221        .unwrap();
222
223        let reward_merkle_tree_v2 = RewardMerkleTreeV2::from_kv_set(
224            REWARD_MERKLE_TREE_V2_HEIGHT,
225            Vec::<(RewardAccountV2, RewardAmount)>::new(),
226        )
227        .unwrap();
228
229        let chain_config = ResolvableChainConfig::from(ChainConfig::default());
230
231        Self {
232            block_merkle_tree,
233            fee_merkle_tree,
234            reward_merkle_tree_v1,
235            reward_merkle_tree_v2,
236            chain_config,
237        }
238    }
239}
240
241impl ValidatedState {
242    /// Prefund an account with a given amount. Only for demo purposes.
243    pub fn prefund_account(&mut self, account: FeeAccount, amount: FeeAmount) {
244        self.fee_merkle_tree.update(account, amount).unwrap();
245    }
246
247    pub fn balance(&mut self, account: FeeAccount) -> Option<FeeAmount> {
248        match self.fee_merkle_tree.lookup(account) {
249            LookupResult::Ok(balance, _) => Some(*balance),
250            LookupResult::NotFound(_) => Some(0.into()),
251            LookupResult::NotInMemory => None,
252        }
253    }
254
255    /// Find accounts that are not in memory.
256    ///
257    /// As an optimization we could try to apply updates and return the
258    /// forgotten accounts to be fetched from peers and update them later.
259    pub fn forgotten_accounts(
260        &self,
261        accounts: impl IntoIterator<Item = FeeAccount>,
262    ) -> Vec<FeeAccount> {
263        accounts
264            .into_iter()
265            .unique()
266            .filter(|account| {
267                self.fee_merkle_tree
268                    .lookup(*account)
269                    .expect_not_in_memory()
270                    .is_ok()
271            })
272            .collect()
273    }
274
275    pub fn forgotten_reward_accounts_v2(
276        &self,
277        accounts: impl IntoIterator<Item = RewardAccountV2>,
278    ) -> Vec<RewardAccountV2> {
279        accounts
280            .into_iter()
281            .unique()
282            .filter(|account| {
283                self.reward_merkle_tree_v2
284                    .lookup(*account)
285                    .expect_not_in_memory()
286                    .is_ok()
287            })
288            .collect()
289    }
290
291    pub fn forgotten_reward_accounts_v1(
292        &self,
293        accounts: impl IntoIterator<Item = RewardAccountV1>,
294    ) -> Vec<RewardAccountV1> {
295        accounts
296            .into_iter()
297            .unique()
298            .filter(|account| {
299                self.reward_merkle_tree_v1
300                    .lookup(*account)
301                    .expect_not_in_memory()
302                    .is_ok()
303            })
304            .collect()
305    }
306
307    /// Check if the merkle tree is available
308    pub fn need_to_fetch_blocks_mt_frontier(&self) -> bool {
309        let num_leaves = self.block_merkle_tree.num_leaves();
310        if num_leaves == 0 {
311            false
312        } else {
313            self.block_merkle_tree
314                .lookup(num_leaves - 1)
315                .expect_ok()
316                .is_err()
317        }
318    }
319
320    /// Insert a fee deposit receipt
321    pub fn insert_fee_deposit(
322        &mut self,
323        fee_info: FeeInfo,
324    ) -> anyhow::Result<LookupResult<FeeAmount, (), ()>> {
325        Ok(self
326            .fee_merkle_tree
327            .update_with(fee_info.account, |balance| {
328                Some(balance.cloned().unwrap_or_default().add(fee_info.amount))
329            })?)
330    }
331
332    pub fn apply_proposal(
333        &mut self,
334        delta: &mut Delta,
335        parent_leaf: &Leaf2,
336        l1_deposits: Vec<FeeInfo>,
337    ) {
338        // pushing a block into merkle tree shouldn't fail
339        self.block_merkle_tree
340            .push(parent_leaf.block_header().commit())
341            .unwrap();
342
343        for FeeInfo { account, amount } in l1_deposits.iter() {
344            self.fee_merkle_tree
345                .update_with(account, |balance| {
346                    Some(balance.cloned().unwrap_or_default().add(*amount))
347                })
348                .expect("update_with succeeds");
349            delta.fees_delta.insert(*account);
350        }
351    }
352
353    pub fn charge_fees(
354        &mut self,
355        delta: &mut Delta,
356        fee_info: Vec<FeeInfo>,
357        recipient: FeeAccount,
358    ) -> Result<(), FeeError> {
359        for fee_info in fee_info {
360            self.charge_fee(fee_info, recipient)?;
361            delta.fees_delta.extend([fee_info.account, recipient]);
362        }
363        Ok(())
364    }
365
366    /// Charge a fee to an account, transferring the funds to the fee recipient account.
367    pub fn charge_fee(&mut self, fee_info: FeeInfo, recipient: FeeAccount) -> Result<(), FeeError> {
368        if fee_info.amount == 0.into() {
369            return Ok(());
370        }
371
372        let fee_state = self.fee_merkle_tree.clone();
373
374        // Deduct the fee from the paying account.
375        let FeeInfo { account, amount } = fee_info;
376        let mut err = None;
377        let fee_state = fee_state.persistent_update_with(account, |balance| {
378            let balance = balance.copied();
379            let Some(updated) = balance.unwrap_or_default().checked_sub(&amount) else {
380                // Return an error without updating the account.
381                err = Some(FeeError::InsufficientFunds { balance, amount });
382                return balance;
383            };
384            if updated == FeeAmount::default() {
385                // Delete the account from the tree if its balance ended up at 0; this saves some
386                // space since the account is no longer carrying any information.
387                None
388            } else {
389                // Otherwise store the updated balance.
390                Some(updated)
391            }
392        })?;
393
394        // Fail if there was an error during `persistent_update_with` (e.g. insufficient balance).
395        if let Some(err) = err {
396            return Err(err);
397        }
398
399        // If we successfully deducted the fee from the source account, increment the balance of the
400        // recipient account.
401        let fee_state = fee_state.persistent_update_with(recipient, |balance| {
402            Some(balance.copied().unwrap_or_default() + amount)
403        })?;
404
405        // If the whole update was successful, update the original state.
406        self.fee_merkle_tree = fee_state;
407        Ok(())
408    }
409}
410/// Block Proposal to be verified and applied.
411#[derive(Debug)]
412pub(crate) struct Proposal<'a> {
413    header: &'a Header,
414    block_size: u32,
415}
416
417impl<'a> Proposal<'a> {
418    pub(crate) fn new(header: &'a Header, block_size: u32) -> Self {
419        Self { header, block_size }
420    }
421    /// The L1 head block number in the proposal must be non-decreasing relative
422    /// to the parent.
423    fn validate_l1_head(&self, parent_l1_head: u64) -> Result<(), ProposalValidationError> {
424        if self.header.l1_head() < parent_l1_head {
425            return Err(ProposalValidationError::DecrementingL1Head);
426        }
427        Ok(())
428    }
429    /// The [`ChainConfig`] of proposal must be equal to the one stored in state.
430    ///
431    /// Equality is checked by comparing commitments.
432    fn validate_chain_config(
433        &self,
434        expected_chain_config: &ChainConfig,
435    ) -> Result<(), ProposalValidationError> {
436        let proposed_chain_config = self.header.chain_config();
437        if proposed_chain_config.commit() != expected_chain_config.commit() {
438            return Err(ProposalValidationError::InvalidChainConfig {
439                expected: Box::new(*expected_chain_config),
440                proposal: Box::new(proposed_chain_config),
441            });
442        }
443        Ok(())
444    }
445
446    /// The timestamp must be non-decreasing relative to parent.
447    fn validate_timestamp_non_dec(
448        &self,
449        parent_timestamp: u64,
450    ) -> Result<(), ProposalValidationError> {
451        if self.header.timestamp() < parent_timestamp {
452            return Err(ProposalValidationError::DecrementingTimestamp {
453                proposal_timestamp: self.header.timestamp(),
454                parent_timestamp,
455            });
456        }
457
458        Ok(())
459    }
460
461    /// The timestamp must not drift too much from local system time.
462    ///
463    /// The tolerance is currently `12` seconds. This value may be moved to
464    /// configuration in the future.
465    fn validate_timestamp_drift(
466        &self,
467        system_time: OffsetDateTime,
468    ) -> Result<(), ProposalValidationError> {
469        // TODO 12 seconds of tolerance should be enough for reasonably
470        // configured nodes, but we should make this configurable.
471        let system_timestamp = system_time.unix_timestamp() as u64;
472        let diff = self.header.timestamp().abs_diff(system_timestamp);
473        if diff > 12 {
474            return Err(ProposalValidationError::InvalidTimestampDrift {
475                proposal: self.header.timestamp(),
476                system: system_timestamp,
477                diff,
478            });
479        }
480
481        Ok(())
482    }
483
484    /// The `timestamp` and `timestamp_millis` fields must be coherent
485    fn validate_timestamp_consistency(&self) -> Result<(), ProposalValidationError> {
486        if self.header.timestamp() != self.header.timestamp_millis() / 1_000 {
487            return Err(ProposalValidationError::InconsistentTimestamps {
488                timestamp: self.header.timestamp(),
489                timestamp_millis: self.header.timestamp_millis(),
490            });
491        }
492
493        Ok(())
494    }
495
496    /// The proposed ['BlockMerkleTree'] must match the one in ['ValidatedState'].
497    fn validate_block_merkle_tree(
498        &self,
499        block_merkle_tree_root: BlockMerkleCommitment,
500    ) -> Result<(), ProposalValidationError> {
501        if self.header.block_merkle_tree_root() != block_merkle_tree_root {
502            return Err(ProposalValidationError::InvalidBlockRoot {
503                expected_root: block_merkle_tree_root,
504                proposal_root: self.header.block_merkle_tree_root(),
505            });
506        }
507
508        Ok(())
509    }
510}
511/// Type to hold cloned validated state and provide validation methods.
512///
513/// The [Self::validate] method must be called to validate the proposal.
514#[derive(Debug)]
515pub(crate) struct ValidatedTransition<'a> {
516    state: ValidatedState,
517    expected_chain_config: ChainConfig,
518    parent: &'a Header,
519    proposal: Proposal<'a>,
520    total_rewards_distributed: Option<RewardAmount>,
521    version: Version,
522    validation_start_time: OffsetDateTime,
523}
524
525impl<'a> ValidatedTransition<'a> {
526    pub(crate) fn new(
527        state: ValidatedState,
528        parent: &'a Header,
529        proposal: Proposal<'a>,
530        total_rewards_distributed: Option<RewardAmount>,
531        version: Version,
532        validation_start_time: OffsetDateTime,
533    ) -> Self {
534        let expected_chain_config = state
535            .chain_config
536            .resolve()
537            .expect("Chain Config not found in validated state");
538        Self {
539            state,
540            expected_chain_config,
541            parent,
542            proposal,
543            total_rewards_distributed,
544            version,
545            validation_start_time,
546        }
547    }
548
549    /// Top level validation routine. Performs all validation units in
550    /// the given order.
551    /// ```ignore
552    /// self.validate_timestamp()?;
553    /// self.validate_builder_fee()?;
554    /// self.validate_height()?;
555    /// self.validate_chain_config()?;
556    /// self.validate_block_size()?;
557    /// self.validate_fee()?;
558    /// self.validate_fee_merkle_tree()?;
559    /// self.validate_block_merkle_tree()?;
560    /// self.validate_l1_finalized()?;
561    /// self.validate_l1_head()?;
562    /// self.validate_namespace_table()?;
563    /// self.validate_total_rewards_distributed()?;
564    /// ```
565    pub(crate) fn validate(self) -> Result<Self, ProposalValidationError> {
566        self.validate_timestamp()?;
567        self.validate_builder_fee()?;
568        self.validate_height()?;
569        self.validate_chain_config()?;
570        self.validate_block_size()?;
571        self.validate_fee()?;
572        self.validate_fee_merkle_tree()?;
573        self.validate_block_merkle_tree()?;
574        self.validate_reward_merkle_tree()?;
575        self.validate_l1_finalized()?;
576        self.validate_l1_head()?;
577        self.validate_namespace_table()?;
578        self.validate_total_rewards_distributed()?;
579
580        Ok(self)
581    }
582
583    /// The proposal [Header::l1_finalized] must be `Some` and non-decreasing relative to parent.
584    fn validate_l1_finalized(&self) -> Result<(), ProposalValidationError> {
585        let proposed_finalized = self.proposal.header.l1_finalized();
586        let parent_finalized = self.parent.l1_finalized();
587
588        if proposed_finalized < parent_finalized {
589            // We are keeping the `Option` in the error b/c its the
590            // cleanest way to represent all the different error
591            // cases. The hash seems less useful and explodes the size
592            // of the error, so we strip it out.
593            return Err(ProposalValidationError::L1FinalizedDecrementing {
594                parent: parent_finalized.map(|block| (block.number, block.timestamp.to::<u64>())),
595                proposed: proposed_finalized
596                    .map(|block| (block.number, block.timestamp.to::<u64>())),
597            });
598        }
599        Ok(())
600    }
601    /// Wait for our view of the L1 chain to catch up to the proposal.
602    ///
603    /// The finalized [L1BlockInfo](super::L1BlockInfo) in the proposal must match the one fetched
604    /// from L1.
605    async fn wait_for_l1(self, l1_client: &L1Client) -> Result<Self, ProposalValidationError> {
606        self.wait_for_l1_head(l1_client).await;
607        self.wait_for_finalized_block(l1_client).await?;
608        Ok(self)
609    }
610
611    /// Wait for our view of the latest L1 block number to catch up to the
612    /// proposal.
613    async fn wait_for_l1_head(&self, l1_client: &L1Client) {
614        let _ = l1_client
615            .wait_for_block(self.proposal.header.l1_head())
616            .await;
617    }
618    /// Wait for our view of the finalized L1 block number to catch up to the
619    /// proposal.
620    async fn wait_for_finalized_block(
621        &self,
622        l1_client: &L1Client,
623    ) -> Result<(), ProposalValidationError> {
624        let proposed_finalized = self.proposal.header.l1_finalized();
625
626        if let Some(proposed_finalized) = proposed_finalized {
627            let finalized = l1_client
628                .wait_for_finalized_block(proposed_finalized.number())
629                .await;
630
631            if finalized != proposed_finalized {
632                return Err(ProposalValidationError::InvalidL1Finalized);
633            }
634        }
635
636        Ok(())
637    }
638
639    /// Ensure that L1 Head on proposal is not decreasing.
640    fn validate_l1_head(&self) -> Result<(), ProposalValidationError> {
641        self.proposal.validate_l1_head(self.parent.l1_head())?;
642        Ok(())
643    }
644    /// Validate basic numerical soundness and builder accounts by
645    /// verifying signatures. Signatures are identified by index of fee `Vec`.
646    fn validate_builder_fee(&self) -> Result<(), ProposalValidationError> {
647        // TODO move logic from stand alone fn to here.
648        if let Err(err) = validate_builder_fee(self.proposal.header) {
649            return Err(ProposalValidationError::BuilderValidationError(err));
650        }
651        Ok(())
652    }
653    /// Validates proposals [`ChainConfig`] against expectation by comparing commitments.
654    fn validate_chain_config(&self) -> Result<(), ProposalValidationError> {
655        self.proposal
656            .validate_chain_config(&self.expected_chain_config)?;
657        Ok(())
658    }
659    /// Validate that proposal block size does not exceed configured
660    /// `ChainConfig.max_block_size`.
661    fn validate_block_size(&self) -> Result<(), ProposalValidationError> {
662        let block_size = self.proposal.block_size as u64;
663        if block_size > *self.expected_chain_config.max_block_size {
664            return Err(ProposalValidationError::MaxBlockSizeExceeded {
665                max_block_size: self.expected_chain_config.max_block_size,
666                block_size: block_size.into(),
667            });
668        }
669        Ok(())
670    }
671    /// Validate that [`FeeAmount`] that is
672    /// sufficient for block size.
673    fn validate_fee(&self) -> Result<(), ProposalValidationError> {
674        // TODO this should be updated to `base_fee * bundle_size` when we have
675        // VID per bundle or namespace.
676        let Some(amount) = self.proposal.header.fee_info().amount() else {
677            return Err(ProposalValidationError::SomeFeeAmountOutOfRange);
678        };
679
680        if amount < self.expected_chain_config.base_fee * U256::from(self.proposal.block_size) {
681            return Err(ProposalValidationError::InsufficientFee {
682                max_block_size: self.expected_chain_config.max_block_size,
683                base_fee: self.expected_chain_config.base_fee,
684                proposed_fee: amount,
685            });
686        }
687        Ok(())
688    }
689    /// Validate that proposal height is `parent_height + 1`.
690    fn validate_height(&self) -> Result<(), ProposalValidationError> {
691        let parent_header = self.parent;
692        if self.proposal.header.height() != parent_header.height() + 1 {
693            return Err(ProposalValidationError::InvalidHeight {
694                parent_height: parent_header.height(),
695                proposal_height: self.proposal.header.height(),
696            });
697        }
698        Ok(())
699    }
700    /// Validate timestamp is not decreasing relative to parent and is
701    /// within a given tolerance of system time. Tolerance is
702    /// currently 12 seconds. This value may be moved to configuration
703    /// in the future. Do this check first so we don't add unnecessary drift.
704    fn validate_timestamp(&self) -> Result<(), ProposalValidationError> {
705        self.proposal.validate_timestamp_consistency()?;
706
707        self.proposal
708            .validate_timestamp_non_dec(self.parent.timestamp())?;
709
710        self.proposal
711            .validate_timestamp_drift(self.validation_start_time)?;
712
713        Ok(())
714    }
715    /// Validate [`BlockMerkleTree`] by comparing proposed commitment
716    /// that stored in [`ValidatedState`].
717    fn validate_block_merkle_tree(&self) -> Result<(), ProposalValidationError> {
718        let block_merkle_tree_root = self.state.block_merkle_tree.commitment();
719        self.proposal
720            .validate_block_merkle_tree(block_merkle_tree_root)?;
721
722        Ok(())
723    }
724
725    /// Validate [`RewardMerkleTreeV2`] by comparing proposed commitment
726    /// against that stored in [`ValidatedState`].
727    fn validate_reward_merkle_tree(&self) -> Result<(), ProposalValidationError> {
728        match self.proposal.header.reward_merkle_tree_root() {
729            Either::Left(proposal_root) => {
730                let expected_root = self.state.reward_merkle_tree_v1.commitment();
731                if proposal_root != expected_root {
732                    return Err(ProposalValidationError::InvalidV1RewardRoot {
733                        expected_root,
734                        proposal_root,
735                    });
736                }
737            },
738            Either::Right(proposal_root) => {
739                let expected_root = self.state.reward_merkle_tree_v2.commitment();
740                if proposal_root != expected_root {
741                    return Err(ProposalValidationError::InvalidV2RewardRoot {
742                        expected_root,
743                        proposal_root,
744                    });
745                }
746            },
747        }
748
749        Ok(())
750    }
751
752    /// Validate [`FeeMerkleTree`] by comparing proposed commitment
753    /// against that stored in [`ValidatedState`].
754    fn validate_fee_merkle_tree(&self) -> Result<(), ProposalValidationError> {
755        let fee_merkle_tree_root = self.state.fee_merkle_tree.commitment();
756        if self.proposal.header.fee_merkle_tree_root() != fee_merkle_tree_root {
757            return Err(ProposalValidationError::InvalidFeeRoot {
758                expected_root: fee_merkle_tree_root,
759                proposal_root: self.proposal.header.fee_merkle_tree_root(),
760            });
761        }
762
763        Ok(())
764    }
765    /// Proxy to [`super::NsTable::validate()`].
766    fn validate_namespace_table(&self) -> Result<(), ProposalValidationError> {
767        self.proposal
768            .header
769            .ns_table()
770            // Should be safe since `u32` will always fit in a `usize`.
771            .validate(&PayloadByteLen(self.proposal.block_size as usize))
772            .map_err(ProposalValidationError::from)
773    }
774
775    /// Validate that the total rewards distributed in the proposed header matches the actual distributed amount.
776    /// This field is only present in >= V4 version.
777    fn validate_total_rewards_distributed(&self) -> Result<(), ProposalValidationError> {
778        if self.version >= DrbAndHeaderUpgradeVersion::version() {
779            let Some(actual_total) = self.total_rewards_distributed else {
780                // This should never happen - if version >= V4, total_rewards_distributed must be Some
781                return Err(ProposalValidationError::TotalRewardsMismatch {
782                    proposed: self
783                        .proposal
784                        .header
785                        .total_reward_distributed()
786                        .unwrap_or_default(),
787                    actual: RewardAmount::from(0),
788                });
789            };
790
791            let proposed_total =
792                self.proposal
793                    .header
794                    .total_reward_distributed()
795                    .ok_or_else(|| ProposalValidationError::TotalRewardsMismatch {
796                        proposed: RewardAmount::from(0),
797                        actual: actual_total,
798                    })?;
799
800            if proposed_total != actual_total {
801                return Err(ProposalValidationError::TotalRewardsMismatch {
802                    proposed: proposed_total,
803                    actual: actual_total,
804                });
805            }
806        }
807        Ok(())
808    }
809}
810
811#[cfg(any(test, feature = "testing"))]
812impl ValidatedState {
813    pub fn forget(&self) -> Self {
814        Self {
815            fee_merkle_tree: FeeMerkleTree::from_commitment(self.fee_merkle_tree.commitment()),
816            block_merkle_tree: BlockMerkleTree::from_commitment(
817                self.block_merkle_tree.commitment(),
818            ),
819            reward_merkle_tree_v2: RewardMerkleTreeV2::from_commitment(
820                self.reward_merkle_tree_v2.commitment(),
821            ),
822            reward_merkle_tree_v1: RewardMerkleTreeV1::from_commitment(
823                self.reward_merkle_tree_v1.commitment(),
824            ),
825            chain_config: ResolvableChainConfig::from(self.chain_config.commit()),
826        }
827    }
828}
829
830impl From<NsTableValidationError> for ProposalValidationError {
831    fn from(err: NsTableValidationError) -> Self {
832        Self::InvalidNsTable(err)
833    }
834}
835
836impl From<ProposalValidationError> for BlockError {
837    fn from(err: ProposalValidationError) -> Self {
838        tracing::error!("Invalid Block Header: {err:#}");
839        BlockError::InvalidBlockHeader(err.to_string())
840    }
841}
842
843impl From<MerkleTreeError> for FeeError {
844    fn from(item: MerkleTreeError) -> Self {
845        Self::MerkleTreeError(item)
846    }
847}
848
849/// Validate builder accounts by verifying signatures. All fees are
850/// verified against signature by index.
851fn validate_builder_fee(proposed_header: &Header) -> Result<(), BuilderValidationError> {
852    // TODO since we are iterating, should we include account/amount in errors?
853    for (fee_info, signature) in proposed_header
854        .fee_info()
855        .iter()
856        .zip(proposed_header.builder_signature())
857    {
858        // check that `amount` fits in a u64
859        fee_info
860            .amount()
861            .as_u64()
862            .ok_or(BuilderValidationError::FeeAmountOutOfRange(fee_info.amount))?;
863
864        // Verify signatures.
865        if !fee_info.account().validate_fee_signature(
866            &signature,
867            fee_info.amount().as_u64().unwrap(),
868            proposed_header.metadata(),
869        ) && !fee_info
870            .account()
871            .validate_fee_signature_with_vid_commitment(
872                &signature,
873                fee_info.amount().as_u64().unwrap(),
874                proposed_header.metadata(),
875                &proposed_header.payload_commitment(),
876            )
877        {
878            return Err(BuilderValidationError::InvalidBuilderSignature);
879        }
880    }
881
882    Ok(())
883}
884
885impl ValidatedState {
886    /// Updates state with [`Header`] proposal.
887    ///   * Clones and updates [`ValidatedState`] (avoiding mutation).
888    ///   * Resolves [`ChainConfig`].
889    ///   * Performs catchup.
890    ///   * Charges fees.
891    pub async fn apply_header(
892        &self,
893        instance: &NodeState,
894        peers: &impl StateCatchup,
895        parent_leaf: &Leaf2,
896        proposed_header: &Header,
897        version: Version,
898        view_number: ViewNumber,
899    ) -> anyhow::Result<(Self, Delta, Option<RewardAmount>)> {
900        // Clone state to avoid mutation. Consumer can take update
901        // through returned value.
902        let mut validated_state = self.clone();
903        validated_state.apply_upgrade(instance, version);
904
905        // TODO double check there is not some possibility we are
906        // validating proposal values against ChainConfig of the proposal.
907        let chain_config = validated_state
908            .get_chain_config(instance, peers, &proposed_header.chain_config())
909            .await?;
910
911        if Some(chain_config) != validated_state.chain_config.resolve() {
912            validated_state.chain_config = chain_config.into();
913        }
914
915        let l1_deposits = get_l1_deposits(
916            instance,
917            proposed_header,
918            parent_leaf,
919            chain_config.fee_contract,
920        )
921        .await;
922
923        // Find missing fee state entries. We will need to use the builder account which is paying a
924        // fee and the recipient account which is receiving it, plus any counts receiving deposits
925        // in this block.
926        let missing_accounts = self.forgotten_accounts(
927            [chain_config.fee_recipient]
928                .into_iter()
929                .chain(proposed_header.fee_info().accounts())
930                .chain(l1_deposits.accounts()),
931        );
932
933        let parent_height = parent_leaf.height();
934        let parent_view = parent_leaf.view_number();
935
936        // Ensure merkle tree has frontier
937        if self.need_to_fetch_blocks_mt_frontier() {
938            tracing::info!(
939                parent_height,
940                ?parent_view,
941                "fetching block frontier from peers"
942            );
943            peers
944                .remember_blocks_merkle_tree(
945                    instance,
946                    parent_height,
947                    parent_view,
948                    &mut validated_state.block_merkle_tree,
949                )
950                .await?;
951        }
952
953        // Fetch missing fee state entries
954        if !missing_accounts.is_empty() {
955            tracing::info!(
956                parent_height,
957                ?parent_view,
958                ?missing_accounts,
959                "fetching missing accounts from peers"
960            );
961
962            let missing_account_proofs = peers
963                .fetch_accounts(
964                    instance,
965                    parent_height,
966                    parent_view,
967                    validated_state.fee_merkle_tree.commitment(),
968                    missing_accounts,
969                )
970                .await?;
971
972            // Remember the fee state entries
973            for proof in missing_account_proofs.iter() {
974                proof
975                    .remember(&mut validated_state.fee_merkle_tree)
976                    .expect("proof previously verified");
977            }
978        }
979
980        let mut delta = Delta::default();
981        validated_state.apply_proposal(&mut delta, parent_leaf, l1_deposits);
982
983        validated_state.charge_fees(
984            &mut delta,
985            proposed_header.fee_info(),
986            chain_config.fee_recipient,
987        )?;
988
989        // total_rewards_distributed is only present in >= V4
990        let total_rewards_distributed = if version < EpochVersion::version() {
991            None
992        } else if let Some(reward_distributor) = distribute_block_reward(
993            instance,
994            &mut validated_state,
995            parent_leaf,
996            view_number,
997            version,
998        )
999        .await?
1000        {
1001            reward_distributor
1002                .update_rewards_delta(&mut delta)
1003                .context("failed to update rewards delta")?;
1004
1005            Some(reward_distributor.total_distributed())
1006        } else {
1007            // Version >= V4 but no rewards were distributed because epoch <= first epoch + 1
1008            Some(Default::default())
1009        };
1010
1011        Ok((validated_state, delta, total_rewards_distributed))
1012    }
1013
1014    /// Updates the `ValidatedState` if a protocol upgrade has occurred.
1015    pub(crate) fn apply_upgrade(&mut self, instance: &NodeState, version: Version) {
1016        // Check for protocol upgrade based on sequencer version
1017        if version <= instance.current_version {
1018            return;
1019        }
1020
1021        let Some(upgrade) = instance.upgrades.get(&version) else {
1022            return;
1023        };
1024
1025        let cf = match upgrade.upgrade_type {
1026            UpgradeType::Fee { chain_config } => chain_config,
1027            UpgradeType::Epoch { chain_config } => chain_config,
1028            UpgradeType::DrbAndHeader { chain_config } => chain_config,
1029            UpgradeType::Da { chain_config } => chain_config,
1030        };
1031
1032        self.chain_config = cf.into();
1033    }
1034
1035    /// Retrieves the `ChainConfig`.
1036    ///
1037    ///  Returns the `NodeState` `ChainConfig` if the `ValidatedState` `ChainConfig` commitment matches the `NodeState` `ChainConfig`` commitment.
1038    ///  If the commitments do not match, it returns the `ChainConfig` available in either `ValidatedState` or proposed header.
1039    ///  If neither has the `ChainConfig`, it fetches the config from the peers.
1040    ///
1041    /// Returns an error if it fails to fetch the `ChainConfig` from the peers.
1042    pub(crate) async fn get_chain_config(
1043        &self,
1044        instance: &NodeState,
1045        peers: &impl StateCatchup,
1046        header_cf: &ResolvableChainConfig,
1047    ) -> anyhow::Result<ChainConfig> {
1048        let state_cf = self.chain_config;
1049
1050        if state_cf.commit() == instance.chain_config.commit() {
1051            return Ok(instance.chain_config);
1052        }
1053
1054        let cf = match (state_cf.resolve(), header_cf.resolve()) {
1055            (Some(cf), _) => cf,
1056            (_, Some(cf)) if cf.commit() == state_cf.commit() => cf,
1057            (_, Some(_)) | (None, None) => peers.fetch_chain_config(state_cf.commit()).await?,
1058        };
1059
1060        Ok(cf)
1061    }
1062}
1063
1064pub async fn get_l1_deposits(
1065    instance: &NodeState,
1066    header: &Header,
1067    parent_leaf: &Leaf2,
1068    fee_contract_address: Option<Address>,
1069) -> Vec<FeeInfo> {
1070    if let (Some(addr), Some(block_info)) = (fee_contract_address, header.l1_finalized()) {
1071        instance
1072            .l1_client
1073            .get_finalized_deposits(
1074                addr,
1075                parent_leaf
1076                    .block_header()
1077                    .l1_finalized()
1078                    .map(|block_info| block_info.number),
1079                block_info.number,
1080            )
1081            .await
1082    } else {
1083        vec![]
1084    }
1085}
1086
1087async fn validate_next_stake_table_hash(
1088    instance: &NodeState,
1089    proposed_header: &Header,
1090) -> Result<(), ProposalValidationError> {
1091    let Some(epoch_height) = instance.epoch_height else {
1092        return Err(ProposalValidationError::NoEpochHeight);
1093    };
1094    if !is_ge_epoch_root(proposed_header.height(), epoch_height) {
1095        return Ok(());
1096    }
1097    let epoch = EpochNumber::new(epoch_from_block_number(
1098        proposed_header.height(),
1099        epoch_height,
1100    ));
1101    let coordinator = instance.coordinator.clone();
1102    let Some(first_epoch) = coordinator.membership().read().await.first_epoch() else {
1103        return Err(ProposalValidationError::NoFirstEpoch);
1104    };
1105
1106    // We only require a `stake_table_hash` for epochs past the second
1107    let Some(proposed_next_stake_table_hash) = proposed_header.next_stake_table_hash() else {
1108        if epoch <= first_epoch {
1109            return Ok(());
1110        } else {
1111            return Err(ProposalValidationError::NextStakeTableHashNotNone);
1112        }
1113    };
1114
1115    let epoch_membership = instance
1116        .coordinator
1117        .stake_table_for_epoch(Some(epoch + 1))
1118        .await
1119        .map_err(|_| ProposalValidationError::NextStakeTableNotFound)?;
1120    let next_stake_table_hash = epoch_membership
1121        .stake_table_hash()
1122        .await
1123        .ok_or(ProposalValidationError::NextStakeTableHashNotFound)?;
1124    if next_stake_table_hash != proposed_next_stake_table_hash {
1125        return Err(ProposalValidationError::NextStakeTableHashMismatch {
1126            expected: next_stake_table_hash,
1127            proposal: proposed_next_stake_table_hash,
1128        });
1129    }
1130    Ok(())
1131}
1132
1133impl HotShotState<SeqTypes> for ValidatedState {
1134    type Error = BlockError;
1135    type Instance = NodeState;
1136
1137    type Time = ViewNumber;
1138
1139    type Delta = Delta;
1140    fn on_commit(&self) {}
1141    /// Validate parent against known values (from state) and validate
1142    /// proposal descends from parent. Returns updated `ValidatedState`.
1143    #[tracing::instrument(
1144        skip_all,
1145        fields(
1146            node_id = instance.node_id,
1147            view = ?parent_leaf.view_number(),
1148            height = parent_leaf.height(),
1149        ),
1150    )]
1151    async fn validate_and_apply_header(
1152        &self,
1153        instance: &Self::Instance,
1154        parent_leaf: &Leaf2,
1155        proposed_header: &Header,
1156        payload_byte_len: u32,
1157        version: Version,
1158        view_number: u64,
1159    ) -> Result<(Self, Self::Delta), Self::Error> {
1160        // Preferably we would do all validation that does not require catchup first, but this would
1161        // require some refactoring of the header validation code that is out of scope for now.
1162        // Record the time when validation started to later use it to validate the timestamp drift.
1163        let validation_start_time = OffsetDateTime::now_utc();
1164
1165        let (validated_state, delta, total_rewards_distributed) = self
1166            // TODO We can add this logic to `ValidatedTransition` or do something similar to that here.
1167            .apply_header(
1168                instance,
1169                &instance.state_catchup,
1170                parent_leaf,
1171                proposed_header,
1172                version,
1173                ViewNumber::new(view_number),
1174            )
1175            .await
1176            .map_err(|e| BlockError::FailedHeaderApply(e.to_string()))?;
1177
1178        if version >= DrbAndHeaderUpgradeVersion::version() {
1179            validate_next_stake_table_hash(instance, proposed_header).await?;
1180        }
1181
1182        // Validate the proposal.
1183        let validated_state = ValidatedTransition::new(
1184            validated_state,
1185            parent_leaf.block_header(),
1186            Proposal::new(proposed_header, payload_byte_len),
1187            total_rewards_distributed,
1188            version,
1189            validation_start_time,
1190        )
1191        .validate()?
1192        .wait_for_l1(&instance.l1_client)
1193        .await?
1194        .state;
1195
1196        // log successful progress about once in 10 - 20 seconds,
1197        // TODO: we may want to make this configurable
1198        if parent_leaf.view_number().u64().is_multiple_of(10) {
1199            tracing::info!("validated and applied new header");
1200        }
1201        Ok((validated_state, delta))
1202    }
1203    /// Construct the state with the given block header.
1204    ///
1205    /// This can also be used to rebuild the state for catchup.
1206    fn from_header(block_header: &Header) -> Self {
1207        let fee_merkle_tree = if block_header.fee_merkle_tree_root().size() == 0 {
1208            // If the commitment tells us that the tree is supposed to be empty, it is convenient to
1209            // just create an empty tree, rather than a commitment-only tree.
1210            FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT)
1211        } else {
1212            FeeMerkleTree::from_commitment(block_header.fee_merkle_tree_root())
1213        };
1214        let block_merkle_tree = if block_header.block_merkle_tree_root().size() == 0 {
1215            // If the commitment tells us that the tree is supposed to be empty, it is convenient to
1216            // just create an empty tree, rather than a commitment-only tree.
1217            BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT)
1218        } else {
1219            BlockMerkleTree::from_commitment(block_header.block_merkle_tree_root())
1220        };
1221
1222        let (reward_merkle_tree_v1, reward_merkle_tree_v2) = match block_header
1223            .reward_merkle_tree_root()
1224        {
1225            Either::Left(reward_tree_v1) => {
1226                let reward_merkle_tree_v2 = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
1227                let reward_merkle_tree_v1 = if reward_tree_v1.size() == 0 {
1228                    RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT)
1229                } else {
1230                    RewardMerkleTreeV1::from_commitment(reward_tree_v1)
1231                };
1232                (reward_merkle_tree_v1, reward_merkle_tree_v2)
1233            },
1234            Either::Right(reward_tree_v2) => {
1235                let reward_merkle_tree_v1 = RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT);
1236                let reward_merkle_tree_v2 = if reward_tree_v2.size() == 0 {
1237                    RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT)
1238                } else {
1239                    RewardMerkleTreeV2::from_commitment(reward_tree_v2)
1240                };
1241                (reward_merkle_tree_v1, reward_merkle_tree_v2)
1242            },
1243        };
1244
1245        Self {
1246            fee_merkle_tree,
1247            block_merkle_tree,
1248            reward_merkle_tree_v2,
1249            reward_merkle_tree_v1,
1250            chain_config: block_header.chain_config(),
1251        }
1252    }
1253    /// Construct a genesis validated state.
1254    fn genesis(instance: &Self::Instance) -> (Self, Self::Delta) {
1255        (instance.genesis_state.clone(), Delta::default())
1256    }
1257}
1258
1259// Required for TestableState
1260#[cfg(any(test, feature = "testing"))]
1261impl std::fmt::Display for ValidatedState {
1262    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1263        write!(f, "{self:#?}")
1264    }
1265}
1266
1267#[cfg(any(test, feature = "testing"))]
1268impl hotshot_types::traits::states::TestableState<SeqTypes> for ValidatedState {
1269    fn create_random_transaction(
1270        _state: Option<&Self>,
1271        rng: &mut dyn rand::RngCore,
1272        _padding: u64,
1273    ) -> crate::Transaction {
1274        crate::Transaction::random(rng)
1275    }
1276}
1277
1278impl MerklizedState<SeqTypes, { Self::ARITY }> for BlockMerkleTree {
1279    type Key = Self::Index;
1280    type Entry = Commitment<Header>;
1281    type T = Sha3Node;
1282    type Commit = Self::Commitment;
1283    type Digest = Sha3Digest;
1284
1285    fn state_type() -> &'static str {
1286        "block_merkle_tree"
1287    }
1288
1289    fn header_state_commitment_field() -> &'static str {
1290        "block_merkle_tree_root"
1291    }
1292
1293    fn tree_height() -> usize {
1294        BLOCK_MERKLE_TREE_HEIGHT
1295    }
1296
1297    fn insert_path(
1298        &mut self,
1299        key: Self::Key,
1300        proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1301    ) -> anyhow::Result<()> {
1302        let Some(elem) = proof.elem() else {
1303            bail!("BlockMerkleTree does not support non-membership proofs");
1304        };
1305        self.remember(key, elem, proof)?;
1306        Ok(())
1307    }
1308}
1309
1310impl MerklizedState<SeqTypes, { Self::ARITY }> for FeeMerkleTree {
1311    type Key = Self::Index;
1312    type Entry = Self::Element;
1313    type T = Sha3Node;
1314    type Commit = Self::Commitment;
1315    type Digest = Sha3Digest;
1316
1317    fn state_type() -> &'static str {
1318        "fee_merkle_tree"
1319    }
1320
1321    fn header_state_commitment_field() -> &'static str {
1322        "fee_merkle_tree_root"
1323    }
1324
1325    fn tree_height() -> usize {
1326        FEE_MERKLE_TREE_HEIGHT
1327    }
1328
1329    fn insert_path(
1330        &mut self,
1331        key: Self::Key,
1332        proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1333    ) -> anyhow::Result<()> {
1334        match proof.elem() {
1335            Some(elem) => self.remember(key, elem, proof)?,
1336            None => self.non_membership_remember(key, proof)?,
1337        }
1338        Ok(())
1339    }
1340}
1341
1342impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV2 {
1343    type Key = Self::Index;
1344    type Entry = Self::Element;
1345    type T = KeccakNode;
1346    type Commit = Self::Commitment;
1347    type Digest = Keccak256Hasher;
1348
1349    fn state_type() -> &'static str {
1350        "reward_merkle_tree_v2"
1351    }
1352
1353    fn header_state_commitment_field() -> &'static str {
1354        "reward_merkle_tree_root"
1355    }
1356
1357    fn tree_height() -> usize {
1358        REWARD_MERKLE_TREE_V2_HEIGHT
1359    }
1360
1361    fn insert_path(
1362        &mut self,
1363        key: Self::Key,
1364        proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1365    ) -> anyhow::Result<()> {
1366        match proof.elem() {
1367            Some(elem) => self.remember(key, elem, proof)?,
1368            None => self.non_membership_remember(key, proof)?,
1369        }
1370        Ok(())
1371    }
1372}
1373
1374impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV1 {
1375    type Key = Self::Index;
1376    type Entry = Self::Element;
1377    type T = Sha3Node;
1378    type Commit = Self::Commitment;
1379    type Digest = Sha3Digest;
1380
1381    fn state_type() -> &'static str {
1382        "reward_merkle_tree"
1383    }
1384
1385    fn header_state_commitment_field() -> &'static str {
1386        "reward_merkle_tree_root"
1387    }
1388
1389    fn tree_height() -> usize {
1390        REWARD_MERKLE_TREE_V1_HEIGHT
1391    }
1392
1393    fn insert_path(
1394        &mut self,
1395        key: Self::Key,
1396        proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1397    ) -> anyhow::Result<()> {
1398        match proof.elem() {
1399            Some(elem) => self.remember(key, elem, proof)?,
1400            None => self.non_membership_remember(key, proof)?,
1401        }
1402        Ok(())
1403    }
1404}
1405
1406#[cfg(test)]
1407mod test {
1408    use std::{sync::Arc, time::Duration};
1409
1410    use hotshot::traits::BlockPayload;
1411    use hotshot_query_service::{testing::mocks::MockVersions, Resolvable};
1412    use hotshot_types::{data::ViewNumber, traits::signature_key::BuilderSignatureKey};
1413    use sequencer_utils::ser::FromStringOrInteger;
1414    use tracing::debug;
1415    use vbs::version::StaticVersion;
1416
1417    use super::*;
1418    use crate::{
1419        eth_signature_key::EthKeyPair, mock::MockStateCatchup, v0_1, v0_2, v0_3, v0_4, v0_5,
1420        BlockSize, FeeAccountProof, FeeMerkleProof, FeeVersion, Leaf, MaxSupportedVersion, Payload,
1421        SequencerVersions, TimestampMillis, Transaction,
1422    };
1423
1424    impl Transaction {
1425        async fn into_mock_header(self) -> (Header, u32) {
1426            let instance = NodeState::mock_v2();
1427            let (payload, metadata) =
1428                Payload::from_transactions([self], &instance.genesis_state, &instance)
1429                    .await
1430                    .unwrap();
1431
1432            let header = Header::genesis::<MockVersions>(&instance, payload.clone(), &metadata);
1433
1434            let header = header.sign();
1435
1436            (header, payload.byte_len().0 as u32)
1437        }
1438    }
1439    impl Header {
1440        /// Build a new header from parent.
1441        fn next(self) -> Self {
1442            let time = OffsetDateTime::now_utc();
1443            let timestamp = time.unix_timestamp() as u64;
1444            let timestamp_millis = TimestampMillis::from_time(&time);
1445
1446            match self {
1447                Header::V1(_) => panic!("You called `Header.next()` on unimplemented version (v1)"),
1448                Header::V2(parent) => Header::V2(v0_2::Header {
1449                    height: parent.height + 1,
1450                    timestamp,
1451                    ..parent.clone()
1452                }),
1453                Header::V3(parent) => Header::V3(v0_3::Header {
1454                    height: parent.height + 1,
1455                    timestamp,
1456                    ..parent.clone()
1457                }),
1458                Header::V4(parent) => Header::V4(v0_4::Header {
1459                    height: parent.height + 1,
1460                    timestamp,
1461                    timestamp_millis,
1462                    ..parent.clone()
1463                }),
1464                Header::V5(parent) => Header::V5(v0_5::Header {
1465                    height: parent.height + 1,
1466                    timestamp,
1467                    timestamp_millis,
1468                    ..parent.clone()
1469                }),
1470            }
1471        }
1472        /// Replaces builder signature w/ invalid one.
1473        fn sign(&self) -> Self {
1474            let key_pair = EthKeyPair::random();
1475            let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1476
1477            let sig = FeeAccount::sign_fee(
1478                &key_pair,
1479                fee_info.amount().as_u64().unwrap(),
1480                self.metadata(),
1481            )
1482            .unwrap();
1483
1484            match self {
1485                Header::V1(_) => panic!("You called `Header.sign()` on unimplemented version (v1)"),
1486                Header::V2(header) => Header::V2(v0_2::Header {
1487                    fee_info,
1488                    builder_signature: Some(sig),
1489                    ..header.clone()
1490                }),
1491                Header::V3(header) => Header::V3(v0_3::Header {
1492                    fee_info,
1493                    builder_signature: Some(sig),
1494                    ..header.clone()
1495                }),
1496                Header::V4(header) => Header::V4(v0_4::Header {
1497                    fee_info,
1498                    builder_signature: Some(sig),
1499                    ..header.clone()
1500                }),
1501                Header::V5(header) => Header::V5(v0_5::Header {
1502                    fee_info,
1503                    builder_signature: Some(sig),
1504                    ..header.clone()
1505                }),
1506            }
1507        }
1508
1509        /// Replaces builder signature w/ invalid one.
1510        fn invalid_builder_signature(&self) -> Self {
1511            let key_pair = EthKeyPair::random();
1512            let key_pair2 = EthKeyPair::random();
1513            let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1514
1515            let sig = FeeAccount::sign_fee(
1516                &key_pair2,
1517                fee_info.amount().as_u64().unwrap(),
1518                self.metadata(),
1519            )
1520            .unwrap();
1521
1522            match self {
1523                Header::V1(_) => panic!(
1524                    "You called `Header.invalid_builder_signature()` on unimplemented version (v1)"
1525                ),
1526                Header::V2(parent) => Header::V2(v0_2::Header {
1527                    fee_info,
1528                    builder_signature: Some(sig),
1529                    ..parent.clone()
1530                }),
1531                Header::V3(parent) => Header::V3(v0_3::Header {
1532                    fee_info,
1533                    builder_signature: Some(sig),
1534                    ..parent.clone()
1535                }),
1536                Header::V4(parent) => Header::V4(v0_4::Header {
1537                    fee_info,
1538                    builder_signature: Some(sig),
1539                    ..parent.clone()
1540                }),
1541                Header::V5(parent) => Header::V5(v0_5::Header {
1542                    fee_info,
1543                    builder_signature: Some(sig),
1544                    ..parent.clone()
1545                }),
1546            }
1547        }
1548    }
1549
1550    impl<'a> ValidatedTransition<'a> {
1551        fn mock(instance: NodeState, parent: &'a Header, proposal: Proposal<'a>) -> Self {
1552            let expected_chain_config = instance.chain_config;
1553            let validation_start_time = OffsetDateTime::now_utc();
1554
1555            Self {
1556                state: instance.genesis_state,
1557                expected_chain_config,
1558                parent,
1559                proposal,
1560                total_rewards_distributed: None,
1561                version: Version { major: 0, minor: 1 },
1562                validation_start_time,
1563            }
1564        }
1565    }
1566
1567    #[test_log::test]
1568    fn test_fee_proofs() {
1569        let mut tree = ValidatedState::default().fee_merkle_tree;
1570        let account1 = Address::random();
1571        let account2 = Address::default();
1572        tracing::info!(%account1, %account2);
1573
1574        let balance1 = U256::from(100);
1575        tree.update(FeeAccount(account1), FeeAmount(balance1))
1576            .unwrap();
1577
1578        // Membership proof.
1579        let (proof1, balance) = FeeAccountProof::prove(&tree, account1).unwrap();
1580        tracing::info!(?proof1, %balance);
1581        assert_eq!(balance, balance1);
1582        assert!(matches!(proof1.proof, FeeMerkleProof::Presence(_)));
1583        assert_eq!(proof1.verify(&tree.commitment()).unwrap(), balance1);
1584
1585        // Non-membership proof.
1586        let (proof2, balance) = FeeAccountProof::prove(&tree, account2).unwrap();
1587        tracing::info!(?proof2, %balance);
1588        assert_eq!(balance, U256::ZERO);
1589        assert!(matches!(proof2.proof, FeeMerkleProof::Absence(_)));
1590        assert_eq!(proof2.verify(&tree.commitment()).unwrap(), U256::ZERO);
1591
1592        // Test forget/remember. We cannot generate proofs in a completely sparse tree:
1593        let mut tree = FeeMerkleTree::from_commitment(tree.commitment());
1594        assert!(FeeAccountProof::prove(&tree, account1).is_none());
1595        assert!(FeeAccountProof::prove(&tree, account2).is_none());
1596        // After remembering the proofs, we can generate proofs again:
1597        proof1.remember(&mut tree).unwrap();
1598        proof2.remember(&mut tree).unwrap();
1599        FeeAccountProof::prove(&tree, account1).unwrap();
1600        FeeAccountProof::prove(&tree, account2).unwrap();
1601    }
1602
1603    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1604    async fn test_validation_l1_head() {
1605        // Setup.
1606        let tx = Transaction::of_size(10);
1607        let (header, block_size) = tx.into_mock_header().await;
1608
1609        // Success Case
1610        let proposal = Proposal::new(&header, block_size);
1611        // Note we are using the same header for parent and proposal,
1612        // this may be OK depending on what we are testing.
1613        ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1614            .validate_l1_head()
1615            .unwrap();
1616
1617        // Error Case
1618        let proposal = Proposal::new(&header, block_size);
1619        let err = proposal.validate_l1_head(u64::MAX).unwrap_err();
1620        assert_eq!(ProposalValidationError::DecrementingL1Head, err);
1621    }
1622
1623    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1624    async fn test_validation_builder_fee() {
1625        // Setup.
1626        let instance = NodeState::mock();
1627        let tx = Transaction::of_size(20);
1628        let (header, block_size) = tx.into_mock_header().await;
1629
1630        // Success Case
1631        let proposal = Proposal::new(&header, block_size);
1632        ValidatedTransition::mock(instance.clone(), &header, proposal)
1633            .validate_builder_fee()
1634            .unwrap();
1635
1636        // Error Case
1637        let header = header.invalid_builder_signature();
1638        let proposal = Proposal::new(&header, block_size);
1639        let err = ValidatedTransition::mock(instance, &header, proposal)
1640            .validate_builder_fee()
1641            .unwrap_err();
1642
1643        tracing::info!(%err, "task failed successfully");
1644        assert_eq!(
1645            ProposalValidationError::BuilderValidationError(
1646                BuilderValidationError::InvalidBuilderSignature
1647            ),
1648            err
1649        );
1650    }
1651
1652    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1653    async fn test_validation_chain_config() {
1654        // Setup.
1655        let instance = NodeState::mock();
1656        let tx = Transaction::of_size(20);
1657        let (header, block_size) = tx.into_mock_header().await;
1658
1659        // Success Case
1660        let proposal = Proposal::new(&header, block_size);
1661        ValidatedTransition::mock(instance.clone(), &header, proposal)
1662            .validate_chain_config()
1663            .unwrap();
1664
1665        // Error Case
1666        let proposal = Proposal::new(&header, block_size);
1667        let expected_chain_config = ChainConfig {
1668            max_block_size: BlockSize(3333),
1669            ..instance.chain_config
1670        };
1671        let err = proposal
1672            .validate_chain_config(&expected_chain_config)
1673            .unwrap_err();
1674
1675        tracing::info!(%err, "task failed successfully");
1676
1677        assert_eq!(
1678            ProposalValidationError::InvalidChainConfig {
1679                expected: Box::new(expected_chain_config),
1680                proposal: Box::new(header.chain_config())
1681            },
1682            err
1683        );
1684    }
1685
1686    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1687    async fn test_validation_max_block_size() {
1688        const MAX_BLOCK_SIZE: usize = 10;
1689
1690        // Setup.
1691        let state = ValidatedState::default();
1692        let expected_chain_config = ChainConfig {
1693            max_block_size: BlockSize::from_integer(MAX_BLOCK_SIZE as u64).unwrap(),
1694            ..state.chain_config.resolve().unwrap()
1695        };
1696        let instance = NodeState::mock().with_chain_config(expected_chain_config);
1697        let tx = Transaction::of_size(20);
1698        let (header, block_size) = tx.into_mock_header().await;
1699
1700        // Error Case
1701        let proposal = Proposal::new(&header, block_size);
1702        let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1703            .validate_block_size()
1704            .unwrap_err();
1705
1706        tracing::info!(%err, "task failed successfully");
1707        assert_eq!(
1708            ProposalValidationError::MaxBlockSizeExceeded {
1709                max_block_size: instance.chain_config.max_block_size,
1710                block_size: BlockSize::from_integer(block_size as u64).unwrap()
1711            },
1712            err
1713        );
1714
1715        // Success Case
1716        let proposal = Proposal::new(&header, 1);
1717        ValidatedTransition::mock(instance, &header, proposal)
1718            .validate_block_size()
1719            .unwrap()
1720    }
1721
1722    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1723    async fn test_validation_base_fee() {
1724        // Setup
1725        let tx = Transaction::of_size(20);
1726        let (header, block_size) = tx.into_mock_header().await;
1727        let state = ValidatedState::default();
1728        let instance = NodeState::mock_v2().with_chain_config(ChainConfig {
1729            base_fee: 1000.into(), // High expected base fee
1730            ..state.chain_config.resolve().unwrap()
1731        });
1732
1733        let proposal = Proposal::new(&header, block_size);
1734        let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1735            .validate_fee()
1736            .unwrap_err();
1737
1738        // Validation fails because the genesis fee (0) is too low.
1739        tracing::info!(%err, "task failed successfully");
1740        assert_eq!(
1741            ProposalValidationError::InsufficientFee {
1742                max_block_size: instance.chain_config.max_block_size,
1743                base_fee: instance.chain_config.base_fee,
1744                proposed_fee: header.fee_info().amount().unwrap()
1745            },
1746            err
1747        );
1748    }
1749
1750    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1751    async fn test_validation_height() {
1752        // Setup
1753        let instance = NodeState::mock_v2();
1754        let tx = Transaction::of_size(10);
1755        let (parent, block_size) = tx.into_mock_header().await;
1756
1757        let proposal = Proposal::new(&parent, block_size);
1758        let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1759            .validate_height()
1760            .unwrap_err();
1761
1762        // Validation fails because the proposal is using same default.
1763        tracing::info!(%err, "task failed successfully");
1764        assert_eq!(
1765            ProposalValidationError::InvalidHeight {
1766                parent_height: parent.height(),
1767                proposal_height: parent.height()
1768            },
1769            err
1770        );
1771
1772        // Success case. Increment height on proposal.
1773        let mut header = parent.clone();
1774        *header.height_mut() += 1;
1775        let proposal = Proposal::new(&header, block_size);
1776
1777        ValidatedTransition::mock(instance, &parent, proposal)
1778            .validate_height()
1779            .unwrap();
1780    }
1781
1782    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1783    async fn test_validation_timestamp_non_dec() {
1784        let tx = Transaction::of_size(10);
1785        let (parent, block_size) = tx.into_mock_header().await;
1786
1787        // Error case
1788        let proposal = Proposal::new(&parent, block_size);
1789        let proposal_timestamp = proposal.header.timestamp();
1790        let err = proposal.validate_timestamp_non_dec(u64::MAX).unwrap_err();
1791
1792        // Validation fails because the proposal is using same default.
1793        tracing::info!(%err, "task failed successfully");
1794        assert_eq!(
1795            ProposalValidationError::DecrementingTimestamp {
1796                proposal_timestamp,
1797                parent_timestamp: u64::MAX,
1798            },
1799            err
1800        );
1801
1802        // Success case (genesis timestamp is `0`).
1803        let proposal = Proposal::new(&parent, block_size);
1804        proposal.validate_timestamp_non_dec(0).unwrap();
1805    }
1806
1807    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1808    async fn test_validation_timestamp_drift() {
1809        // Setup
1810        let instance = NodeState::mock_v2();
1811        let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
1812
1813        let header = parent.clone();
1814        // Error case.
1815        let proposal = Proposal::new(&header, block_size);
1816        let proposal_timestamp = header.timestamp();
1817
1818        let mock_time = OffsetDateTime::now_utc().unix_timestamp() as u64;
1819        // TODO
1820        let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1821            .validate_timestamp()
1822            .unwrap_err();
1823
1824        tracing::info!(%err, "task failed successfully");
1825        assert_eq!(
1826            ProposalValidationError::InvalidTimestampDrift {
1827                proposal: proposal_timestamp,
1828                system: mock_time,
1829                diff: mock_time
1830            },
1831            err
1832        );
1833
1834        let time = OffsetDateTime::now_utc();
1835        let timestamp: u64 = time.unix_timestamp() as u64;
1836        let timestamp_millis = TimestampMillis::from_time(&time).u64();
1837
1838        let mut header = parent.clone();
1839        header.set_timestamp(timestamp - 13, timestamp_millis - 13_000);
1840        let proposal = Proposal::new(&header, block_size);
1841
1842        let err = proposal.validate_timestamp_drift(time).unwrap_err();
1843        tracing::info!(%err, "task failed successfully");
1844        assert_eq!(
1845            ProposalValidationError::InvalidTimestampDrift {
1846                proposal: timestamp - 13,
1847                system: timestamp,
1848                diff: 13
1849            },
1850            err
1851        );
1852
1853        // Success cases.
1854        let mut header = parent.clone();
1855        header.set_timestamp(timestamp, timestamp_millis);
1856        let proposal = Proposal::new(&header, block_size);
1857        proposal.validate_timestamp_drift(time).unwrap();
1858
1859        header.set_timestamp(timestamp - 11, timestamp_millis - 11_000);
1860        let proposal = Proposal::new(&header, block_size);
1861        proposal.validate_timestamp_drift(time).unwrap();
1862
1863        header.set_timestamp(timestamp - 12, timestamp_millis - 12_000);
1864        let proposal = Proposal::new(&header, block_size);
1865        proposal.validate_timestamp_drift(time).unwrap();
1866    }
1867
1868    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1869    async fn test_validation_fee_root() {
1870        // Setup
1871        let instance = NodeState::mock_v2();
1872        let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1873
1874        // Success case.
1875        let proposal = Proposal::new(&header, block_size);
1876        ValidatedTransition::mock(instance.clone(), &header, proposal)
1877            .validate_fee_merkle_tree()
1878            .unwrap();
1879
1880        // Error case.
1881        let proposal = Proposal::new(&header, block_size);
1882
1883        let mut fee_merkle_tree = instance.genesis_state.fee_merkle_tree;
1884        fee_merkle_tree
1885            .update_with(FeeAccount::default(), |_| Some(100.into()))
1886            .unwrap();
1887
1888        let err = proposal
1889            .validate_block_merkle_tree(fee_merkle_tree.commitment())
1890            .unwrap_err();
1891
1892        tracing::info!(%err, "task failed successfully");
1893        assert_eq!(
1894            ProposalValidationError::InvalidBlockRoot {
1895                expected_root: fee_merkle_tree.commitment(),
1896                proposal_root: header.block_merkle_tree_root(),
1897            },
1898            err
1899        );
1900    }
1901
1902    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1903    async fn test_validation_block_root() {
1904        // Setup.
1905        let instance = NodeState::mock_v2();
1906        let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1907
1908        // Success case.
1909        let proposal = Proposal::new(&header, block_size);
1910        ValidatedTransition::mock(instance.clone(), &header, proposal)
1911            .validate_block_merkle_tree()
1912            .unwrap();
1913
1914        // Error case.
1915        let proposal = Proposal::new(&header, block_size);
1916        let mut block_merkle_tree = instance.genesis_state.block_merkle_tree;
1917        block_merkle_tree.push(header.commitment()).unwrap();
1918        block_merkle_tree
1919            .push(header.clone().next().commitment())
1920            .unwrap();
1921
1922        let err = proposal
1923            .validate_block_merkle_tree(block_merkle_tree.commitment())
1924            .unwrap_err();
1925
1926        tracing::info!(%err, "task failed successfully");
1927        assert_eq!(
1928            ProposalValidationError::InvalidBlockRoot {
1929                expected_root: block_merkle_tree.commitment(),
1930                proposal_root: proposal.header.block_merkle_tree_root(),
1931            },
1932            err
1933        );
1934    }
1935
1936    #[test_log::test(tokio::test(flavor = "multi_thread"))]
1937    async fn test_validation_ns_table() {
1938        use NsTableValidationError::InvalidFinalOffset;
1939        // Setup.
1940        let tx = Transaction::of_size(10);
1941        let (header, block_size) = tx.into_mock_header().await;
1942
1943        // Success case.
1944        let proposal = Proposal::new(&header, block_size);
1945        ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1946            .validate_namespace_table()
1947            .unwrap();
1948
1949        // Error case
1950        let proposal = Proposal::new(&header, 40);
1951        let err = ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1952            .validate_namespace_table()
1953            .unwrap_err();
1954        tracing::info!(%err, "task failed successfully");
1955        // TODO NsTable has other error variants, but these should be
1956        // tested in unit tests of `NsTable.validate()`.
1957        assert_eq!(
1958            ProposalValidationError::InvalidNsTable(InvalidFinalOffset),
1959            err
1960        );
1961    }
1962
1963    #[test_log::test]
1964    fn test_charge_fee() {
1965        let src = FeeAccount::generated_from_seed_indexed([0; 32], 0).0;
1966        let dst = FeeAccount::generated_from_seed_indexed([0; 32], 1).0;
1967        let amt = FeeAmount::from(1);
1968
1969        let fee_info = FeeInfo::new(src, amt);
1970
1971        let new_state = || {
1972            let mut state = ValidatedState::default();
1973            state.prefund_account(src, amt);
1974            state
1975        };
1976
1977        tracing::info!("test successful fee");
1978        let mut state = new_state();
1979        state.charge_fee(fee_info, dst).unwrap();
1980        assert_eq!(state.balance(src), Some(0.into()));
1981        assert_eq!(state.balance(dst), Some(amt));
1982
1983        tracing::info!("test insufficient balance");
1984        let err = state.charge_fee(fee_info, dst).unwrap_err();
1985        assert_eq!(state.balance(src), Some(0.into()));
1986        assert_eq!(state.balance(dst), Some(amt));
1987        assert_eq!(
1988            FeeError::InsufficientFunds {
1989                balance: None,
1990                amount: amt
1991            },
1992            err
1993        );
1994
1995        tracing::info!("test src not in memory");
1996        let mut state = new_state();
1997        state.fee_merkle_tree.forget(src).expect_ok().unwrap();
1998        assert_eq!(
1999            FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
2000            state.charge_fee(fee_info, dst).unwrap_err()
2001        );
2002
2003        tracing::info!("test dst not in memory");
2004        let mut state = new_state();
2005        state.prefund_account(dst, amt);
2006        state.fee_merkle_tree.forget(dst).expect_ok().unwrap();
2007        assert_eq!(
2008            FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
2009            state.charge_fee(fee_info, dst).unwrap_err()
2010        );
2011    }
2012
2013    #[test]
2014    fn test_fee_amount_serde_json_as_decimal() {
2015        let amt = FeeAmount::from(123);
2016        let serialized = serde_json::to_string(&amt).unwrap();
2017
2018        // The value is serialized as a decimal string.
2019        assert_eq!(serialized, "\"123\"");
2020
2021        // Deserialization produces the original value
2022        let deserialized: FeeAmount = serde_json::from_str(&serialized).unwrap();
2023        assert_eq!(deserialized, amt);
2024    }
2025
2026    #[test]
2027    fn test_fee_amount_from_units() {
2028        for (unit, multiplier) in [
2029            ("wei", 1),
2030            ("gwei", 1_000_000_000),
2031            ("eth", 1_000_000_000_000_000_000),
2032        ] {
2033            let amt: FeeAmount = serde_json::from_str(&format!("\"1 {unit}\"")).unwrap();
2034            assert_eq!(amt, multiplier.into());
2035        }
2036    }
2037
2038    #[test]
2039    fn test_fee_amount_serde_json_from_hex() {
2040        // For backwards compatibility, fee amounts can also be deserialized from a 0x-prefixed hex
2041        // string.
2042        let amt: FeeAmount = serde_json::from_str("\"0x123\"").unwrap();
2043        assert_eq!(amt, FeeAmount::from(0x123));
2044    }
2045
2046    #[test]
2047    fn test_fee_amount_serde_json_from_number() {
2048        // For convenience, fee amounts can also be deserialized from a JSON number.
2049        let amt: FeeAmount = serde_json::from_str("123").unwrap();
2050        assert_eq!(amt, FeeAmount::from(123));
2051    }
2052
2053    #[test]
2054    fn test_fee_amount_serde_bincode_unchanged() {
2055        // For non-human-readable formats, FeeAmount just serializes as the underlying U256.
2056        // note: for backward compat, it has to be the same as ethers' U256 instead of alloy's
2057        let n = ethers_core::types::U256::from(123);
2058        let amt = FeeAmount(U256::from(123));
2059        assert_eq!(
2060            bincode::serialize(&n).unwrap(),
2061            bincode::serialize(&amt).unwrap(),
2062        );
2063    }
2064
2065    #[test_log::test(tokio::test(flavor = "multi_thread"))]
2066    async fn test_validate_builder_fee() {
2067        let max_block_size = 10;
2068
2069        let validated_state = ValidatedState::default();
2070        let instance_state = NodeState::mock().with_chain_config(ChainConfig {
2071            base_fee: 1000.into(), // High base fee
2072            max_block_size: max_block_size.into(),
2073            ..validated_state.chain_config.resolve().unwrap()
2074        });
2075
2076        let parent: Leaf2 =
2077            Leaf::genesis::<MockVersions>(&instance_state.genesis_state, &instance_state)
2078                .await
2079                .into();
2080        let header = parent.block_header().clone();
2081        let metadata = parent.block_header().metadata();
2082
2083        debug!("{:?}", header.version());
2084
2085        let key_pair = EthKeyPair::random();
2086        let account = key_pair.fee_account();
2087
2088        let data = header.fee_info()[0].amount().as_u64().unwrap();
2089        let sig = FeeAccount::sign_builder_message(&key_pair, &data.to_be_bytes()).unwrap();
2090
2091        // ensure the signature is indeed valid
2092        account
2093            .validate_builder_signature(&sig, &data.to_be_bytes())
2094            .then_some(())
2095            .unwrap();
2096
2097        // test v1 sig
2098        let sig = FeeAccount::sign_fee(&key_pair, data, metadata).unwrap();
2099
2100        let header = match header {
2101            Header::V1(header) => Header::V1(v0_1::Header {
2102                builder_signature: Some(sig),
2103                fee_info: FeeInfo::new(account, data),
2104                ..header
2105            }),
2106            Header::V2(header) => Header::V2(v0_2::Header {
2107                builder_signature: Some(sig),
2108                fee_info: FeeInfo::new(account, data),
2109                ..header
2110            }),
2111            Header::V3(header) => Header::V3(v0_3::Header {
2112                builder_signature: Some(sig),
2113                fee_info: FeeInfo::new(account, data),
2114                ..header
2115            }),
2116            Header::V4(header) => Header::V4(v0_4::Header {
2117                builder_signature: Some(sig),
2118                fee_info: FeeInfo::new(account, data),
2119                ..header
2120            }),
2121            Header::V5(header) => Header::V5(v0_5::Header {
2122                builder_signature: Some(sig),
2123                fee_info: FeeInfo::new(account, data),
2124                ..header
2125            }),
2126        };
2127
2128        validate_builder_fee(&header).unwrap();
2129    }
2130
2131    #[test_log::test(tokio::test(flavor = "multi_thread"))]
2132    async fn test_validate_total_rewards_distributed() {
2133        let instance = NodeState::mock().with_genesis_version(Version { major: 0, minor: 4 });
2134
2135        let (payload, metadata) =
2136            Payload::from_transactions([], &instance.genesis_state, &instance)
2137                .await
2138                .unwrap();
2139
2140        let header = Header::genesis::<SequencerVersions<StaticVersion<0, 4>, StaticVersion<0, 4>>>(
2141            &instance,
2142            payload.clone(),
2143            &metadata,
2144        );
2145
2146        let validated_state = ValidatedState::default();
2147        let actual_total = RewardAmount::from(1000u64);
2148        let block_size = 100u32;
2149
2150        let proposed_header = match header.clone() {
2151            Header::V4(mut h) => {
2152                h.total_reward_distributed = actual_total;
2153                Header::V4(h)
2154            },
2155            _ => unreachable!("Expected V4 header"),
2156        };
2157
2158        let validation_start_time = OffsetDateTime::now_utc();
2159        let validated_transition = ValidatedTransition::new(
2160            validated_state.clone(),
2161            &header,
2162            Proposal::new(&proposed_header, block_size),
2163            Some(actual_total),
2164            StaticVersion::<0, 4>::version(),
2165            validation_start_time,
2166        );
2167
2168        validated_transition
2169            .validate_total_rewards_distributed()
2170            .unwrap();
2171
2172        let wrong_total = RewardAmount::from(2000u64);
2173        let proposed_header = match header.clone() {
2174            Header::V4(mut h) => {
2175                h.total_reward_distributed = wrong_total;
2176                Header::V4(h)
2177            },
2178            _ => unreachable!("Expected V4 header"),
2179        };
2180
2181        ValidatedTransition::new(
2182            validated_state.clone(),
2183            &header,
2184            Proposal::new(&proposed_header, block_size),
2185            Some(actual_total),
2186            StaticVersion::<0, 4>::version(),
2187            validation_start_time,
2188        )
2189        .validate_total_rewards_distributed()
2190        .unwrap_err();
2191    }
2192
2193    #[test_log::test(tokio::test(flavor = "multi_thread"))]
2194    async fn test_regression_slow_validation_timestamp_drift() {
2195        let instance = NodeState::mock_v2();
2196        let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
2197
2198        let validation_start_time = OffsetDateTime::now_utc();
2199        let timestamp = validation_start_time.unix_timestamp() as u64;
2200        let timestamp_millis = TimestampMillis::from_time(&validation_start_time).u64();
2201
2202        let mut header = parent.clone();
2203        header.set_timestamp(timestamp, timestamp_millis);
2204
2205        std::thread::sleep(Duration::from_secs(13));
2206
2207        // Validation fails if we pass the current timestamp (emulates issue before fix)
2208        let proposal_without_fix = Proposal::new(&header, block_size);
2209        let err = ValidatedTransition::new(
2210            instance.genesis_state.clone(),
2211            &parent,
2212            proposal_without_fix,
2213            None,
2214            MaxSupportedVersion::version(),
2215            OffsetDateTime::now_utc(),
2216        )
2217        .validate_timestamp()
2218        .unwrap_err();
2219
2220        assert!(matches!(
2221            err,
2222            ProposalValidationError::InvalidTimestampDrift { .. }
2223        ));
2224
2225        // Validation succeeds if we pass a validation start timestamp
2226        let proposal = Proposal::new(&header, block_size);
2227        ValidatedTransition::new(
2228            instance.genesis_state.clone(),
2229            &parent,
2230            proposal,
2231            None,
2232            MaxSupportedVersion::version(),
2233            validation_start_time,
2234        )
2235        .validate_timestamp()
2236        .unwrap();
2237    }
2238
2239    // Checks that slow catchup does not cause timestamp drift validation to fail.
2240    #[test_log::test(tokio::test(flavor = "multi_thread"))]
2241    async fn test_validate_and_apply_header_slow_catchup_succeeds() {
2242        // Using v2 here because mock_v3 is lacking for epoch related validation to work.
2243        let mut instance = NodeState::mock_v2();
2244
2245        let mut genesis_state = instance.genesis_state.clone();
2246
2247        // We need an element in the tree to forget it and trigger catchup.
2248        genesis_state
2249            .fee_merkle_tree
2250            .update(FeeAccount::default(), FeeAmount::from(1u64))
2251            .unwrap();
2252        instance.genesis_state = genesis_state.clone();
2253
2254        let genesis = Leaf::genesis::<MockVersions>(&genesis_state, &instance).await;
2255        let parent_leaf: Leaf2 = genesis.into();
2256        let parent_header = parent_leaf.block_header().clone();
2257
2258        let mut expected_block_tree = genesis_state.block_merkle_tree.clone();
2259        expected_block_tree.push(parent_header.commit()).unwrap();
2260
2261        let proposed_header = match parent_header {
2262            Header::V2(header) => Header::V2(v0_2::Header {
2263                height: header.height + 1,
2264                timestamp: OffsetDateTime::now_utc().unix_timestamp() as u64,
2265                block_merkle_tree_root: expected_block_tree.commitment(),
2266                chain_config: header.chain_config.commit().into(),
2267                ..header
2268            }),
2269            _ => panic!("Expected V2 header"),
2270        };
2271
2272        let slow_catchup =
2273            MockStateCatchup::from_iter([(ViewNumber::new(0), Arc::new(genesis_state.clone()))])
2274                .with_delay(Duration::from_secs(13));
2275        instance.state_catchup = Arc::new(slow_catchup);
2276
2277        // Forget leaf to trigger catchup
2278        genesis_state
2279            .fee_merkle_tree
2280            .forget(FeeAccount::default())
2281            .expect_ok()
2282            .unwrap();
2283
2284        genesis_state
2285            .validate_and_apply_header(
2286                &instance,
2287                &parent_leaf,
2288                &proposed_header,
2289                0, /* payload_byte_len */
2290                FeeVersion::version(),
2291                0, /* view_number */
2292            )
2293            .await
2294            .unwrap();
2295    }
2296}