cfxcore/
verification.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use crate::{
6    core_error::{BlockError, CoreError as Error},
7    pow::{self, nonce_to_lower_bound, PowComputer, ProofOfWorkProblem},
8    sync::Error as SyncError,
9};
10use cfx_executor::{
11    executive::{eip7623_required_gas, gas_required_for},
12    machine::Machine,
13    spec::TransitionsEpochHeight,
14};
15use cfx_parameters::{block::*, consensus_internal::ELASTICITY_MULTIPLIER};
16use cfx_storage::{
17    into_simple_mpt_key, make_simple_mpt, simple_mpt_merkle_root,
18    simple_mpt_proof, SimpleMpt, TrieProof,
19};
20use cfx_types::{
21    address_util::AddressUtil, AllChainID, BigEndianHash, Space, SpaceMap,
22    H256, U256,
23};
24use cfx_vm_types::{ConsensusGasSpec, Spec};
25use primitives::{
26    block::BlockHeight,
27    block_header::compute_next_price_tuple,
28    transaction::{
29        native_transaction::TypedNativeTransaction, TransactionError,
30        EIP1559_TYPE, EIP7702_TYPE, LEGACY_TX_TYPE,
31    },
32    Action, Block, BlockHeader, BlockReceipts, MerkleHash, Receipt,
33    SignedTransaction, Transaction, TransactionWithSignature,
34};
35use rlp::Encodable;
36use rlp_derive::{RlpDecodable, RlpEncodable};
37use serde_derive::{Deserialize, Serialize};
38use std::{collections::HashSet, convert::TryInto, sync::Arc};
39use unexpected::{Mismatch, OutOfBounds};
40
41#[derive(Clone)]
42pub struct VerificationConfig {
43    pub verify_timestamp: bool,
44    pub referee_bound: usize,
45    pub max_block_size_in_bytes: usize,
46    pub transaction_epoch_bound: u64,
47    pub max_nonce: Option<U256>,
48    machine: Arc<Machine>,
49    pos_enable_height: u64,
50}
51
52/// Create an MPT from the ordered list of block transactions.
53/// Keys are transaction indices, values are transaction hashes.
54fn transaction_trie(transactions: &Vec<Arc<SignedTransaction>>) -> SimpleMpt {
55    make_simple_mpt(
56        transactions
57            .iter()
58            .map(|tx| tx.hash.as_bytes().into())
59            .collect(),
60    )
61}
62
63/// Compute block transaction root.
64/// This value is stored in the `transactions_root` header field.
65pub fn compute_transaction_root(
66    transactions: &Vec<Arc<SignedTransaction>>,
67) -> MerkleHash {
68    simple_mpt_merkle_root(&mut transaction_trie(transactions))
69}
70
71/// Compute a proof for the `tx_index_in_block`-th transaction in a block.
72pub fn compute_transaction_proof(
73    transactions: &Vec<Arc<SignedTransaction>>, tx_index_in_block: usize,
74) -> TrieProof {
75    simple_mpt_proof(
76        &mut transaction_trie(transactions),
77        &into_simple_mpt_key(tx_index_in_block, transactions.len()),
78    )
79}
80
81/// Create an MPT from the ordered list of block receipts.
82/// Keys are receipt indices, values are RLP serialized receipts.
83fn block_receipts_trie(block_receipts: &Vec<Receipt>) -> SimpleMpt {
84    make_simple_mpt(
85        block_receipts
86            .iter()
87            .map(|receipt| receipt.rlp_bytes().to_vec().into_boxed_slice())
88            .collect(),
89    )
90}
91
92/// Compute block receipts root.
93fn compute_block_receipts_root(block_receipts: &Vec<Receipt>) -> MerkleHash {
94    simple_mpt_merkle_root(&mut block_receipts_trie(block_receipts))
95}
96
97/// Compute a proof for the `tx_index_in_block`-th receipt in a block.
98pub fn compute_block_receipt_proof(
99    block_receipts: &Vec<Receipt>, tx_index_in_block: usize,
100) -> TrieProof {
101    simple_mpt_proof(
102        &mut block_receipts_trie(block_receipts),
103        &into_simple_mpt_key(tx_index_in_block, block_receipts.len()),
104    )
105}
106
107/// Create an MPT from the ordered list of epoch receipts.
108/// Keys are block indices in the epoch, values are block receipts roots.
109fn epoch_receipts_trie(epoch_receipts: &Vec<Arc<BlockReceipts>>) -> SimpleMpt {
110    make_simple_mpt(
111        epoch_receipts
112            .iter()
113            .map(|block_receipts| &block_receipts.receipts)
114            .map(|rs| compute_block_receipts_root(&rs).as_bytes().into())
115            .collect(),
116    )
117}
118
119/// Compute epoch receipts root.
120/// This value is stored in the `deferred_receipts_root` header field.
121pub fn compute_receipts_root(
122    epoch_receipts: &Vec<Arc<BlockReceipts>>,
123) -> MerkleHash {
124    simple_mpt_merkle_root(&mut epoch_receipts_trie(epoch_receipts))
125}
126
127#[derive(
128    Clone,
129    Debug,
130    RlpEncodable,
131    RlpDecodable,
132    Default,
133    PartialEq,
134    Serialize,
135    Deserialize,
136)]
137#[serde(rename_all = "camelCase")]
138pub struct EpochReceiptProof {
139    pub block_index_proof: TrieProof,
140    pub block_receipt_proof: TrieProof,
141}
142
143/// Compute a proof for the `tx_index_in_block`-th receipt
144/// in the `block_index_in_epoch`-th block in an epoch.
145pub fn compute_epoch_receipt_proof(
146    epoch_receipts: &Vec<Arc<BlockReceipts>>, block_index_in_epoch: usize,
147    tx_index_in_block: usize,
148) -> EpochReceiptProof {
149    let block_receipt_proof = compute_block_receipt_proof(
150        &epoch_receipts[block_index_in_epoch].receipts,
151        tx_index_in_block,
152    );
153
154    let block_index_proof = simple_mpt_proof(
155        &mut epoch_receipts_trie(epoch_receipts),
156        &into_simple_mpt_key(block_index_in_epoch, epoch_receipts.len()),
157    );
158
159    EpochReceiptProof {
160        block_index_proof,
161        block_receipt_proof,
162    }
163}
164
165/// Use `proof` to verify that `tx_hash` is indeed the `tx_index_in_block`-th
166/// transaction in a block with `num_txs_in_block` transactions and transaction
167/// root `block_tx_root`.
168pub fn is_valid_tx_inclusion_proof(
169    block_tx_root: MerkleHash, tx_index_in_block: usize,
170    num_txs_in_block: usize, tx_hash: H256, proof: &TrieProof,
171) -> bool {
172    let key = &into_simple_mpt_key(tx_index_in_block, num_txs_in_block);
173    proof.is_valid_kv(key, Some(tx_hash.as_bytes()), &block_tx_root)
174}
175
176/// Use `block_index_proof` to get the correct block receipts trie root for the
177/// `block_index_in_epoch`-th block in an epoch with `num_blocks_in_epoch`
178/// blocks and receipts root `verified_epoch_receipts_root`.
179/// Then, use `block_receipt_proof` to verify that `receipt` is indeed the
180/// `tx_index_in_block`-th receipt in a block with `num_txs_in_block`
181/// transactions and the transaction root from the previous step.
182pub fn is_valid_receipt_inclusion_proof(
183    verified_epoch_receipts_root: MerkleHash, block_index_in_epoch: usize,
184    num_blocks_in_epoch: usize, block_index_proof: &TrieProof,
185    tx_index_in_block: usize, num_txs_in_block: usize, receipt: &Receipt,
186    block_receipt_proof: &TrieProof,
187) -> bool {
188    // get block receipts root from block index trie (proof)
189    // traversing along `key` also means we're validating the proof
190    let key = &into_simple_mpt_key(block_index_in_epoch, num_blocks_in_epoch);
191
192    let block_receipts_root_bytes =
193        match block_index_proof.get_value(key, &verified_epoch_receipts_root) {
194            (false, _) => return false,
195            (true, None) => return false,
196            (true, Some(val)) => val,
197        };
198
199    // parse block receipts root as H256
200    let block_receipts_root: H256 = match TryInto::<[u8; 32]>::try_into(
201        block_receipts_root_bytes,
202    ) {
203        Ok(hash) => hash.into(),
204        Err(e) => {
205            // this should not happen
206            error!(
207                "Invalid content found in valid MPT: key = {:?}, value = {:?}; error = {:?}",
208                key, block_receipts_root_bytes, e,
209            );
210            return false;
211        }
212    };
213
214    // validate receipt in the block receipts trie
215    let key = &into_simple_mpt_key(tx_index_in_block, num_txs_in_block);
216
217    block_receipt_proof.is_valid_kv(
218        key,
219        Some(&receipt.rlp_bytes()[..]),
220        &block_receipts_root,
221    )
222}
223
224impl VerificationConfig {
225    pub fn new(
226        test_mode: bool, referee_bound: usize, max_block_size_in_bytes: usize,
227        transaction_epoch_bound: u64, tx_pool_nonce_bits: usize,
228        pos_enable_height: u64, machine: Arc<Machine>,
229    ) -> Self {
230        let max_nonce = if tx_pool_nonce_bits < 256 {
231            Some((U256::one() << tx_pool_nonce_bits) - 1)
232        } else {
233            None
234        };
235        VerificationConfig {
236            verify_timestamp: !test_mode,
237            referee_bound,
238            max_block_size_in_bytes,
239            transaction_epoch_bound,
240            machine,
241            pos_enable_height,
242            max_nonce,
243        }
244    }
245
246    #[inline]
247    /// Note that this function returns *pow_hash* of the block, not its quality
248    pub fn get_or_fill_header_pow_hash(
249        pow: &PowComputer, header: &mut BlockHeader,
250    ) -> H256 {
251        if header.pow_hash.is_none() {
252            header.pow_hash = Some(Self::compute_pow_hash(pow, header));
253        }
254        header.pow_hash.unwrap()
255    }
256
257    pub fn get_or_fill_header_pow_quality(
258        pow: &PowComputer, header: &mut BlockHeader,
259    ) -> U256 {
260        let pow_hash = Self::get_or_fill_header_pow_hash(pow, header);
261        pow::pow_hash_to_quality(&pow_hash, &header.nonce())
262    }
263
264    pub fn get_or_compute_header_pow_quality(
265        pow: &PowComputer, header: &BlockHeader,
266    ) -> U256 {
267        let pow_hash = header
268            .pow_hash
269            .unwrap_or_else(|| Self::compute_pow_hash(pow, header));
270        pow::pow_hash_to_quality(&pow_hash, &header.nonce())
271    }
272
273    fn compute_pow_hash(pow: &PowComputer, header: &BlockHeader) -> H256 {
274        let nonce = header.nonce();
275        pow.compute(&nonce, &header.problem_hash(), header.height())
276    }
277
278    #[inline]
279    pub fn verify_pow(
280        &self, pow: &PowComputer, header: &mut BlockHeader,
281    ) -> Result<(), Error> {
282        let pow_hash = Self::get_or_fill_header_pow_hash(pow, header);
283        if header.difficulty().is_zero() {
284            return Err(BlockError::InvalidDifficulty(OutOfBounds {
285                min: Some(0.into()),
286                max: Some(0.into()),
287                found: 0.into(),
288            })
289            .into());
290        }
291        let boundary = pow::difficulty_to_boundary(header.difficulty());
292        if !ProofOfWorkProblem::validate_hash_against_boundary(
293            &pow_hash,
294            &header.nonce(),
295            &boundary,
296        ) {
297            let lower_bound = nonce_to_lower_bound(&header.nonce());
298            // Because the lower_bound first bit is always zero, as long as the
299            // difficulty is not 1, this should not overflow.
300            // We just use overflowing_add() here to be safe.
301            let (upper_bound, _) = lower_bound.overflowing_add(boundary);
302            warn!("block {} has invalid proof of work. boundary: [{}, {}), pow_hash: {}",
303                  header.hash(), lower_bound.clone(), upper_bound.clone(), pow_hash.clone());
304            return Err(From::from(BlockError::InvalidProofOfWork(
305                OutOfBounds {
306                    min: Some(BigEndianHash::from_uint(&lower_bound)),
307                    max: Some(BigEndianHash::from_uint(&upper_bound)),
308                    found: pow_hash,
309                },
310            )));
311        }
312
313        assert!(
314            Self::get_or_fill_header_pow_quality(pow, header)
315                >= *header.difficulty()
316        );
317
318        Ok(())
319    }
320
321    #[inline]
322    pub fn validate_header_timestamp(
323        &self, header: &BlockHeader, now: u64,
324    ) -> Result<(), SyncError> {
325        let invalid_threshold = now + VALID_TIME_DRIFT;
326        if header.timestamp() > invalid_threshold {
327            warn!("block {} has incorrect timestamp", header.hash());
328            return Err(SyncError::InvalidTimestamp.into());
329        }
330        Ok(())
331    }
332
333    fn is_pos_enabled_at_height(&self, height: u64) -> bool {
334        height >= self.pos_enable_height
335    }
336
337    /// Check basic header parameters.
338    /// This does not require header to be graph or parental tree ready.
339    #[inline]
340    pub fn verify_header_params(
341        &self, pow: &PowComputer, header: &mut BlockHeader,
342    ) -> Result<(), Error> {
343        // Check header custom data length
344        let custom_len = header.custom_data_len();
345        if custom_len > HEADER_CUSTOM_LENGTH_BOUND {
346            return Err(From::from(BlockError::TooLongCustomInHeader(
347                OutOfBounds {
348                    min: Some(0),
349                    max: Some(HEADER_CUSTOM_LENGTH_BOUND),
350                    found: custom_len,
351                },
352            )));
353        }
354
355        if self.is_pos_enabled_at_height(header.height()) {
356            if header.pos_reference().is_none() {
357                bail!(BlockError::MissingPosReference);
358            }
359        } else {
360            if header.pos_reference().is_some() {
361                bail!(BlockError::UnexpectedPosReference);
362            }
363        }
364
365        if header.height() >= self.machine.params().transition_heights.cip1559 {
366            if header.base_price().is_none() {
367                bail!(BlockError::MissingBaseFee);
368            }
369        } else {
370            if header.base_price().is_some() {
371                bail!(BlockError::UnexpectedBaseFee);
372            }
373        }
374
375        // Note that this is just used to rule out deprecated blocks, so the
376        // change of header struct actually happens before the change of
377        // reward is reflected in the state root. The first state root
378        // including results of new rewards will in the header after another
379        // REWARD_EPOCH_COUNT + DEFERRED_STATE_EPOCH_COUNT epochs.
380        if let Some(expected_custom_prefix) =
381            self.machine.params().custom_prefix(header.height())
382        {
383            for (i, expected_bytes) in expected_custom_prefix.iter().enumerate()
384            {
385                // `None` => header custom too short; else prefix mismatch.
386                let matches =
387                    header.custom_item(i).is_some_and(|b| &b == expected_bytes);
388                if !matches {
389                    // Bound the error to the compared prefix; the header may
390                    // carry a huge number of items.
391                    let header_prefix = (0..expected_custom_prefix.len())
392                        .filter_map(|j| header.custom_item(j))
393                        .collect();
394                    return Err(BlockError::InvalidCustom(
395                        header_prefix,
396                        expected_custom_prefix.clone(),
397                    )
398                    .into());
399                }
400            }
401        }
402
403        // verify POW
404        self.verify_pow(pow, header)?;
405
406        // A block will be invalid if it has more than REFEREE_BOUND referees
407        if header.referee_hashes().len() > self.referee_bound {
408            return Err(From::from(BlockError::TooManyReferees(OutOfBounds {
409                min: Some(0),
410                max: Some(self.referee_bound),
411                found: header.referee_hashes().len(),
412            })));
413        }
414
415        // verify non-duplicated parent and referee hashes
416        let mut direct_ancestor_hashes = HashSet::new();
417        let parent_hash = header.parent_hash();
418        direct_ancestor_hashes.insert(parent_hash.clone());
419        for referee_hash in header.referee_hashes() {
420            if direct_ancestor_hashes.contains(referee_hash) {
421                warn!(
422                    "block {} has duplicate parent or referee hashes",
423                    header.hash()
424                );
425                return Err(From::from(
426                    BlockError::DuplicateParentOrRefereeHashes(
427                        referee_hash.clone(),
428                    ),
429                ));
430            }
431            direct_ancestor_hashes.insert(referee_hash.clone());
432        }
433
434        Ok(())
435    }
436
437    /// Verify block data against header: transactions root
438    #[inline]
439    fn verify_block_integrity(&self, block: &Block) -> Result<(), Error> {
440        let expected_root = compute_transaction_root(&block.transactions);
441        if &expected_root != block.block_header.transactions_root() {
442            warn!("Invalid transaction root");
443            bail!(BlockError::InvalidTransactionsRoot(Mismatch {
444                expected: expected_root,
445                found: *block.block_header.transactions_root(),
446            }));
447        }
448        Ok(())
449    }
450
451    /// Phase 1 quick block verification. Only does checks that are cheap.
452    /// Operates on a single block.
453    /// Note that we should first check whether the block body matches its
454    /// header, e.g., check transaction root correctness, and then check the
455    /// body itself. If body does not match header, the block may still be
456    /// valid but we get the wrong body from evil node. So we try to get
457    /// body again from others. However, if the body matches the header and
458    /// the body is incorrect, this means the block is invalid, and we
459    /// should discard this block and all its descendants.
460    #[inline]
461    pub fn verify_sync_graph_block_basic(
462        &self, block: &Block, chain_id: AllChainID,
463    ) -> Result<(), Error> {
464        self.verify_block_integrity(block)?;
465
466        let block_height = block.block_header.height();
467
468        let mut block_size = 0;
469        let transitions = &self.machine.params().transition_heights;
470        let consensus_spec =
471            &self.machine.params().consensus_spec(block_height);
472
473        for t in &block.transactions {
474            self.verify_transaction_common(
475                t,
476                chain_id,
477                block_height,
478                transitions,
479                VerifyTxMode::Remote(consensus_spec),
480            )?;
481            block_size += t.rlp_size();
482        }
483
484        if block_size > self.max_block_size_in_bytes {
485            return Err(From::from(BlockError::InvalidBlockSize(
486                OutOfBounds {
487                    min: None,
488                    max: Some(self.max_block_size_in_bytes as u64),
489                    found: block_size as u64,
490                },
491            )));
492        }
493        Ok(())
494    }
495
496    pub fn verify_sync_graph_ready_block(
497        &self, block: &Block, parent: &BlockHeader,
498    ) -> Result<(), Error> {
499        let mut total_gas: SpaceMap<U256> = SpaceMap::default();
500        for t in &block.transactions {
501            total_gas[t.space()] += *t.gas_limit();
502        }
503
504        if block.block_header.height()
505            >= self.machine.params().transition_heights.cip1559
506        {
507            self.check_base_fee(block, parent, total_gas)?;
508        } else {
509            self.check_hard_gas_limit(block, total_gas)?;
510        }
511        Ok(())
512    }
513
514    fn check_hard_gas_limit(
515        &self, block: &Block, total_gas: SpaceMap<U256>,
516    ) -> Result<(), Error> {
517        let block_height = block.block_header.height();
518
519        let evm_space_gas_limit =
520            if self.machine.params().can_pack_evm_transaction(block_height) {
521                *block.block_header.gas_limit()
522                    / self.machine.params().evm_transaction_gas_ratio
523            } else {
524                U256::zero()
525            };
526
527        let evm_total_gas = total_gas[Space::Ethereum];
528        let block_total_gas = total_gas.map_sum(|x| *x);
529
530        if evm_total_gas > evm_space_gas_limit {
531            return Err(From::from(BlockError::InvalidPackedGasLimit(
532                OutOfBounds {
533                    min: None,
534                    max: Some(evm_space_gas_limit),
535                    found: evm_total_gas,
536                },
537            )));
538        }
539
540        if block_total_gas > *block.block_header.gas_limit() {
541            return Err(From::from(BlockError::InvalidPackedGasLimit(
542                OutOfBounds {
543                    min: None,
544                    max: Some(*block.block_header.gas_limit()),
545                    found: block_total_gas,
546                },
547            )));
548        }
549
550        Ok(())
551    }
552
553    fn check_base_fee(
554        &self, block: &Block, parent: &BlockHeader, total_gas: SpaceMap<U256>,
555    ) -> Result<(), Error> {
556        use Space::*;
557
558        let params = self.machine.params();
559        let cip1559_init = params.transition_heights.cip1559;
560        let block_height = block.block_header.height();
561
562        assert!(block_height >= cip1559_init);
563
564        let core_gas_limit = block.block_header.core_space_gas_limit();
565        let espace_gas_limit = block
566            .block_header
567            .espace_gas_limit(params.can_pack_evm_transaction(block_height));
568
569        if total_gas[Ethereum] > espace_gas_limit {
570            return Err(From::from(BlockError::InvalidPackedGasLimit(
571                OutOfBounds {
572                    min: None,
573                    max: Some(espace_gas_limit),
574                    found: total_gas[Ethereum],
575                },
576            )));
577        }
578
579        if total_gas[Native] > core_gas_limit {
580            return Err(From::from(BlockError::InvalidPackedGasLimit(
581                OutOfBounds {
582                    min: None,
583                    max: Some(core_gas_limit),
584                    found: total_gas[Native],
585                },
586            )));
587        }
588
589        let parent_base_price = if block_height == cip1559_init {
590            params.init_base_price()
591        } else {
592            parent.base_price().unwrap()
593        };
594
595        let gas_limit = SpaceMap::new(core_gas_limit, espace_gas_limit);
596        let gas_target = gas_limit.map_all(|x| x / ELASTICITY_MULTIPLIER);
597        let min_base_price = params.min_base_price();
598
599        let expected_base_price = SpaceMap::zip4(
600            gas_target,
601            total_gas,
602            parent_base_price,
603            min_base_price,
604        )
605        .map_all(compute_next_price_tuple);
606
607        let actual_base_price = block.block_header.base_price().unwrap();
608
609        if actual_base_price != expected_base_price {
610            return Err(From::from(BlockError::InvalidBasePrice(Mismatch {
611                expected: expected_base_price,
612                found: actual_base_price,
613            })));
614        }
615
616        Ok(())
617    }
618
619    pub fn check_transaction_epoch_bound(
620        tx: &TypedNativeTransaction, block_height: u64,
621        transaction_epoch_bound: u64,
622    ) -> i8 {
623        if tx.epoch_height().wrapping_add(transaction_epoch_bound)
624            < block_height
625        {
626            -1
627        } else if *tx.epoch_height() > block_height + transaction_epoch_bound {
628            1
629        } else {
630            0
631        }
632    }
633
634    fn verify_transaction_epoch_height(
635        tx: &TypedNativeTransaction, block_height: u64,
636        transaction_epoch_bound: u64, mode: &VerifyTxMode,
637    ) -> Result<(), TransactionError> {
638        let result = Self::check_transaction_epoch_bound(
639            tx,
640            block_height,
641            transaction_epoch_bound,
642        );
643        let allow_larger_epoch = mode.is_maybe_later();
644
645        if result == 0 || (result > 0 && allow_larger_epoch) {
646            Ok(())
647        } else {
648            bail!(TransactionError::EpochHeightOutOfBound {
649                set: *tx.epoch_height(),
650                block_height,
651                transaction_epoch_bound,
652            });
653        }
654    }
655
656    fn fast_recheck_inner<F>(spec: &Spec, f: F) -> (bool, bool)
657    where F: Fn(&VerifyTxMode) -> bool {
658        let tx_pool_mode =
659            VerifyTxMode::Local(VerifyTxLocalMode::MaybeLater, spec);
660        let packing_mode = VerifyTxMode::Local(VerifyTxLocalMode::Full, spec);
661
662        (f(&packing_mode), f(&tx_pool_mode))
663    }
664
665    pub fn fast_recheck(
666        &self, tx: &TransactionWithSignature, height: BlockHeight,
667        transitions: &TransitionsEpochHeight, spec: &Spec,
668    ) -> PackingCheckResult {
669        let cip90a = height >= transitions.cip90a;
670        let cip1559 = height >= transitions.cip1559;
671        let cip7702 = height >= transitions.cip7702;
672        let cip645 = height >= transitions.cip645;
673
674        let (can_pack, later_pack) = Self::fast_recheck_inner(
675            spec,
676            |mode: &VerifyTxMode| {
677                if !Self::check_eip1559_transaction(tx, cip1559, mode) {
678                    trace!(
679                        "fast_recheck: EIP-1559 transaction check failed at height {} txhash={:?}",
680                        height,
681                        tx.hash()
682                    );
683                    return false;
684                }
685
686                if !Self::check_eip7702_transaction(tx, cip7702, mode) {
687                    trace!(
688                        "fast_recheck: EIP-7702 transaction check failed at height {} txhash={:?}",
689                        height,
690                        tx.hash()
691                    );
692                    return false;
693                }
694
695                if !Self::check_eip3860(tx, cip645) {
696                    trace!(
697                        "fast_recheck: EIP-3860 transaction check failed at height {} txhash={:?}",
698                        height,
699                        tx.hash()
700                    );
701                    return false;
702                }
703
704                if let Transaction::Native(ref tx) = tx.unsigned {
705                    Self::verify_transaction_epoch_height(
706                        tx,
707                        height,
708                        self.transaction_epoch_bound,
709                        mode,
710                    )
711                    .is_ok()
712                } else {
713                    Self::check_eip155_transaction(tx, cip90a, mode)
714                }
715            },
716        );
717
718        match (can_pack, later_pack) {
719            (true, _) => PackingCheckResult::Pack,
720            (false, true) => PackingCheckResult::Pending,
721            (false, false) => PackingCheckResult::Drop,
722        }
723    }
724
725    // Packing transactions, verifying transaction in sync graph and inserting
726    // transactions may have different logics. But they share a lot of similar
727    // rules. We combine them together for convenient in the future upgrades..
728    pub fn verify_transaction_common(
729        &self, tx: &TransactionWithSignature, chain_id: AllChainID,
730        height: BlockHeight, transitions: &TransitionsEpochHeight,
731        mode: VerifyTxMode,
732    ) -> Result<(), TransactionError> {
733        tx.check_low_s()?;
734        tx.check_y_parity()?;
735
736        // Disallow unsigned transactions
737        if tx.is_unsigned() {
738            bail!(TransactionError::InvalidSignature(
739                "Transaction is unsigned".into()
740            ));
741        }
742
743        if let Some(tx_chain_id) = tx.chain_id() {
744            if tx_chain_id != chain_id.in_space(tx.space()) {
745                bail!(TransactionError::ChainIdMismatch {
746                    expected: chain_id.in_space(tx.space()),
747                    got: tx_chain_id,
748                    space: tx.space(),
749                });
750            }
751        }
752
753        // Forbid zero-gas-price tx
754        if tx.gas_price().is_zero() {
755            bail!(TransactionError::ZeroGasPrice);
756        }
757
758        if matches!(mode, VerifyTxMode::Local(..))
759            && tx.space() == Space::Native
760        {
761            if let Action::Call(ref address) = tx.transaction.action() {
762                if !address.is_genesis_valid_address() {
763                    bail!(TransactionError::InvalidReceiver)
764                }
765            }
766        }
767
768        if let (VerifyTxMode::Local(..), Some(max_nonce)) =
769            (mode, self.max_nonce)
770        {
771            if tx.nonce() > &max_nonce {
772                bail!(TransactionError::TooLargeNonce)
773            }
774        }
775
776        // ******************************************
777        // Each constraint depends on a mode or a CIP should be
778        // implemented in a seperated function.
779        // ******************************************
780        let cip76 = height >= transitions.cip76;
781        let cip90a = height >= transitions.cip90a;
782        let cip130 =
783            height >= transitions.cip130 && height < transitions.align_evm;
784        let cip1559 = height >= transitions.cip1559;
785        let cip7702 = height >= transitions.cip7702;
786        let cip645 = height >= transitions.cip645;
787        let eip7623 = height >= transitions.eip7623;
788
789        if let Transaction::Native(ref tx) = tx.unsigned {
790            Self::verify_transaction_epoch_height(
791                tx,
792                height,
793                self.transaction_epoch_bound,
794                &mode,
795            )?;
796        }
797
798        if !Self::check_eip155_transaction(tx, cip90a, &mode) {
799            bail!(TransactionError::FutureTransactionType {
800                tx_type: LEGACY_TX_TYPE,
801                current_height: height,
802                enable_height: transitions.cip90a,
803            });
804        }
805
806        if !Self::check_eip1559_transaction(tx, cip1559, &mode) {
807            bail!(TransactionError::FutureTransactionType {
808                tx_type: EIP1559_TYPE,
809                current_height: height,
810                enable_height: transitions.cip1559,
811            })
812        }
813
814        if !Self::check_eip7702_transaction(tx, cip7702, &mode) {
815            bail!(TransactionError::FutureTransactionType {
816                tx_type: EIP7702_TYPE,
817                current_height: height,
818                enable_height: transitions.cip7702,
819            })
820        }
821
822        if !Self::check_eip3860(tx, cip645) {
823            bail!(TransactionError::CreateInitCodeSizeLimit)
824        }
825
826        Self::check_eip1559_validation(tx, cip645)?;
827        Self::check_eip7702_validation(tx)?;
828
829        Self::check_gas_limit(tx, cip76, eip7623, &mode)?;
830        Self::check_gas_limit_with_calldata(tx, cip130)?;
831
832        Ok(())
833    }
834
835    fn check_eip155_transaction(
836        tx: &TransactionWithSignature, cip90a: bool, mode: &VerifyTxMode,
837    ) -> bool {
838        if tx.space() == Space::Native {
839            return true;
840        }
841
842        use VerifyTxLocalMode::*;
843        match mode {
844            VerifyTxMode::Local(Full, spec) => cip90a && spec.cip90,
845            VerifyTxMode::Local(MaybeLater, _spec) => true,
846            VerifyTxMode::Remote(_) => cip90a,
847        }
848    }
849
850    fn check_eip1559_transaction(
851        tx: &TransactionWithSignature, cip1559: bool, mode: &VerifyTxMode,
852    ) -> bool {
853        if tx.is_legacy() {
854            return true;
855        }
856
857        use VerifyTxLocalMode::*;
858        match mode {
859            VerifyTxMode::Local(Full, spec) => cip1559 && spec.cip1559,
860            VerifyTxMode::Local(MaybeLater, _spec) => true,
861            VerifyTxMode::Remote(_) => cip1559,
862        }
863    }
864
865    fn check_eip7702_transaction(
866        tx: &TransactionWithSignature, cip7702: bool, mode: &VerifyTxMode,
867    ) -> bool {
868        if !tx.after_7702() {
869            return true;
870        }
871
872        use VerifyTxLocalMode::*;
873        match mode {
874            VerifyTxMode::Local(Full, spec) => cip7702 && spec.cip7702,
875            VerifyTxMode::Local(MaybeLater, _spec) => true,
876            VerifyTxMode::Remote(_) => cip7702,
877        }
878    }
879
880    fn check_eip7702_validation(
881        tx: &TransactionWithSignature,
882    ) -> Result<(), TransactionError> {
883        if let Some(author_list) = tx.authorization_list() {
884            if author_list.is_empty() {
885                return Err(TransactionError::EmptyAuthorizationList);
886            }
887        }
888        Ok(())
889    }
890
891    fn check_eip1559_validation(
892        tx: &TransactionWithSignature, cip645: bool,
893    ) -> Result<(), TransactionError> {
894        if !cip645 || !tx.after_1559() {
895            return Ok(());
896        }
897
898        if tx.max_priority_gas_price() > tx.gas_price() {
899            return Err(TransactionError::PriortyGreaterThanMaxFee);
900        }
901        Ok(())
902    }
903
904    fn check_eip3860(tx: &TransactionWithSignature, cip645: bool) -> bool {
905        // TODO: better way to get the specification
906        const SPEC: Spec = Spec::genesis_spec();
907        if !cip645 {
908            return true;
909        }
910        if tx.action() != Action::Create {
911            return true;
912        }
913
914        tx.data().len() <= SPEC.init_code_data_limit
915    }
916
917    /// Check transaction intrinsic gas. Influenced by CIP-76 and EIP-7623.
918    fn check_gas_limit(
919        tx: &TransactionWithSignature, cip76: bool, eip7623: bool,
920        mode: &VerifyTxMode,
921    ) -> Result<(), TransactionError> {
922        let consensus_spec = match mode {
923            VerifyTxMode::Local(_, spec) => spec.to_consensus_spec(),
924            VerifyTxMode::Remote(spec) => {
925                if !eip7623 && cip76 {
926                    return Ok(());
927                } else {
928                    (*spec).clone()
929                }
930            }
931        };
932
933        let tx_intrinsic_gas = gas_required_for(
934            tx.action() == Action::Create,
935            &tx.data(),
936            tx.access_list(),
937            tx.authorization_len(),
938            &consensus_spec,
939        );
940
941        if *tx.gas() < tx_intrinsic_gas.into() {
942            bail!(TransactionError::NotEnoughBaseGas {
943                required: tx_intrinsic_gas.into(),
944                got: *tx.gas()
945            });
946        }
947
948        if eip7623 {
949            let floor_gas = eip7623_required_gas(&tx.data(), &consensus_spec);
950
951            if *tx.gas() < floor_gas.into() {
952                bail!(TransactionError::NotEnoughBaseGas {
953                    required: floor_gas.into(),
954                    got: *tx.gas()
955                });
956            }
957        }
958
959        Ok(())
960    }
961
962    fn check_gas_limit_with_calldata(
963        tx: &TransactionWithSignature, cip130: bool,
964    ) -> Result<(), TransactionError> {
965        if !cip130 {
966            return Ok(());
967        }
968        let data_length = tx.data().len();
969        let min_gas_limit = data_length.saturating_mul(100);
970        if tx.gas() < &U256::from(min_gas_limit) {
971            bail!(TransactionError::NotEnoughBaseGas {
972                required: min_gas_limit.into(),
973                got: *tx.gas()
974            });
975        }
976        Ok(())
977    }
978
979    pub fn check_tx_size(
980        &self, tx: &TransactionWithSignature,
981    ) -> Result<(), TransactionError> {
982        if tx.rlp_size() > self.max_block_size_in_bytes {
983            bail!(TransactionError::TooBig)
984        } else {
985            Ok(())
986        }
987    }
988}
989
990#[derive(Copy, Clone)]
991pub enum PackingCheckResult {
992    Pack,
993    // Transaction can be packed.
994    Pending,
995    // Transaction may be ready to packed in the future.
996    Drop, // Transaction can never be packed.
997}
998
999#[derive(Copy, Clone)]
1000pub enum VerifyTxMode<'a> {
1001    /// Check transactions in local mode, may have more constraints
1002    Local(VerifyTxLocalMode, &'a Spec),
1003    /// Check transactions for received blocks in sync graph, may have less
1004    /// constraints
1005    Remote(&'a ConsensusGasSpec),
1006}
1007
1008#[derive(Copy, Clone)]
1009pub enum VerifyTxLocalMode {
1010    /// Apply all checks
1011    Full,
1012    /// If a transaction is not valid now, but can become valid in the future,
1013    /// the check sould pass
1014    MaybeLater,
1015}
1016
1017impl<'a> VerifyTxMode<'a> {
1018    fn is_maybe_later(&self) -> bool {
1019        if let VerifyTxMode::Local(VerifyTxLocalMode::MaybeLater, _) = self {
1020            true
1021        } else {
1022            false
1023        }
1024    }
1025}
1026
1027#[cfg(test)]
1028mod tests {
1029    use crate::verification::EpochReceiptProof;
1030    use cfx_storage::{
1031        CompressedPathRaw, TrieProof, TrieProofNode, VanillaChildrenTable,
1032    };
1033
1034    #[test]
1035    fn test_rlp_epoch_receipt_proof() {
1036        let proof = EpochReceiptProof::default();
1037        assert_eq!(proof, rlp::decode(&rlp::encode(&proof)).unwrap());
1038
1039        let serialized = serde_json::to_string(&proof).unwrap();
1040        let deserialized: EpochReceiptProof =
1041            serde_json::from_str(&serialized).unwrap();
1042        assert_eq!(proof, deserialized);
1043
1044        let node1 = TrieProofNode::new(
1045            Default::default(),
1046            Some(Box::new([0x03, 0x04, 0x05])),
1047            CompressedPathRaw::new(
1048                &[0x00, 0x01, 0x02],
1049                CompressedPathRaw::first_nibble_mask(),
1050            ),
1051            /* path_without_first_nibble = */ true,
1052        );
1053
1054        let root_node = {
1055            let mut children_table = VanillaChildrenTable::default();
1056            unsafe {
1057                *children_table.get_child_mut_unchecked(2) =
1058                    *node1.get_merkle();
1059                *children_table.get_children_count_mut() = 1;
1060            }
1061            TrieProofNode::new(
1062                children_table,
1063                None,
1064                CompressedPathRaw::default(),
1065                /* path_without_first_nibble = */ false,
1066            )
1067        };
1068        let nodes = [root_node, node1]
1069            .iter()
1070            .cloned()
1071            .cycle()
1072            .take(20)
1073            .collect();
1074        let proof = TrieProof::new(nodes).unwrap();
1075
1076        let epoch_proof = EpochReceiptProof {
1077            block_index_proof: proof.clone(),
1078            block_receipt_proof: proof,
1079        };
1080
1081        assert_eq!(
1082            epoch_proof,
1083            rlp::decode(&rlp::encode(&epoch_proof)).unwrap()
1084        );
1085
1086        let serialized = serde_json::to_string(&epoch_proof).unwrap();
1087        let deserialized: EpochReceiptProof =
1088            serde_json::from_str(&serialized).unwrap();
1089        assert_eq!(epoch_proof, deserialized);
1090    }
1091}