reth_payload_validator/
cancun.rs

1//! Cancun rules for new payloads.
2
3use alloy_consensus::{BlockBody, Transaction, Typed2718};
4use alloy_rpc_types_engine::{CancunPayloadFields, PayloadError};
5use reth_primitives_traits::{AlloyBlockHeader, Block, SealedBlock};
6
7/// Checks block and sidecar w.r.t new Cancun fields and new transaction type EIP-4844.
8///
9/// Checks that:
10/// - Cancun fields are present if Cancun is active
11/// - doesn't contain EIP-4844 transactions unless Cancun is active
12/// - checks blob versioned hashes in block and sidecar match
13#[inline]
14pub fn ensure_well_formed_fields<T, B, H>(
15    block: &SealedBlock<B>,
16    cancun_sidecar_fields: Option<&CancunPayloadFields>,
17    is_cancun_active: bool,
18) -> Result<(), PayloadError>
19where
20    T: Transaction + Typed2718,
21    H: AlloyBlockHeader,
22    B: Block<Header = H, Body = BlockBody<T, H>>,
23{
24    ensure_well_formed_header_and_sidecar_fields(block, cancun_sidecar_fields, is_cancun_active)?;
25    ensure_well_formed_transactions_field_with_sidecar(
26        block.body(),
27        cancun_sidecar_fields,
28        is_cancun_active,
29    )
30}
31
32/// Checks that Cancun fields on block header and sidecar are present if Cancun is active and vv.
33#[inline]
34pub fn ensure_well_formed_header_and_sidecar_fields<T: Block>(
35    block: &SealedBlock<T>,
36    cancun_sidecar_fields: Option<&CancunPayloadFields>,
37    is_cancun_active: bool,
38) -> Result<(), PayloadError> {
39    if is_cancun_active {
40        if block.blob_gas_used().is_none() {
41            // cancun active but blob gas used not present
42            return Err(PayloadError::PostCancunBlockWithoutBlobGasUsed)
43        }
44        if block.excess_blob_gas().is_none() {
45            // cancun active but excess blob gas not present
46            return Err(PayloadError::PostCancunBlockWithoutExcessBlobGas)
47        }
48        if cancun_sidecar_fields.is_none() {
49            // cancun active but cancun fields not present
50            return Err(PayloadError::PostCancunWithoutCancunFields)
51        }
52    } else {
53        if block.blob_gas_used().is_some() {
54            // cancun not active but blob gas used present
55            return Err(PayloadError::PreCancunBlockWithBlobGasUsed)
56        }
57        if block.excess_blob_gas().is_some() {
58            // cancun not active but excess blob gas present
59            return Err(PayloadError::PreCancunBlockWithExcessBlobGas)
60        }
61        if cancun_sidecar_fields.is_some() {
62            // cancun not active but cancun fields present
63            return Err(PayloadError::PreCancunWithCancunFields)
64        }
65    }
66
67    Ok(())
68}
69
70/// Checks transactions field and sidecar w.r.t new Cancun fields and new transaction type EIP-4844.
71///
72/// Checks that:
73/// - doesn't contain EIP-4844 transactions unless Cancun is active
74/// - checks blob versioned hashes in block and sidecar match
75#[inline]
76pub fn ensure_well_formed_transactions_field_with_sidecar<T: Transaction + Typed2718, H>(
77    block_body: &BlockBody<T, H>,
78    cancun_sidecar_fields: Option<&CancunPayloadFields>,
79    is_cancun_active: bool,
80) -> Result<(), PayloadError> {
81    if is_cancun_active {
82        ensure_matching_blob_versioned_hashes(block_body, cancun_sidecar_fields)?
83    } else if block_body.has_eip4844_transactions() {
84        return Err(PayloadError::PreCancunBlockWithBlobTransactions)
85    }
86
87    Ok(())
88}
89
90/// Ensures that the number of blob versioned hashes of a EIP-4844 transactions in block, matches
91/// the number hashes included in the _separate_ `block_versioned_hashes` of the cancun payload
92/// fields on the sidecar.
93pub fn ensure_matching_blob_versioned_hashes<T: Transaction + Typed2718, H>(
94    block_body: &BlockBody<T, H>,
95    cancun_sidecar_fields: Option<&CancunPayloadFields>,
96) -> Result<(), PayloadError> {
97    let num_blob_versioned_hashes = block_body.blob_versioned_hashes_iter().count();
98    // Additional Cancun checks for blob transactions
99    if let Some(versioned_hashes) = cancun_sidecar_fields.map(|fields| &fields.versioned_hashes) {
100        if num_blob_versioned_hashes != versioned_hashes.len() {
101            // Number of blob versioned hashes does not match
102            return Err(PayloadError::InvalidVersionedHashes)
103        }
104        // we can use `zip` safely here because we already compared their length
105        for (payload_versioned_hash, block_versioned_hash) in
106            versioned_hashes.iter().zip(block_body.blob_versioned_hashes_iter())
107        {
108            if payload_versioned_hash != block_versioned_hash {
109                return Err(PayloadError::InvalidVersionedHashes)
110            }
111        }
112    } else {
113        // No Cancun fields, if block includes any blobs, this is an error
114        if num_blob_versioned_hashes > 0 {
115            return Err(PayloadError::InvalidVersionedHashes)
116        }
117    }
118
119    Ok(())
120}