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