1use std::ops::Add;
2
3use alloy::primitives::{Address, U256};
4use anyhow::{bail, Context};
5use committable::{Commitment, Committable};
6use either::Either;
7use hotshot_query_service::merklized_state::MerklizedState;
8use hotshot_types::{
9 data::{BlockError, EpochNumber, ViewNumber},
10 traits::{
11 block_contents::BlockHeader, node_implementation::ConsensusTime,
12 signature_key::BuilderSignatureKey, states::StateDelta, ValidatedState as HotShotState,
13 },
14 utils::{epoch_from_block_number, is_ge_epoch_root},
15};
16use itertools::Itertools;
17use jf_merkle_tree::{
18 prelude::{MerkleProof, Sha3Digest, Sha3Node},
19 AppendableMerkleTreeScheme, ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme,
20 LookupResult, MerkleCommitment, MerkleTreeError, MerkleTreeScheme,
21 PersistentUniversalMerkleTreeScheme, UniversalMerkleTreeScheme,
22};
23use num_traits::CheckedSub;
24use serde::{Deserialize, Serialize};
25use thiserror::Error;
26use time::OffsetDateTime;
27use vbs::version::{StaticVersionType, Version};
28
29use super::{
30 fee_info::FeeError, instance_state::NodeState, v0_1::IterableFeeInfo, BlockMerkleCommitment,
31 BlockSize, EpochVersion, FeeMerkleCommitment, L1Client,
32};
33use crate::{
34 traits::StateCatchup,
35 v0::{
36 impls::{distribute_block_reward, StakeTableHash},
37 sparse_mt::{Keccak256Hasher, KeccakNode},
38 },
39 v0_3::{
40 ChainConfig, ResolvableChainConfig, RewardAccountV1, RewardAmount,
41 RewardMerkleCommitmentV1, RewardMerkleTreeV1, REWARD_MERKLE_TREE_V1_HEIGHT,
42 },
43 v0_4::{
44 Delta, RewardAccountV2, RewardMerkleCommitmentV2, RewardMerkleTreeV2,
45 REWARD_MERKLE_TREE_V2_HEIGHT,
46 },
47 BlockMerkleTree, DrbAndHeaderUpgradeVersion, FeeAccount, FeeAmount, FeeInfo, FeeMerkleTree,
48 Header, Leaf2, NsTableValidationError, PayloadByteLen, SeqTypes, UpgradeType,
49 BLOCK_MERKLE_TREE_HEIGHT, FEE_MERKLE_TREE_HEIGHT,
50};
51
52#[allow(dead_code)]
55pub enum StateValidationError {
56 ProposalValidation(ProposalValidationError),
57 BuilderValidation(BuilderValidationError),
58 Fee(FeeError),
59}
60
61#[derive(Error, Debug, Eq, PartialEq)]
63pub enum BuilderValidationError {
64 #[error("Builder signature not found")]
65 SignatureNotFound,
66 #[error("Fee amount out of range: {0}")]
67 FeeAmountOutOfRange(FeeAmount),
68 #[error("Invalid Builder Signature")]
69 InvalidBuilderSignature,
70}
71
72#[derive(Error, Debug, Eq, PartialEq)]
74pub enum ProposalValidationError {
75 #[error("Next stake table hash mismatch: expected={expected:?}, proposal={proposal:?}")]
76 NextStakeTableHashMismatch {
77 expected: StakeTableHash,
78 proposal: StakeTableHash,
79 },
80 #[error("Invalid ChainConfig: expected={expected:?}, proposal={proposal:?}")]
81 InvalidChainConfig {
82 expected: Box<ChainConfig>,
83 proposal: Box<ResolvableChainConfig>,
84 },
85 #[error(
86 "Invalid Payload Size: (max_block_size={max_block_size}, proposed_block_size={block_size})"
87 )]
88 MaxBlockSizeExceeded {
89 max_block_size: BlockSize,
90 block_size: BlockSize,
91 },
92 #[error(
93 "Insufficient Fee: block_size={max_block_size}, base_fee={base_fee}, \
94 proposed_fee={proposed_fee}"
95 )]
96 InsufficientFee {
97 max_block_size: BlockSize,
98 base_fee: FeeAmount,
99 proposed_fee: FeeAmount,
100 },
101 #[error("Invalid Height: parent_height={parent_height}, proposal_height={proposal_height}")]
102 InvalidHeight {
103 parent_height: u64,
104 proposal_height: u64,
105 },
106 #[error("Invalid Block Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
107 InvalidBlockRoot {
108 expected_root: BlockMerkleCommitment,
109 proposal_root: BlockMerkleCommitment,
110 },
111 #[error("Invalid Fee Root Error: expected={expected_root:?}, proposal={proposal_root:?}")]
112 InvalidFeeRoot {
113 expected_root: FeeMerkleCommitment,
114 proposal_root: FeeMerkleCommitment,
115 },
116 #[error(
117 "Invalid v1 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
118 )]
119 InvalidV1RewardRoot {
120 expected_root: RewardMerkleCommitmentV1,
121 proposal_root: RewardMerkleCommitmentV1,
122 },
123 #[error(
124 "Invalid v2 Reward Root Error: expected={expected_root:?}, proposal={proposal_root:?}"
125 )]
126 InvalidV2RewardRoot {
127 expected_root: RewardMerkleCommitmentV2,
128 proposal_root: RewardMerkleCommitmentV2,
129 },
130 #[error("Invalid namespace table: {0}")]
131 InvalidNsTable(NsTableValidationError),
132 #[error("Some fee amount or their sum total out of range")]
133 SomeFeeAmountOutOfRange,
134 #[error("Invalid timestamp: proposal={proposal_timestamp}, parent={parent_timestamp}")]
135 DecrementingTimestamp {
136 proposal_timestamp: u64,
137 parent_timestamp: u64,
138 },
139 #[error("Timestamp drift too high: proposed:={proposal}, system={system}, diff={diff}")]
140 InvalidTimestampDrift {
141 proposal: u64,
142 system: u64,
143 diff: u64,
144 },
145 #[error(
146 "Inconsistent timestamps on header: timestamp:={timestamp}, \
147 timestamp_millis={timestamp_millis}"
148 )]
149 InconsistentTimestamps {
150 timestamp: u64,
151 timestamp_millis: u64,
152 },
153 #[error("l1_finalized has `None` value")]
154 L1FinalizedNotFound,
155 #[error("l1_finalized height is decreasing: parent={parent:?} proposed={proposed:?}")]
156 L1FinalizedDecrementing {
157 parent: Option<(u64, u64)>,
158 proposed: Option<(u64, u64)>,
159 },
160 #[error("Invalid proposal: l1_head height is decreasing")]
161 DecrementingL1Head,
162 #[error("Builder Validation Error: {0}")]
163 BuilderValidationError(BuilderValidationError),
164 #[error("Invalid proposal: l1 finalized does not match the proposal")]
165 InvalidL1Finalized,
166 #[error("reward root not found")]
167 RewardRootNotFound {},
168 #[error("Next stake table not found")]
169 NextStakeTableNotFound,
170 #[error("Next stake table hash missing")]
171 NextStakeTableHashNotFound,
172 #[error("No Epoch Height")]
173 NoEpochHeight,
174 #[error("No First Epoch Configured")]
175 NoFirstEpoch,
176}
177
178impl StateDelta for Delta {}
179
180#[derive(Hash, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
181pub struct ValidatedState {
183 pub block_merkle_tree: BlockMerkleTree,
185 pub fee_merkle_tree: FeeMerkleTree,
187 pub reward_merkle_tree_v1: RewardMerkleTreeV1,
188 pub reward_merkle_tree_v2: RewardMerkleTreeV2,
189 pub chain_config: ResolvableChainConfig,
191}
192
193impl Default for ValidatedState {
194 fn default() -> Self {
195 let block_merkle_tree = BlockMerkleTree::from_elems(
196 Some(BLOCK_MERKLE_TREE_HEIGHT),
197 Vec::<Commitment<Header>>::new(),
198 )
199 .unwrap();
200
201 let fee_merkle_tree = FeeMerkleTree::from_kv_set(
205 FEE_MERKLE_TREE_HEIGHT,
206 Vec::<(FeeAccount, FeeAmount)>::new(),
207 )
208 .unwrap();
209
210 let reward_merkle_tree_v1 = RewardMerkleTreeV1::from_kv_set(
211 REWARD_MERKLE_TREE_V1_HEIGHT,
212 Vec::<(RewardAccountV1, RewardAmount)>::new(),
213 )
214 .unwrap();
215
216 let reward_merkle_tree_v2 = RewardMerkleTreeV2::from_kv_set(
217 REWARD_MERKLE_TREE_V2_HEIGHT,
218 Vec::<(RewardAccountV2, RewardAmount)>::new(),
219 )
220 .unwrap();
221
222 let chain_config = ResolvableChainConfig::from(ChainConfig::default());
223
224 Self {
225 block_merkle_tree,
226 fee_merkle_tree,
227 reward_merkle_tree_v1,
228 reward_merkle_tree_v2,
229 chain_config,
230 }
231 }
232}
233
234impl ValidatedState {
235 pub fn prefund_account(&mut self, account: FeeAccount, amount: FeeAmount) {
237 self.fee_merkle_tree.update(account, amount).unwrap();
238 }
239
240 pub fn balance(&mut self, account: FeeAccount) -> Option<FeeAmount> {
241 match self.fee_merkle_tree.lookup(account) {
242 LookupResult::Ok(balance, _) => Some(*balance),
243 LookupResult::NotFound(_) => Some(0.into()),
244 LookupResult::NotInMemory => None,
245 }
246 }
247
248 pub fn forgotten_accounts(
253 &self,
254 accounts: impl IntoIterator<Item = FeeAccount>,
255 ) -> Vec<FeeAccount> {
256 accounts
257 .into_iter()
258 .unique()
259 .filter(|account| {
260 self.fee_merkle_tree
261 .lookup(*account)
262 .expect_not_in_memory()
263 .is_ok()
264 })
265 .collect()
266 }
267
268 pub fn forgotten_reward_accounts_v2(
269 &self,
270 accounts: impl IntoIterator<Item = RewardAccountV2>,
271 ) -> Vec<RewardAccountV2> {
272 accounts
273 .into_iter()
274 .unique()
275 .filter(|account| {
276 self.reward_merkle_tree_v2
277 .lookup(*account)
278 .expect_not_in_memory()
279 .is_ok()
280 })
281 .collect()
282 }
283
284 pub fn forgotten_reward_accounts_v1(
285 &self,
286 accounts: impl IntoIterator<Item = RewardAccountV1>,
287 ) -> Vec<RewardAccountV1> {
288 accounts
289 .into_iter()
290 .unique()
291 .filter(|account| {
292 self.reward_merkle_tree_v1
293 .lookup(*account)
294 .expect_not_in_memory()
295 .is_ok()
296 })
297 .collect()
298 }
299
300 pub fn need_to_fetch_blocks_mt_frontier(&self) -> bool {
302 let num_leaves = self.block_merkle_tree.num_leaves();
303 if num_leaves == 0 {
304 false
305 } else {
306 self.block_merkle_tree
307 .lookup(num_leaves - 1)
308 .expect_ok()
309 .is_err()
310 }
311 }
312
313 pub fn insert_fee_deposit(
315 &mut self,
316 fee_info: FeeInfo,
317 ) -> anyhow::Result<LookupResult<FeeAmount, (), ()>> {
318 Ok(self
319 .fee_merkle_tree
320 .update_with(fee_info.account, |balance| {
321 Some(balance.cloned().unwrap_or_default().add(fee_info.amount))
322 })?)
323 }
324
325 pub fn apply_proposal(
326 &mut self,
327 delta: &mut Delta,
328 parent_leaf: &Leaf2,
329 l1_deposits: Vec<FeeInfo>,
330 ) {
331 self.block_merkle_tree
333 .push(parent_leaf.block_header().commit())
334 .unwrap();
335
336 for FeeInfo { account, amount } in l1_deposits.iter() {
337 self.fee_merkle_tree
338 .update_with(account, |balance| {
339 Some(balance.cloned().unwrap_or_default().add(*amount))
340 })
341 .expect("update_with succeeds");
342 delta.fees_delta.insert(*account);
343 }
344 }
345
346 pub fn charge_fees(
347 &mut self,
348 delta: &mut Delta,
349 fee_info: Vec<FeeInfo>,
350 recipient: FeeAccount,
351 ) -> Result<(), FeeError> {
352 for fee_info in fee_info {
353 self.charge_fee(fee_info, recipient)?;
354 delta.fees_delta.extend([fee_info.account, recipient]);
355 }
356 Ok(())
357 }
358
359 pub fn charge_fee(&mut self, fee_info: FeeInfo, recipient: FeeAccount) -> Result<(), FeeError> {
361 if fee_info.amount == 0.into() {
362 return Ok(());
363 }
364
365 let fee_state = self.fee_merkle_tree.clone();
366
367 let FeeInfo { account, amount } = fee_info;
369 let mut err = None;
370 let fee_state = fee_state.persistent_update_with(account, |balance| {
371 let balance = balance.copied();
372 let Some(updated) = balance.unwrap_or_default().checked_sub(&amount) else {
373 err = Some(FeeError::InsufficientFunds { balance, amount });
375 return balance;
376 };
377 if updated == FeeAmount::default() {
378 None
381 } else {
382 Some(updated)
384 }
385 })?;
386
387 if let Some(err) = err {
389 return Err(err);
390 }
391
392 let fee_state = fee_state.persistent_update_with(recipient, |balance| {
395 Some(balance.copied().unwrap_or_default() + amount)
396 })?;
397
398 self.fee_merkle_tree = fee_state;
400 Ok(())
401 }
402}
403#[derive(Debug)]
405pub(crate) struct Proposal<'a> {
406 header: &'a Header,
407 block_size: u32,
408}
409
410impl<'a> Proposal<'a> {
411 pub(crate) fn new(header: &'a Header, block_size: u32) -> Self {
412 Self { header, block_size }
413 }
414 fn validate_l1_head(&self, parent_l1_head: u64) -> Result<(), ProposalValidationError> {
417 if self.header.l1_head() < parent_l1_head {
418 return Err(ProposalValidationError::DecrementingL1Head);
419 }
420 Ok(())
421 }
422 fn validate_chain_config(
426 &self,
427 expected_chain_config: &ChainConfig,
428 ) -> Result<(), ProposalValidationError> {
429 let proposed_chain_config = self.header.chain_config();
430 if proposed_chain_config.commit() != expected_chain_config.commit() {
431 return Err(ProposalValidationError::InvalidChainConfig {
432 expected: Box::new(*expected_chain_config),
433 proposal: Box::new(proposed_chain_config),
434 });
435 }
436 Ok(())
437 }
438
439 fn validate_timestamp_non_dec(
441 &self,
442 parent_timestamp: u64,
443 ) -> Result<(), ProposalValidationError> {
444 if self.header.timestamp() < parent_timestamp {
445 return Err(ProposalValidationError::DecrementingTimestamp {
446 proposal_timestamp: self.header.timestamp(),
447 parent_timestamp,
448 });
449 }
450
451 Ok(())
452 }
453
454 fn validate_timestamp_drift(&self, system_time: u64) -> Result<(), ProposalValidationError> {
459 let diff = self.header.timestamp().abs_diff(system_time);
462 if diff > 12 {
463 return Err(ProposalValidationError::InvalidTimestampDrift {
464 proposal: self.header.timestamp(),
465 system: system_time,
466 diff,
467 });
468 }
469
470 Ok(())
471 }
472
473 fn validate_timestamp_consistency(&self) -> Result<(), ProposalValidationError> {
475 if self.header.timestamp() != self.header.timestamp_millis() / 1_000 {
476 return Err(ProposalValidationError::InconsistentTimestamps {
477 timestamp: self.header.timestamp(),
478 timestamp_millis: self.header.timestamp_millis(),
479 });
480 }
481
482 Ok(())
483 }
484
485 fn validate_block_merkle_tree(
487 &self,
488 block_merkle_tree_root: BlockMerkleCommitment,
489 ) -> Result<(), ProposalValidationError> {
490 if self.header.block_merkle_tree_root() != block_merkle_tree_root {
491 return Err(ProposalValidationError::InvalidBlockRoot {
492 expected_root: block_merkle_tree_root,
493 proposal_root: self.header.block_merkle_tree_root(),
494 });
495 }
496
497 Ok(())
498 }
499}
500#[derive(Debug)]
504pub(crate) struct ValidatedTransition<'a> {
505 state: ValidatedState,
506 expected_chain_config: ChainConfig,
507 parent: &'a Header,
508 proposal: Proposal<'a>,
509}
510
511impl<'a> ValidatedTransition<'a> {
512 pub(crate) fn new(state: ValidatedState, parent: &'a Header, proposal: Proposal<'a>) -> Self {
513 let expected_chain_config = state
514 .chain_config
515 .resolve()
516 .expect("Chain Config not found in validated state");
517 Self {
518 state,
519 expected_chain_config,
520 parent,
521 proposal,
522 }
523 }
524
525 pub(crate) fn validate(self) -> Result<Self, ProposalValidationError> {
541 self.validate_timestamp()?;
542 self.validate_builder_fee()?;
543 self.validate_height()?;
544 self.validate_chain_config()?;
545 self.validate_block_size()?;
546 self.validate_fee()?;
547 self.validate_fee_merkle_tree()?;
548 self.validate_block_merkle_tree()?;
549 self.validate_reward_merkle_tree()?;
550 self.validate_l1_finalized()?;
551 self.validate_l1_head()?;
552 self.validate_namespace_table()?;
553
554 Ok(self)
555 }
556
557 fn validate_l1_finalized(&self) -> Result<(), ProposalValidationError> {
559 let proposed_finalized = self.proposal.header.l1_finalized();
560 let parent_finalized = self.parent.l1_finalized();
561
562 if proposed_finalized < parent_finalized {
563 return Err(ProposalValidationError::L1FinalizedDecrementing {
568 parent: parent_finalized.map(|block| (block.number, block.timestamp.to::<u64>())),
569 proposed: proposed_finalized
570 .map(|block| (block.number, block.timestamp.to::<u64>())),
571 });
572 }
573 Ok(())
574 }
575 async fn wait_for_l1(self, l1_client: &L1Client) -> Result<Self, ProposalValidationError> {
580 self.wait_for_l1_head(l1_client).await;
581 self.wait_for_finalized_block(l1_client).await?;
582 Ok(self)
583 }
584
585 async fn wait_for_l1_head(&self, l1_client: &L1Client) {
588 let _ = l1_client
589 .wait_for_block(self.proposal.header.l1_head())
590 .await;
591 }
592 async fn wait_for_finalized_block(
595 &self,
596 l1_client: &L1Client,
597 ) -> Result<(), ProposalValidationError> {
598 let proposed_finalized = self.proposal.header.l1_finalized();
599
600 if let Some(proposed_finalized) = proposed_finalized {
601 let finalized = l1_client
602 .wait_for_finalized_block(proposed_finalized.number())
603 .await;
604
605 if finalized != proposed_finalized {
606 return Err(ProposalValidationError::InvalidL1Finalized);
607 }
608 }
609
610 Ok(())
611 }
612
613 fn validate_l1_head(&self) -> Result<(), ProposalValidationError> {
615 self.proposal.validate_l1_head(self.parent.l1_head())?;
616 Ok(())
617 }
618 fn validate_builder_fee(&self) -> Result<(), ProposalValidationError> {
621 if let Err(err) = validate_builder_fee(self.proposal.header) {
623 return Err(ProposalValidationError::BuilderValidationError(err));
624 }
625 Ok(())
626 }
627 fn validate_chain_config(&self) -> Result<(), ProposalValidationError> {
629 self.proposal
630 .validate_chain_config(&self.expected_chain_config)?;
631 Ok(())
632 }
633 fn validate_block_size(&self) -> Result<(), ProposalValidationError> {
636 let block_size = self.proposal.block_size as u64;
637 if block_size > *self.expected_chain_config.max_block_size {
638 return Err(ProposalValidationError::MaxBlockSizeExceeded {
639 max_block_size: self.expected_chain_config.max_block_size,
640 block_size: block_size.into(),
641 });
642 }
643 Ok(())
644 }
645 fn validate_fee(&self) -> Result<(), ProposalValidationError> {
648 let Some(amount) = self.proposal.header.fee_info().amount() else {
651 return Err(ProposalValidationError::SomeFeeAmountOutOfRange);
652 };
653
654 if amount < self.expected_chain_config.base_fee * U256::from(self.proposal.block_size) {
655 return Err(ProposalValidationError::InsufficientFee {
656 max_block_size: self.expected_chain_config.max_block_size,
657 base_fee: self.expected_chain_config.base_fee,
658 proposed_fee: amount,
659 });
660 }
661 Ok(())
662 }
663 fn validate_height(&self) -> Result<(), ProposalValidationError> {
665 let parent_header = self.parent;
666 if self.proposal.header.height() != parent_header.height() + 1 {
667 return Err(ProposalValidationError::InvalidHeight {
668 parent_height: parent_header.height(),
669 proposal_height: self.proposal.header.height(),
670 });
671 }
672 Ok(())
673 }
674 fn validate_timestamp(&self) -> Result<(), ProposalValidationError> {
679 self.proposal.validate_timestamp_consistency()?;
680
681 self.proposal
682 .validate_timestamp_non_dec(self.parent.timestamp())?;
683
684 let system_time: u64 = OffsetDateTime::now_utc().unix_timestamp() as u64;
686 self.proposal.validate_timestamp_drift(system_time)?;
687
688 Ok(())
689 }
690 fn validate_block_merkle_tree(&self) -> Result<(), ProposalValidationError> {
693 let block_merkle_tree_root = self.state.block_merkle_tree.commitment();
694 self.proposal
695 .validate_block_merkle_tree(block_merkle_tree_root)?;
696
697 Ok(())
698 }
699
700 fn validate_reward_merkle_tree(&self) -> Result<(), ProposalValidationError> {
703 match self.proposal.header.reward_merkle_tree_root() {
704 Either::Left(proposal_root) => {
705 let expected_root = self.state.reward_merkle_tree_v1.commitment();
706 if proposal_root != expected_root {
707 return Err(ProposalValidationError::InvalidV1RewardRoot {
708 expected_root,
709 proposal_root,
710 });
711 }
712 },
713 Either::Right(proposal_root) => {
714 let expected_root = self.state.reward_merkle_tree_v2.commitment();
715 if proposal_root != expected_root {
716 return Err(ProposalValidationError::InvalidV2RewardRoot {
717 expected_root,
718 proposal_root,
719 });
720 }
721 },
722 }
723
724 Ok(())
725 }
726
727 fn validate_fee_merkle_tree(&self) -> Result<(), ProposalValidationError> {
730 let fee_merkle_tree_root = self.state.fee_merkle_tree.commitment();
731 if self.proposal.header.fee_merkle_tree_root() != fee_merkle_tree_root {
732 return Err(ProposalValidationError::InvalidFeeRoot {
733 expected_root: fee_merkle_tree_root,
734 proposal_root: self.proposal.header.fee_merkle_tree_root(),
735 });
736 }
737
738 Ok(())
739 }
740 fn validate_namespace_table(&self) -> Result<(), ProposalValidationError> {
742 self.proposal
743 .header
744 .ns_table()
745 .validate(&PayloadByteLen(self.proposal.block_size as usize))
747 .map_err(ProposalValidationError::from)
748 }
749}
750
751#[cfg(any(test, feature = "testing"))]
752impl ValidatedState {
753 pub fn forget(&self) -> Self {
754 Self {
755 fee_merkle_tree: FeeMerkleTree::from_commitment(self.fee_merkle_tree.commitment()),
756 block_merkle_tree: BlockMerkleTree::from_commitment(
757 self.block_merkle_tree.commitment(),
758 ),
759 reward_merkle_tree_v2: RewardMerkleTreeV2::from_commitment(
760 self.reward_merkle_tree_v2.commitment(),
761 ),
762 reward_merkle_tree_v1: RewardMerkleTreeV1::from_commitment(
763 self.reward_merkle_tree_v1.commitment(),
764 ),
765 chain_config: ResolvableChainConfig::from(self.chain_config.commit()),
766 }
767 }
768}
769
770impl From<NsTableValidationError> for ProposalValidationError {
771 fn from(err: NsTableValidationError) -> Self {
772 Self::InvalidNsTable(err)
773 }
774}
775
776impl From<ProposalValidationError> for BlockError {
777 fn from(err: ProposalValidationError) -> Self {
778 tracing::error!("Invalid Block Header: {err:#}");
779 BlockError::InvalidBlockHeader(err.to_string())
780 }
781}
782
783impl From<MerkleTreeError> for FeeError {
784 fn from(item: MerkleTreeError) -> Self {
785 Self::MerkleTreeError(item)
786 }
787}
788
789fn validate_builder_fee(proposed_header: &Header) -> Result<(), BuilderValidationError> {
792 for (fee_info, signature) in proposed_header
794 .fee_info()
795 .iter()
796 .zip(proposed_header.builder_signature())
797 {
798 fee_info
800 .amount()
801 .as_u64()
802 .ok_or(BuilderValidationError::FeeAmountOutOfRange(fee_info.amount))?;
803
804 if !fee_info.account().validate_fee_signature(
806 &signature,
807 fee_info.amount().as_u64().unwrap(),
808 proposed_header.metadata(),
809 ) && !fee_info
810 .account()
811 .validate_fee_signature_with_vid_commitment(
812 &signature,
813 fee_info.amount().as_u64().unwrap(),
814 proposed_header.metadata(),
815 &proposed_header.payload_commitment(),
816 )
817 {
818 return Err(BuilderValidationError::InvalidBuilderSignature);
819 }
820 }
821
822 Ok(())
823}
824
825impl ValidatedState {
826 pub async fn apply_header(
832 &self,
833 instance: &NodeState,
834 peers: &impl StateCatchup,
835 parent_leaf: &Leaf2,
836 proposed_header: &Header,
837 version: Version,
838 view_number: ViewNumber,
839 ) -> anyhow::Result<(Self, Delta)> {
840 let mut validated_state = self.clone();
843 validated_state.apply_upgrade(instance, version);
844
845 let chain_config = validated_state
848 .get_chain_config(instance, peers, &proposed_header.chain_config())
849 .await?;
850
851 if Some(chain_config) != validated_state.chain_config.resolve() {
852 validated_state.chain_config = chain_config.into();
853 }
854
855 let l1_deposits = get_l1_deposits(
856 instance,
857 proposed_header,
858 parent_leaf,
859 chain_config.fee_contract,
860 )
861 .await;
862
863 let missing_accounts = self.forgotten_accounts(
867 [chain_config.fee_recipient]
868 .into_iter()
869 .chain(proposed_header.fee_info().accounts())
870 .chain(l1_deposits.accounts()),
871 );
872
873 let parent_height = parent_leaf.height();
874 let parent_view = parent_leaf.view_number();
875
876 if self.need_to_fetch_blocks_mt_frontier() {
878 tracing::info!(
879 parent_height,
880 ?parent_view,
881 "fetching block frontier from peers"
882 );
883 peers
884 .remember_blocks_merkle_tree(
885 instance,
886 parent_height,
887 parent_view,
888 &mut validated_state.block_merkle_tree,
889 )
890 .await?;
891 }
892
893 if !missing_accounts.is_empty() {
895 tracing::info!(
896 parent_height,
897 ?parent_view,
898 ?missing_accounts,
899 "fetching missing accounts from peers"
900 );
901
902 let missing_account_proofs = peers
903 .fetch_accounts(
904 instance,
905 parent_height,
906 parent_view,
907 validated_state.fee_merkle_tree.commitment(),
908 missing_accounts,
909 )
910 .await?;
911
912 for proof in missing_account_proofs.iter() {
914 proof
915 .remember(&mut validated_state.fee_merkle_tree)
916 .expect("proof previously verified");
917 }
918 }
919
920 let mut delta = Delta::default();
921 validated_state.apply_proposal(&mut delta, parent_leaf, l1_deposits);
922
923 validated_state.charge_fees(
924 &mut delta,
925 proposed_header.fee_info(),
926 chain_config.fee_recipient,
927 )?;
928
929 if version >= EpochVersion::version() {
930 let reward_distributor = distribute_block_reward(
931 instance,
932 &mut validated_state,
933 parent_leaf,
934 view_number,
935 version,
936 )
937 .await?;
938 if let Some(reward_distributor) = reward_distributor {
939 reward_distributor
940 .update_rewards_delta(&mut delta)
941 .context("failed to update rewards delta")?;
942 }
943 }
944
945 Ok((validated_state, delta))
946 }
947
948 pub(crate) fn apply_upgrade(&mut self, instance: &NodeState, version: Version) {
950 if version <= instance.current_version {
952 return;
953 }
954
955 let Some(upgrade) = instance.upgrades.get(&version) else {
956 return;
957 };
958
959 let cf = match upgrade.upgrade_type {
960 UpgradeType::Fee { chain_config } => chain_config,
961 UpgradeType::Epoch { chain_config } => chain_config,
962 UpgradeType::DrbAndHeader { chain_config } => chain_config,
963 };
964
965 self.chain_config = cf.into();
966 }
967
968 pub(crate) async fn get_chain_config(
976 &self,
977 instance: &NodeState,
978 peers: &impl StateCatchup,
979 header_cf: &ResolvableChainConfig,
980 ) -> anyhow::Result<ChainConfig> {
981 let state_cf = self.chain_config;
982
983 if state_cf.commit() == instance.chain_config.commit() {
984 return Ok(instance.chain_config);
985 }
986
987 let cf = match (state_cf.resolve(), header_cf.resolve()) {
988 (Some(cf), _) => cf,
989 (_, Some(cf)) if cf.commit() == state_cf.commit() => cf,
990 (_, Some(_)) | (None, None) => peers.fetch_chain_config(state_cf.commit()).await?,
991 };
992
993 Ok(cf)
994 }
995}
996
997pub async fn get_l1_deposits(
998 instance: &NodeState,
999 header: &Header,
1000 parent_leaf: &Leaf2,
1001 fee_contract_address: Option<Address>,
1002) -> Vec<FeeInfo> {
1003 if let (Some(addr), Some(block_info)) = (fee_contract_address, header.l1_finalized()) {
1004 instance
1005 .l1_client
1006 .get_finalized_deposits(
1007 addr,
1008 parent_leaf
1009 .block_header()
1010 .l1_finalized()
1011 .map(|block_info| block_info.number),
1012 block_info.number,
1013 )
1014 .await
1015 } else {
1016 vec![]
1017 }
1018}
1019
1020async fn validate_next_stake_table_hash(
1021 instance: &NodeState,
1022 proposed_header: &Header,
1023) -> Result<(), ProposalValidationError> {
1024 let Some(epoch_height) = instance.epoch_height else {
1025 return Err(ProposalValidationError::NoEpochHeight);
1026 };
1027 if !is_ge_epoch_root(proposed_header.height(), epoch_height) {
1028 return Ok(());
1029 }
1030 let epoch = EpochNumber::new(epoch_from_block_number(
1031 proposed_header.height(),
1032 epoch_height,
1033 ));
1034 let coordinator = instance.coordinator.clone();
1035 let Some(first_epoch) = coordinator.membership().read().await.first_epoch() else {
1036 return Err(ProposalValidationError::NoFirstEpoch);
1037 };
1038
1039 if epoch <= first_epoch + 1 {
1042 return Ok(());
1043 }
1044
1045 let epoch_membership = instance
1046 .coordinator
1047 .stake_table_for_epoch(Some(epoch + 1))
1048 .await
1049 .map_err(|_| ProposalValidationError::NextStakeTableNotFound)?;
1050 let next_stake_table_hash = epoch_membership
1051 .stake_table_hash()
1052 .await
1053 .ok_or(ProposalValidationError::NextStakeTableNotFound)?;
1054 let Some(proposed_next_stake_table_hash) = proposed_header.next_stake_table_hash() else {
1055 return Err(ProposalValidationError::NextStakeTableHashNotFound);
1056 };
1057 if next_stake_table_hash != proposed_next_stake_table_hash {
1058 return Err(ProposalValidationError::NextStakeTableHashMismatch {
1059 expected: next_stake_table_hash,
1060 proposal: proposed_next_stake_table_hash,
1061 });
1062 }
1063 Ok(())
1064}
1065
1066impl HotShotState<SeqTypes> for ValidatedState {
1067 type Error = BlockError;
1068 type Instance = NodeState;
1069
1070 type Time = ViewNumber;
1071
1072 type Delta = Delta;
1073 fn on_commit(&self) {}
1074 #[tracing::instrument(
1077 skip_all,
1078 fields(
1079 node_id = instance.node_id,
1080 view = ?parent_leaf.view_number(),
1081 height = parent_leaf.height(),
1082 ),
1083 )]
1084 async fn validate_and_apply_header(
1085 &self,
1086 instance: &Self::Instance,
1087 parent_leaf: &Leaf2,
1088 proposed_header: &Header,
1089 payload_byte_len: u32,
1090 version: Version,
1091 view_number: u64,
1092 ) -> Result<(Self, Self::Delta), Self::Error> {
1093 let (validated_state, delta) = self
1094 .apply_header(
1096 instance,
1097 &instance.state_catchup,
1098 parent_leaf,
1099 proposed_header,
1100 version,
1101 ViewNumber::new(view_number),
1102 )
1103 .await
1104 .map_err(|e| BlockError::FailedHeaderApply(e.to_string()))?;
1105
1106 if version >= DrbAndHeaderUpgradeVersion::version() {
1107 validate_next_stake_table_hash(instance, proposed_header).await?;
1108 }
1109
1110 let validated_state = ValidatedTransition::new(
1112 validated_state,
1113 parent_leaf.block_header(),
1114 Proposal::new(proposed_header, payload_byte_len),
1115 )
1116 .validate()?
1117 .wait_for_l1(&instance.l1_client)
1118 .await?
1119 .state;
1120
1121 if parent_leaf.view_number().u64() % 10 == 0 {
1124 tracing::info!("validated and applied new header");
1125 }
1126 Ok((validated_state, delta))
1127 }
1128 fn from_header(block_header: &Header) -> Self {
1132 let fee_merkle_tree = if block_header.fee_merkle_tree_root().size() == 0 {
1133 FeeMerkleTree::new(FEE_MERKLE_TREE_HEIGHT)
1136 } else {
1137 FeeMerkleTree::from_commitment(block_header.fee_merkle_tree_root())
1138 };
1139 let block_merkle_tree = if block_header.block_merkle_tree_root().size() == 0 {
1140 BlockMerkleTree::new(BLOCK_MERKLE_TREE_HEIGHT)
1143 } else {
1144 BlockMerkleTree::from_commitment(block_header.block_merkle_tree_root())
1145 };
1146
1147 let (reward_merkle_tree_v1, reward_merkle_tree_v2) = match block_header
1148 .reward_merkle_tree_root()
1149 {
1150 Either::Left(reward_tree_v1) => {
1151 let reward_merkle_tree_v2 = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
1152 let reward_merkle_tree_v1 = if reward_tree_v1.size() == 0 {
1153 RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT)
1154 } else {
1155 RewardMerkleTreeV1::from_commitment(reward_tree_v1)
1156 };
1157 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1158 },
1159 Either::Right(reward_tree_v2) => {
1160 let reward_merkle_tree_v1 = RewardMerkleTreeV1::new(REWARD_MERKLE_TREE_V1_HEIGHT);
1161 let reward_merkle_tree_v2 = if reward_tree_v2.size() == 0 {
1162 RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT)
1163 } else {
1164 RewardMerkleTreeV2::from_commitment(reward_tree_v2)
1165 };
1166 (reward_merkle_tree_v1, reward_merkle_tree_v2)
1167 },
1168 };
1169
1170 Self {
1171 fee_merkle_tree,
1172 block_merkle_tree,
1173 reward_merkle_tree_v2,
1174 reward_merkle_tree_v1,
1175 chain_config: block_header.chain_config(),
1176 }
1177 }
1178 fn genesis(instance: &Self::Instance) -> (Self, Self::Delta) {
1180 (instance.genesis_state.clone(), Delta::default())
1181 }
1182}
1183
1184#[cfg(any(test, feature = "testing"))]
1186impl std::fmt::Display for ValidatedState {
1187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1188 write!(f, "{self:#?}")
1189 }
1190}
1191
1192#[cfg(any(test, feature = "testing"))]
1193impl hotshot_types::traits::states::TestableState<SeqTypes> for ValidatedState {
1194 fn create_random_transaction(
1195 _state: Option<&Self>,
1196 rng: &mut dyn rand::RngCore,
1197 _padding: u64,
1198 ) -> crate::Transaction {
1199 crate::Transaction::random(rng)
1200 }
1201}
1202
1203impl MerklizedState<SeqTypes, { Self::ARITY }> for BlockMerkleTree {
1204 type Key = Self::Index;
1205 type Entry = Commitment<Header>;
1206 type T = Sha3Node;
1207 type Commit = Self::Commitment;
1208 type Digest = Sha3Digest;
1209
1210 fn state_type() -> &'static str {
1211 "block_merkle_tree"
1212 }
1213
1214 fn header_state_commitment_field() -> &'static str {
1215 "block_merkle_tree_root"
1216 }
1217
1218 fn tree_height() -> usize {
1219 BLOCK_MERKLE_TREE_HEIGHT
1220 }
1221
1222 fn insert_path(
1223 &mut self,
1224 key: Self::Key,
1225 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1226 ) -> anyhow::Result<()> {
1227 let Some(elem) = proof.elem() else {
1228 bail!("BlockMerkleTree does not support non-membership proofs");
1229 };
1230 self.remember(key, elem, proof)?;
1231 Ok(())
1232 }
1233}
1234
1235impl MerklizedState<SeqTypes, { Self::ARITY }> for FeeMerkleTree {
1236 type Key = Self::Index;
1237 type Entry = Self::Element;
1238 type T = Sha3Node;
1239 type Commit = Self::Commitment;
1240 type Digest = Sha3Digest;
1241
1242 fn state_type() -> &'static str {
1243 "fee_merkle_tree"
1244 }
1245
1246 fn header_state_commitment_field() -> &'static str {
1247 "fee_merkle_tree_root"
1248 }
1249
1250 fn tree_height() -> usize {
1251 FEE_MERKLE_TREE_HEIGHT
1252 }
1253
1254 fn insert_path(
1255 &mut self,
1256 key: Self::Key,
1257 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1258 ) -> anyhow::Result<()> {
1259 match proof.elem() {
1260 Some(elem) => self.remember(key, elem, proof)?,
1261 None => self.non_membership_remember(key, proof)?,
1262 }
1263 Ok(())
1264 }
1265}
1266
1267impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV2 {
1268 type Key = Self::Index;
1269 type Entry = Self::Element;
1270 type T = KeccakNode;
1271 type Commit = Self::Commitment;
1272 type Digest = Keccak256Hasher;
1273
1274 fn state_type() -> &'static str {
1275 "reward_merkle_tree_v2"
1276 }
1277
1278 fn header_state_commitment_field() -> &'static str {
1279 "reward_merkle_tree_root"
1280 }
1281
1282 fn tree_height() -> usize {
1283 REWARD_MERKLE_TREE_V2_HEIGHT
1284 }
1285
1286 fn insert_path(
1287 &mut self,
1288 key: Self::Key,
1289 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1290 ) -> anyhow::Result<()> {
1291 match proof.elem() {
1292 Some(elem) => self.remember(key, elem, proof)?,
1293 None => self.non_membership_remember(key, proof)?,
1294 }
1295 Ok(())
1296 }
1297}
1298
1299impl MerklizedState<SeqTypes, { Self::ARITY }> for RewardMerkleTreeV1 {
1300 type Key = Self::Index;
1301 type Entry = Self::Element;
1302 type T = Sha3Node;
1303 type Commit = Self::Commitment;
1304 type Digest = Sha3Digest;
1305
1306 fn state_type() -> &'static str {
1307 "reward_merkle_tree"
1308 }
1309
1310 fn header_state_commitment_field() -> &'static str {
1311 "reward_merkle_tree_root"
1312 }
1313
1314 fn tree_height() -> usize {
1315 REWARD_MERKLE_TREE_V1_HEIGHT
1316 }
1317
1318 fn insert_path(
1319 &mut self,
1320 key: Self::Key,
1321 proof: &MerkleProof<Self::Entry, Self::Key, Self::T, { Self::ARITY }>,
1322 ) -> anyhow::Result<()> {
1323 match proof.elem() {
1324 Some(elem) => self.remember(key, elem, proof)?,
1325 None => self.non_membership_remember(key, proof)?,
1326 }
1327 Ok(())
1328 }
1329}
1330
1331#[cfg(test)]
1332mod test {
1333 use hotshot::traits::BlockPayload;
1334 use hotshot_query_service::{testing::mocks::MockVersions, Resolvable};
1335 use hotshot_types::traits::signature_key::BuilderSignatureKey;
1336 use sequencer_utils::ser::FromStringOrInteger;
1337 use tracing::debug;
1338
1339 use super::*;
1340 use crate::{
1341 eth_signature_key::EthKeyPair, v0_1, v0_2, v0_3, v0_4, BlockSize, FeeAccountProof,
1342 FeeMerkleProof, Leaf, Payload, TimestampMillis, Transaction,
1343 };
1344
1345 impl Transaction {
1346 async fn into_mock_header(self) -> (Header, u32) {
1347 let instance = NodeState::mock_v2();
1348 let (payload, metadata) =
1349 Payload::from_transactions([self], &instance.genesis_state, &instance)
1350 .await
1351 .unwrap();
1352
1353 let header = Header::genesis::<MockVersions>(&instance, payload.clone(), &metadata);
1354
1355 let header = header.sign();
1356
1357 (header, payload.byte_len().0 as u32)
1358 }
1359 }
1360 impl Header {
1361 fn next(self) -> Self {
1363 let time = OffsetDateTime::now_utc();
1364 let timestamp = time.unix_timestamp() as u64;
1365 let timestamp_millis = TimestampMillis::from_time(&time);
1366
1367 match self {
1368 Header::V1(_) => panic!("You called `Header.next()` on unimplemented version (v1)"),
1369 Header::V2(parent) => Header::V2(v0_2::Header {
1370 height: parent.height + 1,
1371 timestamp,
1372 ..parent.clone()
1373 }),
1374 Header::V3(parent) => Header::V3(v0_3::Header {
1375 height: parent.height + 1,
1376 timestamp,
1377 ..parent.clone()
1378 }),
1379 Header::V4(parent) => Header::V4(v0_4::Header {
1380 height: parent.height + 1,
1381 timestamp,
1382 timestamp_millis,
1383 ..parent.clone()
1384 }),
1385 }
1386 }
1387 fn sign(&self) -> Self {
1389 let key_pair = EthKeyPair::random();
1390 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1391
1392 let sig = FeeAccount::sign_fee(
1393 &key_pair,
1394 fee_info.amount().as_u64().unwrap(),
1395 self.metadata(),
1396 )
1397 .unwrap();
1398
1399 match self {
1400 Header::V1(_) => panic!("You called `Header.sign()` on unimplemented version (v1)"),
1401 Header::V2(header) => Header::V2(v0_2::Header {
1402 fee_info,
1403 builder_signature: Some(sig),
1404 ..header.clone()
1405 }),
1406 Header::V3(header) => Header::V3(v0_3::Header {
1407 fee_info,
1408 builder_signature: Some(sig),
1409 ..header.clone()
1410 }),
1411 Header::V4(header) => Header::V4(v0_4::Header {
1412 fee_info,
1413 builder_signature: Some(sig),
1414 ..header.clone()
1415 }),
1416 }
1417 }
1418
1419 fn invalid_builder_signature(&self) -> Self {
1421 let key_pair = EthKeyPair::random();
1422 let key_pair2 = EthKeyPair::random();
1423 let fee_info = FeeInfo::new(key_pair.fee_account(), 1);
1424
1425 let sig = FeeAccount::sign_fee(
1426 &key_pair2,
1427 fee_info.amount().as_u64().unwrap(),
1428 self.metadata(),
1429 )
1430 .unwrap();
1431
1432 match self {
1433 Header::V1(_) => panic!(
1434 "You called `Header.invalid_builder_signature()` on unimplemented version (v1)"
1435 ),
1436 Header::V2(parent) => Header::V2(v0_2::Header {
1437 fee_info,
1438 builder_signature: Some(sig),
1439 ..parent.clone()
1440 }),
1441 Header::V3(parent) => Header::V3(v0_3::Header {
1442 fee_info,
1443 builder_signature: Some(sig),
1444 ..parent.clone()
1445 }),
1446 Header::V4(parent) => Header::V4(v0_4::Header {
1447 fee_info,
1448 builder_signature: Some(sig),
1449 ..parent.clone()
1450 }),
1451 }
1452 }
1453 }
1454
1455 impl<'a> ValidatedTransition<'a> {
1456 fn mock(instance: NodeState, parent: &'a Header, proposal: Proposal<'a>) -> Self {
1457 let expected_chain_config = instance.chain_config;
1458
1459 Self {
1460 state: instance.genesis_state,
1461 expected_chain_config,
1462 parent,
1463 proposal,
1464 }
1465 }
1466 }
1467
1468 #[test_log::test]
1469 fn test_fee_proofs() {
1470 let mut tree = ValidatedState::default().fee_merkle_tree;
1471 let account1 = Address::random();
1472 let account2 = Address::default();
1473 tracing::info!(%account1, %account2);
1474
1475 let balance1 = U256::from(100);
1476 tree.update(FeeAccount(account1), FeeAmount(balance1))
1477 .unwrap();
1478
1479 let (proof1, balance) = FeeAccountProof::prove(&tree, account1).unwrap();
1481 tracing::info!(?proof1, %balance);
1482 assert_eq!(balance, balance1);
1483 assert!(matches!(proof1.proof, FeeMerkleProof::Presence(_)));
1484 assert_eq!(proof1.verify(&tree.commitment()).unwrap(), balance1);
1485
1486 let (proof2, balance) = FeeAccountProof::prove(&tree, account2).unwrap();
1488 tracing::info!(?proof2, %balance);
1489 assert_eq!(balance, U256::ZERO);
1490 assert!(matches!(proof2.proof, FeeMerkleProof::Absence(_)));
1491 assert_eq!(proof2.verify(&tree.commitment()).unwrap(), U256::ZERO);
1492
1493 let mut tree = FeeMerkleTree::from_commitment(tree.commitment());
1495 assert!(FeeAccountProof::prove(&tree, account1).is_none());
1496 assert!(FeeAccountProof::prove(&tree, account2).is_none());
1497 proof1.remember(&mut tree).unwrap();
1499 proof2.remember(&mut tree).unwrap();
1500 FeeAccountProof::prove(&tree, account1).unwrap();
1501 FeeAccountProof::prove(&tree, account2).unwrap();
1502 }
1503
1504 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1505 async fn test_validation_l1_head() {
1506 let tx = Transaction::of_size(10);
1508 let (header, block_size) = tx.into_mock_header().await;
1509
1510 let proposal = Proposal::new(&header, block_size);
1512 ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1515 .validate_l1_head()
1516 .unwrap();
1517
1518 let proposal = Proposal::new(&header, block_size);
1520 let err = proposal.validate_l1_head(u64::MAX).unwrap_err();
1521 assert_eq!(ProposalValidationError::DecrementingL1Head, err);
1522 }
1523
1524 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1525 async fn test_validation_builder_fee() {
1526 let instance = NodeState::mock();
1528 let tx = Transaction::of_size(20);
1529 let (header, block_size) = tx.into_mock_header().await;
1530
1531 let proposal = Proposal::new(&header, block_size);
1533 ValidatedTransition::mock(instance.clone(), &header, proposal)
1534 .validate_builder_fee()
1535 .unwrap();
1536
1537 let header = header.invalid_builder_signature();
1539 let proposal = Proposal::new(&header, block_size);
1540 let err = ValidatedTransition::mock(instance, &header, proposal)
1541 .validate_builder_fee()
1542 .unwrap_err();
1543
1544 tracing::info!(%err, "task failed successfully");
1545 assert_eq!(
1546 ProposalValidationError::BuilderValidationError(
1547 BuilderValidationError::InvalidBuilderSignature
1548 ),
1549 err
1550 );
1551 }
1552
1553 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1554 async fn test_validation_chain_config() {
1555 let instance = NodeState::mock();
1557 let tx = Transaction::of_size(20);
1558 let (header, block_size) = tx.into_mock_header().await;
1559
1560 let proposal = Proposal::new(&header, block_size);
1562 ValidatedTransition::mock(instance.clone(), &header, proposal)
1563 .validate_chain_config()
1564 .unwrap();
1565
1566 let proposal = Proposal::new(&header, block_size);
1568 let expected_chain_config = ChainConfig {
1569 max_block_size: BlockSize(3333),
1570 ..instance.chain_config
1571 };
1572 let err = proposal
1573 .validate_chain_config(&expected_chain_config)
1574 .unwrap_err();
1575
1576 tracing::info!(%err, "task failed successfully");
1577
1578 assert_eq!(
1579 ProposalValidationError::InvalidChainConfig {
1580 expected: Box::new(expected_chain_config),
1581 proposal: Box::new(header.chain_config())
1582 },
1583 err
1584 );
1585 }
1586
1587 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1588 async fn test_validation_max_block_size() {
1589 const MAX_BLOCK_SIZE: usize = 10;
1590
1591 let state = ValidatedState::default();
1593 let expected_chain_config = ChainConfig {
1594 max_block_size: BlockSize::from_integer(MAX_BLOCK_SIZE as u64).unwrap(),
1595 ..state.chain_config.resolve().unwrap()
1596 };
1597 let instance = NodeState::mock().with_chain_config(expected_chain_config);
1598 let tx = Transaction::of_size(20);
1599 let (header, block_size) = tx.into_mock_header().await;
1600
1601 let proposal = Proposal::new(&header, block_size);
1603 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1604 .validate_block_size()
1605 .unwrap_err();
1606
1607 tracing::info!(%err, "task failed successfully");
1608 assert_eq!(
1609 ProposalValidationError::MaxBlockSizeExceeded {
1610 max_block_size: instance.chain_config.max_block_size,
1611 block_size: BlockSize::from_integer(block_size as u64).unwrap()
1612 },
1613 err
1614 );
1615
1616 let proposal = Proposal::new(&header, 1);
1618 ValidatedTransition::mock(instance, &header, proposal)
1619 .validate_block_size()
1620 .unwrap()
1621 }
1622
1623 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1624 async fn test_validation_base_fee() {
1625 let tx = Transaction::of_size(20);
1627 let (header, block_size) = tx.into_mock_header().await;
1628 let state = ValidatedState::default();
1629 let instance = NodeState::mock_v2().with_chain_config(ChainConfig {
1630 base_fee: 1000.into(), ..state.chain_config.resolve().unwrap()
1632 });
1633
1634 let proposal = Proposal::new(&header, block_size);
1635 let err = ValidatedTransition::mock(instance.clone(), &header, proposal)
1636 .validate_fee()
1637 .unwrap_err();
1638
1639 tracing::info!(%err, "task failed successfully");
1641 assert_eq!(
1642 ProposalValidationError::InsufficientFee {
1643 max_block_size: instance.chain_config.max_block_size,
1644 base_fee: instance.chain_config.base_fee,
1645 proposed_fee: header.fee_info().amount().unwrap()
1646 },
1647 err
1648 );
1649 }
1650
1651 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1652 async fn test_validation_height() {
1653 let instance = NodeState::mock_v2();
1655 let tx = Transaction::of_size(10);
1656 let (parent, block_size) = tx.into_mock_header().await;
1657
1658 let proposal = Proposal::new(&parent, block_size);
1659 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1660 .validate_height()
1661 .unwrap_err();
1662
1663 tracing::info!(%err, "task failed successfully");
1665 assert_eq!(
1666 ProposalValidationError::InvalidHeight {
1667 parent_height: parent.height(),
1668 proposal_height: parent.height()
1669 },
1670 err
1671 );
1672
1673 let mut header = parent.clone();
1675 *header.height_mut() += 1;
1676 let proposal = Proposal::new(&header, block_size);
1677
1678 ValidatedTransition::mock(instance, &parent, proposal)
1679 .validate_height()
1680 .unwrap();
1681 }
1682
1683 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1684 async fn test_validation_timestamp_non_dec() {
1685 let tx = Transaction::of_size(10);
1686 let (parent, block_size) = tx.into_mock_header().await;
1687
1688 let proposal = Proposal::new(&parent, block_size);
1690 let proposal_timestamp = proposal.header.timestamp();
1691 let err = proposal.validate_timestamp_non_dec(u64::MAX).unwrap_err();
1692
1693 tracing::info!(%err, "task failed successfully");
1695 assert_eq!(
1696 ProposalValidationError::DecrementingTimestamp {
1697 proposal_timestamp,
1698 parent_timestamp: u64::MAX,
1699 },
1700 err
1701 );
1702
1703 let proposal = Proposal::new(&parent, block_size);
1705 proposal.validate_timestamp_non_dec(0).unwrap();
1706 }
1707
1708 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1709 async fn test_validation_timestamp_drift() {
1710 let instance = NodeState::mock_v2();
1712 let (parent, block_size) = Transaction::of_size(10).into_mock_header().await;
1713
1714 let header = parent.clone();
1715 let proposal = Proposal::new(&header, block_size);
1717 let proposal_timestamp = header.timestamp();
1718
1719 let mock_time = OffsetDateTime::now_utc().unix_timestamp() as u64;
1720 let err = ValidatedTransition::mock(instance.clone(), &parent, proposal)
1722 .validate_timestamp()
1723 .unwrap_err();
1724
1725 tracing::info!(%err, "task failed successfully");
1726 assert_eq!(
1727 ProposalValidationError::InvalidTimestampDrift {
1728 proposal: proposal_timestamp,
1729 system: mock_time,
1730 diff: mock_time
1731 },
1732 err
1733 );
1734
1735 let time = OffsetDateTime::now_utc();
1736 let timestamp: u64 = time.unix_timestamp() as u64;
1737 let timestamp_millis = TimestampMillis::from_time(&time).u64();
1738
1739 let mut header = parent.clone();
1740 header.set_timestamp(timestamp - 13, timestamp_millis - 13_000);
1741 let proposal = Proposal::new(&header, block_size);
1742
1743 let err = proposal.validate_timestamp_drift(timestamp).unwrap_err();
1744 tracing::info!(%err, "task failed successfully");
1745 assert_eq!(
1746 ProposalValidationError::InvalidTimestampDrift {
1747 proposal: timestamp - 13,
1748 system: timestamp,
1749 diff: 13
1750 },
1751 err
1752 );
1753
1754 let mut header = parent.clone();
1756 header.set_timestamp(timestamp, timestamp_millis);
1757 let proposal = Proposal::new(&header, block_size);
1758 proposal.validate_timestamp_drift(timestamp).unwrap();
1759
1760 header.set_timestamp(timestamp - 11, timestamp_millis - 11_000);
1761 let proposal = Proposal::new(&header, block_size);
1762 proposal.validate_timestamp_drift(timestamp).unwrap();
1763
1764 header.set_timestamp(timestamp - 12, timestamp_millis - 12_000);
1765 let proposal = Proposal::new(&header, block_size);
1766 proposal.validate_timestamp_drift(timestamp).unwrap();
1767 }
1768
1769 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1770 async fn test_validation_fee_root() {
1771 let instance = NodeState::mock_v2();
1773 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1774
1775 let proposal = Proposal::new(&header, block_size);
1777 ValidatedTransition::mock(instance.clone(), &header, proposal)
1778 .validate_fee_merkle_tree()
1779 .unwrap();
1780
1781 let proposal = Proposal::new(&header, block_size);
1783
1784 let mut fee_merkle_tree = instance.genesis_state.fee_merkle_tree;
1785 fee_merkle_tree
1786 .update_with(FeeAccount::default(), |_| Some(100.into()))
1787 .unwrap();
1788
1789 let err = proposal
1790 .validate_block_merkle_tree(fee_merkle_tree.commitment())
1791 .unwrap_err();
1792
1793 tracing::info!(%err, "task failed successfully");
1794 assert_eq!(
1795 ProposalValidationError::InvalidBlockRoot {
1796 expected_root: fee_merkle_tree.commitment(),
1797 proposal_root: header.block_merkle_tree_root(),
1798 },
1799 err
1800 );
1801 }
1802
1803 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1804 async fn test_validation_block_root() {
1805 let instance = NodeState::mock_v2();
1807 let (header, block_size) = Transaction::of_size(10).into_mock_header().await;
1808
1809 let proposal = Proposal::new(&header, block_size);
1811 ValidatedTransition::mock(instance.clone(), &header, proposal)
1812 .validate_block_merkle_tree()
1813 .unwrap();
1814
1815 let proposal = Proposal::new(&header, block_size);
1817 let mut block_merkle_tree = instance.genesis_state.block_merkle_tree;
1818 block_merkle_tree.push(header.commitment()).unwrap();
1819 block_merkle_tree
1820 .push(header.clone().next().commitment())
1821 .unwrap();
1822
1823 let err = proposal
1824 .validate_block_merkle_tree(block_merkle_tree.commitment())
1825 .unwrap_err();
1826
1827 tracing::info!(%err, "task failed successfully");
1828 assert_eq!(
1829 ProposalValidationError::InvalidBlockRoot {
1830 expected_root: block_merkle_tree.commitment(),
1831 proposal_root: proposal.header.block_merkle_tree_root(),
1832 },
1833 err
1834 );
1835 }
1836
1837 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1838 async fn test_validation_ns_table() {
1839 use NsTableValidationError::InvalidFinalOffset;
1840 let tx = Transaction::of_size(10);
1842 let (header, block_size) = tx.into_mock_header().await;
1843
1844 let proposal = Proposal::new(&header, block_size);
1846 ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1847 .validate_namespace_table()
1848 .unwrap();
1849
1850 let proposal = Proposal::new(&header, 40);
1852 let err = ValidatedTransition::mock(NodeState::mock_v2(), &header, proposal)
1853 .validate_namespace_table()
1854 .unwrap_err();
1855 tracing::info!(%err, "task failed successfully");
1856 assert_eq!(
1859 ProposalValidationError::InvalidNsTable(InvalidFinalOffset),
1860 err
1861 );
1862 }
1863
1864 #[test_log::test]
1865 fn test_charge_fee() {
1866 let src = FeeAccount::generated_from_seed_indexed([0; 32], 0).0;
1867 let dst = FeeAccount::generated_from_seed_indexed([0; 32], 1).0;
1868 let amt = FeeAmount::from(1);
1869
1870 let fee_info = FeeInfo::new(src, amt);
1871
1872 let new_state = || {
1873 let mut state = ValidatedState::default();
1874 state.prefund_account(src, amt);
1875 state
1876 };
1877
1878 tracing::info!("test successful fee");
1879 let mut state = new_state();
1880 state.charge_fee(fee_info, dst).unwrap();
1881 assert_eq!(state.balance(src), Some(0.into()));
1882 assert_eq!(state.balance(dst), Some(amt));
1883
1884 tracing::info!("test insufficient balance");
1885 let err = state.charge_fee(fee_info, dst).unwrap_err();
1886 assert_eq!(state.balance(src), Some(0.into()));
1887 assert_eq!(state.balance(dst), Some(amt));
1888 assert_eq!(
1889 FeeError::InsufficientFunds {
1890 balance: None,
1891 amount: amt
1892 },
1893 err
1894 );
1895
1896 tracing::info!("test src not in memory");
1897 let mut state = new_state();
1898 state.fee_merkle_tree.forget(src).expect_ok().unwrap();
1899 assert_eq!(
1900 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
1901 state.charge_fee(fee_info, dst).unwrap_err()
1902 );
1903
1904 tracing::info!("test dst not in memory");
1905 let mut state = new_state();
1906 state.prefund_account(dst, amt);
1907 state.fee_merkle_tree.forget(dst).expect_ok().unwrap();
1908 assert_eq!(
1909 FeeError::MerkleTreeError(MerkleTreeError::ForgottenLeaf),
1910 state.charge_fee(fee_info, dst).unwrap_err()
1911 );
1912 }
1913
1914 #[test]
1915 fn test_fee_amount_serde_json_as_decimal() {
1916 let amt = FeeAmount::from(123);
1917 let serialized = serde_json::to_string(&amt).unwrap();
1918
1919 assert_eq!(serialized, "\"123\"");
1921
1922 let deserialized: FeeAmount = serde_json::from_str(&serialized).unwrap();
1924 assert_eq!(deserialized, amt);
1925 }
1926
1927 #[test]
1928 fn test_fee_amount_from_units() {
1929 for (unit, multiplier) in [
1930 ("wei", 1),
1931 ("gwei", 1_000_000_000),
1932 ("eth", 1_000_000_000_000_000_000),
1933 ] {
1934 let amt: FeeAmount = serde_json::from_str(&format!("\"1 {unit}\"")).unwrap();
1935 assert_eq!(amt, multiplier.into());
1936 }
1937 }
1938
1939 #[test]
1940 fn test_fee_amount_serde_json_from_hex() {
1941 let amt: FeeAmount = serde_json::from_str("\"0x123\"").unwrap();
1944 assert_eq!(amt, FeeAmount::from(0x123));
1945 }
1946
1947 #[test]
1948 fn test_fee_amount_serde_json_from_number() {
1949 let amt: FeeAmount = serde_json::from_str("123").unwrap();
1951 assert_eq!(amt, FeeAmount::from(123));
1952 }
1953
1954 #[test]
1955 fn test_fee_amount_serde_bincode_unchanged() {
1956 let n = ethers_core::types::U256::from(123);
1959 let amt = FeeAmount(U256::from(123));
1960 assert_eq!(
1961 bincode::serialize(&n).unwrap(),
1962 bincode::serialize(&amt).unwrap(),
1963 );
1964 }
1965
1966 #[test_log::test(tokio::test(flavor = "multi_thread"))]
1967 async fn test_validate_builder_fee() {
1968 let max_block_size = 10;
1969
1970 let validated_state = ValidatedState::default();
1971 let instance_state = NodeState::mock().with_chain_config(ChainConfig {
1972 base_fee: 1000.into(), max_block_size: max_block_size.into(),
1974 ..validated_state.chain_config.resolve().unwrap()
1975 });
1976
1977 let parent: Leaf2 =
1978 Leaf::genesis::<MockVersions>(&instance_state.genesis_state, &instance_state)
1979 .await
1980 .into();
1981 let header = parent.block_header().clone();
1982 let metadata = parent.block_header().metadata();
1983
1984 debug!("{:?}", header.version());
1985
1986 let key_pair = EthKeyPair::random();
1987 let account = key_pair.fee_account();
1988
1989 let data = header.fee_info()[0].amount().as_u64().unwrap();
1990 let sig = FeeAccount::sign_builder_message(&key_pair, &data.to_be_bytes()).unwrap();
1991
1992 account
1994 .validate_builder_signature(&sig, &data.to_be_bytes())
1995 .then_some(())
1996 .unwrap();
1997
1998 let sig = FeeAccount::sign_fee(&key_pair, data, metadata).unwrap();
2000
2001 let header = match header {
2002 Header::V1(header) => Header::V1(v0_1::Header {
2003 builder_signature: Some(sig),
2004 fee_info: FeeInfo::new(account, data),
2005 ..header
2006 }),
2007 Header::V2(header) => Header::V2(v0_2::Header {
2008 builder_signature: Some(sig),
2009 fee_info: FeeInfo::new(account, data),
2010 ..header
2011 }),
2012 Header::V3(header) => Header::V3(v0_3::Header {
2013 builder_signature: Some(sig),
2014 fee_info: FeeInfo::new(account, data),
2015 ..header
2016 }),
2017 Header::V4(header) => Header::V4(v0_4::Header {
2018 builder_signature: Some(sig),
2019 fee_info: FeeInfo::new(account, data),
2020 ..header
2021 }),
2022 };
2023
2024 validate_builder_fee(&header).unwrap();
2025 }
2026}