Skip to main content

phos_data_network/
spec.rs

1use std::{collections::HashMap, sync::Arc};
2
3use alloy::{
4    consensus::{
5        error::UnsupportedTransactionType,
6        proofs::{calculate_transaction_root, calculate_withdrawals_root},
7        Receipt, ReceiptWithBloom, TxReceipt, TxType, TypedTransaction,
8    },
9    eips::{BlockId, Encodable2718},
10    network::{
11        BuildResult, Network, NetworkWallet, TransactionBuilder, TransactionBuilderError,
12        UnbuiltTransactionError,
13    },
14    primitives::{Address, Bytes, ChainId, TxKind, U256},
15    rpc::types::{state::StateOverride, AccessList, Log, TransactionRequest},
16};
17use async_trait::async_trait;
18use revm::context::result::{ExecutionResult, HaltReason};
19
20use helios_common::{
21    execution_provider::ExecutionProvider,
22    fork_schedule::ForkSchedule,
23    network_spec::NetworkSpec,
24    types::{Account, EvmError},
25};
26
27use crate::evm::DataNetworkEvm;
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub struct DataNetwork;
31
32#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
33#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
34impl NetworkSpec for DataNetwork {
35    type HaltReason = HaltReason;
36
37    fn encode_receipt(receipt: &Self::ReceiptResponse) -> Vec<u8> {
38        let tx_type = receipt.transaction_type();
39        let receipt = receipt.inner.as_receipt_with_bloom().unwrap();
40        let logs = receipt
41            .logs()
42            .iter()
43            .map(|l| l.inner.clone())
44            .collect::<Vec<_>>();
45
46        let consensus_receipt = Receipt {
47            cumulative_gas_used: receipt.cumulative_gas_used(),
48            status: receipt.status_or_post_state(),
49            logs,
50        };
51
52        let rwb = ReceiptWithBloom::new(consensus_receipt, receipt.bloom());
53        let encoded = alloy::rlp::encode(rwb);
54
55        match tx_type {
56            TxType::Legacy => encoded,
57            _ => [vec![tx_type as u8], encoded].concat(),
58        }
59    }
60
61    fn encode_transaction(tx: &Self::TransactionResponse) -> Vec<u8> {
62        tx.inner.encoded_2718()
63    }
64
65    fn is_hash_valid(block: &Self::BlockResponse) -> bool {
66        if block.header.hash_slow() != block.header.hash {
67            return false;
68        }
69
70        if let Some(txs) = block.transactions.as_transactions() {
71            let txs_root = calculate_transaction_root(
72                &txs.iter().map(|t| t.clone().inner).collect::<Vec<_>>(),
73            );
74            if txs_root != block.header.transactions_root {
75                return false;
76            }
77        }
78
79        if let Some(withdrawals) = &block.withdrawals {
80            let withdrawals_root =
81                calculate_withdrawals_root(&withdrawals.iter().copied().collect::<Vec<_>>());
82            if Some(withdrawals_root) != block.header.withdrawals_root {
83                return false;
84            }
85        }
86
87        true
88    }
89
90    fn receipt_contains(list: &[Self::ReceiptResponse], elem: &Self::ReceiptResponse) -> bool {
91        for receipt in list {
92            if receipt == elem {
93                return true;
94            }
95        }
96
97        false
98    }
99
100    fn receipt_logs(receipt: &Self::ReceiptResponse) -> Vec<Log> {
101        receipt.inner.logs().to_vec()
102    }
103
104    async fn transact<E: ExecutionProvider<Self>>(
105        tx: &Self::TransactionRequest,
106        validate_tx: bool,
107        execution: Arc<E>,
108        chain_id: u64,
109        fork_schedule: ForkSchedule,
110        block_id: BlockId,
111        state_overrides: Option<StateOverride>,
112    ) -> Result<(ExecutionResult, HashMap<Address, Account>), EvmError> {
113        let mut evm = DataNetworkEvm::new(execution, chain_id, fork_schedule, block_id);
114
115        evm.transact_inner(tx, validate_tx, state_overrides).await
116    }
117}
118
119impl Network for DataNetwork {
120    type TxType = alloy::consensus::TxType;
121    type TxEnvelope = alloy::consensus::TxEnvelope;
122    type UnsignedTx = alloy::consensus::TypedTransaction;
123    type ReceiptEnvelope = alloy::consensus::ReceiptEnvelope;
124    type Header = alloy::consensus::Header;
125    type TransactionRequest = alloy::rpc::types::TransactionRequest;
126    type TransactionResponse = alloy::rpc::types::Transaction;
127    type ReceiptResponse = alloy::rpc::types::TransactionReceipt;
128    type HeaderResponse = alloy::rpc::types::Header;
129    type BlockResponse = alloy::rpc::types::Block<Self::TransactionResponse, Self::HeaderResponse>;
130}
131
132impl TransactionBuilder<DataNetwork> for TransactionRequest {
133    fn chain_id(&self) -> Option<ChainId> {
134        self.chain_id
135    }
136
137    fn set_chain_id(&mut self, chain_id: ChainId) {
138        self.chain_id = Some(chain_id);
139    }
140
141    fn nonce(&self) -> Option<u64> {
142        self.nonce
143    }
144
145    fn set_nonce(&mut self, nonce: u64) {
146        self.nonce = Some(nonce);
147    }
148
149    fn take_nonce(&mut self) -> Option<u64> {
150        self.nonce.take()
151    }
152
153    fn input(&self) -> Option<&Bytes> {
154        self.input.input()
155    }
156
157    fn set_input<T: Into<Bytes>>(&mut self, input: T) {
158        self.input.input = Some(input.into());
159    }
160
161    fn from(&self) -> Option<Address> {
162        self.from
163    }
164
165    fn set_from(&mut self, from: Address) {
166        self.from = Some(from);
167    }
168
169    fn kind(&self) -> Option<TxKind> {
170        self.to
171    }
172
173    fn clear_kind(&mut self) {
174        self.to = None;
175    }
176
177    fn set_kind(&mut self, kind: TxKind) {
178        self.to = Some(kind);
179    }
180
181    fn value(&self) -> Option<U256> {
182        self.value
183    }
184
185    fn set_value(&mut self, value: U256) {
186        self.value = Some(value)
187    }
188
189    fn gas_price(&self) -> Option<u128> {
190        self.gas_price
191    }
192
193    fn set_gas_price(&mut self, gas_price: u128) {
194        self.gas_price = Some(gas_price);
195    }
196
197    fn max_fee_per_gas(&self) -> Option<u128> {
198        self.max_fee_per_gas
199    }
200
201    fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
202        self.max_fee_per_gas = Some(max_fee_per_gas);
203    }
204
205    fn max_priority_fee_per_gas(&self) -> Option<u128> {
206        self.max_priority_fee_per_gas
207    }
208
209    fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
210        self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas);
211    }
212
213    fn gas_limit(&self) -> Option<u64> {
214        self.gas
215    }
216
217    fn set_gas_limit(&mut self, gas_limit: u64) {
218        self.gas = Some(gas_limit);
219    }
220
221    fn access_list(&self) -> Option<&AccessList> {
222        self.access_list.as_ref()
223    }
224
225    fn set_access_list(&mut self, access_list: AccessList) {
226        self.access_list = Some(access_list);
227    }
228
229    fn complete_type(&self, ty: TxType) -> Result<(), Vec<&'static str>> {
230        match ty {
231            TxType::Legacy => self.complete_legacy(),
232            TxType::Eip2930 => self.complete_2930(),
233            TxType::Eip1559 => self.complete_1559(),
234            TxType::Eip4844 => self.complete_4844(),
235            TxType::Eip7702 => self.complete_7702(),
236        }
237    }
238
239    fn can_submit(&self) -> bool {
240        // value and data may be None. If they are, they will be set to default.
241        // gas fields and nonce may be None, if they are, they will be populated
242        // with default values by the RPC server
243        self.from.is_some()
244    }
245
246    fn can_build(&self) -> bool {
247        // value and data may be none. If they are, they will be set to default
248        // values.
249
250        // chain_id and from may be none.
251        let common = self.gas.is_some() && self.nonce.is_some();
252
253        let legacy = self.gas_price.is_some();
254        let eip2930 = legacy
255            && <TransactionRequest as TransactionBuilder<DataNetwork>>::access_list(self).is_some();
256
257        let eip1559 = self.max_fee_per_gas.is_some() && self.max_priority_fee_per_gas.is_some();
258
259        let eip4844 = eip1559 && self.sidecar.is_some() && self.to.is_some();
260        common && (legacy || eip2930 || eip1559 || eip4844)
261    }
262
263    #[doc(alias = "output_transaction_type")]
264    fn output_tx_type(&self) -> TxType {
265        self.preferred_type()
266    }
267
268    #[doc(alias = "output_transaction_type_checked")]
269    fn output_tx_type_checked(&self) -> Option<TxType> {
270        match self.buildable_type() {
271            Some(TxType::Eip4844) => None,
272            tx_type => tx_type,
273        }
274    }
275
276    fn prep_for_submission(&mut self) {
277        self.transaction_type = Some(self.preferred_type() as u8);
278        self.trim_conflicting_keys();
279        self.populate_blob_hashes();
280    }
281
282    fn build_unsigned(self) -> BuildResult<TypedTransaction, DataNetwork> {
283        if let Err((tx_type, missing)) = self.missing_keys() {
284            return Err(
285                TransactionBuilderError::InvalidTransactionRequest(tx_type, missing)
286                    .into_unbuilt(self),
287            );
288        }
289
290        if let Some(TxType::Eip4844) = self.buildable_type() {
291            return Err(UnbuiltTransactionError {
292                request: self,
293                error: TransactionBuilderError::Custom(Box::new(UnsupportedTransactionType::new(
294                    TxType::Eip4844,
295                ))),
296            });
297        }
298
299        Ok(self.build_typed_tx().expect("checked by missing_keys"))
300    }
301
302    async fn build<W: NetworkWallet<DataNetwork>>(
303        self,
304        wallet: &W,
305    ) -> Result<<DataNetwork as Network>::TxEnvelope, TransactionBuilderError<DataNetwork>> {
306        Ok(wallet.sign_request(self).await?)
307    }
308}