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