Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EVM: Implement getBlockByNumber query #884

Merged
merged 13 commits into from
Sep 19, 2023
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<_>>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to collect it here, this causes unnecessary allocation on heap.


// 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