Skip to content

Commit

Permalink
Enable pending block tag in simulation endpoints (#1303)
Browse files Browse the repository at this point in the history
* treat pending tag same as latest

* fix lint

* return new sealed block for pending

* handle pending tag externally

* revert enabling pending in some endpoints

* get blockenv instead of sealed block

* address review comments

* implement tests for eth_call, eth_estimateGas, eth_createAccessList pending blocks

* rename tests

---------

Co-authored-by: eyusufatik <esadyusufatik@gmail.com>
  • Loading branch information
exeokan and eyusufatik authored Oct 9, 2024
1 parent a3cee8d commit 31a2b52
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 41 deletions.
9 changes: 7 additions & 2 deletions crates/ethereum-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,15 @@ fn register_rpc_methods<C: sov_modules_api::Context, Da: DaService>(

let mut working_set = WorkingSet::<C>::new(ethereum.storage.clone());
let evm = Evm::<C>::default();
let latest_block_number: u64 = evm.block_number(&mut working_set)?.saturating_to();

let block_number = match block_number {
BlockNumberOrTag::Number(block_number) => block_number,
BlockNumberOrTag::Latest => evm.block_number(&mut working_set)?.saturating_to(),
_ => return Err(EthApiError::Unsupported("Earliest, pending, safe and finalized are not supported for debug_traceBlockByNumber").into()),
BlockNumberOrTag::Latest => latest_block_number,
_ => return Err(EthApiError::Unsupported(
"Earliest, pending, safe and finalized are not supported for debug_traceBlockByNumber",
)
.into()),
};

debug_trace_by_block_number(block_number, None, &ethereum, &evm, &mut working_set, opts)
Expand Down
8 changes: 5 additions & 3 deletions crates/ethereum-rpc/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ pub async fn handle_debug_trace_chain<C: sov_modules_api::Context, Da: DaService
}
BlockNumberOrTag::Latest => latest_block_number,
_ => {
pending.reject(EthApiError::Unsupported(
"Earliest, pending, safe and finalized are not supported for traceChain end block",
)).await;
pending
.reject(EthApiError::Unsupported(
"Earliest, pending, safe and finalized are not supported for traceChain end block",
))
.await;
return;
}
};
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip
sov-modules-api = { path = "../sovereign-sdk/module-system/sov-modules-api", features = ["macros"] }
sov-prover-storage-manager = { path = "../sovereign-sdk/full-node/sov-prover-storage-manager", features = ["test-utils"] }
sov-rollup-interface = { path = "../sovereign-sdk/rollup-interface", features = ["testing"] }
sov-stf-runner = { path = "../sovereign-sdk/full-node/sov-stf-runner" }
sov-stf-runner = { path = "../sovereign-sdk/full-node/sov-stf-runner", features = ["native"] }
tempfile = { workspace = true }
tracing-subscriber = { workspace = true }
walkdir = "2.3.3"
Expand Down
120 changes: 91 additions & 29 deletions crates/evm/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use alloy_consensus::Eip658Value;
use alloy_eips::eip2930::AccessListWithGasUsed;
use alloy_primitives::Uint;
use alloy_rlp::Encodable;
use citrea_primitives::basefee::calculate_next_block_base_fee;
use jsonrpsee::core::RpcResult;
use reth_primitives::TxKind::{Call, Create};
use reth_primitives::{
Expand Down Expand Up @@ -39,7 +40,6 @@ use crate::evm::DbAccount;
use crate::handler::{diff_size_send_eth_eoa, TxInfo};
use crate::rpc_helpers::*;
use crate::{BloomFilter, Evm, EvmChainConfig, FilterBlockOption, FilterError};

/// Gas per transaction not creating a contract.
pub const MIN_TRANSACTION_GAS: u64 = 21_000u64;

Expand Down Expand Up @@ -148,7 +148,6 @@ impl<C: sov_modules_api::Context> Evm<C> {
Some(sealed_block) => sealed_block,
None => return Ok(None), // if block doesn't exist return null
};

// Build rpc header response
let mut header = from_primitive_with_hash(sealed_block.header.clone());
header.total_difficulty = Some(header.difficulty);
Expand Down Expand Up @@ -511,11 +510,15 @@ impl<C: sov_modules_api::Context> Evm<C> {
};

let (block_env, mut cfg_env) = {
let block = self
.get_sealed_block_by_number(Some(block_number), working_set)?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block_env = BlockEnv::from(&block);

let block_env = match block_number {
BlockNumberOrTag::Pending => get_pending_block_env(self, working_set),
_ => {
let block = self
.get_sealed_block_by_number(Some(block_number), working_set)?
.ok_or(EthApiError::UnknownBlockNumber)?;
BlockEnv::from(&block)
}
};
// Set evm state to block if needed
match block_number {
BlockNumberOrTag::Pending | BlockNumberOrTag::Latest => {}
Expand Down Expand Up @@ -581,11 +584,22 @@ impl<C: sov_modules_api::Context> Evm<C> {
let mut request = request.clone();

let (l1_fee_rate, block_env, mut cfg_env) = {
let block = self
.get_sealed_block_by_number(block_number, working_set)?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block_env = BlockEnv::from(&block);

let (l1_fee_rate, block_env) = match block_number {
Some(BlockNumberOrTag::Pending) => {
let l1_fee_rate = self
.blocks
.last(&mut working_set.accessory_state())
.expect("Head block must be set")
.l1_fee_rate;
(l1_fee_rate, get_pending_block_env(self, working_set))
}
_ => {
let block = self
.get_sealed_block_by_number(block_number, working_set)?
.ok_or(EthApiError::UnknownBlockNumber)?;
(block.l1_fee_rate, BlockEnv::from(&block))
}
};
match block_number {
None | Some(BlockNumberOrTag::Pending | BlockNumberOrTag::Latest) => {}
_ => set_state_to_end_of_evm_block(block_env.number, working_set),
Expand All @@ -597,7 +611,7 @@ impl<C: sov_modules_api::Context> Evm<C> {
.expect("EVM chain config should be set");
let cfg_env = get_cfg_env(&block_env, cfg);

(block.l1_fee_rate, block_env, cfg_env)
(l1_fee_rate, block_env, cfg_env)
};

// we want to disable this in eth_createAccessList, since this is common practice used by
Expand Down Expand Up @@ -673,23 +687,29 @@ impl<C: sov_modules_api::Context> Evm<C> {
working_set: &mut WorkingSet<C>,
) -> RpcResult<EstimatedTxExpenses> {
let (l1_fee_rate, block_env, cfg_env) = {
let block = self
.get_sealed_block_by_number(block_number, working_set)?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block_env = BlockEnv::from(&block);

match block_number {
None | Some(BlockNumberOrTag::Pending | BlockNumberOrTag::Latest) => {}
_ => set_state_to_end_of_evm_block(block_env.number, working_set),
let (l1_fee_rate, block_env) = match block_number {
Some(BlockNumberOrTag::Pending) => {
let l1_fee_rate = self
.blocks
.last(&mut working_set.accessory_state())
.expect("Head block must be set")
.l1_fee_rate;
(l1_fee_rate, get_pending_block_env(self, working_set))
}
_ => {
let block = self
.get_sealed_block_by_number(block_number, working_set)?
.ok_or(EthApiError::UnknownBlockNumber)?;
(block.l1_fee_rate, BlockEnv::from(&block))
}
};

let cfg = self
.cfg
.get(working_set)
.expect("EVM chain config should be set");
let cfg_env = get_cfg_env(&block_env, cfg);

(block.l1_fee_rate, block_env, cfg_env)
(l1_fee_rate, block_env, cfg_env)
};

self.estimate_gas_with_env(request, l1_fee_rate, block_env, cfg_env, working_set)
Expand Down Expand Up @@ -1364,13 +1384,15 @@ impl<C: sov_modules_api::Context> Evm<C> {
block_id: &BlockNumberOrTag,
working_set: &mut WorkingSet<C>,
) -> Result<u64, EthApiError> {
let latest_block_number = self
.blocks
.last(&mut working_set.accessory_state())
.map(|block| block.header.number)
.expect("Head block must be set");
match block_id {
BlockNumberOrTag::Earliest => Ok(0),
BlockNumberOrTag::Latest => Ok(self
.blocks
.last(&mut working_set.accessory_state())
.map(|block| block.header.number)
.expect("Head block must be set")),
BlockNumberOrTag::Latest => Ok(latest_block_number),
BlockNumberOrTag::Pending => Err(EthApiError::UnknownBlockNumber),
BlockNumberOrTag::Number(block_number) => {
if *block_number < self.blocks.len(&mut working_set.accessory_state()) as u64 {
Ok(*block_number)
Expand All @@ -1379,7 +1401,7 @@ impl<C: sov_modules_api::Context> Evm<C> {
}
}
_ => Err(EthApiError::InvalidParams(
"Please provide a number or earliest/latest tag".to_string(),
"Please provide a number or earliest/latest/pending tag".to_string(),
)),
}
}
Expand Down Expand Up @@ -1673,3 +1695,43 @@ fn set_state_to_end_of_evm_block<C: sov_modules_api::Context>(
// so every block is offset by 1
working_set.set_archival_version(block_number + 1);
}

/// Creates the next blocks `BlockEnv` based on the latest block
/// Also updates `Evm::latest_block_hashes` with the new block hash
fn get_pending_block_env<C: sov_modules_api::Context>(
evm: &Evm<C>,
working_set: &mut WorkingSet<C>,
) -> BlockEnv {
let latest_block = evm
.blocks
.last(&mut working_set.accessory_state())
.expect("Head block must be set");

evm.latest_block_hashes.set(
&U256::from(latest_block.header.number),
&latest_block.header.hash(),
working_set,
);

let cfg = evm
.cfg
.get(working_set)
.expect("EVM chain config should be set");

let mut block_env = BlockEnv::from(&latest_block);
block_env.number += 1;
block_env.basefee = calculate_next_block_base_fee(
latest_block.header.gas_used as u128,
latest_block.header.gas_limit as u128,
latest_block.header.base_fee_per_gas,
cfg.base_fee_params,
)
.unwrap_or_default();

if block_env.number > 256 {
evm.latest_block_hashes
.remove(&U256::from(block_env.number - 257), working_set);
}

block_env
}
38 changes: 34 additions & 4 deletions crates/evm/src/tests/call_tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::str::FromStr;

use alloy_eips::BlockId;
use reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT;
use reth_primitives::{address, b256, Address, BlockNumberOrTag, Bytes, Log, LogData, TxKind, U64};
use reth_rpc_types::request::{TransactionInput, TransactionRequest};
Expand Down Expand Up @@ -786,10 +787,7 @@ fn test_block_hash_in_evm() {
for i in 0..=1000 {
request.input.input = Some(BlockHashContract::default().get_block_hash(i).into());
let resp = evm.get_call(request.clone(), None, None, None, &mut working_set);
if !(260..=515).contains(&i) {
// Should be 0, there is more than 256 blocks between the last block and the block number
assert_eq!(resp.unwrap().to_vec(), vec![0u8; 32]);
} else {
if (260..=515).contains(&i) {
// Should be equal to the hash in accessory state
let block = evm
.blocks
Expand All @@ -798,8 +796,40 @@ fn test_block_hash_in_evm() {
resp.unwrap().to_vec(),
block.unwrap().header.hash().to_vec()
);
} else {
// Should be 0, there is more than 256 blocks between the last block and the block number
assert_eq!(resp.unwrap().to_vec(), vec![0u8; 32]);
}
}

// last produced block is 516, eth_call with pending should return latest block's hash
let latest_block = evm.blocks.get(516, &mut working_set.accessory_state());
request.input.input = Some(BlockHashContract::default().get_block_hash(516).into());

let resp = evm.get_call(
request.clone(),
Some(BlockId::pending()),
None,
None,
&mut working_set,
);

assert_eq!(
resp.unwrap().to_vec(),
latest_block.unwrap().header.hash().to_vec()
);

// but not 260's hash
request.input.input = Some(BlockHashContract::default().get_block_hash(260).into());
let resp = evm.get_call(
request.clone(),
Some(BlockId::pending()),
None,
None,
&mut working_set,
);

assert_eq!(resp.unwrap().to_vec(), vec![0u8; 32]);
}

#[test]
Expand Down
61 changes: 59 additions & 2 deletions crates/evm/src/tests/queries/estimate_gas_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{EstimatedDiffSize, Evm};
type C = DefaultContext;

#[test]
fn payable_contract_value_test() {
fn test_payable_contract_value() {
let (evm, mut working_set, signer) = init_evm_single_block();

let tx_req = TransactionRequest {
Expand Down Expand Up @@ -338,7 +338,7 @@ fn test_access_list() {
}

#[test]
fn estimate_gas_with_varied_inputs_test() {
fn test_estimate_gas_with_varied_inputs() {
let (evm, mut working_set, signer, _) = init_evm();

let simple_call_data = 0;
Expand All @@ -363,6 +363,63 @@ fn estimate_gas_with_varied_inputs_test() {
);
}

#[test]
fn test_pending_env() {
let (evm, mut working_set, signer) = init_evm_single_block();

let tx_req = TransactionRequest {
from: Some(signer.address()),
to: Some(TxKind::Call(address!(
"819c5497b157177315e1204f52e588b393771719"
))), // Address of the payable contract.
gas: Some(100000),
gas_price: Some(100000000),
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
value: Some(U256::from(3100000)),
input: TransactionInput {
input: None,
data: None,
},
nonce: Some(1u64),
chain_id: Some(1u64),
access_list: None,
max_fee_per_blob_gas: None,
blob_versioned_hashes: None,
transaction_type: None,
sidecar: None,
};

let result = evm
.eth_estimate_gas(
tx_req.clone(),
Some(BlockNumberOrTag::Latest),
&mut working_set,
)
.unwrap();

let result_pending = evm.eth_estimate_gas(
tx_req.clone(),
Some(BlockNumberOrTag::Pending),
&mut working_set,
);
assert_eq!(result_pending.unwrap(), result);

let result = evm
.create_access_list(tx_req.clone(), None, &mut working_set)
.unwrap();

let result_pending = evm
.create_access_list(
tx_req.clone(),
Some(BlockNumberOrTag::Pending),
&mut working_set,
)
.unwrap();

assert_eq!(result_pending, result);
}

fn test_estimate_gas_with_input(
evm: &Evm<C>,
working_set: &mut WorkingSet<C>,
Expand Down

0 comments on commit 31a2b52

Please sign in to comment.