Skip to main content

espresso_types/v0/impls/
state.rs

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