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