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