Skip to content

Commit

Permalink
EVM: Implement getBlockByNumber query (#884)
Browse files Browse the repository at this point in the history
* implement getBlockByNumber query

* remove todo on get-block-by-number

* lint: get-block-by-number

* fix: unnecessary block db loads

* Extract helper for block by number

* lint: get block by number

* implement getBlockByNumber query

* Extract helper for block by number

* fix storage context

* lint: get block by number

* test: get block by number
  • Loading branch information
orkunkilic authored Sep 19, 2023
1 parent e68cc2e commit 1879a45
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 28 deletions.
50 changes: 46 additions & 4 deletions examples/demo-rollup/tests/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ethereum_types::H160;
use ethers_core::abi::Address;
use ethers_core::k256::ecdsa::SigningKey;
use ethers_core::types::transaction::eip2718::TypedTransaction;
use ethers_core::types::Eip1559TransactionRequest;
use ethers_core::types::{Block, Eip1559TransactionRequest, Transaction, TxHash};
use ethers_middleware::SignerMiddleware;
use ethers_providers::{Http, Middleware, PendingTransaction, Provider};
use ethers_signers::{LocalWallet, Signer, Wallet};
Expand Down Expand Up @@ -146,6 +146,23 @@ impl TestClient {
chain_id.as_u64()
}

async fn eth_get_block_by_number(&self, block_number: Option<String>) -> Block<TxHash> {
self.http_client
.request("eth_getBlockByNumber", rpc_params![block_number, false])
.await
.unwrap()
}

async fn eth_get_block_by_number_with_detail(
&self,
block_number: Option<String>,
) -> Block<Transaction> {
self.http_client
.request("eth_getBlockByNumber", rpc_params![block_number, true])
.await
.unwrap()
}

async fn execute(self) -> Result<(), Box<dyn std::error::Error>> {
let contract_address = {
let deploy_contract_req = self.deploy_contract().await?;
Expand All @@ -158,18 +175,32 @@ impl TestClient {
.unwrap()
};

// Check that the first block has published
// It should have a single transaction, deploying the contract
let first_block = self.eth_get_block_by_number(Some("1".to_owned())).await;
assert_eq!(first_block.number.unwrap().as_u64(), 1);
assert_eq!(first_block.transactions.len(), 1);

let set_arg = 923;
{
let tx_hash = {
let set_value_req = self.set_value(contract_address, set_arg, 1).await;
self.send_publish_batch_request().await;
set_value_req.await.unwrap();
}
set_value_req.await.unwrap().unwrap().transaction_hash
};

{
let get_arg = self.query_contract(contract_address, 2).await?;
assert_eq!(set_arg, get_arg.as_u32());
}

// Check that the second block has published
// None should return the latest block
// It should have a single transaction, setting the value
let latest_block = self.eth_get_block_by_number_with_detail(None).await;
assert_eq!(latest_block.number.unwrap().as_u64(), 2);
assert_eq!(latest_block.transactions.len(), 1);
assert_eq!(latest_block.transactions[0].hash, tx_hash);

// Create a blob with multiple transactions.
let mut requests = Vec::default();
let mut nonce = 2;
Expand Down Expand Up @@ -213,6 +244,17 @@ async fn send_tx_test_to_eth(rpc_address: SocketAddr) -> Result<(), Box<dyn std:
let eth_chain_id = test_client.eth_chain_id().await;
assert_eq!(chain_id, eth_chain_id);

// No block exists yet
let latest_block = test_client
.eth_get_block_by_number(Some("latest".to_owned()))
.await;
let earliest_block = test_client
.eth_get_block_by_number(Some("earliest".to_owned()))
.await;

assert_eq!(latest_block, earliest_block);
assert_eq!(latest_block.number.unwrap().as_u64(), 0);

test_client.execute().await
}

Expand Down
98 changes: 74 additions & 24 deletions module-system/module-implementations/sov-evm/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@ use crate::Evm;

#[rpc_gen(client, server, namespace = "eth")]
impl<C: sov_modules_api::Context> Evm<C> {
fn get_sealed_block_by_number(
&self,
block_number: Option<String>,
working_set: &mut WorkingSet<C>,
) -> SealedBlock {
// safe, finalized, and pending are not supported
match block_number {
Some(ref block_number) if block_number == "earliest" => self
.blocks
.get(0, &mut working_set.accessory_state())
.expect("Genesis block must be set"),
Some(ref block_number) if block_number == "latest" => self
.blocks
.last(&mut working_set.accessory_state())
.expect("Head block must be set"),
Some(ref block_number) => {
let block_number =
usize::from_str_radix(block_number, 16).expect("Block number must be hex");
self.blocks
.get(block_number, &mut working_set.accessory_state())
.expect("Block must be set")
}
None => self
.blocks
.last(&mut working_set.accessory_state())
.expect("Head block must be set"),
}
}

#[rpc_method(name = "chainId")]
pub fn chain_id(
&self,
Expand All @@ -32,42 +61,63 @@ impl<C: sov_modules_api::Context> Evm<C> {
Ok(Some(chain_id))
}

// TODO https://github.com/Sovereign-Labs/sovereign-sdk/issues/502
#[rpc_method(name = "getBlockByNumber")]
pub fn get_block_by_number(
&self,
_block_number: Option<String>,
_details: Option<bool>,
_working_set: &mut WorkingSet<C>,
block_number: Option<String>,
details: Option<bool>,
working_set: &mut WorkingSet<C>,
) -> RpcResult<Option<reth_rpc_types::RichBlock>> {
info!("evm module: eth_getBlockByNumber");

let header = reth_rpc_types::Header {
hash: Default::default(),
parent_hash: Default::default(),
uncles_hash: Default::default(),
miner: Default::default(),
state_root: Default::default(),
transactions_root: Default::default(),
receipts_root: Default::default(),
logs_bloom: Default::default(),
difficulty: Default::default(),
number: Default::default(),
gas_limit: Default::default(),
gas_used: Default::default(),
timestamp: Default::default(),
extra_data: Default::default(),
mix_hash: Default::default(),
nonce: Default::default(),
base_fee_per_gas: Some(reth_primitives::U256::from(100)),
withdrawals_root: Default::default(),
let block = self.get_sealed_block_by_number(block_number, working_set);

// Build rpc header response
let header = reth_rpc_types::Header::from_primitive_with_hash(block.header.clone());

// Collect transactions with ids from db
let transactions_with_ids = block
.transactions
.clone()
.map(|id| {
let tx = self
.transactions
.get(id as usize, &mut working_set.accessory_state())
.expect("Transaction must be set");
(id, tx)
})
.collect::<Vec<_>>();

// Build rpc transactions response
let transactions = match details {
Some(true) => reth_rpc_types::BlockTransactions::Full(
transactions_with_ids
.iter()
.map(|(id, tx)| {
reth_rpc_types::Transaction::from_recovered_with_block_context(
tx.clone().into(),
block.header.hash,
block.header.number,
block.header.base_fee_per_gas,
U256::from(id - block.transactions.start),
)
})
.collect::<Vec<_>>(),
),
_ => reth_rpc_types::BlockTransactions::Hashes({
transactions_with_ids
.iter()
.map(|(_, tx)| tx.signed_transaction.hash)
.collect::<Vec<_>>()
}),
};

// Build rpc block response
let block = reth_rpc_types::Block {
header,
total_difficulty: Default::default(),
uncles: Default::default(),
transactions: reth_rpc_types::BlockTransactions::Hashes(Default::default()),
transactions,
size: Default::default(),
withdrawals: Default::default(),
};
Expand Down

0 comments on commit 1879a45

Please sign in to comment.