Skip to main content

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