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("Total rewards mismatch: proposed header has {proposed} but actual is {actual}")]
187 TotalRewardsMismatch {
188 proposed: RewardAmount,
189 actual: RewardAmount,
190 },
191 #[error("Leader counts missing in V6 header")]
192 LeaderCountsMissing,
193 #[error("Leader index missing for V6 validation")]
194 LeaderIndexMissing,
195 #[error("Leader counts should reset at epoch start but didn't")]
196 LeaderCountsNotReset,
197 #[error("Invalid leader counts: expected {expected:?}, proposed {proposed:?}")]
198 InvalidLeaderCounts {
199 expected: Box<LeaderCounts>,
200 proposed: Box<LeaderCounts>,
201 },
202}
203
204impl StateDelta for Delta {}
205
206#[derive(Hash, Clone, Deserialize, Serialize, PartialEq, Eq)]
207pub struct ValidatedState {
209 pub block_merkle_tree: BlockMerkleTree,
211 pub fee_merkle_tree: FeeMerkleTree,
213 pub reward_merkle_tree_v1: RewardMerkleTreeV1,
214 pub reward_merkle_tree_v2: RewardMerkleTreeV2,
215 pub chain_config: ResolvableChainConfig,
217}
218
219impl std::fmt::Debug for ValidatedState {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 f.debug_struct("ValidatedState")
222 .field("block_merkle_tree", &self.block_merkle_tree.commitment())
223 .field("fee_merkle_tree", &self.fee_merkle_tree.commitment())
224 .field(
225 "reward_merkle_tree_v1",
226 &self.reward_merkle_tree_v1.commitment(),
227 )
228 .field(
229 "reward_merkle_tree_v2",
230 &self.reward_merkle_tree_v2.commitment(),
231 )
232 .field("chain_config", &self.chain_config)
233 .finish()
234 }
235}
236
237impl Default for ValidatedState {
238 fn default() -> Self {
239 let block_merkle_tree = BlockMerkleTree::from_elems(
240 Some(BLOCK_MERKLE_TREE_HEIGHT),
241 Vec::<Commitment<Header>>::new(),
242 )
243 .unwrap();
244
245 let fee_merkle_tree = FeeMerkleTree::from_kv_set(
249 FEE_MERKLE_TREE_HEIGHT,
250 Vec::<(FeeAccount, FeeAmount)>::new(),
251 )
252 .unwrap();
253
254 let reward_merkle_tree_v1 = RewardMerkleTreeV1::from_kv_set(
255 REWARD_MERKLE_TREE_V1_HEIGHT,
256 Vec::<(RewardAccountV1, RewardAmount)>::new(),
257 )
258 .unwrap();
259
260 let reward_merkle_tree_v2 = RewardMerkleTreeV2::from_kv_set(
261 REWARD_MERKLE_TREE_V2_HEIGHT,
262 Vec::<(RewardAccountV2, RewardAmount)>::new(),
263 )
264 .unwrap();
265
266 let chain_config = ResolvableChainConfig::from(ChainConfig::default());
267
268 Self {
269 block_merkle_tree,
270 fee_merkle_tree,
271 reward_merkle_tree_v1,
272 reward_merkle_tree_v2,
273 chain_config,
274 }
275 }
276}
277
278impl ValidatedState {
279 pub fn prefund_account(&mut self, account: FeeAccount, amount: FeeAmount) {
281 self.fee_merkle_tree.update(account, amount).unwrap();
282 }
283
284 pub fn balance(&mut self, account: FeeAccount) -> Option<FeeAmount> {
285 match self.fee_merkle_tree.lookup(account) {
286 LookupResult::Ok(balance, _) => Some(balance),
287 LookupResult::NotFound(_) => Some(0.into()),
288 LookupResult::NotInMemory => None,
289 }
290 }
291
292 pub fn forgotten_accounts(
297 &self,
298 accounts: impl IntoIterator<Item = FeeAccount>,
299 ) -> Vec<FeeAccount> {
300 accounts
301 .into_iter()
302 .unique()
303 .filter(|account| {
304 self.fee_merkle_tree
305 .lookup(*account)
306 .expect_not_in_memory()
307 .is_ok()
308 })
309 .collect()
310 }
311
312 pub fn forgotten_reward_accounts_v2(
313 &self,
314 accounts: impl IntoIterator<Item = RewardAccountV2>,
315 ) -> Vec<RewardAccountV2> {
316 accounts
317 .into_iter()
318 .filter(|account| {
319 self.reward_merkle_tree_v2
320 .lookup(*account)
321 .expect_not_in_memory()
322 .is_ok()
323 })
324 .collect()
325 }
326
327 pub fn forgotten_reward_accounts_v1(
328 &self,
329 accounts: impl IntoIterator<Item = RewardAccountV1>,
330 ) -> Vec<RewardAccountV1> {
331 accounts
332 .into_iter()
333 .unique()
334 .filter(|account| {
335 self.reward_merkle_tree_v1
336 .lookup(*account)
337 .expect_not_in_memory()
338 .is_ok()
339 })
340 .collect()
341 }
342
343 pub fn need_to_fetch_blocks_mt_frontier(&self) -> bool {
345 let num_leaves = self.block_merkle_tree.num_leaves();
346 if num_leaves == 0 {
347 false
348 } else {
349 self.block_merkle_tree
350 .lookup(num_leaves - 1)
351 .expect_ok()
352 .is_err()
353 }
354 }
355
356 pub fn insert_fee_deposit(
358 &mut self,
359 fee_info: FeeInfo,
360 ) -> anyhow::Result<LookupResult<FeeAmount, (), ()>> {
361 Ok(self
362 .fee_merkle_tree
363 .update_with(fee_info.account, |balance| {
364 Some(balance.cloned().unwrap_or_default().add(fee_info.amount))
365 })?)
366 }
367
368 pub fn apply_proposal(
369 &mut self,
370 delta: &mut Delta,
371 parent_leaf: &Leaf2,
372 l1_deposits: Vec<FeeInfo>,
373 ) {
374 self.block_merkle_tree
376 .push(parent_leaf.block_header().commit())
377 .unwrap();
378
379 for FeeInfo { account, amount } in l1_deposits.iter() {
380 self.fee_merkle_tree
381 .update_with(account, |balance| {
382 Some(balance.cloned().unwrap_or_default().add(*amount))
383 })
384 .expect("update_with succeeds");
385 delta.fees_delta.insert(*account);
386 }
387 }
388
389 pub fn charge_fees(
390 &mut self,
391 delta: &mut Delta,
392 fee_info: Vec<FeeInfo>,
393 recipient: FeeAccount,
394 ) -> Result<(), FeeError> {
395 for fee_info in fee_info {
396 self.charge_fee(fee_info, recipient)?;
397 delta.fees_delta.extend([fee_info.account, recipient]);
398 }
399 Ok(())
400 }
401
402 pub fn charge_fee(&mut self, fee_info: FeeInfo, recipient: FeeAccount) -> Result<(), FeeError> {
404 if fee_info.amount == 0.into() {
405 return Ok(());
406 }
407
408 let fee_state = self.fee_merkle_tree.clone();
409
410 let FeeInfo { account, amount } = fee_info;
412 let mut err = None;
413 let fee_state = fee_state.persistent_update_with(account, |balance| {
414 let balance = balance.copied();
415 let Some(updated) = balance.unwrap_or_default().checked_sub(&amount) else {
416 err = Some(FeeError::InsufficientFunds { balance, amount });
418 return balance;
419 };
420 if updated == FeeAmount::default() {
421 None
424 } else {
425 Some(updated)
427 }
428 })?;
429
430 if let Some(err) = err {
432 return Err(err);
433 }
434
435 let fee_state = fee_state.persistent_update_with(recipient, |balance| {
438 Some(balance.copied().unwrap_or_default() + amount)
439 })?;
440
441 self.fee_merkle_tree = fee_state;
443 Ok(())
444 }
445}
446#[cfg_attr(not(feature = "node"), allow(dead_code))]
448#[derive(Debug)]
449pub(crate) struct Proposal<'a> {
450 header: &'a Header,
451 block_size: Option<u32>,
453}
454
455#[cfg_attr(not(feature = "node"), allow(dead_code))]
456impl<'a> Proposal<'a> {
457 pub(crate) fn new(header: &'a Header, block_size: u32) -> Self {
458 Self {
459 header,
460 block_size: Some(block_size),
461 }
462 }
463
464 pub(crate) fn without_size(header: &'a Header) -> Self {
469 Self {
470 header,
471 block_size: None,
472 }
473 }
474 fn validate_l1_head(&self, parent_l1_head: u64) -> Result<(), ProposalValidationError> {
477 if self.header.l1_head() < parent_l1_head {
478 return Err(ProposalValidationError::DecrementingL1Head);
479 }
480 Ok(())
481 }
482 fn validate_chain_config(
486 &self,
487 expected_chain_config: &ChainConfig,
488 ) -> Result<(), ProposalValidationError> {
489 let proposed_chain_config = self.header.chain_config();
490 if proposed_chain_config.commit() != expected_chain_config.commit() {
491 return Err(ProposalValidationError::InvalidChainConfig {
492 expected: Box::new(*expected_chain_config),
493 proposal: Box::new(proposed_chain_config),
494 });
495 }
496 Ok(())
497 }
498
499 fn validate_timestamp_non_dec(
501 &self,
502 parent_timestamp: u64,
503 ) -> Result<(), ProposalValidationError> {
504 if self.header.timestamp() < parent_timestamp {
505 return Err(ProposalValidationError::DecrementingTimestamp {
506 proposal_timestamp: self.header.timestamp(),
507 parent_timestamp,
508 });
509 }
510
511 Ok(())
512 }
513
514 fn validate_timestamp_drift(
519 &self,
520 system_time: OffsetDateTime,
521 ) -> Result<(), ProposalValidationError> {
522 let system_timestamp = system_time.unix_timestamp() as u64;
525 let diff = self.header.timestamp().abs_diff(system_timestamp);
526 if diff > 12 {
527 return Err(ProposalValidationError::InvalidTimestampDrift {
528 proposal: self.header.timestamp(),
529 system: system_timestamp,
530 diff,
531 });
532 }
533
534 Ok(())
535 }
536
537 fn validate_timestamp_consistency(&self) -> Result<(), ProposalValidationError> {
539 if self.header.timestamp() != self.header.timestamp_millis() / 1_000 {
540 return Err(ProposalValidationError::InconsistentTimestamps {
541 timestamp: self.header.timestamp(),
542 timestamp_millis: self.header.timestamp_millis(),
543 });
544 }
545
546 Ok(())
547 }
548
549 fn validate_block_merkle_tree(
551 &self,
552 block_merkle_tree_root: BlockMerkleCommitment,
553 ) -> Result<(), ProposalValidationError> {
554 if self.header.block_merkle_tree_root() != block_merkle_tree_root {
555 return Err(ProposalValidationError::InvalidBlockRoot {
556 expected_root: block_merkle_tree_root,
557 proposal_root: self.header.block_merkle_tree_root(),
558 });
559 }
560
561 Ok(())
562 }
563}
564#[cfg_attr(not(feature = "node"), allow(dead_code))]
568#[derive(Debug)]
569pub(crate) struct ValidatedTransition<'a> {
570 state: ValidatedState,
571 expected_chain_config: ChainConfig,
572 parent: &'a Header,
573 proposal: Proposal<'a>,
574 total_rewards_distributed: Option<RewardAmount>,
575 version: Version,
576 validation_start_time: OffsetDateTime,
577 epoch_height: Option<u64>,
578 leader_index: Option<usize>,
579}
580
581#[cfg_attr(not(feature = "node"), allow(dead_code))]
582impl<'a> ValidatedTransition<'a> {
583 #[allow(clippy::too_many_arguments)]
584 pub(crate) fn new(
585 state: ValidatedState,
586 parent: &'a Header,
587 proposal: Proposal<'a>,
588 total_rewards_distributed: Option<RewardAmount>,
589 version: Version,
590 validation_start_time: OffsetDateTime,
591 epoch_height: Option<u64>,
592 leader_index: Option<usize>,
593 ) -> Self {
594 let expected_chain_config = state
595 .chain_config
596 .resolve()
597 .expect("Chain Config not found in validated state");
598 Self {
599 state,
600 expected_chain_config,
601 parent,
602 proposal,
603 total_rewards_distributed,
604 version,
605 validation_start_time,
606 epoch_height,
607 leader_index,
608 }
609 }
610
611 pub(crate) fn validate(self) -> Result<Self, ProposalValidationError> {
628 self.validate_timestamp()?;
629 self.validate_builder_fee()?;
630 self.validate_height()?;
631 self.validate_chain_config()?;
632 self.validate_block_size()?;
633 self.validate_fee()?;
634 self.validate_fee_merkle_tree()?;
635 self.validate_block_merkle_tree()?;
636 self.validate_reward_merkle_tree()?;
637 self.validate_l1_finalized()?;
638 self.validate_l1_head()?;
639 self.validate_namespace_table()?;
640 self.validate_total_rewards_distributed()?;
641 self.validate_leader_counts()?;
642
643 Ok(self)
644 }
645
646 fn validate_l1_finalized(&self) -> Result<(), ProposalValidationError> {
648 let proposed_finalized = self.proposal.header.l1_finalized();
649 let parent_finalized = self.parent.l1_finalized();
650
651 if proposed_finalized < parent_finalized {
652 return Err(ProposalValidationError::L1FinalizedDecrementing {
657 parent: parent_finalized.map(|block| (block.number, block.timestamp.to::<u64>())),
658 proposed: proposed_finalized
659 .map(|block| (block.number, block.timestamp.to::<u64>())),
660 });
661 }
662 Ok(())
663 }
664 #[cfg(feature = "node")]
669 async fn wait_for_l1(self, l1_client: &L1Client) -> Result<Self, ProposalValidationError> {
670 self.wait_for_l1_head(l1_client).await;
671 self.wait_for_finalized_block(l1_client).await?;
672 Ok(self)
673 }
674
675 #[cfg(feature = "node")]
678 async fn wait_for_l1_head(&self, l1_client: &L1Client) {
679 let _ = l1_client
680 .wait_for_block(self.proposal.header.l1_head())
681 .await;
682 }
683 #[cfg(feature = "node")]
686 async fn wait_for_finalized_block(
687 &self,
688 l1_client: &L1Client,
689 ) -> Result<(), ProposalValidationError> {
690 let proposed_finalized = self.proposal.header.l1_finalized();
691
692 if let Some(proposed_finalized) = proposed_finalized {
693 let finalized = l1_client
694 .wait_for_finalized_block(proposed_finalized.number())
695 .await;
696
697 if finalized != proposed_finalized {
698 return Err(ProposalValidationError::InvalidL1Finalized);
699 }
700 }
701
702 Ok(())
703 }
704
705 fn validate_l1_head(&self) -> Result<(), ProposalValidationError> {
707 self.proposal.validate_l1_head(self.parent.l1_head())?;
708 Ok(())
709 }
710 fn validate_builder_fee(&self) -> Result<(), ProposalValidationError> {
713 if let Err(err) = validate_builder_fee(self.proposal.header, self.version) {
715 return Err(ProposalValidationError::BuilderValidationError(err));
716 }
717 Ok(())
718 }
719 fn validate_chain_config(&self) -> Result<(), ProposalValidationError> {
721 self.proposal
722 .validate_chain_config(&self.expected_chain_config)?;
723 Ok(())
724 }
725 fn validate_block_size(&self) -> Result<(), ProposalValidationError> {
728 let Some(block_size) = self.proposal.block_size else {
729 return Ok(());
730 };
731 let block_size = block_size as u64;
732 if block_size > *self.expected_chain_config.max_block_size {
733 return Err(ProposalValidationError::MaxBlockSizeExceeded {
734 max_block_size: self.expected_chain_config.max_block_size,
735 block_size: block_size.into(),
736 });
737 }
738 Ok(())
739 }
740 fn validate_fee(&self) -> Result<(), ProposalValidationError> {
743 let Some(amount) = self.proposal.header.fee_info().amount() else {
746 return Err(ProposalValidationError::SomeFeeAmountOutOfRange);
747 };
748 let Some(block_size) = self.proposal.block_size else {
749 return Ok(());
750 };
751
752 if amount < self.expected_chain_config.base_fee * U256::from(block_size) {
753 return Err(ProposalValidationError::InsufficientFee {
754 max_block_size: self.expected_chain_config.max_block_size,
755 base_fee: self.expected_chain_config.base_fee,
756 proposed_fee: amount,
757 });
758 }
759 Ok(())
760 }
761 fn validate_height(&self) -> Result<(), ProposalValidationError> {
763 let parent_header = self.parent;
764 if self.proposal.header.height() != parent_header.height() + 1 {
765 return Err(ProposalValidationError::InvalidHeight {
766 parent_height: parent_header.height(),
767 proposal_height: self.proposal.header.height(),
768 });
769 }
770 Ok(())
771 }
772 fn validate_timestamp(&self) -> Result<(), ProposalValidationError> {
777 self.proposal.validate_timestamp_consistency()?;
778
779 self.proposal
780 .validate_timestamp_non_dec(self.parent.timestamp())?;
781
782 self.proposal
783 .validate_timestamp_drift(self.validation_start_time)?;
784
785 Ok(())
786 }
787 fn validate_block_merkle_tree(&self) -> Result<(), ProposalValidationError> {
790 let block_merkle_tree_root = self.state.block_merkle_tree.commitment();
791 self.proposal
792 .validate_block_merkle_tree(block_merkle_tree_root)?;
793
794 Ok(())
795 }
796
797 fn validate_reward_merkle_tree(&self) -> Result<(), ProposalValidationError> {
800 match self.proposal.header.reward_merkle_tree_root() {
801 Either::Left(proposal_root) => {
802 let expected_root = self.state.reward_merkle_tree_v1.commitment();
803 if proposal_root != expected_root {
804 return Err(ProposalValidationError::InvalidV1RewardRoot {
805 expected_root,
806 proposal_root,
807 });
808 }
809 },
810 Either::Right(proposal_root) => {
811 let expected_root = self.state.reward_merkle_tree_v2.commitment();
812 if proposal_root != expected_root {
813 return Err(ProposalValidationError::InvalidV2RewardRoot {
814 expected_root,
815 proposal_root,
816 });
817 }
818 },
819 }
820
821 Ok(())
822 }
823
824 fn validate_fee_merkle_tree(&self) -> Result<(), ProposalValidationError> {
827 let fee_merkle_tree_root = self.state.fee_merkle_tree.commitment();
828 if self.proposal.header.fee_merkle_tree_root() != fee_merkle_tree_root {
829 return Err(ProposalValidationError::InvalidFeeRoot {
830 expected_root: fee_merkle_tree_root,
831 proposal_root: self.proposal.header.fee_merkle_tree_root(),
832 });
833 }
834
835 Ok(())
836 }
837 fn validate_namespace_table(&self) -> Result<(), ProposalValidationError> {
839 let Some(block_size) = self.proposal.block_size else {
840 return Ok(());
841 };
842 self.proposal
843 .header
844 .ns_table()
845 .validate(&PayloadByteLen(block_size as usize))
847 .map_err(ProposalValidationError::from)
848 }
849
850 fn validate_total_rewards_distributed(&self) -> Result<(), ProposalValidationError> {
853 if self.version >= DRB_AND_HEADER_UPGRADE_VERSION {
854 let Some(actual_total) = self.total_rewards_distributed else {
855 return Err(ProposalValidationError::TotalRewardsMismatch {
857 proposed: self
858 .proposal
859 .header
860 .total_reward_distributed()
861 .unwrap_or_default(),
862 actual: RewardAmount::from(0),
863 });
864 };
865
866 let proposed_total =
867 self.proposal
868 .header
869 .total_reward_distributed()
870 .ok_or_else(|| ProposalValidationError::TotalRewardsMismatch {
871 proposed: RewardAmount::from(0),
872 actual: actual_total,
873 })?;
874
875 if proposed_total != actual_total {
876 return Err(ProposalValidationError::TotalRewardsMismatch {
877 proposed: proposed_total,
878 actual: actual_total,
879 });
880 }
881 }
882 Ok(())
883 }
884
885 fn validate_leader_counts(&self) -> Result<(), ProposalValidationError> {
890 if self.version < EPOCH_REWARD_VERSION {
891 return Ok(());
892 }
893
894 let Some(epoch_height) = self.epoch_height else {
895 return Err(ProposalValidationError::NoEpochHeight);
896 };
897
898 let Some(leader_index) = self.leader_index else {
899 return Err(ProposalValidationError::LeaderIndexMissing);
900 };
901
902 let proposed_counts = self
903 .proposal
904 .header
905 .leader_counts()
906 .ok_or(ProposalValidationError::LeaderCountsMissing)?;
907
908 let proposed_height = self.proposal.header.height();
909
910 let expected_counts = Header::calculate_leader_counts(
911 self.parent,
912 proposed_height,
913 leader_index,
914 epoch_height,
915 );
916
917 if proposed_counts != &expected_counts {
918 return Err(ProposalValidationError::InvalidLeaderCounts {
919 expected: Box::new(expected_counts),
920 proposed: Box::new(*proposed_counts),
921 });
922 }
923
924 Ok(())
925 }
926}
927
928#[cfg(any(test, feature = "testing"))]
929impl ValidatedState {
930 pub fn forget(&self) -> Self {
931 Self {
932 fee_merkle_tree: FeeMerkleTree::from_commitment(self.fee_merkle_tree.commitment()),
933 block_merkle_tree: BlockMerkleTree::from_commitment(
934 self.block_merkle_tree.commitment(),
935 ),
936 reward_merkle_tree_v2: RewardMerkleTreeV2::from_commitment(
937 self.reward_merkle_tree_v2.commitment(),
938 ),
939 reward_merkle_tree_v1: RewardMerkleTreeV1::from_commitment(
940 self.reward_merkle_tree_v1.commitment(),
941 ),
942 chain_config: ResolvableChainConfig::from(self.chain_config.commit()),
943 }
944 }
945}
946
947impl From<NsTableValidationError> for ProposalValidationError {
948 fn from(err: NsTableValidationError) -> Self {
949 Self::InvalidNsTable(err)
950 }
951}
952
953impl From<ProposalValidationError> for BlockError {
954 fn from(err: ProposalValidationError) -> Self {
955 tracing::error!("Invalid Block Header: {err:#}");
956 BlockError::InvalidBlockHeader(err.to_string())
957 }
958}
959
960impl From<MerkleTreeError> for FeeError {
961 fn from(item: MerkleTreeError) -> Self {
962 Self::MerkleTreeError(item)
963 }
964}
965
966#[cfg_attr(not(feature = "node"), allow(dead_code))]
969fn validate_builder_fee(
970 proposed_header: &Header,
971 version: Version,
972) -> Result<(), BuilderValidationError> {
973 let fee_info = proposed_header.fee_info();
974 let builder_signature = proposed_header.builder_signature();
975
976 if version >= EPOCH_REWARD_VERSION && fee_info.len() != builder_signature.len() {
977 return Err(BuilderValidationError::FeeAndSignatureCountMismatch {
978 fee_count: fee_info.len(),
979 signature_count: builder_signature.len(),
980 });
981 }
982
983 for (fee_info, signature) in fee_info.iter().zip(builder_signature) {
985 fee_info
987 .amount()
988 .as_u64()
989 .ok_or(BuilderValidationError::FeeAmountOutOfRange(fee_info.amount))?;
990
991 if !fee_info.account().validate_fee_signature(
993 &signature,
994 fee_info.amount().as_u64().unwrap(),
995 proposed_header.metadata(),
996 ) && !fee_info
997 .account()
998 .validate_fee_signature_with_vid_commitment(
999 &signature,
1000 fee_info.amount().as_u64().unwrap(),
1001 proposed_header.metadata(),
1002 &proposed_header.payload_commitment(),
1003 )
1004 {
1005 return Err(BuilderValidationError::InvalidBuilderSignature);
1006 }
1007 }
1008
1009 Ok(())
1010}
1011
1012impl ValidatedState {
1013 pub async fn apply_header(
1019 &self,
1020 instance: &NodeState,
1021 peers: &impl StateCatchup,
1022 parent_leaf: &Leaf2,
1023 proposed_header: &Header,
1024 version: Version,
1025 view_number: ViewNumber,
1026 ) -> anyhow::Result<(Self, Delta, Option<RewardAmount>)> {
1027 let mut validated_state = self.clone();
1030 validated_state.apply_upgrade(instance, version);
1031
1032 let chain_config = validated_state
1035 .get_chain_config(instance, peers, &proposed_header.chain_config())
1036 .await?;
1037
1038 if Some(chain_config) != validated_state.chain_config.resolve() {
1039 validated_state.chain_config = chain_config.into();
1040 }
1041
1042 let (l1_deposits, missing_accounts) = if version < NEW_PROTOCOL_VERSION {
1044 let l1_deposits = get_l1_deposits(
1045 instance,
1046 proposed_header,
1047 parent_leaf,
1048 chain_config.fee_contract,
1049 )
1050 .await;
1051
1052 let missing_accounts = self.forgotten_accounts(
1056 [chain_config.fee_recipient]
1057 .into_iter()
1058 .chain(proposed_header.fee_info().accounts())
1059 .chain(l1_deposits.accounts()),
1060 );
1061 (l1_deposits, missing_accounts)
1062 } else {
1063 (Vec::new(), Vec::new())
1064 };
1065
1066 let parent_height = parent_leaf.height();
1067 let parent_view = parent_leaf.view_number();
1068
1069 if self.need_to_fetch_blocks_mt_frontier() {
1071 tracing::info!(
1072 parent_height,
1073 ?parent_view,
1074 "fetching block frontier from peers"
1075 );
1076 peers
1077 .remember_blocks_merkle_tree(
1078 instance,
1079 parent_height,
1080 parent_view,
1081 &mut validated_state.block_merkle_tree,
1082 )
1083 .await?;
1084 }
1085
1086 if !missing_accounts.is_empty() {
1088 tracing::info!(
1089 parent_height,
1090 ?parent_view,
1091 ?missing_accounts,
1092 "fetching missing accounts from peers"
1093 );
1094
1095 let missing_account_proofs = peers
1096 .fetch_accounts(
1097 instance,
1098 parent_height,
1099 parent_view,
1100 validated_state.fee_merkle_tree.commitment(),
1101 missing_accounts,
1102 )
1103 .await?;
1104
1105 for proof in missing_account_proofs.iter() {
1107 proof
1108 .remember(&mut validated_state.fee_merkle_tree)
1109 .expect("proof previously verified");
1110 }
1111 }
1112
1113 let mut delta = Delta::default();
1114 validated_state.apply_proposal(&mut delta, parent_leaf, l1_deposits);
1115
1116 if version < NEW_PROTOCOL_VERSION {
1118 validated_state.charge_fees(
1119 &mut delta,
1120 proposed_header.fee_info(),
1121 chain_config.fee_recipient,
1122 )?;
1123 }
1124
1125 let total_rewards_distributed = if version < EPOCH_VERSION {
1127 None
1128 } else if version >= EPOCH_REWARD_VERSION {
1129 let parent_header = parent_leaf.block_header();
1130 let epoch_height = instance
1131 .epoch_height
1132 .context("epoch height not in instance state for V6")?;
1133 let leader_index = Header::get_leader_index(
1134 version,
1135 proposed_header.height(),
1136 view_number.u64(),
1137 instance,
1138 )
1139 .await?
1140 .context("leader index not found for V6")?;
1141 let leader_counts = Header::calculate_leader_counts(
1142 parent_header,
1143 proposed_header.height(),
1144 leader_index,
1145 epoch_height,
1146 );
1147 let (epoch_rewards_applied, changed_accounts) = Header::handle_epoch_rewards(
1148 proposed_header.height(),
1149 &leader_counts,
1150 instance,
1151 &mut validated_state,
1152 proposed_header.reward_merkle_tree_root().right(),
1153 )
1154 .await?;
1155
1156 delta.rewards_delta.extend(changed_accounts);
1157
1158 let parent_total = parent_leaf
1160 .block_header()
1161 .total_reward_distributed()
1162 .unwrap_or_default();
1163 Some(RewardAmount(parent_total.0 + epoch_rewards_applied.0))
1164 } else if let Some(reward_distributor) = distribute_block_reward(
1165 instance,
1166 &mut validated_state,
1167 parent_leaf,
1168 view_number,
1169 version,
1170 )
1171 .await?
1172 {
1173 reward_distributor
1174 .update_rewards_delta(&mut delta)
1175 .context("failed to update rewards delta")?;
1176
1177 Some(reward_distributor.total_distributed())
1178 } else {
1179 Some(Default::default())
1181 };
1182
1183 Ok((validated_state, delta, total_rewards_distributed))
1184 }
1185
1186 pub(crate) fn apply_upgrade(&mut self, instance: &NodeState, version: Version) {
1188 if version <= instance.current_version {
1190 return;
1191 }
1192
1193 let Some(upgrade) = instance.upgrades.get(&version) else {
1194 return;
1195 };
1196
1197 let cf = match upgrade.upgrade_type {
1198 UpgradeType::Fee { chain_config } => chain_config,
1199 UpgradeType::Epoch { chain_config } => chain_config,
1200 UpgradeType::DrbAndHeader { chain_config } => chain_config,
1201 UpgradeType::NewProtocol { chain_config } => chain_config,
1202 UpgradeType::EpochReward { chain_config } => chain_config,
1203 UpgradeType::LargeBlock { chain_config } => chain_config,
1204 };
1205
1206 self.chain_config = cf.into();
1207 }
1208
1209 pub(crate) async fn get_chain_config(
1217 &self,
1218 instance: &NodeState,
1219 peers: &impl StateCatchup,
1220 header_cf: &ResolvableChainConfig,
1221 ) -> anyhow::Result<ChainConfig> {
1222 let state_cf = self.chain_config;
1223
1224 if state_cf.commit() == instance.chain_config.commit() {
1225 return Ok(instance.chain_config);
1226 }
1227
1228 let cf = match (state_cf.resolve(), header_cf.resolve()) {
1229 (Some(cf), _) => cf,
1230 (_, Some(cf)) if cf.commit() == state_cf.commit() => cf,
1231 (_, Some(_)) | (None, None) => peers.fetch_chain_config(state_cf.commit()).await?,
1232 };
1233
1234 Ok(cf)
1235 }
1236}
1237
1238#[cfg(feature = "node")]
1239pub async fn get_l1_deposits(
1240 instance: &NodeState,
1241 header: &Header,
1242 parent_leaf: &Leaf2,
1243 fee_contract_address: Option<Address>,
1244) -> Vec<FeeInfo> {
1245 if let (Some(addr), Some(block_info)) = (fee_contract_address, header.l1_finalized()) {
1246 instance
1247 .l1_client
1248 .get_finalized_deposits(
1249 addr,
1250 parent_leaf
1251 .block_header()
1252 .l1_finalized()
1253 .map(|block_info| block_info.number),
1254 block_info.number,
1255 )
1256 .await
1257 } else {
1258 vec![]
1259 }
1260}
1261
1262#[cfg(not(feature = "node"))]
1266pub async fn get_l1_deposits(
1267 _instance: &NodeState,
1268 header: &Header,
1269 _parent_leaf: &Leaf2,
1270 fee_contract_address: Option<Address>,
1271) -> Vec<FeeInfo> {
1272 if fee_contract_address.is_some() && header.l1_finalized().is_some() {
1273 unimplemented!("fetching L1 deposits requires the node feature")
1274 } else {
1275 vec![]
1276 }
1277}
1278
1279#[cfg_attr(not(feature = "node"), allow(dead_code))]
1280async fn validate_next_stake_table_hash(
1281 instance: &NodeState,
1282 proposed_header: &Header,
1283) -> Result<(), ProposalValidationError> {
1284 let Some(epoch_height) = instance.epoch_height else {
1285 return Err(ProposalValidationError::NoEpochHeight);
1286 };
1287 if !is_ge_epoch_root(proposed_header.height(), epoch_height) {
1288 return Ok(());
1289 }
1290 let epoch = EpochNumber::new(epoch_from_block_number(
1291 proposed_header.height(),
1292 epoch_height,
1293 ));
1294 let coordinator = instance.coordinator.clone();
1295 let first_epoch = coordinator.membership().first_epoch();
1296
1297 let Some(proposed_next_stake_table_hash) = proposed_header.next_stake_table_hash() else {
1299 if epoch <= first_epoch {
1300 return Ok(());
1301 } else {
1302 return Err(ProposalValidationError::NextStakeTableHashNotNone);
1303 }
1304 };
1305
1306 let epoch_membership = instance
1307 .coordinator
1308 .stake_table_for_epoch(Some(epoch + 1))
1309 .map_err(|_| ProposalValidationError::NextStakeTableNotFound)?;
1310 let next_stake_table_hash = epoch_membership
1311 .stake_table_hash()
1312 .ok_or(ProposalValidationError::NextStakeTableHashNotFound)?;
1313 if next_stake_table_hash != proposed_next_stake_table_hash {
1314 return Err(ProposalValidationError::NextStakeTableHashMismatch {
1315 expected: next_stake_table_hash,
1316 proposal: proposed_next_stake_table_hash,
1317 });
1318 }
1319 Ok(())
1320}
1321
1322impl HotShotState<SeqTypes> for ValidatedState {
1323 type Error = BlockError;
1324 type Instance = NodeState;
1325
1326 type Delta = Delta;
1327 fn on_commit(&self) {}
1328 #[tracing::instrument(
1331 skip_all,
1332 fields(
1333 node_id = instance.node_id,
1334 view = ?parent_leaf.view_number(),
1335 height = parent_leaf.height(),
1336 ),
1337 )]
1338 #[cfg_attr(not(feature = "node"), allow(unused_variables))]
1339 async fn validate_and_apply_header(
1340 &self,
1341 instance: &Self::Instance,
1342 parent_leaf: &Leaf2,
1343 proposed_header: &Header,
1344 payload_byte_len: Option<u32>,
1345 version: Version,
1346 view_number: u64,
1347 ) -> Result<(Self, Self::Delta), Self::Error> {
1348 #[cfg(not(feature = "node"))]
1350 {
1351 unimplemented!("validate_and_apply_header requires the node feature");
1352 }
1353 #[cfg(feature = "node")]
1354 {
1355 let validation_start_time = OffsetDateTime::now_utc();
1360
1361 let (validated_state, delta, total_rewards_distributed) = self
1362 .apply_header(
1364 instance,
1365 &instance.state_catchup,
1366 parent_leaf,
1367 proposed_header,
1368 version,
1369 ViewNumber::new(view_number),
1370 )
1371 .await
1372 .map_err(|e| BlockError::FailedHeaderApply(e.to_string()))?;
1373
1374 if version >= DRB_AND_HEADER_UPGRADE_VERSION {
1375 validate_next_stake_table_hash(instance, proposed_header).await?;
1376 }
1377
1378 let leader_index =
1380 Header::get_leader_index(version, proposed_header.height(), view_number, instance)
1381 .await
1382 .map_err(|e| BlockError::InvalidBlockHeader(e.to_string()))?;
1383
1384 let validated_state = ValidatedTransition::new(
1386 validated_state,
1387 parent_leaf.block_header(),
1388 match payload_byte_len {
1389 Some(len) => Proposal::new(proposed_header, len),
1390 None => Proposal::without_size(proposed_header),
1391 },
1392 total_rewards_distributed,
1393 version,
1394 validation_start_time,
1395 instance.epoch_height,
1396 leader_index,
1397 )
1398 .validate()?
1399 .wait_for_l1(&instance.l1_client)
1400 .await?
1401 .state;
1402
1403 if parent_leaf.view_number().u64().is_multiple_of(10) {
1406 tracing::info!("validated and applied new header");
1407 }
1408 Ok((validated_state, delta))
1409 }
1410 }
1411 fn from_header(block_header: &Header) -> Self {
1415 let fee_merkle_tree = if block_header.fee_merkle_tree_root().size() == 0 {
1416 FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT)
1419 } else {
1420 FeeMerkleTree::from_commitment(block_header.fee_merkle_tree_root())
1421 };
1422 let block_merkle_tree = if block_header.block_merkle_tree_root().size() == 0 {
1423 BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT)
1426 } else {
1427 BlockMerkleTree::from_commitment(block_header.block_merkle_tree_root())
1428 };
1429
1430 let (reward_merkle_tree_v1, reward_merkle_tree_v2) = match block_header
1431 .reward_merkle_tree_root()
1432 {
1433 Either::Left(reward_tree_v1) => {
1434 let reward_merkle_tree_v2 = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
1435 let reward_merkle_tree_v1 = if reward_tree_v1.size() == 0 {
1436 RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT)
1437 } else {
1438 RewardMerkleTreeV1::from_commitment(reward_tree_v1)
1439 };
1440 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1441 },
1442 Either::Right(reward_tree_v2) => {
1443 let reward_merkle_tree_v1 = RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT);
1444 let reward_merkle_tree_v2 = if reward_tree_v2.size() == 0 {
1445 RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT)
1446 } else {
1447 RewardMerkleTreeV2::from_commitment(reward_tree_v2)
1448 };
1449 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1450 },
1451 };
1452
1453 Self {
1454 fee_merkle_tree,
1455 block_merkle_tree,
1456 reward_merkle_tree_v2,
1457 reward_merkle_tree_v1,
1458 chain_config: block_header.chain_config(),
1459 }
1460 }
1461 fn genesis(instance: &Self::Instance) -> (Self, Self::Delta) {
1463 (instance.genesis_state.clone(), Delta::default())
1464 }
1465}
1466
1467#[cfg(any(test, feature = "testing"))]
1469impl std::fmt::Display for ValidatedState {
1470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1471 write!(f, "{self:#?}")
1472 }
1473}
1474
1475#[cfg(any(test, feature = "testing"))]
1476impl hotshot_types::traits::states::TestableState<SeqTypes> for ValidatedState {
1477 fn create_random_transaction(
1478 _state: Option<&Self>,
1479 rng: &mut dyn rand::RngCore,
1480 _padding: u64,
1481 ) -> crate::Transaction {
1482 crate::Transaction::random(rng)
1483 }
1484}
1485
1486impl MerklizedState<SeqTypes, { Self::ARITY }> for BlockMerkleTree {
1487 type Key = Self::Index;
1488 type Entry = Commitment<Header>;
1489 type T = Sha3Node;
1490 type Commit = Self::Commitment;
1491 type Digest = Sha3Digest;
1492
1493 fn state_type() -> &'static str {
1494 "block_merkle_tree"
1495 }
1496
1497 fn header_state_commitment_field() -> &'static str {
1498 "block_merkle_tree_root"
1499 }
1500
1501 fn tree_height() -> usize {
1502 BLOCK_MERKLE_TREE_HEIGHT
1503 }
1504
1505 fn insert_path(
1506 &mut self,
1507 key: Self::Key,
1508 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1509 ) -> anyhow::Result<()> {
1510 let Some(elem) = proof.elem() else {
1511 bail!("BlockMerkleTree does not support non-membership proofs");
1512 };
1513 self.remember(key, elem, proof)?;
1514 Ok(())
1515 }
1516}
1517
1518impl MerklizedState<SeqTypes, { Self::ARITY }> for FeeMerkleTree {
1519 type Key = Self::Index;
1520 type Entry = Self::Element;
1521 type T = Sha3Node;
1522 type Commit = Self::Commitment;
1523 type Digest = Sha3Digest;
1524
1525 fn state_type() -> &'static str {
1526 "fee_merkle_tree"
1527 }
1528
1529 fn header_state_commitment_field() -> &'static str {
1530 "fee_merkle_tree_root"
1531 }
1532
1533 fn tree_height() -> usize {
1534 FEE_MERKLE_TREE_HEIGHT
1535 }
1536
1537 fn insert_path(
1538 &mut self,
1539 key: Self::Key,
1540 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1541 ) -> anyhow::Result<()> {
1542 match proof.elem() {
1543 Some(elem) => self.remember(key, elem, proof)?,
1544 None => self.non_membership_remember(key, proof)?,
1545 }
1546 Ok(())
1547 }
1548}
1549
1550impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV2 {
1551 type Key = Self::Index;
1552 type Entry = Self::Element;
1553 type T = KeccakNode;
1554 type Commit = Self::Commitment;
1555 type Digest = Keccak256Hasher;
1556
1557 fn state_type() -> &'static str {
1558 "reward_merkle_tree_v2"
1559 }
1560
1561 fn header_state_commitment_field() -> &'static str {
1562 "reward_merkle_tree_root"
1563 }
1564
1565 fn tree_height() -> usize {
1566 REWARD_MERKLE_TREE_V2_HEIGHT
1567 }
1568
1569 fn insert_path(
1570 &mut self,
1571 key: Self::Key,
1572 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1573 ) -> anyhow::Result<()> {
1574 match proof.elem() {
1575 Some(elem) => self.remember(key, elem, proof)?,
1576 None => self.non_membership_remember(key, proof)?,
1577 }
1578 Ok(())
1579 }
1580}
1581
1582impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV1 {
1583 type Key = Self::Index;
1584 type Entry = Self::Element;
1585 type T = Sha3Node;
1586 type Commit = Self::Commitment;
1587 type Digest = Sha3Digest;
1588
1589 fn state_type() -> &'static str {
1590 "reward_merkle_tree"
1591 }
1592
1593 fn header_state_commitment_field() -> &'static str {
1594 "reward_merkle_tree_root"
1595 }
1596
1597 fn tree_height() -> usize {
1598 REWARD_MERKLE_TREE_V1_HEIGHT
1599 }
1600
1601 fn insert_path(
1602 &mut self,
1603 key: Self::Key,
1604 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1605 ) -> anyhow::Result<()> {
1606 match proof.elem() {
1607 Some(elem) => self.remember(key, elem, proof)?,
1608 None => self.non_membership_remember(key, proof)?,
1609 }
1610 Ok(())
1611 }
1612}
1613
1614#[cfg(test)]
1615mod test {
1616 use std::{sync::Arc, time::Duration};
1617
1618 use espresso_utils::ser::FromStringOrInteger;
1619 use hotshot_example_types::node_types::TEST_VERSIONS;
1620 use hotshot_query_service::{Resolvable, testing::mocks::MOCK_UPGRADE};
1621 use hotshot_types::{
1622 data::ViewNumber,
1623 traits::{BlockPayload, signature_key::BuilderSignatureKey},
1624 };
1625 use tracing::debug;
1626 use versions::{
1627 FEE_VERSION, LARGE_BLOCK_VERSION, MAX_SUPPORTED_VERSION, NEW_PROTOCOL_VERSION, version,
1628 };
1629
1630 use super::*;
1631 use crate::{
1632 BlockSize, FeeAccountProof, FeeMerkleProof, Leaf, Payload, TimestampMillis, Transaction,
1633 UpgradeMode, UpgradeType, ViewBasedUpgrade,
1634 eth_signature_key::{BuilderSignature, EthKeyPair},
1635 mock::MockStateCatchup,
1636 v0_1, v0_2, v0_3, v0_4, v0_5, v0_6, v0_7,
1637 };
1638
1639 impl Transaction {
1640 async fn into_mock_header(self) -> (Header, u32) {
1641 let instance = NodeState::mock();
1642 let (payload, metadata) =
1643 Payload::from_transactions(&[self], &instance.genesis_state, &instance)
1644 .await
1645 .unwrap();
1646
1647 let header = Header::genesis(
1648 &instance,
1649 payload.clone(),
1650 &metadata,
1651 instance.genesis_version,
1652 );
1653
1654 let header = header.sign();
1655
1656 (header, payload.byte_len().0 as u32)
1657 }
1658 }
1659 impl Header {
1660 fn next(self) -> Self {
1662 let time = OffsetDateTime::now_utc();
1663 let timestamp = time.unix_timestamp() as u64;
1664 let timestamp_millis = TimestampMillis::from_time(&time);
1665
1666 match self {
1667 Header::V1(_) => panic!("You called `Header.next()` on unimplemented version (v1)"),
1668 Header::V2(parent) => Header::V2(v0_2::Header {
1669 height: parent.height + 1,
1670 timestamp,
1671 ..parent.clone()
1672 }),
1673 Header::V3(parent) => Header::V3(v0_3::Header {
1674 height: parent.height + 1,
1675 timestamp,
1676 ..parent.clone()
1677 }),
1678 Header::V4(parent) => Header::V4(v0_4::Header {
1679 height: parent.height + 1,
1680 timestamp,
1681 timestamp_millis,
1682 ..parent.clone()
1683 }),
1684 Header::V5(parent) => Header::V5(v0_5::Header {
1685 height: parent.height + 1,
1686 timestamp,
1687 timestamp_millis,
1688 ..parent.clone()
1689 }),
1690 Header::V6(parent) => Header::V6(v0_6::Header {
1691 height: parent.height + 1,
1692 timestamp,
1693 timestamp_millis,
1694 ..parent.clone()
1695 }),
1696 Header::V7(parent) => Header::V7(v0_7::Header {
1697 height: parent.height + 1,
1698 timestamp,
1699 timestamp_millis,
1700 ..parent.clone()
1701 }),
1702 }
1703 }
1704 fn sign(&self) -> Self {
1706 let key_pair = EthKeyPair::random();
1707 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1708
1709 let sig = FeeAccount::sign_fee(
1710 &key_pair,
1711 fee_info.amount().as_u64().unwrap(),
1712 self.metadata(),
1713 )
1714 .unwrap();
1715
1716 match self {
1717 Header::V1(_) => panic!("You called `Header.sign()` on unimplemented version (v1)"),
1718 Header::V2(header) => Header::V2(v0_2::Header {
1719 fee_info,
1720 builder_signature: Some(sig),
1721 ..header.clone()
1722 }),
1723 Header::V3(header) => Header::V3(v0_3::Header {
1724 fee_info,
1725 builder_signature: Some(sig),
1726 ..header.clone()
1727 }),
1728 Header::V4(header) => Header::V4(v0_4::Header {
1729 fee_info,
1730 builder_signature: Some(sig),
1731 ..header.clone()
1732 }),
1733 Header::V5(header) => Header::V5(v0_5::Header {
1734 fee_info,
1735 builder_signature: Some(sig),
1736 ..header.clone()
1737 }),
1738 Header::V6(header) => Header::V6(v0_6::Header {
1739 fee_info,
1740 builder_signature: Some(sig),
1741 ..header.clone()
1742 }),
1743 Header::V7(header) => Header::V7(v0_7::Header {
1744 fee_info,
1745 builder_signature: Some(sig),
1746 ..header.clone()
1747 }),
1748 }
1749 }
1750
1751 fn invalid_builder_signature(&self) -> Self {
1753 let key_pair = EthKeyPair::random();
1754 let key_pair2 = EthKeyPair::random();
1755 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1756
1757 let sig = FeeAccount::sign_fee(
1758 &key_pair2,
1759 fee_info.amount().as_u64().unwrap(),
1760 self.metadata(),
1761 )
1762 .unwrap();
1763 self.with_fee(fee_info, Some(sig))
1764 }
1765
1766 fn with_fee(&self, fee_info: FeeInfo, builder_signature: Option<BuilderSignature>) -> Self {
1768 match self {
1769 Header::V1(_) => {
1770 panic!("You called `Header.with_fee()` on unimplemented version (v1)")
1771 },
1772 Header::V2(parent) => Header::V2(v0_2::Header {
1773 fee_info,
1774 builder_signature,
1775 ..parent.clone()
1776 }),
1777 Header::V3(parent) => Header::V3(v0_3::Header {
1778 fee_info,
1779 builder_signature,
1780 ..parent.clone()
1781 }),
1782 Header::V4(parent) => Header::V4(v0_4::Header {
1783 fee_info,
1784 builder_signature,
1785 ..parent.clone()
1786 }),
1787 Header::V5(parent) => Header::V5(v0_5::Header {
1788 fee_info,
1789 builder_signature,
1790 ..parent.clone()
1791 }),
1792 Header::V6(parent) => Header::V6(v0_6::Header {
1793 fee_info,
1794 builder_signature,
1795 ..parent.clone()
1796 }),
1797 Header::V7(parent) => Header::V7(v0_7::Header {
1798 fee_info,
1799 builder_signature,
1800 ..parent.clone()
1801 }),
1802 }
1803 }
1804 }
1805
1806 impl<'a> ValidatedTransition<'a> {
1807 fn mock(instance: NodeState, parent: &'a Header, proposal: Proposal<'a>) -> Self {
1808 let expected_chain_config = instance.chain_config;
1809 let validation_start_time = OffsetDateTime::now_utc();
1810
1811 Self {
1812 state: instance.genesis_state,
1813 expected_chain_config,
1814 parent,
1815 proposal,
1816 total_rewards_distributed: None,
1817 epoch_height: instance.epoch_height,
1818 version: instance.current_version,
1819 validation_start_time,
1820 leader_index: None,
1821 }
1822 }
1823 }
1824
1825 #[test_log::test]
1826 fn test_fee_proofs() {
1827 let mut tree = ValidatedState::default().fee_merkle_tree;
1828 let account1 = Address::random();
1829 let account2 = Address::default();
1830 tracing::info!(%account1, %account2);
1831
1832 let balance1 = U256::from(100);
1833 tree.update(FeeAccount(account1), FeeAmount(balance1))
1834 .unwrap();
1835
1836 let (proof1, balance) = FeeAccountProof::prove(&tree, account1).unwrap();
1838 tracing::info!(?proof1, %balance);
1839 assert_eq!(balance, balance1);
1840 assert!(matches!(proof1.proof, FeeMerkleProof::Presence(_)));
1841 assert_eq!(proof1.verify(&tree.commitment()).unwrap(), balance1);
1842
1843 let (proof2, balance) = FeeAccountProof::prove(&tree, account2).unwrap();
1845 tracing::info!(?proof2, %balance);
1846 assert_eq!(balance, U256::ZERO);
1847 assert!(matches!(proof2.proof, FeeMerkleProof::Absence(_)));
1848 assert_eq!(proof2.verify(&tree.commitment()).unwrap(), U256::ZERO);
1849
1850 let mut tree = FeeMerkleTree::from_commitment(tree.commitment());
1852 assert!(FeeAccountProof::prove(&tree, account1).is_none());
1853 assert!(FeeAccountProof::prove(&tree, account2).is_none());
1854 proof1.remember(&mut tree).unwrap();
1856 proof2.remember(&mut tree).unwrap();
1857 FeeAccountProof::prove(&tree, account1).unwrap();
1858 FeeAccountProof::prove(&tree, account2).unwrap();
1859 }
1860
1861 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1862 async fn test_validation_l1_head() {
1863 let tx = Transaction::of_size(10);
1865 let (header, block_size) = tx.into_mock_header().await;
1866
1867 let proposal = Proposal::new(&header, block_size);
1869 ValidatedTransition::mock(NodeState::mock(), &header, proposal)
1872 .validate_l1_head()
1873 .unwrap();
1874
1875 let proposal = Proposal::new(&header, block_size);
1877 let err = proposal.validate_l1_head(u64::MAX).unwrap_err();
1878 assert_eq!(ProposalValidationError::DecrementingL1Head, err);
1879 }
1880
1881 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1882 async fn test_validation_builder_fee() {
1883 let instance = NodeState::mock();
1885 let tx = Transaction::of_size(20);
1886 let (header, block_size) = tx.into_mock_header().await;
1887
1888 let proposal = Proposal::new(&header, block_size);
1890 ValidatedTransition::mock(instance.clone(), &header, proposal)
1891 .validate_builder_fee()
1892 .unwrap();
1893
1894 let header = header.invalid_builder_signature();
1896 let proposal = Proposal::new(&header, block_size);
1897 let err = ValidatedTransition::mock(instance, &header, proposal)
1898 .validate_builder_fee()
1899 .unwrap_err();
1900
1901 tracing::info!(%err, "task failed successfully");
1902 assert_eq!(
1903 ProposalValidationError::BuilderValidationError(
1904 BuilderValidationError::InvalidBuilderSignature
1905 ),
1906 err
1907 );
1908 }
1909
1910 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1913 async fn test_chain_config_resolved_from_instance() {
1914 let instance = NodeState::mock().with_chain_config(ChainConfig {
1917 max_block_size: BlockSize(300),
1918 ..Default::default()
1919 });
1920 let commitment_only = ResolvableChainConfig::from(instance.chain_config.commit());
1921 let state = ValidatedState {
1922 chain_config: commitment_only,
1923 ..Default::default()
1924 };
1925
1926 let chain_config = state
1927 .get_chain_config(&instance, &MockStateCatchup::default(), &commitment_only)
1928 .await
1929 .unwrap();
1930
1931 assert_eq!(chain_config, instance.chain_config);
1932 }
1933
1934 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1935 async fn test_validation_chain_config() {
1936 let instance = NodeState::mock();
1938 let tx = Transaction::of_size(20);
1939 let (header, block_size) = tx.into_mock_header().await;
1940
1941 let proposal = Proposal::new(&header, block_size);
1943 ValidatedTransition::mock(instance.clone(), &header, proposal)
1944 .validate_chain_config()
1945 .unwrap();
1946
1947 let proposal = Proposal::new(&header, block_size);
1949 let expected_chain_config = ChainConfig {
1950 max_block_size: BlockSize(3333),
1951 ..instance.chain_config
1952 };
1953 let err = proposal
1954 .validate_chain_config(&expected_chain_config)
1955 .unwrap_err();
1956
1957 tracing::info!(%err, "task failed successfully");
1958
1959 assert_eq!(
1960 ProposalValidationError::InvalidChainConfig {
1961 expected: Box::new(expected_chain_config),
1962 proposal: Box::new(header.chain_config())
1963 },
1964 err
1965 );
1966 }
1967
1968 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1969 async fn test_validation_max_block_size() {
1970 const MAX_BLOCK_SIZE: usize = 10;
1971
1972 let state = ValidatedState::default();
1974 let expected_chain_config = ChainConfig {
1975 max_block_size: BlockSize::from_integer(MAX_BLOCK_SIZE as u64).unwrap(),
1976 ..state.chain_config.resolve().unwrap()
1977 };
1978 let instance = NodeState::mock().with_chain_config(expected_chain_config);
1979 let tx = Transaction::of_size(20);
1980 let (header, block_size) = tx.into_mock_header().await;
1981
1982 let proposal = Proposal::new(&header, block_size);
1984 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1985 .validate_block_size()
1986 .unwrap_err();
1987
1988 tracing::info!(%err, "task failed successfully");
1989 assert_eq!(
1990 ProposalValidationError::MaxBlockSizeExceeded {
1991 max_block_size: instance.chain_config.max_block_size,
1992 block_size: BlockSize::from_integer(block_size as u64).unwrap()
1993 },
1994 err
1995 );
1996
1997 let proposal = Proposal::new(&header, 1);
1999 ValidatedTransition::mock(instance, &header, proposal)
2000 .validate_block_size()
2001 .unwrap()
2002 }
2003
2004 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2005 async fn test_validation_base_fee() {
2006 let tx = Transaction::of_size(20);
2008 let (header, block_size) = tx.into_mock_header().await;
2009 let state = ValidatedState::default();
2010 let instance = NodeState::mock().with_chain_config(ChainConfig {
2011 base_fee: 1000.into(), ..state.chain_config.resolve().unwrap()
2013 });
2014
2015 let proposal = Proposal::new(&header, block_size);
2016 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
2017 .validate_fee()
2018 .unwrap_err();
2019
2020 tracing::info!(%err, "task failed successfully");
2022 assert_eq!(
2023 ProposalValidationError::InsufficientFee {
2024 max_block_size: instance.chain_config.max_block_size,
2025 base_fee: instance.chain_config.base_fee,
2026 proposed_fee: header.fee_info().amount().unwrap()
2027 },
2028 err
2029 );
2030 }
2031
2032 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2033 async fn test_validation_height() {
2034 let instance = NodeState::mock();
2036 let tx = Transaction::of_size(10);
2037 let (parent, block_size) = tx.into_mock_header().await;
2038
2039 let proposal = Proposal::new(&parent, block_size);
2040 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
2041 .validate_height()
2042 .unwrap_err();
2043
2044 tracing::info!(%err, "task failed successfully");
2046 assert_eq!(
2047 ProposalValidationError::InvalidHeight {
2048 parent_height: parent.height(),
2049 proposal_height: parent.height()
2050 },
2051 err
2052 );
2053
2054 let mut header = parent.clone();
2056 *header.height_mut() += 1;
2057 let proposal = Proposal::new(&header, block_size);
2058
2059 ValidatedTransition::mock(instance, &parent, proposal)
2060 .validate_height()
2061 .unwrap();
2062 }
2063
2064 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2065 async fn test_validation_timestamp_non_dec() {
2066 let tx = Transaction::of_size(10);
2067 let (parent, block_size) = tx.into_mock_header().await;
2068
2069 let proposal = Proposal::new(&parent, block_size);
2071 let proposal_timestamp = proposal.header.timestamp();
2072 let err = proposal.validate_timestamp_non_dec(u64::MAX).unwrap_err();
2073
2074 tracing::info!(%err, "task failed successfully");
2076 assert_eq!(
2077 ProposalValidationError::DecrementingTimestamp {
2078 proposal_timestamp,
2079 parent_timestamp: u64::MAX,
2080 },
2081 err
2082 );
2083
2084 let proposal = Proposal::new(&parent, block_size);
2086 proposal.validate_timestamp_non_dec(0).unwrap();
2087 }
2088
2089 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2090 async fn test_validation_timestamp_drift() {
2091 let instance = NodeState::mock();
2093 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
2094
2095 let header = parent.clone();
2096 let proposal = Proposal::new(&header, block_size);
2098 let proposal_timestamp = header.timestamp();
2099
2100 let mock_time = OffsetDateTime::now_utc().unix_timestamp() as u64;
2101 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
2103 .validate_timestamp()
2104 .unwrap_err();
2105
2106 tracing::info!(%err, "task failed successfully");
2107 assert_eq!(
2108 ProposalValidationError::InvalidTimestampDrift {
2109 proposal: proposal_timestamp,
2110 system: mock_time,
2111 diff: mock_time
2112 },
2113 err
2114 );
2115
2116 let time = OffsetDateTime::now_utc();
2117 let timestamp: u64 = time.unix_timestamp() as u64;
2118 let timestamp_millis = TimestampMillis::from_time(&time).u64();
2119
2120 let mut header = parent.clone();
2121 header.set_timestamp(timestamp - 13, timestamp_millis - 13_000);
2122 let proposal = Proposal::new(&header, block_size);
2123
2124 let err = proposal.validate_timestamp_drift(time).unwrap_err();
2125 tracing::info!(%err, "task failed successfully");
2126 assert_eq!(
2127 ProposalValidationError::InvalidTimestampDrift {
2128 proposal: timestamp - 13,
2129 system: timestamp,
2130 diff: 13
2131 },
2132 err
2133 );
2134
2135 let mut header = parent.clone();
2137 header.set_timestamp(timestamp, timestamp_millis);
2138 let proposal = Proposal::new(&header, block_size);
2139 proposal.validate_timestamp_drift(time).unwrap();
2140
2141 header.set_timestamp(timestamp - 11, timestamp_millis - 11_000);
2142 let proposal = Proposal::new(&header, block_size);
2143 proposal.validate_timestamp_drift(time).unwrap();
2144
2145 header.set_timestamp(timestamp - 12, timestamp_millis - 12_000);
2146 let proposal = Proposal::new(&header, block_size);
2147 proposal.validate_timestamp_drift(time).unwrap();
2148 }
2149
2150 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2151 async fn test_validation_fee_root() {
2152 let instance = NodeState::mock();
2154 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
2155
2156 let proposal = Proposal::new(&header, block_size);
2158 ValidatedTransition::mock(instance.clone(), &header, proposal)
2159 .validate_fee_merkle_tree()
2160 .unwrap();
2161
2162 let proposal = Proposal::new(&header, block_size);
2164
2165 let mut fee_merkle_tree = instance.genesis_state.fee_merkle_tree;
2166 fee_merkle_tree
2167 .update_with(FeeAccount::default(), |_| Some(100.into()))
2168 .unwrap();
2169
2170 let err = proposal
2171 .validate_block_merkle_tree(fee_merkle_tree.commitment())
2172 .unwrap_err();
2173
2174 tracing::info!(%err, "task failed successfully");
2175 assert_eq!(
2176 ProposalValidationError::InvalidBlockRoot {
2177 expected_root: fee_merkle_tree.commitment(),
2178 proposal_root: header.block_merkle_tree_root(),
2179 },
2180 err
2181 );
2182 }
2183
2184 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2185 async fn test_validation_block_root() {
2186 let instance = NodeState::mock();
2188 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
2189
2190 let proposal = Proposal::new(&header, block_size);
2192 ValidatedTransition::mock(instance.clone(), &header, proposal)
2193 .validate_block_merkle_tree()
2194 .unwrap();
2195
2196 let proposal = Proposal::new(&header, block_size);
2198 let mut block_merkle_tree = instance.genesis_state.block_merkle_tree;
2199 block_merkle_tree.push(header.commitment()).unwrap();
2200 block_merkle_tree
2201 .push(header.clone().next().commitment())
2202 .unwrap();
2203
2204 let err = proposal
2205 .validate_block_merkle_tree(block_merkle_tree.commitment())
2206 .unwrap_err();
2207
2208 tracing::info!(%err, "task failed successfully");
2209 assert_eq!(
2210 ProposalValidationError::InvalidBlockRoot {
2211 expected_root: block_merkle_tree.commitment(),
2212 proposal_root: proposal.header.block_merkle_tree_root(),
2213 },
2214 err
2215 );
2216 }
2217
2218 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2219 async fn test_validation_ns_table() {
2220 use NsTableValidationError::InvalidFinalOffset;
2221 let tx = Transaction::of_size(10);
2223 let (header, block_size) = tx.into_mock_header().await;
2224
2225 let proposal = Proposal::new(&header, block_size);
2227 ValidatedTransition::mock(NodeState::mock(), &header, proposal)
2228 .validate_namespace_table()
2229 .unwrap();
2230
2231 let proposal = Proposal::new(&header, 40);
2233 let err = ValidatedTransition::mock(NodeState::mock(), &header, proposal)
2234 .validate_namespace_table()
2235 .unwrap_err();
2236 tracing::info!(%err, "task failed successfully");
2237 assert_eq!(
2240 ProposalValidationError::InvalidNsTable(InvalidFinalOffset),
2241 err
2242 );
2243 }
2244
2245 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2246 async fn test_validation_without_payload_size_skips_size_checks() {
2247 use NsTableValidationError::InvalidFinalOffset;
2248 let tx = Transaction::of_size(20);
2252 let (header, block_size) = tx.into_mock_header().await;
2253 let instance = NodeState::mock().with_chain_config(ChainConfig {
2254 max_block_size: BlockSize::from_integer(10).unwrap(),
2255 base_fee: 1000.into(),
2256 ..ValidatedState::default().chain_config.resolve().unwrap()
2257 });
2258
2259 let sized = ValidatedTransition::mock(
2260 instance.clone(),
2261 &header,
2262 Proposal::new(&header, block_size),
2263 );
2264 assert!(matches!(
2265 sized.validate_block_size(),
2266 Err(ProposalValidationError::MaxBlockSizeExceeded { .. })
2267 ));
2268 assert!(matches!(
2269 sized.validate_fee(),
2270 Err(ProposalValidationError::InsufficientFee { .. })
2271 ));
2272 let zero_sized =
2275 ValidatedTransition::mock(instance.clone(), &header, Proposal::new(&header, 0));
2276 assert_eq!(
2277 ProposalValidationError::InvalidNsTable(InvalidFinalOffset),
2278 zero_sized.validate_namespace_table().unwrap_err()
2279 );
2280
2281 let sizeless =
2282 ValidatedTransition::mock(instance.clone(), &header, Proposal::without_size(&header));
2283 sizeless.validate_block_size().unwrap();
2284 sizeless.validate_fee().unwrap();
2285 sizeless.validate_namespace_table().unwrap();
2286
2287 let fee_account = EthKeyPair::random().fee_account();
2289 let header = header.with_fee(FeeInfo::new(fee_account, FeeAmount(U256::MAX)), None);
2290 let err = ValidatedTransition::mock(instance, &header, Proposal::without_size(&header))
2291 .validate_fee()
2292 .unwrap_err();
2293 assert_eq!(ProposalValidationError::SomeFeeAmountOutOfRange, err);
2294 }
2295
2296 #[test_log::test]
2297 fn test_charge_fee() {
2298 let src = FeeAccount::generated_from_seed_indexed([0; 32], 0).0;
2299 let dst = FeeAccount::generated_from_seed_indexed([0; 32], 1).0;
2300 let amt = FeeAmount::from(1);
2301
2302 let fee_info = FeeInfo::new(src, amt);
2303
2304 let new_state = || {
2305 let mut state = ValidatedState::default();
2306 state.prefund_account(src, amt);
2307 state
2308 };
2309
2310 tracing::info!("test successful fee");
2311 let mut state = new_state();
2312 state.charge_fee(fee_info, dst).unwrap();
2313 assert_eq!(state.balance(src), Some(0.into()));
2314 assert_eq!(state.balance(dst), Some(amt));
2315
2316 tracing::info!("test insufficient balance");
2317 let err = state.charge_fee(fee_info, dst).unwrap_err();
2318 assert_eq!(state.balance(src), Some(0.into()));
2319 assert_eq!(state.balance(dst), Some(amt));
2320 assert_eq!(
2321 FeeError::InsufficientFunds {
2322 balance: None,
2323 amount: amt
2324 },
2325 err
2326 );
2327
2328 tracing::info!("test src not in memory");
2329 let mut state = new_state();
2330 state.fee_merkle_tree.forget(src).expect_ok().unwrap();
2331 assert_eq!(
2332 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
2333 state.charge_fee(fee_info, dst).unwrap_err()
2334 );
2335
2336 tracing::info!("test dst not in memory");
2337 let mut state = new_state();
2338 state.prefund_account(dst, amt);
2339 state.fee_merkle_tree.forget(dst).expect_ok().unwrap();
2340 assert_eq!(
2341 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
2342 state.charge_fee(fee_info, dst).unwrap_err()
2343 );
2344 }
2345
2346 #[test]
2347 fn test_fee_amount_serde_json_as_decimal() {
2348 let amt = FeeAmount::from(123);
2349 let serialized = serde_json::to_string(&amt).unwrap();
2350
2351 assert_eq!(serialized, "\"123\"");
2353
2354 let deserialized: FeeAmount = serde_json::from_str(&serialized).unwrap();
2356 assert_eq!(deserialized, amt);
2357 }
2358
2359 #[test]
2360 fn test_fee_amount_from_units() {
2361 for (unit, multiplier) in [
2362 ("wei", 1),
2363 ("gwei", 1_000_000_000),
2364 ("eth", 1_000_000_000_000_000_000),
2365 ] {
2366 let amt: FeeAmount = serde_json::from_str(&format!("\"1 {unit}\"")).unwrap();
2367 assert_eq!(amt, multiplier.into());
2368 }
2369 }
2370
2371 #[test]
2372 fn test_fee_amount_serde_json_from_hex() {
2373 let amt: FeeAmount = serde_json::from_str("\"0x123\"").unwrap();
2376 assert_eq!(amt, FeeAmount::from(0x123));
2377 }
2378
2379 #[test]
2380 fn test_fee_amount_serde_json_from_number() {
2381 let amt: FeeAmount = serde_json::from_str("123").unwrap();
2383 assert_eq!(amt, FeeAmount::from(123));
2384 }
2385
2386 #[test]
2387 fn test_fee_amount_serde_bincode_unchanged() {
2388 let n = ethers_core::types::U256::from(123);
2391 let amt = FeeAmount(U256::from(123));
2392 assert_eq!(
2393 bincode::serialize(&n).unwrap(),
2394 bincode::serialize(&amt).unwrap(),
2395 );
2396 }
2397
2398 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2399 async fn test_validate_builder_fee() {
2400 let max_block_size = 10;
2401
2402 let validated_state = ValidatedState::default();
2403 let instance_state = NodeState::mock().with_chain_config(ChainConfig {
2404 base_fee: 1000.into(), max_block_size: max_block_size.into(),
2406 ..validated_state.chain_config.resolve().unwrap()
2407 });
2408
2409 let parent: Leaf2 = Leaf::genesis(
2410 &instance_state.genesis_state,
2411 &instance_state,
2412 MOCK_UPGRADE.base,
2413 )
2414 .await
2415 .into();
2416 let header = parent.block_header().clone();
2417 let metadata = parent.block_header().metadata();
2418
2419 debug!("{:?}", header.version());
2420
2421 let key_pair = EthKeyPair::random();
2422 let account = key_pair.fee_account();
2423
2424 let data = header.fee_info()[0].amount().as_u64().unwrap();
2425 let sig = FeeAccount::sign_builder_message(&key_pair, &data.to_be_bytes()).unwrap();
2426
2427 account
2429 .validate_builder_signature(&sig, &data.to_be_bytes())
2430 .then_some(())
2431 .unwrap();
2432
2433 let sig = FeeAccount::sign_fee(&key_pair, data, metadata).unwrap();
2435
2436 let header = match header {
2437 Header::V1(header) => Header::V1(v0_1::Header {
2438 builder_signature: Some(sig),
2439 fee_info: FeeInfo::new(account, data),
2440 ..header
2441 }),
2442 Header::V2(header) => Header::V2(v0_2::Header {
2443 builder_signature: Some(sig),
2444 fee_info: FeeInfo::new(account, data),
2445 ..header
2446 }),
2447 Header::V3(header) => Header::V3(v0_3::Header {
2448 builder_signature: Some(sig),
2449 fee_info: FeeInfo::new(account, data),
2450 ..header
2451 }),
2452 Header::V4(header) => Header::V4(v0_4::Header {
2453 builder_signature: Some(sig),
2454 fee_info: FeeInfo::new(account, data),
2455 ..header
2456 }),
2457 Header::V5(header) => Header::V5(v0_5::Header {
2458 builder_signature: Some(sig),
2459 fee_info: FeeInfo::new(account, data),
2460 ..header
2461 }),
2462 Header::V6(header) => Header::V6(v0_6::Header {
2463 builder_signature: Some(sig),
2464 fee_info: FeeInfo::new(account, data),
2465 ..header
2466 }),
2467 Header::V7(header) => Header::V7(v0_7::Header {
2468 builder_signature: Some(sig),
2469 fee_info: FeeInfo::new(account, data),
2470 ..header
2471 }),
2472 };
2473
2474 let version = header.version();
2475 validate_builder_fee(&header, version).unwrap();
2476 }
2477
2478 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2479 async fn test_validate_total_rewards_distributed() {
2480 let instance = NodeState::mock().with_genesis_version(version(0, 4));
2481
2482 let (payload, metadata) =
2483 Payload::from_transactions(&[], &instance.genesis_state, &instance)
2484 .await
2485 .unwrap();
2486
2487 let header = Header::genesis(
2488 &instance,
2489 payload.clone(),
2490 &metadata,
2491 TEST_VERSIONS.da_committee.base,
2492 );
2493
2494 let validated_state = ValidatedState::default();
2495 let actual_total = RewardAmount::from(1000u64);
2496 let block_size = 100u32;
2497
2498 let proposed_header = match header.clone() {
2499 Header::V4(mut h) => {
2500 h.total_reward_distributed = actual_total;
2501 Header::V4(h)
2502 },
2503 _ => unreachable!("Expected V4 header"),
2504 };
2505
2506 let validation_start_time = OffsetDateTime::now_utc();
2507 let validated_transition = ValidatedTransition::new(
2508 validated_state.clone(),
2509 &header,
2510 Proposal::new(&proposed_header, block_size),
2511 Some(actual_total),
2512 version(0, 4),
2513 validation_start_time,
2514 None, None,
2516 );
2517
2518 validated_transition
2519 .validate_total_rewards_distributed()
2520 .unwrap();
2521
2522 let wrong_total = RewardAmount::from(2000u64);
2523 let proposed_header = match header.clone() {
2524 Header::V4(mut h) => {
2525 h.total_reward_distributed = wrong_total;
2526 Header::V4(h)
2527 },
2528 _ => unreachable!("Expected V4 header"),
2529 };
2530
2531 ValidatedTransition::new(
2532 validated_state.clone(),
2533 &header,
2534 Proposal::new(&proposed_header, block_size),
2535 Some(actual_total),
2536 version(0, 4),
2537 validation_start_time,
2538 None, None,
2540 )
2541 .validate_total_rewards_distributed()
2542 .unwrap_err();
2543 }
2544
2545 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2546 async fn test_regression_slow_validation_timestamp_drift() {
2547 let instance = NodeState::mock();
2548 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
2549
2550 let validation_start_time = OffsetDateTime::now_utc();
2551 let timestamp = validation_start_time.unix_timestamp() as u64;
2552 let timestamp_millis = TimestampMillis::from_time(&validation_start_time).u64();
2553
2554 let mut header = parent.clone();
2555 header.set_timestamp(timestamp, timestamp_millis);
2556
2557 std::thread::sleep(Duration::from_secs(13));
2558
2559 let proposal_without_fix = Proposal::new(&header, block_size);
2561 let err = ValidatedTransition::new(
2562 instance.genesis_state.clone(),
2563 &parent,
2564 proposal_without_fix,
2565 None,
2566 MAX_SUPPORTED_VERSION,
2567 OffsetDateTime::now_utc(),
2568 None, None,
2570 )
2571 .validate_timestamp()
2572 .unwrap_err();
2573
2574 assert!(matches!(
2575 err,
2576 ProposalValidationError::InvalidTimestampDrift { .. }
2577 ));
2578
2579 let proposal = Proposal::new(&header, block_size);
2581 ValidatedTransition::new(
2582 instance.genesis_state.clone(),
2583 &parent,
2584 proposal,
2585 None,
2586 MAX_SUPPORTED_VERSION,
2587 validation_start_time,
2588 None, None,
2590 )
2591 .validate_timestamp()
2592 .unwrap();
2593 }
2594
2595 #[test_log::test(tokio::test(flavor = "multi_thread"))]
2597 async fn test_validate_and_apply_header_slow_catchup_succeeds() {
2598 let mut instance = NodeState::mock_v2();
2600
2601 let mut genesis_state = instance.genesis_state.clone();
2602
2603 genesis_state
2605 .fee_merkle_tree
2606 .update(FeeAccount::default(), FeeAmount::from(1u64))
2607 .unwrap();
2608 instance.genesis_state = genesis_state.clone();
2609
2610 let genesis = Leaf::genesis(&genesis_state, &instance, MOCK_UPGRADE.base).await;
2611 let parent_leaf: Leaf2 = genesis.into();
2612 let parent_header = parent_leaf.block_header().clone();
2613
2614 let mut expected_block_tree = genesis_state.block_merkle_tree.clone();
2615 expected_block_tree.push(parent_header.commit()).unwrap();
2616
2617 let proposed_header = match parent_header {
2618 Header::V2(header) => Header::V2(v0_2::Header {
2619 height: header.height + 1,
2620 timestamp: OffsetDateTime::now_utc().unix_timestamp() as u64,
2621 block_merkle_tree_root: expected_block_tree.commitment(),
2622 chain_config: header.chain_config.commit().into(),
2623 ..header
2624 }),
2625 _ => panic!("Expected V2 header"),
2626 };
2627
2628 let slow_catchup =
2629 MockStateCatchup::from_iter([(ViewNumber::new(0), Arc::new(genesis_state.clone()))])
2630 .with_delay(Duration::from_secs(13));
2631 instance.state_catchup = Arc::new(slow_catchup);
2632
2633 genesis_state
2635 .fee_merkle_tree
2636 .forget(FeeAccount::default())
2637 .expect_ok()
2638 .unwrap();
2639
2640 genesis_state
2641 .validate_and_apply_header(
2642 &instance,
2643 &parent_leaf,
2644 &proposed_header,
2645 Some(0), FEE_VERSION,
2647 0, )
2649 .await
2650 .unwrap();
2651 }
2652
2653 #[test]
2654 fn test_apply_large_block_upgrade() {
2655 let upgraded_chain_config = ChainConfig {
2656 max_block_size: (10 * 1024 * 1024).into(),
2657 ..Default::default()
2658 };
2659 let upgrade = crate::Upgrade {
2660 mode: UpgradeMode::View(ViewBasedUpgrade {
2661 start_voting_view: None,
2662 stop_voting_view: None,
2663 start_proposing_view: 1,
2664 stop_proposing_view: 10,
2665 }),
2666 upgrade_type: UpgradeType::LargeBlock {
2667 chain_config: upgraded_chain_config,
2668 },
2669 };
2670 let instance = NodeState::mock()
2671 .with_current_version(NEW_PROTOCOL_VERSION)
2672 .with_upgrades([(LARGE_BLOCK_VERSION, upgrade)].into());
2673
2674 let mut state = ValidatedState::default();
2675 let pre_upgrade_chain_config = state.chain_config;
2676 state.apply_upgrade(&instance, NEW_PROTOCOL_VERSION);
2677 assert_eq!(state.chain_config, pre_upgrade_chain_config);
2678
2679 state.apply_upgrade(&instance, LARGE_BLOCK_VERSION);
2680 assert_eq!(state.chain_config, upgraded_chain_config.into());
2681 assert_eq!(
2682 state.chain_config.resolve().unwrap().max_block_size,
2683 (10 * 1024 * 1024).into()
2684 );
2685 }
2686}