Skip to main content

ethrex_rpc/eth/
transaction.rs

1use std::sync::Arc;
2
3use crate::{
4    eth::block,
5    rpc::{RpcApiContext, RpcHandler},
6    types::{
7        block_identifier::{BlockIdentifier, BlockIdentifierOrHash},
8        transaction::{RpcTransaction, SendRawTransactionRequest},
9    },
10    utils::RpcErr,
11};
12use ethrex_blockchain::{Blockchain, vm::StoreVmDatabase};
13use ethrex_common::{
14    H256,
15    types::{AccessListEntry, BlockHash, BlockHeader, BlockNumber, GenericTransaction, TxKind},
16};
17
18use ethrex_rlp::encode::RLPEncode;
19use ethrex_storage::Store;
20
21use ethrex_vm::{ExecutionResult, backends::levm::get_max_allowed_gas_limit};
22use serde::Serialize;
23
24use serde_json::Value;
25use tracing::debug;
26
27pub const ESTIMATE_ERROR_RATIO: f64 = 0.015;
28pub const CALL_STIPEND: u64 = 2_300; // Free gas given at beginning of call.
29pub const TRANSACTION_GAS: u64 = 21_000; // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
30
31pub struct CallRequest {
32    transaction: GenericTransaction,
33    block: Option<BlockIdentifierOrHash>,
34}
35
36pub struct GetTransactionByBlockNumberAndIndexRequest {
37    pub block: BlockIdentifier,
38    pub transaction_index: usize,
39}
40
41pub struct GetTransactionByBlockHashAndIndexRequest {
42    pub block: BlockHash,
43    pub transaction_index: usize,
44}
45
46pub struct GetTransactionByHashRequest {
47    pub transaction_hash: H256,
48}
49
50pub struct GetTransactionReceiptRequest {
51    pub transaction_hash: H256,
52}
53
54pub struct CreateAccessListRequest {
55    pub transaction: GenericTransaction,
56    pub block: Option<BlockIdentifier>,
57}
58pub struct EstimateGasRequest {
59    pub transaction: GenericTransaction,
60    pub block: Option<BlockIdentifier>,
61}
62
63pub struct GetRawTransaction {
64    pub transaction_hash: H256,
65}
66
67#[derive(Serialize)]
68#[serde(rename_all = "camelCase")]
69pub struct AccessListResult {
70    access_list: Vec<AccessListEntry>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    error: Option<String>,
73    #[serde(with = "ethrex_common::serde_utils::u64::hex_str")]
74    gas_used: u64,
75}
76
77impl RpcHandler for CallRequest {
78    fn parse(params: &Option<Vec<Value>>) -> Result<CallRequest, RpcErr> {
79        let params = params
80            .as_ref()
81            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
82        if params.is_empty() {
83            return Err(RpcErr::BadParams("No params provided".to_owned()));
84        }
85        if params.len() > 2 {
86            return Err(RpcErr::BadParams(format!(
87                "Expected one or two params and {} were provided",
88                params.len()
89            )));
90        }
91        let block = match params.get(1) {
92            // Differentiate between missing and bad block param
93            Some(value) => Some(BlockIdentifierOrHash::parse(value.clone(), 1)?),
94            None => None,
95        };
96        Ok(CallRequest {
97            transaction: serde_json::from_value(params[0].clone())?,
98            block,
99        })
100    }
101    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
102        let block = self
103            .block
104            .clone()
105            .unwrap_or(BlockIdentifierOrHash::Identifier(BlockIdentifier::default()));
106        debug!("Requested call on block: {}", block);
107        let header = match block.resolve_block_header(&context.storage).await? {
108            Some(header) => header,
109            // Block not found
110            _ => return Ok(Value::Null),
111        };
112        // Run transaction
113        let result = simulate_tx(
114            &self.transaction,
115            &header,
116            context.storage,
117            context.blockchain,
118        )?;
119        serde_json::to_value(format!("0x{:#x}", result.output()))
120            .map_err(|error| RpcErr::Internal(error.to_string()))
121    }
122}
123
124impl RpcHandler for GetTransactionByBlockNumberAndIndexRequest {
125    fn parse(
126        params: &Option<Vec<Value>>,
127    ) -> Result<GetTransactionByBlockNumberAndIndexRequest, RpcErr> {
128        let params = params
129            .as_ref()
130            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
131        if params.len() != 2 {
132            return Err(RpcErr::BadParams(format!(
133                "Expected two params and {} were provided",
134                params.len()
135            )));
136        };
137        let index_as_string: String = serde_json::from_value(params[1].clone())?;
138        Ok(GetTransactionByBlockNumberAndIndexRequest {
139            block: BlockIdentifier::parse(params[0].clone(), 0)?,
140            transaction_index: usize::from_str_radix(index_as_string.trim_start_matches("0x"), 16)
141                .map_err(|error| RpcErr::BadParams(error.to_string()))?,
142        })
143    }
144
145    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
146        debug!(
147            "Requested transaction at index: {} of block with number: {}",
148            self.transaction_index, self.block,
149        );
150        let block_number = match self.block.resolve_block_number(&context.storage).await? {
151            Some(block_number) => block_number,
152            _ => return Ok(Value::Null),
153        };
154        let block_body = match context.storage.get_block_body(block_number).await? {
155            Some(block_body) => block_body,
156            _ => return Ok(Value::Null),
157        };
158        let block_header = match context.storage.get_block_header(block_number)? {
159            Some(block_body) => block_body,
160            _ => return Ok(Value::Null),
161        };
162        let tx = match block_body.transactions.get(self.transaction_index) {
163            Some(tx) => tx,
164            None => return Ok(Value::Null),
165        };
166        let tx = RpcTransaction::build(
167            tx.clone(),
168            Some(block_number),
169            Some(block_header.hash()),
170            Some(self.transaction_index),
171        )?;
172        serde_json::to_value(tx).map_err(|error| RpcErr::Internal(error.to_string()))
173    }
174}
175
176impl RpcHandler for GetTransactionByBlockHashAndIndexRequest {
177    fn parse(
178        params: &Option<Vec<Value>>,
179    ) -> Result<GetTransactionByBlockHashAndIndexRequest, RpcErr> {
180        let params = params
181            .as_ref()
182            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
183        if params.len() != 2 {
184            return Err(RpcErr::BadParams(format!(
185                "Expected two param and {} were provided",
186                params.len()
187            )));
188        };
189        let index_as_string: String = serde_json::from_value(params[1].clone())?;
190        Ok(GetTransactionByBlockHashAndIndexRequest {
191            block: serde_json::from_value(params[0].clone())?,
192            transaction_index: usize::from_str_radix(index_as_string.trim_start_matches("0x"), 16)
193                .map_err(|error| RpcErr::BadParams(error.to_string()))?,
194        })
195    }
196    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
197        debug!(
198            "Requested transaction at index: {} of block with hash: {:#x}",
199            self.transaction_index, self.block,
200        );
201        let block_number = match context.storage.get_block_number(self.block).await? {
202            Some(number) => number,
203            _ => return Ok(Value::Null),
204        };
205        let block_body = match context.storage.get_block_body(block_number).await? {
206            Some(block_body) => block_body,
207            _ => return Ok(Value::Null),
208        };
209        let tx = match block_body.transactions.get(self.transaction_index) {
210            Some(tx) => tx,
211            None => return Ok(Value::Null),
212        };
213        let tx = RpcTransaction::build(
214            tx.clone(),
215            Some(block_number),
216            Some(self.block),
217            Some(self.transaction_index),
218        )?;
219        serde_json::to_value(tx).map_err(|error| RpcErr::Internal(error.to_string()))
220    }
221}
222
223impl RpcHandler for GetTransactionByHashRequest {
224    fn parse(params: &Option<Vec<Value>>) -> Result<GetTransactionByHashRequest, RpcErr> {
225        let params = params
226            .as_ref()
227            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
228        if params.len() != 1 {
229            return Err(RpcErr::BadParams(format!(
230                "Expected one param and {} were provided",
231                params.len()
232            )));
233        };
234        Ok(GetTransactionByHashRequest {
235            transaction_hash: serde_json::from_value(params[0].clone())?,
236        })
237    }
238    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
239        let storage = &context.storage;
240        debug!(
241            "Requested transaction with hash: {:#x}",
242            self.transaction_hash,
243        );
244        let transaction = if let Some((block_number, block_hash, index)) = storage
245            .get_transaction_location(self.transaction_hash)
246            .await?
247        {
248            let Some(tx) = storage
249                .get_transaction_by_location(block_hash, index)
250                .await?
251            else {
252                return Ok(Value::Null);
253            };
254            RpcTransaction::build(
255                tx,
256                Some(block_number),
257                Some(block_hash),
258                Some(index as usize),
259            )?
260        } else {
261            let Some(tx) = context
262                .blockchain
263                .mempool
264                .get_transaction_by_hash(self.transaction_hash)?
265            else {
266                return Ok(Value::Null);
267            };
268            RpcTransaction::build(tx, None, None, None)?
269        };
270        serde_json::to_value(transaction).map_err(|error| RpcErr::Internal(error.to_string()))
271    }
272}
273
274impl RpcHandler for GetTransactionReceiptRequest {
275    fn parse(params: &Option<Vec<Value>>) -> Result<GetTransactionReceiptRequest, RpcErr> {
276        let params = params
277            .as_ref()
278            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
279        if params.len() != 1 {
280            return Err(RpcErr::BadParams(format!(
281                "Expected one param and {} were provided",
282                params.len()
283            )));
284        };
285        Ok(GetTransactionReceiptRequest {
286            transaction_hash: serde_json::from_value(params[0].clone())?,
287        })
288    }
289    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
290        let storage = &context.storage;
291        debug!(
292            "Requested receipt for transaction {:#x}",
293            self.transaction_hash,
294        );
295        let (_block_number, block_hash, index) = match storage
296            .get_transaction_location(self.transaction_hash)
297            .await?
298        {
299            Some(location) => location,
300            _ => return Ok(Value::Null),
301        };
302        let block = match storage.get_block_by_hash(block_hash).await? {
303            Some(block) => block,
304            None => return Ok(Value::Null),
305        };
306        let receipts =
307            block::get_all_block_rpc_receipts(block.header, block.body, storage, Some(index))
308                .await?;
309
310        serde_json::to_value(receipts.get(index as usize))
311            .map_err(|error| RpcErr::Internal(error.to_string()))
312    }
313}
314
315impl RpcHandler for CreateAccessListRequest {
316    fn parse(params: &Option<Vec<Value>>) -> Result<CreateAccessListRequest, RpcErr> {
317        let params = params
318            .as_ref()
319            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
320        if params.is_empty() {
321            return Err(RpcErr::BadParams("No params provided".to_owned()));
322        }
323        if params.len() > 2 {
324            return Err(RpcErr::BadParams(format!(
325                "Expected one or two params and {} were provided",
326                params.len()
327            )));
328        }
329        let block = match params.get(1) {
330            // Differentiate between missing and bad block param
331            Some(value) => Some(BlockIdentifier::parse(value.clone(), 1)?),
332            None => None,
333        };
334        Ok(CreateAccessListRequest {
335            transaction: serde_json::from_value(params[0].clone())?,
336            block,
337        })
338    }
339    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
340        let block = self.block.clone().unwrap_or_default();
341        debug!("Requested access list creation for tx on block: {}", block);
342        let block_number = match block.resolve_block_number(&context.storage).await? {
343            Some(block_number) => block_number,
344            _ => return Ok(Value::Null),
345        };
346        let header = match context.storage.get_block_header(block_number)? {
347            Some(header) => header,
348            // Block not found
349            _ => return Ok(Value::Null),
350        };
351
352        let vm_db = StoreVmDatabase::new(context.storage.clone(), header.clone())?;
353        let mut vm = context.blockchain.new_evm(vm_db)?;
354
355        // Run transaction and obtain access list
356        let (gas_used, access_list, error) = vm.create_access_list(&self.transaction, &header)?;
357        let result = AccessListResult {
358            access_list: access_list
359                .into_iter()
360                .map(|(address, storage_keys)| AccessListEntry {
361                    address,
362                    storage_keys,
363                })
364                .collect(),
365            error,
366            gas_used,
367        };
368
369        serde_json::to_value(result).map_err(|error| RpcErr::Internal(error.to_string()))
370    }
371}
372
373impl RpcHandler for GetRawTransaction {
374    fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
375        let params = params
376            .as_ref()
377            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
378        if params.len() != 1 {
379            return Err(RpcErr::BadParams(format!(
380                "Expected one param and {} were provided",
381                params.len()
382            )));
383        };
384
385        let transaction_str: String = serde_json::from_value(params[0].clone())?;
386        if !transaction_str.starts_with("0x") {
387            return Err(RpcErr::BadHexFormat(0));
388        }
389
390        Ok(GetRawTransaction {
391            transaction_hash: serde_json::from_value(params[0].clone())?,
392        })
393    }
394
395    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
396        let mut tx = context
397            .storage
398            .get_transaction_by_hash(self.transaction_hash)
399            .await?;
400        if tx.is_none() {
401            tx = context
402                .blockchain
403                .mempool
404                .get_transaction_by_hash(self.transaction_hash)?;
405        }
406        let tx = match tx {
407            Some(tx) => tx,
408            _ => return Ok(Value::Null),
409        };
410        serde_json::to_value(format!("0x{}", &hex::encode(tx.encode_to_vec())))
411            .map_err(|error| RpcErr::Internal(error.to_string()))
412    }
413}
414
415impl RpcHandler for EstimateGasRequest {
416    fn parse(params: &Option<Vec<Value>>) -> Result<EstimateGasRequest, RpcErr> {
417        let params = params
418            .as_ref()
419            .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
420        if params.is_empty() {
421            return Err(RpcErr::BadParams("No params provided".to_owned()));
422        }
423        if params.len() > 2 {
424            return Err(RpcErr::BadParams(format!(
425                "Expected one or two params and {} were provided",
426                params.len()
427            )));
428        }
429        let block = match params.get(1) {
430            // Differentiate between missing and bad block param
431            Some(value) => Some(BlockIdentifier::parse(value.clone(), 1)?),
432            None => None,
433        };
434        Ok(EstimateGasRequest {
435            transaction: serde_json::from_value(params[0].clone())?,
436            block,
437        })
438    }
439    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
440        let storage = &context.storage;
441        let blockchain = &context.blockchain;
442        let block = self.block.clone().unwrap_or_default();
443        let chain_config = storage.get_chain_config();
444
445        debug!("Requested estimate on block: {}", block);
446        let block_header = match block.resolve_block_header(storage).await? {
447            Some(header) => header,
448            // Block not found
449            _ => return Ok(Value::Null),
450        };
451
452        let current_fork = chain_config.fork(block_header.timestamp);
453
454        let transaction = match self.transaction.nonce {
455            Some(_nonce) => self.transaction.clone(),
456            None => {
457                let transaction_nonce = storage
458                    .get_nonce_by_account_address(block_header.number, self.transaction.from)
459                    .await?;
460
461                let mut cloned_transaction = self.transaction.clone();
462                cloned_transaction.nonce = transaction_nonce;
463                cloned_transaction
464            }
465        };
466
467        // If the transaction is a plain value transfer, short circuit estimation.
468        if let TxKind::Call(address) = transaction.to {
469            let account_info = storage
470                .get_account_info(block_header.number, address)
471                .await?;
472            let code = account_info.map(|info| storage.get_account_code(info.code_hash));
473            if code.is_none() {
474                let mut value_transfer_transaction = transaction.clone();
475                value_transfer_transaction.gas = Some(TRANSACTION_GAS);
476                let result: Result<ExecutionResult, RpcErr> = simulate_tx(
477                    &value_transfer_transaction,
478                    &block_header,
479                    storage.clone(),
480                    blockchain.clone(),
481                );
482                if let Ok(ExecutionResult::Success { .. }) = result {
483                    return serde_json::to_value(format!("{TRANSACTION_GAS:#x}"))
484                        .map_err(|error| RpcErr::Internal(error.to_string()));
485                }
486            }
487        }
488
489        // Prepare binary search
490        let highest_gas_limit = get_max_allowed_gas_limit(block_header.gas_limit, current_fork);
491        let mut highest_gas_limit = match transaction.gas {
492            Some(gas) => gas.min(highest_gas_limit),
493            None => highest_gas_limit,
494        };
495
496        if !transaction.gas_price.is_zero() {
497            highest_gas_limit = recap_with_account_balances(
498                highest_gas_limit,
499                &transaction,
500                storage,
501                block_header.number,
502            )
503            .await?;
504        }
505
506        // Check whether the execution is possible
507        let mut transaction = transaction.clone();
508        transaction.gas = Some(highest_gas_limit);
509        let result = simulate_tx(
510            &transaction,
511            &block_header,
512            storage.clone(),
513            blockchain.clone(),
514        )?;
515
516        let gas_used = result.gas_used();
517        let gas_refunded = result.gas_refunded();
518
519        // Choose an optimistic start limit. See https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L135
520        let optimistic_limit = (gas_used + gas_refunded + CALL_STIPEND) * 64 / 63;
521        let mut lowest_gas_limit = gas_used.saturating_sub(1);
522        let mut middle_gas_limit = (optimistic_limit + lowest_gas_limit) / 2;
523
524        while lowest_gas_limit + 1 < highest_gas_limit {
525            if (highest_gas_limit - lowest_gas_limit) as f64 / (highest_gas_limit as f64)
526                < ESTIMATE_ERROR_RATIO
527            {
528                break;
529            };
530
531            if middle_gas_limit > lowest_gas_limit * 2 {
532                // Favor the low side, since most transactions don't need much higher gas limit than their gas used.
533                middle_gas_limit = lowest_gas_limit * 2;
534            }
535            transaction.gas = Some(middle_gas_limit);
536
537            let result = simulate_tx(
538                &transaction,
539                &block_header,
540                storage.clone(),
541                blockchain.clone(),
542            );
543            if let Ok(ExecutionResult::Success { .. }) = result {
544                highest_gas_limit = middle_gas_limit;
545            } else {
546                lowest_gas_limit = middle_gas_limit;
547            };
548            middle_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2;
549        }
550
551        serde_json::to_value(format!("{highest_gas_limit:#x}"))
552            .map_err(|error| RpcErr::Internal(error.to_string()))
553    }
554}
555
556async fn recap_with_account_balances(
557    highest_gas_limit: u64,
558    transaction: &GenericTransaction,
559    storage: &Store,
560    block_number: BlockNumber,
561) -> Result<u64, RpcErr> {
562    let account_balance = storage
563        .get_account_info(block_number, transaction.from)
564        .await?
565        .map(|acc| acc.balance)
566        .unwrap_or_default();
567    let account_gas = account_balance.saturating_sub(transaction.value) / transaction.gas_price;
568    // If account_gas exceeds u64, the account can afford any gas limit.
569    let account_gas = u64::try_from(account_gas).unwrap_or(highest_gas_limit);
570    Ok(highest_gas_limit.min(account_gas))
571}
572
573fn simulate_tx(
574    transaction: &GenericTransaction,
575    block_header: &BlockHeader,
576    storage: Store,
577    blockchain: Arc<Blockchain>,
578) -> Result<ExecutionResult, RpcErr> {
579    let vm_db = StoreVmDatabase::new(storage, block_header.clone())?;
580    let mut vm = blockchain.new_evm(vm_db)?;
581
582    match vm.simulate_tx_from_generic(transaction, block_header)? {
583        ExecutionResult::Revert {
584            gas_used: _,
585            output,
586        } => Err(RpcErr::Revert {
587            data: format!("0x{output:#x}"),
588        }),
589        ExecutionResult::Halt { reason, gas_used } => Err(RpcErr::Halt { reason, gas_used }),
590        success => Ok(success),
591    }
592}
593
594impl RpcHandler for SendRawTransactionRequest {
595    fn parse(params: &Option<Vec<Value>>) -> Result<SendRawTransactionRequest, RpcErr> {
596        let data = get_transaction_data(params)?;
597
598        let transaction = SendRawTransactionRequest::decode_canonical(&data)
599            .map_err(|error| RpcErr::BadParams(error.to_string()))?;
600
601        if matches!(transaction, SendRawTransactionRequest::PrivilegedL2(_)) {
602            return Err(RpcErr::BadParams("Invalid transaction type".to_string()));
603        }
604
605        Ok(transaction)
606    }
607
608    async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
609        let hash = if let SendRawTransactionRequest::EIP4844(wrapped_blob_tx) = self {
610            context
611                .blockchain
612                .add_blob_transaction_to_pool(
613                    wrapped_blob_tx.tx.clone(),
614                    wrapped_blob_tx.blobs_bundle.clone(),
615                )
616                .await
617        } else {
618            context
619                .blockchain
620                .add_transaction_to_pool(self.to_transaction())
621                .await
622        }?;
623        serde_json::to_value(format!("{hash:#x}"))
624            .map_err(|error| RpcErr::Internal(error.to_string()))
625    }
626}
627
628fn get_transaction_data(rpc_req_params: &Option<Vec<Value>>) -> Result<Vec<u8>, RpcErr> {
629    let params = rpc_req_params
630        .as_ref()
631        .ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
632    if params.len() != 1 {
633        return Err(RpcErr::BadParams(format!(
634            "Expected one param and {} were provided",
635            params.len()
636        )));
637    };
638
639    let str_data = serde_json::from_value::<String>(params[0].clone())?;
640    let str_data = str_data
641        .strip_prefix("0x")
642        .ok_or(RpcErr::BadParams("Params are note 0x prefixed".to_owned()))?;
643    hex::decode(str_data).map_err(|error| RpcErr::BadParams(error.to_string()))
644}