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("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)]
186/// State to be validated by replicas.
187pub struct ValidatedState {
188    /// Frontier of [`BlockMerkleTree`]
189    pub block_merkle_tree: BlockMerkleTree,
190    /// Frontier of [`FeeMerkleTree`]
191    pub fee_merkle_tree: FeeMerkleTree,
192    pub reward_merkle_tree_v1: RewardMerkleTreeV1,
193    pub reward_merkle_tree_v2: RewardMerkleTreeV2,
194    /// Configuration [`Header`] proposals will be validated against.
195    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        // Words of wisdom from @mrain: "capacity = arity^height"
207        // "For index space 2^160, arity 256 (2^8),
208        // you should set the height as 160/8=20"
209        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    /// Prefund an account with a given amount. Only for demo purposes.
241    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    /// Find accounts that are not in memory.
254    ///
255    /// As an optimization we could try to apply updates and return the
256    /// forgotten accounts to be fetched from peers and update them later.
257    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    /// Check if the merkle tree is available
306    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    /// Insert a fee deposit receipt
319    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        // pushing a block into merkle tree shouldn't fail
337        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    /// Charge a fee to an account, transferring the funds to the fee recipient account.
365    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        // Deduct the fee from the paying account.
373        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                // Return an error without updating the account.
379                err = Some(FeeError::InsufficientFunds { balance, amount });
380                return balance;
381            };
382            if updated == FeeAmount::default() {
383                // Delete the account from the tree if its balance ended up at 0; this saves some
384                // space since the account is no longer carrying any information.
385                None
386            } else {
387                // Otherwise store the updated balance.
388                Some(updated)
389            }
390        })?;
391
392        // Fail if there was an error during `persistent_update_with` (e.g. insufficient balance).
393        if let Some(err) = err {
394            return Err(err);
395        }
396
397        // If we successfully deducted the fee from the source account, increment the balance of the
398        // recipient account.
399        let fee_state = fee_state.persistent_update_with(recipient, |balance| {
400            Some(balance.copied().unwrap_or_default() + amount)
401        })?;
402
403        // If the whole update was successful, update the original state.
404        self.fee_merkle_tree = fee_state;
405        Ok(())
406    }
407}
408/// Block Proposal to be verified and applied.
409#[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    /// The L1 head block number in the proposal must be non-decreasing relative
420    /// to the parent.
421    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    /// The [`ChainConfig`] of proposal must be equal to the one stored in state.
428    ///
429    /// Equality is checked by comparing commitments.
430    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    /// The timestamp must be non-decreasing relative to parent.
445    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    /// The timestamp must not drift too much from local system time.
460    ///
461    /// The tolerance is currently `12` seconds. This value may be moved to
462    /// configuration in the future.
463    fn validate_timestamp_drift(&self, system_time: u64) -> Result<(), ProposalValidationError> {
464        // TODO 12 seconds of tolerance should be enough for reasonably
465        // configured nodes, but we should make this configurable.
466        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    /// The `timestamp` and `timestamp_millis` fields must be coherent
479    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    /// The proposed ['BlockMerkleTree'] must match the one in ['ValidatedState'].
491    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/// Type to hold cloned validated state and provide validation methods.
506///
507/// The [Self::validate] method must be called to validate the proposal.
508#[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    /// Top level validation routine. Performs all validation units in
541    /// the given order.
542    /// ```ignore
543    /// self.validate_timestamp()?;
544    /// self.validate_builder_fee()?;
545    /// self.validate_height()?;
546    /// self.validate_chain_config()?;
547    /// self.validate_block_size()?;
548    /// self.validate_fee()?;
549    /// self.validate_fee_merkle_tree()?;
550    /// self.validate_block_merkle_tree()?;
551    /// self.validate_l1_finalized()?;
552    /// self.validate_l1_head()?;
553    /// self.validate_namespace_table()?;
554    /// self.validate_total_rewards_distributed()?;
555    /// ```
556    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    /// The proposal [Header::l1_finalized] must be `Some` and non-decreasing relative to parent.
575    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            // We are keeping the `Option` in the error b/c its the
581            // cleanest way to represent all the different error
582            // cases. The hash seems less useful and explodes the size
583            // of the error, so we strip it out.
584            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    /// Wait for our view of the L1 chain to catch up to the proposal.
593    ///
594    /// The finalized [L1BlockInfo](super::L1BlockInfo) in the proposal must match the one fetched
595    /// from L1.
596    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    /// Wait for our view of the latest L1 block number to catch up to the
603    /// proposal.
604    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    /// Wait for our view of the finalized L1 block number to catch up to the
610    /// proposal.
611    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    /// Ensure that L1 Head on proposal is not decreasing.
631    fn validate_l1_head(&self) -> Result<(), ProposalValidationError> {
632        self.proposal.validate_l1_head(self.parent.l1_head())?;
633        Ok(())
634    }
635    /// Validate basic numerical soundness and builder accounts by
636    /// verifying signatures. Signatures are identified by index of fee `Vec`.
637    fn validate_builder_fee(&self) -> Result<(), ProposalValidationError> {
638        // TODO move logic from stand alone fn to here.
639        if let Err(err) = validate_builder_fee(self.proposal.header) {
640            return Err(ProposalValidationError::BuilderValidationError(err));
641        }
642        Ok(())
643    }
644    /// Validates proposals [`ChainConfig`] against expectation by comparing commitments.
645    fn validate_chain_config(&self) -> Result<(), ProposalValidationError> {
646        self.proposal
647            .validate_chain_config(&self.expected_chain_config)?;
648        Ok(())
649    }
650    /// Validate that proposal block size does not exceed configured
651    /// `ChainConfig.max_block_size`.
652    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    /// Validate that [`FeeAmount`] that is
663    /// sufficient for block size.
664    fn validate_fee(&self) -> Result<(), ProposalValidationError> {
665        // TODO this should be updated to `base_fee * bundle_size` when we have
666        // VID per bundle or namespace.
667        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    /// Validate that proposal height is `parent_height + 1`.
681    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    /// Validate timestamp is not decreasing relative to parent and is
692    /// within a given tolerance of system time. Tolerance is
693    /// currently 12 seconds. This value may be moved to configuration
694    /// in the future. Do this check first so we don't add unnecessary drift.
695    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        // Validate timestamp hasn't drifted too much from system time.
702        let system_time: u64 = OffsetDateTime::now_utc().unix_timestamp() as u64;
703        self.proposal.validate_timestamp_drift(system_time)?;
704
705        Ok(())
706    }
707    /// Validate [`BlockMerkleTree`] by comparing proposed commitment
708    /// that stored in [`ValidatedState`].
709    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    /// Validate [`RewardMerkleTreeV2`] by comparing proposed commitment
718    /// against that stored in [`ValidatedState`].
719    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    /// Validate [`FeeMerkleTree`] by comparing proposed commitment
745    /// against that stored in [`ValidatedState`].
746    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    /// Proxy to [`super::NsTable::validate()`].
758    fn validate_namespace_table(&self) -> Result<(), ProposalValidationError> {
759        self.proposal
760            .header
761            .ns_table()
762            // Should be safe since `u32` will always fit in a `usize`.
763            .validate(&PayloadByteLen(self.proposal.block_size as usize))
764            .map_err(ProposalValidationError::from)
765    }
766
767    /// Validate that the total rewards distributed in the proposed header matches the actual distributed amount.
768    /// This field is only present in >= V4 version.
769    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                // This should never happen - if version >= V4, total_rewards_distributed must be Some
773                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
841/// Validate builder accounts by verifying signatures. All fees are
842/// verified against signature by index.
843fn validate_builder_fee(proposed_header: &Header) -> Result<(), BuilderValidationError> {
844    // TODO since we are iterating, should we include account/amount in errors?
845    for (fee_info, signature) in proposed_header
846        .fee_info()
847        .iter()
848        .zip(proposed_header.builder_signature())
849    {
850        // check that `amount` fits in a u64
851        fee_info
852            .amount()
853            .as_u64()
854            .ok_or(BuilderValidationError::FeeAmountOutOfRange(fee_info.amount))?;
855
856        // Verify signatures.
857        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    /// Updates state with [`Header`] proposal.
879    ///   * Clones and updates [`ValidatedState`] (avoiding mutation).
880    ///   * Resolves [`ChainConfig`].
881    ///   * Performs catchup.
882    ///   * Charges fees.
883    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        // Clone state to avoid mutation. Consumer can take update
893        // through returned value.
894        let mut validated_state = self.clone();
895        validated_state.apply_upgrade(instance, version);
896
897        // TODO double check there is not some possibility we are
898        // validating proposal values against ChainConfig of the proposal.
899        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        // Find missing fee state entries. We will need to use the builder account which is paying a
916        // fee and the recipient account which is receiving it, plus any counts receiving deposits
917        // in this block.
918        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        // Ensure merkle tree has frontier
929        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        // Fetch missing fee state entries
946        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            // Remember the fee state entries
965            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        // total_rewards_distributed is only present in >= V4
982        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            // Version >= V4 but no rewards were distributed because epoch <= first epoch + 1
1000            Some(Default::default())
1001        };
1002
1003        Ok((validated_state, delta, total_rewards_distributed))
1004    }
1005
1006    /// Updates the `ValidatedState` if a protocol upgrade has occurred.
1007    pub(crate) fn apply_upgrade(&mut self, instance: &NodeState, version: Version) {
1008        // Check for protocol upgrade based on sequencer version
1009        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    /// Retrieves the `ChainConfig`.
1028    ///
1029    ///  Returns the `NodeState` `ChainConfig` if the `ValidatedState` `ChainConfig` commitment matches the `NodeState` `ChainConfig`` commitment.
1030    ///  If the commitments do not match, it returns the `ChainConfig` available in either `ValidatedState` or proposed header.
1031    ///  If neither has the `ChainConfig`, it fetches the config from the peers.
1032    ///
1033    /// Returns an error if it fails to fetch the `ChainConfig` from the peers.
1034    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    // Rewards are distributed only if the current epoch is not the first or second epoch
1099    // this is because we don't have stake table from the contract for the first two epochs
1100    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    /// Validate parent against known values (from state) and validate
1134    /// proposal descends from parent. Returns updated `ValidatedState`.
1135    #[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            // TODO We can add this logic to `ValidatedTransition` or do something similar to that here.
1154            .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        // Validate the proposal.
1170        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        // log successful progress about once in 10 - 20 seconds,
1183        // TODO: we may want to make this configurable
1184        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    /// Construct the state with the given block header.
1190    ///
1191    /// This can also be used to rebuild the state for catchup.
1192    fn from_header(block_header: &Header) -> Self {
1193        let fee_merkle_tree = if block_header.fee_merkle_tree_root().size() == 0 {
1194            // If the commitment tells us that the tree is supposed to be empty, it is convenient to
1195            // just create an empty tree, rather than a commitment-only tree.
1196            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            // If the commitment tells us that the tree is supposed to be empty, it is convenient to
1202            // just create an empty tree, rather than a commitment-only tree.
1203            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    /// Construct a genesis validated state.
1240    fn genesis(instance: &Self::Instance) -> (Self, Self::Delta) {
1241        (instance.genesis_state.clone(), Delta::default())
1242    }
1243}
1244
1245// Required for TestableState
1246#[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        /// Build a new header from parent.
1424        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        /// Replaces builder signature w/ invalid one.
1456        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        /// Replaces builder signature w/ invalid one.
1493        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        // Membership proof.
1560        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        // Non-membership proof.
1567        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        // Test forget/remember. We cannot generate proofs in a completely sparse tree:
1574        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        // After remembering the proofs, we can generate proofs again:
1578        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        // Setup.
1587        let tx = Transaction::of_size(10);
1588        let (header, block_size) = tx.into_mock_header().await;
1589
1590        // Success Case
1591        let proposal = Proposal::new(&header, block_size);
1592        // Note we are using the same header for parent and proposal,
1593        // this may be OK depending on what we are testing.
1594        ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1595            .validate_l1_head()
1596            .unwrap();
1597
1598        // Error Case
1599        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        // Setup.
1607        let instance = NodeState::mock();
1608        let tx = Transaction::of_size(20);
1609        let (header, block_size) = tx.into_mock_header().await;
1610
1611        // Success Case
1612        let proposal = Proposal::new(&header, block_size);
1613        ValidatedTransition::mock(instance.clone(), &header, proposal)
1614            .validate_builder_fee()
1615            .unwrap();
1616
1617        // Error Case
1618        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        // Setup.
1636        let instance = NodeState::mock();
1637        let tx = Transaction::of_size(20);
1638        let (header, block_size) = tx.into_mock_header().await;
1639
1640        // Success Case
1641        let proposal = Proposal::new(&header, block_size);
1642        ValidatedTransition::mock(instance.clone(), &header, proposal)
1643            .validate_chain_config()
1644            .unwrap();
1645
1646        // Error Case
1647        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        // Setup.
1672        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        // Error Case
1682        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        // Success Case
1697        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        // Setup
1706        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(), // High expected base fee
1711            ..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        // Validation fails because the genesis fee (0) is too low.
1720        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        // Setup
1734        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        // Validation fails because the proposal is using same default.
1744        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        // Success case. Increment height on proposal.
1754        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        // Error case
1769        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        // Validation fails because the proposal is using same default.
1774        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        // Success case (genesis timestamp is `0`).
1784        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        // Setup
1791        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        // Error case.
1796        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        // TODO
1801        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        // Success cases.
1835        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        // Setup
1852        let instance = NodeState::mock_v2();
1853        let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1854
1855        // Success case.
1856        let proposal = Proposal::new(&header, block_size);
1857        ValidatedTransition::mock(instance.clone(), &header, proposal)
1858            .validate_fee_merkle_tree()
1859            .unwrap();
1860
1861        // Error case.
1862        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        // Setup.
1886        let instance = NodeState::mock_v2();
1887        let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1888
1889        // Success case.
1890        let proposal = Proposal::new(&header, block_size);
1891        ValidatedTransition::mock(instance.clone(), &header, proposal)
1892            .validate_block_merkle_tree()
1893            .unwrap();
1894
1895        // Error case.
1896        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        // Setup.
1921        let tx = Transaction::of_size(10);
1922        let (header, block_size) = tx.into_mock_header().await;
1923
1924        // Success case.
1925        let proposal = Proposal::new(&header, block_size);
1926        ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1927            .validate_namespace_table()
1928            .unwrap();
1929
1930        // Error case
1931        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        // TODO NsTable has other error variants, but these should be
1937        // tested in unit tests of `NsTable.validate()`.
1938        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        // The value is serialized as a decimal string.
2000        assert_eq!(serialized, "\"123\"");
2001
2002        // Deserialization produces the original value
2003        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        // For backwards compatibility, fee amounts can also be deserialized from a 0x-prefixed hex
2022        // string.
2023        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        // For convenience, fee amounts can also be deserialized from a JSON number.
2030        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        // For non-human-readable formats, FeeAmount just serializes as the underlying U256.
2037        // note: for backward compat, it has to be the same as ethers' U256 instead of alloy's
2038        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(), // High base fee
2053            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        // ensure the signature is indeed valid
2073        account
2074            .validate_builder_signature(&sig, &data.to_be_bytes())
2075            .then_some(())
2076            .unwrap();
2077
2078        // test v1 sig
2079        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}