alloy_consensus/transaction/
eip4844.rs

1use super::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, TxEip4844Sidecar};
2use crate::{SignableTransaction, Signed, Transaction, TxType};
3use alloc::vec::Vec;
4use alloy_eips::{
5    eip2718::IsTyped2718,
6    eip2930::AccessList,
7    eip4844::{BlobTransactionSidecar, DATA_GAS_PER_BLOB},
8    eip7702::SignedAuthorization,
9    Typed2718,
10};
11use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256};
12use alloy_rlp::{BufMut, Decodable, Encodable, Header};
13use core::mem;
14
15#[cfg(feature = "kzg")]
16use alloy_eips::eip4844::BlobTransactionValidationError;
17
18/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
19///
20/// A transaction with blob hashes and max blob fee.
21/// It can either be a standalone transaction, mainly seen when retrieving historical transactions,
22/// or a transaction with a sidecar, which is used when submitting a transaction to the network and
23/// when receiving and sending transactions during the gossip stage.
24#[derive(Clone, Debug, PartialEq, Eq, Hash)]
25#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27#[cfg_attr(feature = "serde", serde(untagged))]
28#[doc(alias = "Eip4844TransactionVariant")]
29pub enum TxEip4844Variant {
30    /// A standalone transaction with blob hashes and max blob fee.
31    TxEip4844(TxEip4844),
32    /// A transaction with a sidecar, which contains the blob data, commitments, and proofs.
33    TxEip4844WithSidecar(TxEip4844WithSidecar),
34}
35
36#[cfg(feature = "serde")]
37impl<'de> serde::Deserialize<'de> for TxEip4844Variant {
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: serde::Deserializer<'de>,
41    {
42        #[derive(serde::Deserialize)]
43        struct TxEip4844SerdeHelper {
44            #[serde(flatten)]
45            #[doc(alias = "transaction")]
46            tx: TxEip4844,
47            #[serde(flatten)]
48            sidecar: Option<BlobTransactionSidecar>,
49        }
50
51        let tx = TxEip4844SerdeHelper::deserialize(deserializer)?;
52
53        if let Some(sidecar) = tx.sidecar {
54            Ok(TxEip4844WithSidecar::from_tx_and_sidecar(tx.tx, sidecar).into())
55        } else {
56            Ok(tx.tx.into())
57        }
58    }
59}
60
61impl From<Signed<TxEip4844>> for Signed<TxEip4844Variant> {
62    fn from(value: Signed<TxEip4844>) -> Self {
63        let (tx, signature, hash) = value.into_parts();
64        Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash)
65    }
66}
67
68impl From<Signed<TxEip4844WithSidecar>> for Signed<TxEip4844Variant> {
69    fn from(value: Signed<TxEip4844WithSidecar>) -> Self {
70        let (tx, signature, hash) = value.into_parts();
71        Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash)
72    }
73}
74
75impl From<TxEip4844WithSidecar> for TxEip4844Variant {
76    fn from(tx: TxEip4844WithSidecar) -> Self {
77        Self::TxEip4844WithSidecar(tx)
78    }
79}
80
81impl From<TxEip4844> for TxEip4844Variant {
82    fn from(tx: TxEip4844) -> Self {
83        Self::TxEip4844(tx)
84    }
85}
86
87impl From<(TxEip4844, BlobTransactionSidecar)> for TxEip4844Variant {
88    fn from((tx, sidecar): (TxEip4844, BlobTransactionSidecar)) -> Self {
89        TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar).into()
90    }
91}
92
93impl From<TxEip4844Variant> for TxEip4844 {
94    fn from(tx: TxEip4844Variant) -> Self {
95        match tx {
96            TxEip4844Variant::TxEip4844(tx) => tx,
97            TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.tx,
98        }
99    }
100}
101
102impl AsRef<TxEip4844> for TxEip4844Variant {
103    fn as_ref(&self) -> &TxEip4844 {
104        match self {
105            Self::TxEip4844(tx) => tx,
106            Self::TxEip4844WithSidecar(tx) => &tx.tx,
107        }
108    }
109}
110
111impl AsMut<TxEip4844> for TxEip4844Variant {
112    fn as_mut(&mut self) -> &mut TxEip4844 {
113        match self {
114            Self::TxEip4844(tx) => tx,
115            Self::TxEip4844WithSidecar(tx) => &mut tx.tx,
116        }
117    }
118}
119
120impl AsRef<Self> for TxEip4844 {
121    fn as_ref(&self) -> &Self {
122        self
123    }
124}
125
126impl AsMut<Self> for TxEip4844 {
127    fn as_mut(&mut self) -> &mut Self {
128        self
129    }
130}
131
132impl TxEip4844Variant {
133    /// Verifies that the transaction's blob data, commitments, and proofs are all valid.
134    ///
135    /// See also [TxEip4844::validate_blob]
136    #[cfg(feature = "kzg")]
137    pub fn validate(
138        &self,
139        proof_settings: &c_kzg::KzgSettings,
140    ) -> Result<(), BlobTransactionValidationError> {
141        match self {
142            Self::TxEip4844(_) => Err(BlobTransactionValidationError::MissingSidecar),
143            Self::TxEip4844WithSidecar(tx) => tx.validate_blob(proof_settings),
144        }
145    }
146
147    /// Get the transaction type.
148    #[doc(alias = "transaction_type")]
149    pub const fn tx_type() -> TxType {
150        TxType::Eip4844
151    }
152
153    /// Get access to the inner tx [TxEip4844].
154    #[doc(alias = "transaction")]
155    pub const fn tx(&self) -> &TxEip4844 {
156        match self {
157            Self::TxEip4844(tx) => tx,
158            Self::TxEip4844WithSidecar(tx) => tx.tx(),
159        }
160    }
161
162    /// Calculates a heuristic for the in-memory size of the [TxEip4844Variant] transaction.
163    #[inline]
164    pub fn size(&self) -> usize {
165        match self {
166            Self::TxEip4844(tx) => tx.size(),
167            Self::TxEip4844WithSidecar(tx) => tx.size(),
168        }
169    }
170
171    /// Tries to unwrap the [`TxEip4844WithSidecar`] returns the transaction as error if it is not a
172    /// [`TxEip4844WithSidecar`]
173    pub fn try_into_4844_with_sidecar(self) -> Result<TxEip4844WithSidecar, Self> {
174        match self {
175            Self::TxEip4844WithSidecar(tx) => Ok(tx),
176            _ => Err(self),
177        }
178    }
179}
180
181impl Transaction for TxEip4844Variant {
182    #[inline]
183    fn chain_id(&self) -> Option<ChainId> {
184        match self {
185            Self::TxEip4844(tx) => Some(tx.chain_id),
186            Self::TxEip4844WithSidecar(tx) => Some(tx.tx().chain_id),
187        }
188    }
189
190    #[inline]
191    fn nonce(&self) -> u64 {
192        match self {
193            Self::TxEip4844(tx) => tx.nonce,
194            Self::TxEip4844WithSidecar(tx) => tx.tx().nonce,
195        }
196    }
197
198    #[inline]
199    fn gas_limit(&self) -> u64 {
200        match self {
201            Self::TxEip4844(tx) => tx.gas_limit,
202            Self::TxEip4844WithSidecar(tx) => tx.tx().gas_limit,
203        }
204    }
205
206    #[inline]
207    fn gas_price(&self) -> Option<u128> {
208        None
209    }
210
211    #[inline]
212    fn max_fee_per_gas(&self) -> u128 {
213        match self {
214            Self::TxEip4844(tx) => tx.max_fee_per_gas(),
215            Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_gas(),
216        }
217    }
218
219    #[inline]
220    fn max_priority_fee_per_gas(&self) -> Option<u128> {
221        match self {
222            Self::TxEip4844(tx) => tx.max_priority_fee_per_gas(),
223            Self::TxEip4844WithSidecar(tx) => tx.max_priority_fee_per_gas(),
224        }
225    }
226
227    #[inline]
228    fn max_fee_per_blob_gas(&self) -> Option<u128> {
229        match self {
230            Self::TxEip4844(tx) => tx.max_fee_per_blob_gas(),
231            Self::TxEip4844WithSidecar(tx) => tx.max_fee_per_blob_gas(),
232        }
233    }
234
235    #[inline]
236    fn priority_fee_or_price(&self) -> u128 {
237        match self {
238            Self::TxEip4844(tx) => tx.priority_fee_or_price(),
239            Self::TxEip4844WithSidecar(tx) => tx.priority_fee_or_price(),
240        }
241    }
242
243    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
244        match self {
245            Self::TxEip4844(tx) => tx.effective_gas_price(base_fee),
246            Self::TxEip4844WithSidecar(tx) => tx.effective_gas_price(base_fee),
247        }
248    }
249
250    #[inline]
251    fn is_dynamic_fee(&self) -> bool {
252        match self {
253            Self::TxEip4844(tx) => tx.is_dynamic_fee(),
254            Self::TxEip4844WithSidecar(tx) => tx.is_dynamic_fee(),
255        }
256    }
257
258    #[inline]
259    fn kind(&self) -> TxKind {
260        match self {
261            Self::TxEip4844(tx) => tx.to,
262            Self::TxEip4844WithSidecar(tx) => tx.tx.to,
263        }
264        .into()
265    }
266
267    #[inline]
268    fn is_create(&self) -> bool {
269        false
270    }
271
272    #[inline]
273    fn value(&self) -> U256 {
274        match self {
275            Self::TxEip4844(tx) => tx.value,
276            Self::TxEip4844WithSidecar(tx) => tx.tx.value,
277        }
278    }
279
280    #[inline]
281    fn input(&self) -> &Bytes {
282        match self {
283            Self::TxEip4844(tx) => tx.input(),
284            Self::TxEip4844WithSidecar(tx) => tx.tx().input(),
285        }
286    }
287
288    #[inline]
289    fn access_list(&self) -> Option<&AccessList> {
290        match self {
291            Self::TxEip4844(tx) => tx.access_list(),
292            Self::TxEip4844WithSidecar(tx) => tx.access_list(),
293        }
294    }
295
296    #[inline]
297    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
298        match self {
299            Self::TxEip4844(tx) => tx.blob_versioned_hashes(),
300            Self::TxEip4844WithSidecar(tx) => tx.blob_versioned_hashes(),
301        }
302    }
303
304    #[inline]
305    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
306        None
307    }
308}
309impl Typed2718 for TxEip4844 {
310    fn ty(&self) -> u8 {
311        TxType::Eip4844 as u8
312    }
313}
314
315impl RlpEcdsaEncodableTx for TxEip4844Variant {
316    fn rlp_encoded_fields_length(&self) -> usize {
317        match self {
318            Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(),
319            Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(),
320        }
321    }
322
323    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
324        match self {
325            Self::TxEip4844(inner) => inner.rlp_encode_fields(out),
326            Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out),
327        }
328    }
329
330    fn rlp_header_signed(&self, signature: &Signature) -> Header {
331        match self {
332            Self::TxEip4844(inner) => inner.rlp_header_signed(signature),
333            Self::TxEip4844WithSidecar(inner) => inner.rlp_header_signed(signature),
334        }
335    }
336
337    fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
338        match self {
339            Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out),
340            Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out),
341        }
342    }
343
344    fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
345        match self {
346            Self::TxEip4844(inner) => inner.tx_hash_with_type(signature, ty),
347            Self::TxEip4844WithSidecar(inner) => inner.tx_hash_with_type(signature, ty),
348        }
349    }
350}
351
352impl RlpEcdsaDecodableTx for TxEip4844Variant {
353    const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
354
355    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
356        let needle = &mut &**buf;
357
358        // We also need to do a trial decoding of WithSidecar to see if it
359        // works. The trial ref is consumed to look for a WithSidecar.
360        let trial = &mut &**buf;
361
362        // If the next bytes are a header, one of 3 things is true:
363        // - If the header is a list, this is a WithSidecar tx
364        // - If there is no header, this is a non-sidecar tx with a single-byte chain ID.
365        // - If there is a string header, this is a non-sidecar tx with a multi-byte chain ID.
366        // To check these, we first try to decode the header. If it fails or is
367        // not a list, we lmow that it is a non-sidecar transaction.
368        if Header::decode(needle).is_ok_and(|h| h.list) {
369            if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) {
370                *buf = *trial;
371                return Ok(tx.into());
372            }
373        }
374        TxEip4844::rlp_decode_fields(buf).map(Into::into)
375    }
376
377    fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
378        // We need to determine if this has a sidecar tx or not. The needle ref
379        // is consumed to look for headers.
380        let needle = &mut &**buf;
381
382        // We also need to do a trial decoding of WithSidecar to see if it
383        // works. The original ref is consumed to look for a WithSidecar.
384        let trial = &mut &**buf;
385
386        // First we decode the outer header
387        Header::decode(needle)?;
388
389        // If the next bytes are a header, one of 3 things is true:
390        // - If the header is a list, this is a WithSidecar tx
391        // - If there is no header, this is a non-sidecar tx with a single-byte chain ID.
392        // - If there is a string header, this is a non-sidecar tx with a multi-byte chain ID.
393        // To check these, we first try to decode the header. If it fails or is
394        // not a list, we lmow that it is a non-sidecar transaction.
395        if Header::decode(needle).is_ok_and(|h| h.list) {
396            if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) {
397                // If successful, we need to consume the trial buffer up to
398                // the same point.
399                *buf = *trial;
400                return Ok((tx.into(), signature));
401            }
402        }
403        TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature))
404    }
405}
406
407impl Typed2718 for TxEip4844Variant {
408    fn ty(&self) -> u8 {
409        TxType::Eip4844 as u8
410    }
411}
412
413impl IsTyped2718 for TxEip4844 {
414    fn is_type(type_id: u8) -> bool {
415        matches!(type_id, 0x03)
416    }
417}
418
419impl SignableTransaction<Signature> for TxEip4844Variant {
420    fn set_chain_id(&mut self, chain_id: ChainId) {
421        match self {
422            Self::TxEip4844(inner) => {
423                inner.set_chain_id(chain_id);
424            }
425            Self::TxEip4844WithSidecar(inner) => {
426                inner.set_chain_id(chain_id);
427            }
428        }
429    }
430
431    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
432        // A signature for a [TxEip4844WithSidecar] is a signature over the [TxEip4844Variant]
433        // EIP-2718 payload fields:
434        // (BLOB_TX_TYPE ||
435        //   rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value,
436        //     data, access_list, max_fee_per_blob_gas, blob_versioned_hashes]))
437        self.tx().encode_for_signing(out);
438    }
439
440    fn payload_len_for_signature(&self) -> usize {
441        self.tx().payload_len_for_signature()
442    }
443}
444
445/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
446///
447/// A transaction with blob hashes and max blob fee. It does not have the Blob sidecar.
448#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
449#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
450#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
451#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
452#[doc(alias = "Eip4844Transaction", alias = "TransactionEip4844", alias = "Eip4844Tx")]
453pub struct TxEip4844 {
454    /// Added as EIP-pub 155: Simple replay attack protection
455    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
456    pub chain_id: ChainId,
457    /// A scalar value equal to the number of transactions sent by the sender; formally Tn.
458    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
459    pub nonce: u64,
460    /// A scalar value equal to the maximum
461    /// amount of gas that should be used in executing
462    /// this transaction. This is paid up-front, before any
463    /// computation is done and may not be increased
464    /// later; formally Tg.
465    #[cfg_attr(
466        feature = "serde",
467        serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")
468    )]
469    pub gas_limit: u64,
470    /// A scalar value equal to the maximum
471    /// amount of gas that should be used in executing
472    /// this transaction. This is paid up-front, before any
473    /// computation is done and may not be increased
474    /// later; formally Tg.
475    ///
476    /// As ethereum circulation is around 120mil eth as of 2022 that is around
477    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
478    /// 340282366920938463463374607431768211455
479    ///
480    /// This is also known as `GasFeeCap`
481    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
482    pub max_fee_per_gas: u128,
483    /// Max Priority fee that transaction is paying
484    ///
485    /// As ethereum circulation is around 120mil eth as of 2022 that is around
486    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
487    /// 340282366920938463463374607431768211455
488    ///
489    /// This is also known as `GasTipCap`
490    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
491    pub max_priority_fee_per_gas: u128,
492    /// The 160-bit address of the message call’s recipient.
493    pub to: Address,
494    /// A scalar value equal to the number of Wei to
495    /// be transferred to the message call’s recipient or,
496    /// in the case of contract creation, as an endowment
497    /// to the newly created account; formally Tv.
498    pub value: U256,
499    /// The accessList specifies a list of addresses and storage keys;
500    /// these addresses and storage keys are added into the `accessed_addresses`
501    /// and `accessed_storage_keys` global sets (introduced in EIP-2929).
502    /// A gas cost is charged, though at a discount relative to the cost of
503    /// accessing outside the list.
504    pub access_list: AccessList,
505
506    /// It contains a vector of fixed size hash(32 bytes)
507    pub blob_versioned_hashes: Vec<B256>,
508
509    /// Max fee per data gas
510    ///
511    /// aka BlobFeeCap or blobGasFeeCap
512    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
513    pub max_fee_per_blob_gas: u128,
514
515    /// Input has two uses depending if transaction is Create or Call (if `to` field is None or
516    /// Some). pub init: An unlimited size byte array specifying the
517    /// EVM-code for the account initialisation procedure CREATE,
518    /// data: An unlimited size byte array specifying the
519    /// input data of the message call, formally Td.
520    pub input: Bytes,
521}
522
523impl TxEip4844 {
524    /// Returns the total gas for all blobs in this transaction.
525    #[inline]
526    pub fn blob_gas(&self) -> u64 {
527        // SAFETY: we don't expect u64::MAX / DATA_GAS_PER_BLOB hashes in a single transaction
528        self.blob_versioned_hashes.len() as u64 * DATA_GAS_PER_BLOB
529    }
530
531    /// Verifies that the given blob data, commitments, and proofs are all valid for this
532    /// transaction.
533    ///
534    /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters
535    /// derived from the KZG trusted setup.
536    ///
537    /// This ensures that the blob transaction payload has the same number of blob data elements,
538    /// commitments, and proofs. Each blob data element is verified against its commitment and
539    /// proof.
540    ///
541    /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response
542    /// fails to verify, or if the versioned hashes in the transaction do not match the actual
543    /// commitment versioned hashes.
544    #[cfg(feature = "kzg")]
545    pub fn validate_blob<T: TxEip4844Sidecar>(
546        &self,
547        sidecar: &T,
548        proof_settings: &c_kzg::KzgSettings,
549    ) -> Result<(), BlobTransactionValidationError> {
550        sidecar.validate(&self.blob_versioned_hashes, proof_settings)
551    }
552
553    /// Get transaction type.
554    #[doc(alias = "transaction_type")]
555    pub const fn tx_type() -> TxType {
556        TxType::Eip4844
557    }
558
559    /// Attaches the blob sidecar to the transaction
560    pub fn with_sidecar(self, sidecar: BlobTransactionSidecar) -> TxEip4844WithSidecar {
561        TxEip4844WithSidecar::from_tx_and_sidecar(self, sidecar)
562    }
563
564    /// Calculates a heuristic for the in-memory size of the [TxEip4844Variant] transaction.
565    #[inline]
566    pub fn size(&self) -> usize {
567        mem::size_of::<ChainId>() + // chain_id
568        mem::size_of::<u64>() + // nonce
569        mem::size_of::<u64>() + // gas_limit
570        mem::size_of::<u128>() + // max_fee_per_gas
571        mem::size_of::<u128>() + // max_priority_fee_per_gas
572        mem::size_of::<Address>() + // to
573        mem::size_of::<U256>() + // value
574        self.access_list.size() + // access_list
575        self.input.len() +  // input
576        self.blob_versioned_hashes.capacity() * mem::size_of::<B256>() + // blob hashes size
577        mem::size_of::<u128>() // max_fee_per_data_gas
578    }
579}
580
581impl RlpEcdsaEncodableTx for TxEip4844 {
582    fn rlp_encoded_fields_length(&self) -> usize {
583        self.chain_id.length()
584            + self.nonce.length()
585            + self.gas_limit.length()
586            + self.max_fee_per_gas.length()
587            + self.max_priority_fee_per_gas.length()
588            + self.to.length()
589            + self.value.length()
590            + self.access_list.length()
591            + self.blob_versioned_hashes.length()
592            + self.max_fee_per_blob_gas.length()
593            + self.input.0.length()
594    }
595
596    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
597        self.chain_id.encode(out);
598        self.nonce.encode(out);
599        self.max_priority_fee_per_gas.encode(out);
600        self.max_fee_per_gas.encode(out);
601        self.gas_limit.encode(out);
602        self.to.encode(out);
603        self.value.encode(out);
604        self.input.0.encode(out);
605        self.access_list.encode(out);
606        self.max_fee_per_blob_gas.encode(out);
607        self.blob_versioned_hashes.encode(out);
608    }
609}
610
611impl RlpEcdsaDecodableTx for TxEip4844 {
612    const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
613
614    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
615        Ok(Self {
616            chain_id: Decodable::decode(buf)?,
617            nonce: Decodable::decode(buf)?,
618            max_priority_fee_per_gas: Decodable::decode(buf)?,
619            max_fee_per_gas: Decodable::decode(buf)?,
620            gas_limit: Decodable::decode(buf)?,
621            to: Decodable::decode(buf)?,
622            value: Decodable::decode(buf)?,
623            input: Decodable::decode(buf)?,
624            access_list: Decodable::decode(buf)?,
625            max_fee_per_blob_gas: Decodable::decode(buf)?,
626            blob_versioned_hashes: Decodable::decode(buf)?,
627        })
628    }
629}
630
631impl SignableTransaction<Signature> for TxEip4844 {
632    fn set_chain_id(&mut self, chain_id: ChainId) {
633        self.chain_id = chain_id;
634    }
635
636    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
637        out.put_u8(Self::tx_type() as u8);
638        self.encode(out);
639    }
640
641    fn payload_len_for_signature(&self) -> usize {
642        self.length() + 1
643    }
644}
645
646impl Transaction for TxEip4844 {
647    #[inline]
648    fn chain_id(&self) -> Option<ChainId> {
649        Some(self.chain_id)
650    }
651
652    #[inline]
653    fn nonce(&self) -> u64 {
654        self.nonce
655    }
656
657    #[inline]
658    fn gas_limit(&self) -> u64 {
659        self.gas_limit
660    }
661
662    #[inline]
663    fn gas_price(&self) -> Option<u128> {
664        None
665    }
666
667    #[inline]
668    fn max_fee_per_gas(&self) -> u128 {
669        self.max_fee_per_gas
670    }
671
672    #[inline]
673    fn max_priority_fee_per_gas(&self) -> Option<u128> {
674        Some(self.max_priority_fee_per_gas)
675    }
676
677    #[inline]
678    fn max_fee_per_blob_gas(&self) -> Option<u128> {
679        Some(self.max_fee_per_blob_gas)
680    }
681
682    #[inline]
683    fn priority_fee_or_price(&self) -> u128 {
684        self.max_priority_fee_per_gas
685    }
686
687    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
688        base_fee.map_or(self.max_fee_per_gas, |base_fee| {
689            // if the tip is greater than the max priority fee per gas, set it to the max
690            // priority fee per gas + base fee
691            let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
692            if tip > self.max_priority_fee_per_gas {
693                self.max_priority_fee_per_gas + base_fee as u128
694            } else {
695                // otherwise return the max fee per gas
696                self.max_fee_per_gas
697            }
698        })
699    }
700
701    #[inline]
702    fn is_dynamic_fee(&self) -> bool {
703        true
704    }
705
706    #[inline]
707    fn kind(&self) -> TxKind {
708        self.to.into()
709    }
710
711    #[inline]
712    fn is_create(&self) -> bool {
713        false
714    }
715
716    #[inline]
717    fn value(&self) -> U256 {
718        self.value
719    }
720
721    #[inline]
722    fn input(&self) -> &Bytes {
723        &self.input
724    }
725
726    #[inline]
727    fn access_list(&self) -> Option<&AccessList> {
728        Some(&self.access_list)
729    }
730
731    #[inline]
732    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
733        Some(&self.blob_versioned_hashes)
734    }
735
736    #[inline]
737    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
738        None
739    }
740}
741
742impl Encodable for TxEip4844 {
743    fn encode(&self, out: &mut dyn BufMut) {
744        self.rlp_encode(out);
745    }
746
747    fn length(&self) -> usize {
748        self.rlp_encoded_length()
749    }
750}
751
752impl Decodable for TxEip4844 {
753    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
754        Self::rlp_decode(buf)
755    }
756}
757
758impl From<TxEip4844WithSidecar> for TxEip4844 {
759    /// Consumes the [TxEip4844WithSidecar] and returns the inner [TxEip4844].
760    fn from(tx_with_sidecar: TxEip4844WithSidecar) -> Self {
761        tx_with_sidecar.tx
762    }
763}
764
765/// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction)
766///
767/// A transaction with blob hashes and max blob fee, which also includes the
768/// [BlobTransactionSidecar]. This is the full type sent over the network as a raw transaction. It
769/// wraps a [TxEip4844] to include the sidecar and the ability to decode it properly.
770///
771/// This is defined in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#networking) as an element
772/// of a `PooledTransactions` response, and is also used as the format for sending raw transactions
773/// through the network (eth_sendRawTransaction/eth_sendTransaction).
774#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
775#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
776#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
777#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
778#[doc(alias = "Eip4844TransactionWithSidecar", alias = "Eip4844TxWithSidecar")]
779pub struct TxEip4844WithSidecar<T = BlobTransactionSidecar> {
780    /// The actual transaction.
781    #[cfg_attr(feature = "serde", serde(flatten))]
782    #[doc(alias = "transaction")]
783    pub tx: TxEip4844,
784    /// The sidecar.
785    #[cfg_attr(feature = "serde", serde(flatten))]
786    pub sidecar: T,
787}
788
789impl<T> TxEip4844WithSidecar<T> {
790    /// Constructs a new [TxEip4844WithSidecar] from a [TxEip4844] and a sidecar.
791    #[doc(alias = "from_transaction_and_sidecar")]
792    pub const fn from_tx_and_sidecar(tx: TxEip4844, sidecar: T) -> Self {
793        Self { tx, sidecar }
794    }
795
796    /// Get the transaction type.
797    #[doc(alias = "transaction_type")]
798    pub const fn tx_type() -> TxType {
799        TxEip4844::tx_type()
800    }
801
802    /// Get access to the inner tx [TxEip4844].
803    #[doc(alias = "transaction")]
804    pub const fn tx(&self) -> &TxEip4844 {
805        &self.tx
806    }
807
808    /// Get access to the inner sidecar.
809    pub const fn sidecar(&self) -> &T {
810        &self.sidecar
811    }
812
813    /// Consumes the [TxEip4844WithSidecar] and returns the inner sidecar.
814    pub fn into_sidecar(self) -> T {
815        self.sidecar
816    }
817
818    /// Consumes the [TxEip4844WithSidecar] and returns the inner [TxEip4844] and a sidecar.
819    pub fn into_parts(self) -> (TxEip4844, T) {
820        (self.tx, self.sidecar)
821    }
822}
823
824impl<T: TxEip4844Sidecar> TxEip4844WithSidecar<T> {
825    /// Verifies that the transaction's blob data, commitments, and proofs are all valid.
826    ///
827    /// See also [TxEip4844::validate_blob]
828    #[cfg(feature = "kzg")]
829    pub fn validate_blob(
830        &self,
831        proof_settings: &c_kzg::KzgSettings,
832    ) -> Result<(), BlobTransactionValidationError> {
833        self.tx.validate_blob(&self.sidecar, proof_settings)
834    }
835
836    /// Calculates a heuristic for the in-memory size of the [TxEip4844WithSidecar] transaction.
837    #[inline]
838    pub fn size(&self) -> usize {
839        self.tx.size() + self.sidecar.size()
840    }
841}
842
843impl SignableTransaction<Signature> for TxEip4844WithSidecar {
844    fn set_chain_id(&mut self, chain_id: ChainId) {
845        self.tx.chain_id = chain_id;
846    }
847
848    fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) {
849        // A signature for a [TxEip4844WithSidecar] is a signature over the [TxEip4844] EIP-2718
850        // payload fields:
851        // (BLOB_TX_TYPE ||
852        //   rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value,
853        //     data, access_list, max_fee_per_blob_gas, blob_versioned_hashes]))
854        self.tx.encode_for_signing(out);
855    }
856
857    fn payload_len_for_signature(&self) -> usize {
858        // The payload length is the length of the `transaction_payload_body` list.
859        // The sidecar is NOT included.
860        self.tx.payload_len_for_signature()
861    }
862}
863
864impl Transaction for TxEip4844WithSidecar {
865    #[inline]
866    fn chain_id(&self) -> Option<ChainId> {
867        self.tx.chain_id()
868    }
869
870    #[inline]
871    fn nonce(&self) -> u64 {
872        self.tx.nonce()
873    }
874
875    #[inline]
876    fn gas_limit(&self) -> u64 {
877        self.tx.gas_limit()
878    }
879
880    #[inline]
881    fn gas_price(&self) -> Option<u128> {
882        self.tx.gas_price()
883    }
884
885    #[inline]
886    fn max_fee_per_gas(&self) -> u128 {
887        self.tx.max_fee_per_gas()
888    }
889
890    #[inline]
891    fn max_priority_fee_per_gas(&self) -> Option<u128> {
892        self.tx.max_priority_fee_per_gas()
893    }
894
895    #[inline]
896    fn max_fee_per_blob_gas(&self) -> Option<u128> {
897        self.tx.max_fee_per_blob_gas()
898    }
899
900    #[inline]
901    fn priority_fee_or_price(&self) -> u128 {
902        self.tx.priority_fee_or_price()
903    }
904
905    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
906        self.tx.effective_gas_price(base_fee)
907    }
908
909    #[inline]
910    fn is_dynamic_fee(&self) -> bool {
911        self.tx.is_dynamic_fee()
912    }
913
914    #[inline]
915    fn kind(&self) -> TxKind {
916        self.tx.kind()
917    }
918
919    #[inline]
920    fn is_create(&self) -> bool {
921        false
922    }
923
924    #[inline]
925    fn value(&self) -> U256 {
926        self.tx.value()
927    }
928
929    #[inline]
930    fn input(&self) -> &Bytes {
931        self.tx.input()
932    }
933
934    #[inline]
935    fn access_list(&self) -> Option<&AccessList> {
936        Some(&self.tx.access_list)
937    }
938
939    #[inline]
940    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
941        self.tx.blob_versioned_hashes()
942    }
943
944    #[inline]
945    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
946        None
947    }
948}
949
950impl Typed2718 for TxEip4844WithSidecar {
951    fn ty(&self) -> u8 {
952        TxType::Eip4844 as u8
953    }
954}
955
956impl RlpEcdsaEncodableTx for TxEip4844WithSidecar {
957    fn rlp_encoded_fields_length(&self) -> usize {
958        self.sidecar.rlp_encoded_fields_length() + self.tx.rlp_encoded_length()
959    }
960
961    fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
962        self.tx.rlp_encode(out);
963        self.sidecar.rlp_encode_fields(out);
964    }
965
966    fn rlp_header_signed(&self, signature: &Signature) -> Header {
967        let payload_length = self.tx.rlp_encoded_length_with_signature(signature)
968            + self.sidecar.rlp_encoded_fields_length();
969        Header { list: true, payload_length }
970    }
971
972    fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) {
973        self.rlp_header_signed(signature).encode(out);
974        self.tx.rlp_encode_signed(signature, out);
975        self.sidecar.rlp_encode_fields(out);
976    }
977
978    fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash {
979        // eip4844 tx_hash is always based on the non-sidecar encoding
980        self.tx.tx_hash_with_type(signature, ty)
981    }
982}
983
984impl RlpEcdsaDecodableTx for TxEip4844WithSidecar {
985    const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 };
986
987    fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
988        let tx = TxEip4844::rlp_decode(buf)?;
989        let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
990        Ok(Self { tx, sidecar })
991    }
992
993    fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> {
994        let header = Header::decode(buf)?;
995        if !header.list {
996            return Err(alloy_rlp::Error::UnexpectedString);
997        }
998        let remaining = buf.len();
999
1000        let (tx, signature) = TxEip4844::rlp_decode_with_signature(buf)?;
1001        let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?;
1002
1003        if buf.len() + header.payload_length != remaining {
1004            return Err(alloy_rlp::Error::UnexpectedLength);
1005        }
1006
1007        Ok((Self { tx, sidecar }, signature))
1008    }
1009}
1010
1011#[cfg(test)]
1012mod tests {
1013    use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
1014    use crate::{transaction::eip4844::TxEip4844Variant, SignableTransaction, TxEnvelope};
1015    use alloy_eips::eip2930::AccessList;
1016    use alloy_primitives::{address, b256, bytes, Signature, U256};
1017    use alloy_rlp::{Decodable, Encodable};
1018
1019    #[test]
1020    fn different_sidecar_same_hash() {
1021        // this should make sure that the hash calculated for the `into_signed` conversion does not
1022        // change if the sidecar is different
1023        let tx = TxEip4844 {
1024            chain_id: 1,
1025            nonce: 1,
1026            max_priority_fee_per_gas: 1,
1027            max_fee_per_gas: 1,
1028            gas_limit: 1,
1029            to: Default::default(),
1030            value: U256::from(1),
1031            access_list: Default::default(),
1032            blob_versioned_hashes: vec![Default::default()],
1033            max_fee_per_blob_gas: 1,
1034            input: Default::default(),
1035        };
1036        let sidecar = BlobTransactionSidecar {
1037            blobs: vec![[2; 131072].into()],
1038            commitments: vec![[3; 48].into()],
1039            proofs: vec![[4; 48].into()],
1040        };
1041        let mut tx = TxEip4844WithSidecar { tx, sidecar };
1042        let signature = Signature::test_signature();
1043
1044        // turn this transaction into_signed
1045        let expected_signed = tx.clone().into_signed(signature);
1046
1047        // change the sidecar, adding a single (blob, commitment, proof) pair
1048        tx.sidecar = BlobTransactionSidecar {
1049            blobs: vec![[1; 131072].into()],
1050            commitments: vec![[1; 48].into()],
1051            proofs: vec![[1; 48].into()],
1052        };
1053
1054        // turn this transaction into_signed
1055        let actual_signed = tx.into_signed(signature);
1056
1057        // the hashes should be the same
1058        assert_eq!(expected_signed.hash(), actual_signed.hash());
1059
1060        // convert to envelopes
1061        let expected_envelope: TxEnvelope = expected_signed.into();
1062        let actual_envelope: TxEnvelope = actual_signed.into();
1063
1064        // now encode the transaction and check the length
1065        let len = expected_envelope.length();
1066        let mut buf = Vec::with_capacity(len);
1067        expected_envelope.encode(&mut buf);
1068        assert_eq!(buf.len(), len);
1069
1070        // ensure it's also the same size that `actual` claims to be, since we just changed the
1071        // sidecar values.
1072        assert_eq!(buf.len(), actual_envelope.length());
1073
1074        // now decode the transaction and check the values
1075        let decoded = TxEnvelope::decode(&mut &buf[..]).unwrap();
1076        assert_eq!(decoded, expected_envelope);
1077    }
1078
1079    #[test]
1080    fn test_4844_variant_into_signed_correct_hash() {
1081        // Taken from <https://etherscan.io/tx/0x93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87>
1082        let tx =
1083            TxEip4844 {
1084                chain_id: 1,
1085                nonce: 15435,
1086                gas_limit: 8000000,
1087                max_fee_per_gas: 10571233596,
1088                max_priority_fee_per_gas: 1000000000,
1089                to: address!("a8cb082a5a689e0d594d7da1e2d72a3d63adc1bd"),
1090                value: U256::ZERO,
1091                access_list: AccessList::default(),
1092                blob_versioned_hashes: vec![
1093                    b256!("01e5276d91ac1ddb3b1c2d61295211220036e9a04be24c00f76916cc2659d004"),
1094                    b256!("0128eb58aff09fd3a7957cd80aa86186d5849569997cdfcfa23772811b706cc2"),
1095                ],
1096                max_fee_per_blob_gas: 1,
1097                input: bytes!("701f58c50000000000000000000000000000000000000000000000000000000000073fb1ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000000000000000000000000000000000000000000000000000000123971da000000000000000000000000000000000000000000000000000000000000000ac39b2a24e1dbdd11a1e7bd7c0f4dfd7d9b9cfa0997d033ad05f961ba3b82c6c83312c967f10daf5ed2bffe309249416e03ee0b101f2b84d2102b9e38b0e4dfdf0000000000000000000000000000000000000000000000000000000066254c8b538dcc33ecf5334bbd294469f9d4fd084a3090693599a46d6c62567747cbc8660000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000073fb20000000000000000000000000000000000000000000000000000000066254da10000000000000000000000000000000000000000000000000000000012397d5e20b09b263779fda4171c341e720af8fa469621ff548651f8dbbc06c2d320400c000000000000000000000000000000000000000000000000000000000000000b50a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d8fc3c411b99159939ac1c16d21d3057ddc8b2333d1331ab34c938cff0eb29ce2e43241c170344db6819f76b1f1e0ab8206f3ec34120312d275c4f5bbea7f5c55700000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000031800000000000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000004ed12e288def5b439ea074b398dbb4c967f2852baac3238c5fe4b62b871a59a6d00000ca8000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000300000000000000000000000066254da100000000000000000000000066254e9d00010ca80000000000000000000000000000000000008001000000000000000000000000000000000000000000000000000000000000000550a833bb11af92814e99c6ff7cf7ba7042827549d6f306a04270753702d897d800010ca800000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000b00010ca8000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000075c1cd5bd0fd333ce9d7c8edfc79f43b8f345b4a394f6aba12a2cc78ce4012ed700010ca80000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000845392775318aa47beaafbdc827da38c9f1e88c3bdcabba2cb493062e17cbf21e00010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000c094e20e7ac9b433f44a5885e3bdc07e51b309aeb993caa24ba84a661ac010c100010ca800000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000001ab42db8f4ed810bdb143368a2b641edf242af6e3d0de8b1486e2b0e7880d431100010ca8000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000022d94e4cc4525e4e2d81e8227b6172e97076431a2cf98792d978035edd6e6f3100000000000000000000000000000000000000000000000000000000000000000000000000000012101c74dfb80a80fccb9a4022b2406f79f56305e6a7c931d30140f5d372fe793837e93f9ec6b8d89a9d0ab222eeb27547f66b90ec40fbbdd2a4936b0b0c19ca684ff78888fbf5840d7c8dc3c493b139471750938d7d2c443e2d283e6c5ee9fde3765a756542c42f002af45c362b4b5b1687a8fc24cbf16532b903f7bb289728170dcf597f5255508c623ba247735538376f494cdcdd5bd0c4cb067526eeda0f4745a28d8baf8893ecc1b8cee80690538d66455294a028da03ff2add9d8a88e6ee03ba9ffe3ad7d91d6ac9c69a1f28c468f00fe55eba5651a2b32dc2458e0d14b4dd6d0173df255cd56aa01e8e38edec17ea8933f68543cbdc713279d195551d4211bed5c91f77259a695e6768f6c4b110b2158fcc42423a96dcc4e7f6fddb3e2369d00000000000000000000000000000000000000000000000000000000000000") };
1098        let variant = TxEip4844Variant::TxEip4844(tx);
1099
1100        let signature = Signature::new(
1101            b256!("6c173c3c8db3e3299f2f728d293b912c12e75243e3aa66911c2329b58434e2a4").into(),
1102            b256!("7dd4d1c228cedc5a414a668ab165d9e888e61e4c3b44cd7daf9cdcc4cec5d6b2").into(),
1103            false,
1104        );
1105
1106        let signed = variant.into_signed(signature);
1107        assert_eq!(
1108            *signed.hash(),
1109            b256!("93fc9daaa0726c3292a2e939df60f7e773c6a6a726a61ce43f4a217c64d85e87")
1110        );
1111    }
1112}