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