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