1use 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 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}