alloy_network/any/
mod.rs

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