reth_primitives/
alloy_compat.rs

1//! Common conversions from alloy types.
2
3use crate::{Block, BlockBody, Transaction, TransactionSigned};
4use alloc::{string::ToString, vec::Vec};
5use alloy_consensus::{constants::EMPTY_TRANSACTIONS, Header, TxEnvelope};
6use alloy_network::{AnyHeader, AnyRpcBlock, AnyRpcTransaction, AnyTxEnvelope};
7use alloy_serde::WithOtherFields;
8#[cfg(feature = "optimism")]
9use op_alloy_rpc_types as _;
10
11impl TryFrom<AnyRpcBlock> for Block {
12    type Error = alloy_rpc_types::ConversionError;
13
14    fn try_from(block: AnyRpcBlock) -> Result<Self, Self::Error> {
15        use alloy_rpc_types::ConversionError;
16
17        let block = block.inner;
18
19        let transactions = {
20            let transactions: Result<Vec<TransactionSigned>, ConversionError> = match block
21                .transactions
22            {
23                alloy_rpc_types::BlockTransactions::Full(transactions) => {
24                    transactions.into_iter().map(|tx| tx.try_into()).collect()
25                }
26                alloy_rpc_types::BlockTransactions::Hashes(_) |
27                alloy_rpc_types::BlockTransactions::Uncle => {
28                    // alloy deserializes empty blocks into `BlockTransactions::Hashes`, if the tx
29                    // root is the empty root then we can just return an empty vec.
30                    if block.header.transactions_root == EMPTY_TRANSACTIONS {
31                        Ok(Vec::new())
32                    } else {
33                        Err(ConversionError::Custom("missing transactions".to_string()))
34                    }
35                }
36            };
37            transactions?
38        };
39
40        let AnyHeader {
41            parent_hash,
42            ommers_hash,
43            beneficiary,
44            state_root,
45            transactions_root,
46            receipts_root,
47            logs_bloom,
48            difficulty,
49            number,
50            gas_limit,
51            gas_used,
52            timestamp,
53            extra_data,
54            mix_hash,
55            nonce,
56            base_fee_per_gas,
57            withdrawals_root,
58            blob_gas_used,
59            excess_blob_gas,
60            parent_beacon_block_root,
61            requests_hash,
62            target_blobs_per_block,
63        } = block.header.inner;
64
65        Ok(Self {
66            header: Header {
67                parent_hash,
68                ommers_hash,
69                beneficiary,
70                state_root,
71                transactions_root,
72                receipts_root,
73                logs_bloom,
74                difficulty,
75                number,
76                gas_limit,
77                gas_used,
78                timestamp,
79                extra_data,
80                mix_hash: mix_hash
81                    .ok_or_else(|| ConversionError::Custom("missing mixHash".to_string()))?,
82                nonce: nonce.ok_or_else(|| ConversionError::Custom("missing nonce".to_string()))?,
83                base_fee_per_gas,
84                withdrawals_root,
85                blob_gas_used,
86                excess_blob_gas,
87                parent_beacon_block_root,
88                requests_hash,
89                target_blobs_per_block,
90            },
91            body: BlockBody {
92                transactions,
93                ommers: Default::default(),
94                withdrawals: block.withdrawals.map(|w| w.into_inner().into()),
95            },
96        })
97    }
98}
99
100impl TryFrom<AnyRpcTransaction> for TransactionSigned {
101    type Error = alloy_rpc_types::ConversionError;
102
103    fn try_from(tx: AnyRpcTransaction) -> Result<Self, Self::Error> {
104        use alloy_rpc_types::ConversionError;
105
106        let WithOtherFields { inner: tx, other: _ } = tx;
107
108        let (transaction, signature, hash) = match tx.inner {
109            AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(tx)) => {
110                let (tx, signature, hash) = tx.into_parts();
111                (Transaction::Legacy(tx), signature, hash)
112            }
113            AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(tx)) => {
114                let (tx, signature, hash) = tx.into_parts();
115                (Transaction::Eip2930(tx), signature, hash)
116            }
117            AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(tx)) => {
118                let (tx, signature, hash) = tx.into_parts();
119                (Transaction::Eip1559(tx), signature, hash)
120            }
121            AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(tx)) => {
122                let (tx, signature, hash) = tx.into_parts();
123                (Transaction::Eip4844(tx.into()), signature, hash)
124            }
125            AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(tx)) => {
126                let (tx, signature, hash) = tx.into_parts();
127                (Transaction::Eip7702(tx), signature, hash)
128            }
129            #[cfg(feature = "optimism")]
130            AnyTxEnvelope::Unknown(alloy_network::UnknownTxEnvelope { hash, inner }) => {
131                use alloy_consensus::{Transaction as _, Typed2718};
132
133                if inner.ty() == crate::TxType::Deposit {
134                    let fields: op_alloy_rpc_types::OpTransactionFields = inner
135                        .fields
136                        .clone()
137                        .deserialize_into::<op_alloy_rpc_types::OpTransactionFields>()
138                        .map_err(|e| ConversionError::Custom(e.to_string()))?;
139                    (
140                        Transaction::Deposit(op_alloy_consensus::TxDeposit {
141                            source_hash: fields.source_hash.ok_or_else(|| {
142                                ConversionError::Custom("MissingSourceHash".to_string())
143                            })?,
144                            from: tx.from,
145                            to: revm_primitives::TxKind::from(inner.to()),
146                            mint: fields.mint.filter(|n| *n != 0),
147                            value: inner.value(),
148                            gas_limit: inner.gas_limit(),
149                            is_system_transaction: fields.is_system_tx.unwrap_or(false),
150                            input: inner.input().clone(),
151                        }),
152                        op_alloy_consensus::TxDeposit::signature(),
153                        hash,
154                    )
155                } else {
156                    return Err(ConversionError::Custom("unknown transaction type".to_string()))
157                }
158            }
159            _ => return Err(ConversionError::Custom("unknown transaction type".to_string())),
160        };
161
162        Ok(Self { transaction, signature, hash: hash.into() })
163    }
164}
165
166#[cfg(test)]
167#[cfg(feature = "optimism")]
168mod tests {
169    use super::*;
170    use alloy_primitives::{address, Address, B256, U256};
171    use revm_primitives::TxKind;
172
173    #[test]
174    fn optimism_deposit_tx_conversion_no_mint() {
175        let input = r#"{
176            "blockHash": "0xef664d656f841b5ad6a2b527b963f1eb48b97d7889d742f6cbff6950388e24cd",
177            "blockNumber": "0x73a78fd",
178            "depositReceiptVersion": "0x1",
179            "from": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
180            "gas": "0xc27a8",
181            "gasPrice": "0x0",
182            "hash": "0x0bf1845c5d7a82ec92365d5027f7310793d53004f3c86aa80965c67bf7e7dc80",
183            "input": "0xd764ad0b000100000000000000000000000000000000000000000000000000000001cf5400000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be100000000000000000000000042000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007a12000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e40166a07a0000000000000000000000000994206dfe8de6ec6920ff4d779b0d950605fb53000000000000000000000000d533a949740bb3306d119cc777fa900ba034cd52000000000000000000000000ca74f404e0c7bfa35b13b511097df966d5a65597000000000000000000000000ca74f404e0c7bfa35b13b511097df966d5a65597000000000000000000000000000000000000000000000216614199391dbba2ba00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
184            "mint": "0x0",
185            "nonce": "0x74060",
186            "r": "0x0",
187            "s": "0x0",
188            "sourceHash": "0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3",
189            "to": "0x4200000000000000000000000000000000000007",
190            "transactionIndex": "0x1",
191            "type": "0x7e",
192            "v": "0x0",
193            "value": "0x0"
194        }"#;
195        let alloy_tx: WithOtherFields<alloy_rpc_types::Transaction<AnyTxEnvelope>> =
196            serde_json::from_str(input).expect("failed to deserialize");
197
198        let TransactionSigned { transaction: reth_tx, .. } =
199            alloy_tx.try_into().expect("failed to convert");
200        if let Transaction::Deposit(deposit_tx) = reth_tx {
201            assert_eq!(
202                deposit_tx.source_hash,
203                "0x074adb22f2e6ed9bdd31c52eefc1f050e5db56eb85056450bccd79a6649520b3"
204                    .parse::<B256>()
205                    .unwrap()
206            );
207            assert_eq!(
208                deposit_tx.from,
209                "0x36bde71c97b33cc4729cf772ae268934f7ab70b2".parse::<Address>().unwrap()
210            );
211            assert_eq!(
212                deposit_tx.to,
213                TxKind::from(address!("4200000000000000000000000000000000000007"))
214            );
215            assert_eq!(deposit_tx.mint, None);
216            assert_eq!(deposit_tx.value, U256::ZERO);
217            assert_eq!(deposit_tx.gas_limit, 796584);
218            assert!(!deposit_tx.is_system_transaction);
219        } else {
220            panic!("Expected Deposit transaction");
221        }
222    }
223
224    #[test]
225    fn optimism_deposit_tx_conversion_mint() {
226        let input = r#"{
227            "blockHash": "0x7194f63b105e93fb1a27c50d23d62e422d4185a68536c55c96284911415699b2",
228            "blockNumber": "0x73a82cc",
229            "depositReceiptVersion": "0x1",
230            "from": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
231            "gas": "0x7812e",
232            "gasPrice": "0x0",
233            "hash": "0xf7e83886d3c6864f78e01c453ebcd57020c5795d96089e8f0e0b90a467246ddb",
234            "input": "0xd764ad0b000100000000000000000000000000000000000000000000000000000001cf5f00000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be100000000000000000000000042000000000000000000000000000000000000100000000000000000000000000000000000000000000000239c2e16a5ca5900000000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e41635f5fd0000000000000000000000002ce910fbba65b454bbaf6a18c952a70f3bcd82990000000000000000000000002ce910fbba65b454bbaf6a18c952a70f3bcd82990000000000000000000000000000000000000000000000239c2e16a5ca590000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
235            "mint": "0x239c2e16a5ca590000",
236            "nonce": "0x7406b",
237            "r": "0x0",
238            "s": "0x0",
239            "sourceHash": "0xe0358cd2b2686d297c5c859646a613124a874fb9d9c4a2c88636a46a65c06e48",
240            "to": "0x4200000000000000000000000000000000000007",
241            "transactionIndex": "0x1",
242            "type": "0x7e",
243            "v": "0x0",
244            "value": "0x239c2e16a5ca590000"
245        }"#;
246        let alloy_tx: WithOtherFields<alloy_rpc_types::Transaction<AnyTxEnvelope>> =
247            serde_json::from_str(input).expect("failed to deserialize");
248
249        let TransactionSigned { transaction: reth_tx, .. } =
250            alloy_tx.try_into().expect("failed to convert");
251
252        if let Transaction::Deposit(deposit_tx) = reth_tx {
253            assert_eq!(
254                deposit_tx.source_hash,
255                "0xe0358cd2b2686d297c5c859646a613124a874fb9d9c4a2c88636a46a65c06e48"
256                    .parse::<B256>()
257                    .unwrap()
258            );
259            assert_eq!(
260                deposit_tx.from,
261                "0x36bde71c97b33cc4729cf772ae268934f7ab70b2".parse::<Address>().unwrap()
262            );
263            assert_eq!(
264                deposit_tx.to,
265                TxKind::from(address!("4200000000000000000000000000000000000007"))
266            );
267            assert_eq!(deposit_tx.mint, Some(656890000000000000000));
268            assert_eq!(deposit_tx.value, U256::from(0x239c2e16a5ca590000_u128));
269            assert_eq!(deposit_tx.gas_limit, 491822);
270            assert!(!deposit_tx.is_system_transaction);
271        } else {
272            panic!("Expected Deposit transaction");
273        }
274    }
275}