anvil_polkadot/api_server/
txpool_helpers.rs1use alloy_primitives::{Address, B256, U256, keccak256};
8use alloy_rpc_types::txpool::TxpoolInspectSummary;
9use codec::{DecodeLimit, Encode};
10use polkadot_sdk::{
11 pallet_revive::evm::TransactionSigned,
12 sp_core::{self, H256},
13};
14use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16use substrate_runtime::{RuntimeCall, UncheckedExtrinsic};
17
18use crate::substrate_node::host::recover_maybe_impersonated_address;
19
20const MAX_EXTRINSIC_DEPTH: u32 = 256;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct TxpoolTransactionInfo {
25 pub hash: H256,
26 pub block_hash: Option<H256>,
27 pub block_number: Option<sp_core::U256>,
28 pub transaction_index: Option<sp_core::U256>,
29 pub from: sp_core::H160,
30 pub transaction_signed: TransactionSigned,
31}
32
33pub(super) fn decode_eth_transaction(
35 tx_data: &Arc<polkadot_sdk::sp_runtime::OpaqueExtrinsic>,
36) -> Option<(Vec<u8>, TransactionSigned)> {
37 let encoded = tx_data.encode();
38 let ext =
39 UncheckedExtrinsic::decode_all_with_depth_limit(MAX_EXTRINSIC_DEPTH, &mut &encoded[..])
40 .ok()?;
41
42 let polkadot_sdk::sp_runtime::generic::UncheckedExtrinsic {
43 function: RuntimeCall::Revive(polkadot_sdk::pallet_revive::Call::eth_transact { payload }),
44 ..
45 } = ext.0
46 else {
47 return None;
48 };
49
50 let signed_tx = TransactionSigned::decode(&payload).ok()?;
51
52 Some((payload, signed_tx))
53}
54
55pub(super) fn transaction_matches_eth_hash(
57 tx_data: &Arc<polkadot_sdk::sp_runtime::OpaqueExtrinsic>,
58 target_eth_hash: B256,
59) -> bool {
60 let Some((payload, _signed_tx)) = decode_eth_transaction(tx_data) else {
61 return false;
62 };
63
64 let tx_eth_hash = keccak256(&payload);
65 B256::from_slice(tx_eth_hash.as_ref()) == target_eth_hash
66}
67
68pub(super) struct TransactionFields {
70 pub nonce: sp_core::U256,
71 pub to: Option<sp_core::H160>,
72 pub value: sp_core::U256,
73 pub gas: sp_core::U256,
74 pub gas_price: sp_core::U256,
75}
76
77fn extract_tx_fields(signed_tx: &TransactionSigned) -> TransactionFields {
79 match signed_tx {
80 TransactionSigned::TransactionLegacySigned(tx) => {
81 let t = &tx.transaction_legacy_unsigned;
82 TransactionFields {
83 nonce: t.nonce,
84 to: t.to,
85 value: t.value,
86 gas: t.gas,
87 gas_price: t.gas_price,
88 }
89 }
90 TransactionSigned::Transaction2930Signed(tx) => {
91 let t = &tx.transaction_2930_unsigned;
92 TransactionFields {
93 nonce: t.nonce,
94 to: t.to,
95 value: t.value,
96 gas: t.gas,
97 gas_price: t.gas_price,
98 }
99 }
100 TransactionSigned::Transaction1559Signed(tx) => {
101 let t = &tx.transaction_1559_unsigned;
102 TransactionFields {
103 nonce: t.nonce,
104 to: t.to,
105 value: t.value,
106 gas: t.gas,
107 gas_price: t.max_fee_per_gas,
108 }
109 }
110 TransactionSigned::Transaction4844Signed(tx) => {
111 let t = &tx.transaction_4844_unsigned;
112 TransactionFields {
113 nonce: t.nonce,
114 to: Some(t.to),
115 value: t.value,
116 gas: t.gas,
117 gas_price: t.max_fee_per_gas,
118 }
119 }
120 TransactionSigned::Transaction7702Signed(tx) => {
121 let t = &tx.transaction_7702_unsigned;
122 TransactionFields {
123 nonce: t.nonce,
124 to: Some(t.to),
125 value: t.value,
126 gas: t.gas,
127 gas_price: t.max_fee_per_gas,
128 }
129 }
130 }
131}
132
133pub(super) fn extract_tx_summary(
135 tx_data: &Arc<polkadot_sdk::sp_runtime::OpaqueExtrinsic>,
136) -> Option<(Address, u64, TxpoolInspectSummary)> {
137 let (_payload, signed_tx) = decode_eth_transaction(tx_data)?;
138
139 let from = recover_maybe_impersonated_address(&signed_tx).ok()?;
140 let sender = Address::from_slice(from.as_bytes());
141
142 let fields = extract_tx_fields(&signed_tx);
143
144 let to_addr = fields.to.map(|addr| Address::from_slice(addr.as_bytes()));
145 let value_u256 = U256::from_limbs(fields.value.0);
146 let gas_u64 = fields.gas.as_u64();
147 let gas_price_u128 = fields.gas_price.as_u128();
148 let nonce_u64 = fields.nonce.as_u64();
149
150 Some((
151 sender,
152 nonce_u64,
153 TxpoolInspectSummary {
154 to: to_addr,
155 value: value_u256,
156 gas: gas_u64,
157 gas_price: gas_price_u128,
158 },
159 ))
160}
161
162pub(super) fn extract_tx_info(
164 tx_data: &Arc<polkadot_sdk::sp_runtime::OpaqueExtrinsic>,
165) -> Option<(Address, u64, TxpoolTransactionInfo)> {
166 let (payload, signed_tx) = decode_eth_transaction(tx_data)?;
167
168 let eth_hash = keccak256(&payload);
169 let eth_hash_h256 = H256::from_slice(eth_hash.as_ref());
170
171 let from = recover_maybe_impersonated_address(&signed_tx).ok()?;
172 let sender = Address::from_slice(from.as_bytes());
173
174 let fields = extract_tx_fields(&signed_tx);
175 let nonce_u64 = fields.nonce.as_u64();
176
177 let tx_info = TxpoolTransactionInfo {
178 hash: eth_hash_h256,
179 block_hash: None,
180 block_number: None,
181 transaction_index: None,
182 from,
183 transaction_signed: signed_tx,
184 };
185
186 Some((sender, nonce_u64, tx_info))
187}
188
189pub(super) fn extract_sender(
192 tx_data: &Arc<polkadot_sdk::sp_runtime::OpaqueExtrinsic>,
193) -> Option<Address> {
194 let (_payload, signed_tx) = decode_eth_transaction(tx_data)?;
195
196 let from = recover_maybe_impersonated_address(&signed_tx).ok()?;
197 let sender = Address::from_slice(from.as_bytes());
198
199 Some(sender)
200}