alloy_network/any/
mod.rs

1mod builder;
2mod either;
3
4pub mod error;
5
6use alloy_consensus::{
7    Sealed, Signed, TxEip1559, TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope, TxLegacy,
8};
9use alloy_eips::{eip7702::SignedAuthorization, Typed2718};
10use alloy_primitives::{Bytes, ChainId, TxKind, B256, U256};
11pub use either::{AnyTxEnvelope, AnyTypedTransaction};
12use std::error::Error;
13
14mod unknowns;
15pub use unknowns::{AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction};
16
17pub use alloy_consensus_any::{AnyHeader, AnyReceiptEnvelope};
18
19use crate::{any::error::AnyConversionError, Network};
20use alloy_consensus::{
21    error::ValueError,
22    transaction::{Either, Recovered},
23};
24use alloy_network_primitives::{BlockResponse, TransactionResponse};
25pub use alloy_rpc_types_any::{AnyRpcHeader, AnyTransactionReceipt};
26use alloy_rpc_types_eth::{AccessList, Block, BlockTransactions, Transaction, TransactionRequest};
27use alloy_serde::WithOtherFields;
28use derive_more::From;
29use serde::{Deserialize, Serialize};
30use std::ops::{Deref, DerefMut};
31
32/// Types for a catch-all network.
33///
34/// `AnyNetwork`'s associated types allow for many different types of
35/// transactions, using catch-all fields. This [`Network`] should be used
36/// only when the application needs to support multiple networks via the same
37/// codepaths without knowing the networks at compile time.
38///
39/// ## Rough Edges
40///
41/// Supporting arbitrary unknown types is hard, and users of this network
42/// should be aware of the following:
43///
44/// - The implementation of [`Decodable2718`] for [`AnyTxEnvelope`] will not work for non-Ethereum
45///   transaction types. It will successfully decode an Ethereum [`TxEnvelope`], but will decode
46///   only the type for any unknown transaction type. It will also leave the buffer unconsumed,
47///   which will cause further deserialization to produce erroneous results.
48/// - The implementation of [`Encodable2718`] for [`AnyTypedTransaction`] will not work for
49///   non-Ethereum transaction types. It will encode the type for any unknown transaction type, but
50///   will not encode any other fields. This is symmetric with the decoding behavior, but still
51///   erroneous.
52/// - The [`TransactionRequest`] will build ONLY Ethereum types. It will error when attempting to
53///   build any unknown type.
54/// - The [`Network::TransactionResponse`] may deserialize unknown metadata fields into the inner
55///   [`AnyTxEnvelope`], rather than into the outer [`WithOtherFields`].
56///
57/// [`Decodable2718`]: alloy_eips::eip2718::Decodable2718
58/// [`Encodable2718`]: alloy_eips::eip2718::Encodable2718
59/// [`TxEnvelope`]: alloy_consensus::TxEnvelope
60#[derive(Clone, Copy, Debug)]
61pub struct AnyNetwork {
62    _private: (),
63}
64
65impl Network for AnyNetwork {
66    type TxType = AnyTxType;
67
68    type TxEnvelope = AnyTxEnvelope;
69
70    type UnsignedTx = AnyTypedTransaction;
71
72    type ReceiptEnvelope = AnyReceiptEnvelope;
73
74    type Header = AnyHeader;
75
76    type TransactionRequest = WithOtherFields<TransactionRequest>;
77
78    type TransactionResponse = AnyRpcTransaction;
79
80    type ReceiptResponse = AnyTransactionReceipt;
81
82    type HeaderResponse = AnyRpcHeader;
83
84    type BlockResponse = AnyRpcBlock;
85}
86
87/// A wrapper for [`AnyRpcBlock`] that allows for handling unknown block types.
88///
89/// This type wraps:
90///  - rpc transaction
91///  - additional fields
92#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
93pub struct AnyRpcBlock(pub WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>);
94
95impl AnyRpcBlock {
96    /// Create a new [`AnyRpcBlock`].
97    pub const fn new(inner: WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>) -> Self {
98        Self(inner)
99    }
100
101    /// Consumes the type and returns the wrapped rpc block.
102    pub fn into_inner(self) -> Block<AnyRpcTransaction, AnyRpcHeader> {
103        self.0.into_inner()
104    }
105
106    /// Attempts to convert the inner RPC [`Block`] into a consensus block.
107    ///
108    /// Returns an [`AnyConversionError`] if any of the conversions fail.
109    pub fn try_into_consensus<T, H>(
110        self,
111    ) -> Result<alloy_consensus::Block<T, H>, AnyConversionError>
112    where
113        T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
114        H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
115    {
116        self.into_inner()
117            .map_header(|h| h.into_consensus())
118            .try_convert_header()
119            .map_err(AnyConversionError::new)?
120            .try_convert_transactions()
121            .map_err(AnyConversionError::new)
122            .map(Block::into_consensus_block)
123    }
124
125    /// Attempts to convert the inner RPC [`Block`] into a sealed consensus block.
126    ///
127    /// Uses the block hash from the RPC header to seal the block.
128    ///
129    /// Returns an [`AnyConversionError`] if any of the conversions fail.
130    pub fn try_into_sealed<T, H>(
131        self,
132    ) -> Result<Sealed<alloy_consensus::Block<T, H>>, AnyConversionError>
133    where
134        T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
135        H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
136    {
137        let block_hash = self.header.hash;
138        let block = self.try_into_consensus()?;
139        Ok(Sealed::new_unchecked(block, block_hash))
140    }
141
142    /// Tries to convert inner transactions into a vector of [`AnyRpcTransaction`].
143    ///
144    /// Returns an error if the block contains only transaction hashes or if it is an uncle block.
145    pub fn try_into_transactions(
146        self,
147    ) -> Result<Vec<AnyRpcTransaction>, ValueError<BlockTransactions<AnyRpcTransaction>>> {
148        self.0.inner.try_into_transactions()
149    }
150
151    /// Consumes the type and returns an iterator over the transactions in this block
152    pub fn into_transactions_iter(self) -> impl Iterator<Item = AnyRpcTransaction> {
153        self.into_inner().transactions.into_transactions()
154    }
155}
156
157impl BlockResponse for AnyRpcBlock {
158    type Header = AnyRpcHeader;
159    type Transaction = AnyRpcTransaction;
160
161    fn header(&self) -> &Self::Header {
162        &self.0.inner.header
163    }
164
165    fn transactions(&self) -> &BlockTransactions<Self::Transaction> {
166        &self.0.inner.transactions
167    }
168
169    fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
170        &mut self.0.inner.transactions
171    }
172
173    fn other_fields(&self) -> Option<&alloy_serde::OtherFields> {
174        self.0.other_fields()
175    }
176}
177
178impl AsRef<WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>> for AnyRpcBlock {
179    fn as_ref(&self) -> &WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
180        &self.0
181    }
182}
183
184impl Deref for AnyRpcBlock {
185    type Target = WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>>;
186
187    fn deref(&self) -> &Self::Target {
188        &self.0
189    }
190}
191
192impl DerefMut for AnyRpcBlock {
193    fn deref_mut(&mut self) -> &mut Self::Target {
194        &mut self.0
195    }
196}
197
198impl From<Block> for AnyRpcBlock {
199    fn from(value: Block) -> Self {
200        let block = value.map_header(|h| h.map(|h| h.into())).map_transactions(|tx| {
201            AnyRpcTransaction::new(WithOtherFields::new(tx.map(AnyTxEnvelope::Ethereum)))
202        });
203
204        Self(WithOtherFields::new(block))
205    }
206}
207
208impl From<AnyRpcBlock> for Block<AnyRpcTransaction, AnyRpcHeader> {
209    fn from(value: AnyRpcBlock) -> Self {
210        value.into_inner()
211    }
212}
213impl From<AnyRpcBlock> for WithOtherFields<Block<AnyRpcTransaction, AnyRpcHeader>> {
214    fn from(value: AnyRpcBlock) -> Self {
215        value.0
216    }
217}
218
219impl<T, H> TryFrom<AnyRpcBlock> for alloy_consensus::Block<T, H>
220where
221    T: TryFrom<AnyRpcTransaction, Error: Error + Send + Sync + 'static>,
222    H: TryFrom<AnyHeader, Error: Error + Send + Sync + 'static>,
223{
224    type Error = AnyConversionError;
225
226    fn try_from(value: AnyRpcBlock) -> Result<Self, Self::Error> {
227        value.try_into_consensus()
228    }
229}
230
231/// A wrapper for [`AnyRpcTransaction`] that allows for handling unknown transaction types.
232#[derive(Clone, Debug, From, PartialEq, Eq, Deserialize, Serialize)]
233pub struct AnyRpcTransaction(pub WithOtherFields<Transaction<AnyTxEnvelope>>);
234
235impl AnyRpcTransaction {
236    /// Create a new [`AnyRpcTransaction`].
237    pub const fn new(inner: WithOtherFields<Transaction<AnyTxEnvelope>>) -> Self {
238        Self(inner)
239    }
240
241    /// Split the transaction into its parts.
242    pub fn into_parts(self) -> (Transaction<AnyTxEnvelope>, alloy_serde::OtherFields) {
243        let WithOtherFields { inner, other } = self.0;
244        (inner, other)
245    }
246
247    /// Consumes the outer layer for this transaction and returns the inner transaction.
248    pub fn into_inner(self) -> Transaction<AnyTxEnvelope> {
249        self.0.into_inner()
250    }
251
252    /// Returns the inner transaction [`TxEnvelope`] if inner tx type if
253    /// [`AnyTxEnvelope::Ethereum`].
254    pub fn as_envelope(&self) -> Option<&TxEnvelope> {
255        self.inner.inner.as_envelope()
256    }
257
258    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
259    /// If the transaction is not an Ethereum transaction, it is returned as an error.
260    pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<AnyTxEnvelope>> {
261        self.0.inner.inner.into_inner().try_into_envelope()
262    }
263
264    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
265    pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
266        self.0.inner().inner.as_legacy()
267    }
268
269    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
270    pub fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
271        self.0.inner().inner.as_eip2930()
272    }
273
274    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
275    pub fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
276        self.0.inner().inner.as_eip1559()
277    }
278
279    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
280    pub fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
281        self.0.inner().inner.as_eip4844()
282    }
283
284    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
285    pub fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
286        self.0.inner().inner.as_eip7702()
287    }
288
289    /// Returns true if the transaction is a legacy transaction.
290    #[inline]
291    pub fn is_legacy(&self) -> bool {
292        self.0.inner().inner.is_legacy()
293    }
294
295    /// Returns true if the transaction is an EIP-2930 transaction.
296    #[inline]
297    pub fn is_eip2930(&self) -> bool {
298        self.0.inner().inner.is_eip2930()
299    }
300
301    /// Returns true if the transaction is an EIP-1559 transaction.
302    #[inline]
303    pub fn is_eip1559(&self) -> bool {
304        self.0.inner().inner.is_eip1559()
305    }
306
307    /// Returns true if the transaction is an EIP-4844 transaction.
308    #[inline]
309    pub fn is_eip4844(&self) -> bool {
310        self.0.inner().inner.is_eip4844()
311    }
312
313    /// Returns true if the transaction is an EIP-7702 transaction.
314    #[inline]
315    pub fn is_eip7702(&self) -> bool {
316        self.0.inner().inner.is_eip7702()
317    }
318
319    /// Attempts to convert the [`AnyRpcTransaction`] into `Either::Right` if this is an unknown
320    /// variant.
321    ///
322    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
323    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
324    pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
325    where
326        T: TryFrom<Self>,
327    {
328        if self.0.inner.inner.inner().is_ethereum() {
329            Ok(Either::Left(self.0.inner.inner.into_inner().try_into_envelope().unwrap()))
330        } else {
331            T::try_from(self).map(Either::Right)
332        }
333    }
334
335    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an unknown
336    /// variant.
337    ///
338    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
339    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
340    pub fn try_unknown_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
341    where
342        T: TryFrom<UnknownTxEnvelope>,
343    {
344        self.0.inner.inner.into_inner().try_into_either()
345    }
346
347    /// Applies the given closure to the inner transaction type.
348    ///
349    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
350    /// Applies the given closure to the inner transaction type.
351    pub fn map<Tx>(self, f: impl FnOnce(AnyTxEnvelope) -> Tx) -> Transaction<Tx> {
352        self.into_inner().map(f)
353    }
354
355    /// Applies the given fallible closure to the inner transactions.
356    ///
357    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
358    pub fn try_map<Tx, E>(
359        self,
360        f: impl FnOnce(AnyTxEnvelope) -> Result<Tx, E>,
361    ) -> Result<Transaction<Tx>, E> {
362        self.into_inner().try_map(f)
363    }
364
365    /// Converts the transaction type to the given alternative that is `From<T>`.
366    ///
367    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
368    pub fn convert<U>(self) -> Transaction<U>
369    where
370        U: From<AnyTxEnvelope>,
371    {
372        self.into_inner().map(U::from)
373    }
374
375    /// Converts the transaction to the given alternative that is `TryFrom<T>`
376    ///
377    /// Returns the transaction with the new transaction type if all conversions were successful.
378    ///
379    /// [`alloy_serde::OtherFields`] are stripped away while mapping.
380    pub fn try_convert<U>(self) -> Result<Transaction<U>, U::Error>
381    where
382        U: TryFrom<AnyTxEnvelope>,
383    {
384        self.into_inner().try_map(U::try_from)
385    }
386}
387
388impl AsRef<AnyTxEnvelope> for AnyRpcTransaction {
389    fn as_ref(&self) -> &AnyTxEnvelope {
390        &self.0.inner.inner
391    }
392}
393
394impl Deref for AnyRpcTransaction {
395    type Target = WithOtherFields<Transaction<AnyTxEnvelope>>;
396
397    fn deref(&self) -> &Self::Target {
398        &self.0
399    }
400}
401
402impl DerefMut for AnyRpcTransaction {
403    fn deref_mut(&mut self) -> &mut Self::Target {
404        &mut self.0
405    }
406}
407
408impl From<Transaction<TxEnvelope>> for AnyRpcTransaction {
409    fn from(tx: Transaction<TxEnvelope>) -> Self {
410        let tx = tx.map(AnyTxEnvelope::Ethereum);
411        Self(WithOtherFields::new(tx))
412    }
413}
414
415impl From<AnyRpcTransaction> for AnyTxEnvelope {
416    fn from(tx: AnyRpcTransaction) -> Self {
417        tx.0.inner.into_inner()
418    }
419}
420
421impl From<AnyRpcTransaction> for Transaction<AnyTxEnvelope> {
422    fn from(tx: AnyRpcTransaction) -> Self {
423        tx.0.inner
424    }
425}
426
427impl From<AnyRpcTransaction> for WithOtherFields<Transaction<AnyTxEnvelope>> {
428    fn from(tx: AnyRpcTransaction) -> Self {
429        tx.0
430    }
431}
432
433impl From<AnyRpcTransaction> for Recovered<AnyTxEnvelope> {
434    fn from(tx: AnyRpcTransaction) -> Self {
435        tx.0.inner.inner
436    }
437}
438
439impl TryFrom<AnyRpcTransaction> for TxEnvelope {
440    type Error = ValueError<AnyTxEnvelope>;
441
442    fn try_from(value: AnyRpcTransaction) -> Result<Self, Self::Error> {
443        value.try_into_envelope()
444    }
445}
446
447impl alloy_consensus::Transaction for AnyRpcTransaction {
448    fn chain_id(&self) -> Option<ChainId> {
449        self.inner.chain_id()
450    }
451
452    fn nonce(&self) -> u64 {
453        self.inner.nonce()
454    }
455
456    fn gas_limit(&self) -> u64 {
457        self.inner.gas_limit()
458    }
459
460    fn gas_price(&self) -> Option<u128> {
461        alloy_consensus::Transaction::gas_price(&self.0.inner)
462    }
463
464    fn max_fee_per_gas(&self) -> u128 {
465        alloy_consensus::Transaction::max_fee_per_gas(&self.inner)
466    }
467
468    fn max_priority_fee_per_gas(&self) -> Option<u128> {
469        self.inner.max_priority_fee_per_gas()
470    }
471
472    fn max_fee_per_blob_gas(&self) -> Option<u128> {
473        self.inner.max_fee_per_blob_gas()
474    }
475
476    fn priority_fee_or_price(&self) -> u128 {
477        self.inner.priority_fee_or_price()
478    }
479
480    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
481        self.inner.effective_gas_price(base_fee)
482    }
483
484    fn is_dynamic_fee(&self) -> bool {
485        self.inner.is_dynamic_fee()
486    }
487
488    fn kind(&self) -> TxKind {
489        self.inner.kind()
490    }
491
492    fn is_create(&self) -> bool {
493        self.inner.is_create()
494    }
495
496    fn value(&self) -> U256 {
497        self.inner.value()
498    }
499
500    fn input(&self) -> &Bytes {
501        self.inner.input()
502    }
503
504    fn access_list(&self) -> Option<&AccessList> {
505        self.inner.access_list()
506    }
507
508    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
509        self.inner.blob_versioned_hashes()
510    }
511
512    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
513        self.inner.authorization_list()
514    }
515}
516
517impl TransactionResponse for AnyRpcTransaction {
518    fn tx_hash(&self) -> alloy_primitives::TxHash {
519        self.inner.tx_hash()
520    }
521
522    fn block_hash(&self) -> Option<alloy_primitives::BlockHash> {
523        self.0.inner.block_hash
524    }
525
526    fn block_number(&self) -> Option<u64> {
527        self.inner.block_number
528    }
529
530    fn transaction_index(&self) -> Option<u64> {
531        self.inner.transaction_index
532    }
533
534    fn from(&self) -> alloy_primitives::Address {
535        self.inner.from()
536    }
537
538    fn gas_price(&self) -> Option<u128> {
539        self.inner.effective_gas_price
540    }
541}
542
543impl Typed2718 for AnyRpcTransaction {
544    fn ty(&self) -> u8 {
545        self.inner.ty()
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552    use alloy_primitives::B64;
553
554    #[test]
555    fn convert_any_block() {
556        let block = AnyRpcBlock::new(
557            Block::new(
558                AnyRpcHeader::from_sealed(
559                    AnyHeader {
560                        nonce: Some(B64::ZERO),
561                        mix_hash: Some(B256::ZERO),
562                        ..Default::default()
563                    }
564                    .seal(B256::ZERO),
565                ),
566                BlockTransactions::Full(vec![]),
567            )
568            .into(),
569        );
570
571        let _block: alloy_consensus::Block<TxEnvelope, alloy_consensus::Header> =
572            block.try_into().unwrap();
573    }
574}