alloy_consensus/transaction/
eip4844.rs

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