From 78a23aa8b0fa6857771498728c92982ceb8e435d Mon Sep 17 00:00:00 2001 From: Jouzo <15011228+Jouzo@users.noreply.github.com> Date: Thu, 13 Apr 2023 22:59:17 +0100 Subject: [PATCH] Add cache storage layer and GetTransactionBy* RPCs (#1908) * Add cache storage and RPC impl * Restore block flush() --- lib/Cargo.lock | 33 ++++++- lib/ain-evm/Cargo.toml | 3 + lib/ain-evm/src/cache.rs | 124 +++++++++++++++++++++++++ lib/ain-evm/src/handler.rs | 31 ++++--- lib/ain-evm/src/lib.rs | 2 + lib/ain-evm/src/runtime.rs | 1 + lib/ain-evm/src/storage.rs | 68 ++++++++++++++ lib/ain-grpc/Cargo.toml | 1 + lib/ain-grpc/src/impls.rs | 73 +++++++++++++++ lib/ain-grpc/src/lib.rs | 1 + lib/ain-grpc/src/rpc.rs | 185 +++++++++++++++++++------------------ lib/ain-grpc/src/tests.rs | 179 ++++++++++++++++++++++++++++++++++- lib/proto/rpc/eth.proto | 10 +- lib/proto/types/eth.proto | 6 +- 14 files changed, 603 insertions(+), 114 deletions(-) create mode 100644 lib/ain-evm/src/cache.rs create mode 100644 lib/ain-evm/src/storage.rs create mode 100644 lib/ain-grpc/src/impls.rs diff --git a/lib/Cargo.lock b/lib/Cargo.lock index 0ff0b2d7f7e..253e152ba5e 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -35,6 +46,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", + "lru", "primitive-types", "rand", "rlp", @@ -53,6 +65,7 @@ dependencies = [ "env_logger", "ethereum", "heck", + "hex", "jsonrpsee-core", "jsonrpsee-http-client", "jsonrpsee-http-server", @@ -737,6 +750,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.4.1" @@ -921,7 +943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1140,6 +1162,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lru" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "matchit" version = "0.7.0" diff --git a/lib/ain-evm/Cargo.toml b/lib/ain-evm/Cargo.toml index 77a8f4db091..e5ce20f5051 100644 --- a/lib/ain-evm/Cargo.toml +++ b/lib/ain-evm/Cargo.toml @@ -23,3 +23,6 @@ jsonrpsee-core = "0.15" jsonrpsee-http-server = "0.15" jsonrpsee-types = "0.15" tokio = { version = "1.1", features = ["rt-multi-thread"] } + +# Cache dependencies +lru = "0.10.0" diff --git a/lib/ain-evm/src/cache.rs b/lib/ain-evm/src/cache.rs new file mode 100644 index 00000000000..1a7ba119046 --- /dev/null +++ b/lib/ain-evm/src/cache.rs @@ -0,0 +1,124 @@ +use std::{num::NonZeroUsize, sync::RwLock}; + +use ethereum::{BlockAny, TransactionV2}; +use lru::LruCache; +use primitive_types::{H256, U256}; +use std::borrow::ToOwned; + +#[derive(Debug)] +pub struct Cache { + transactions: RwLock>, + blocks: RwLock>, + block_hashes: RwLock>, + latest_block: RwLock>, +} + +impl Cache { + const DEFAULT_CACHE_SIZE: usize = 1000; + + pub fn new(cache_size: Option) -> Self { + Cache { + transactions: RwLock::new(LruCache::new( + NonZeroUsize::new(cache_size.unwrap_or(Self::DEFAULT_CACHE_SIZE)).unwrap(), + )), + blocks: RwLock::new(LruCache::new( + NonZeroUsize::new(cache_size.unwrap_or(Self::DEFAULT_CACHE_SIZE)).unwrap(), + )), + block_hashes: RwLock::new(LruCache::new( + NonZeroUsize::new(cache_size.unwrap_or(Self::DEFAULT_CACHE_SIZE)).unwrap(), + )), + latest_block: RwLock::new(None), + } + } +} + +impl Cache { + pub fn extend_transactions_from_block(&self, block: &BlockAny) { + let mut cache = self.transactions.write().unwrap(); + + for transaction in &block.transactions { + let hash = transaction.hash(); + cache.put(hash, transaction.clone()); + } + } + + pub fn get_transaction_by_hash(&self, hash: &H256) -> Option { + self.transactions + .write() + .unwrap() + .get(hash) + .map(ToOwned::to_owned) + } + + pub fn get_transaction_by_block_hash_and_index( + &self, + block_hash: &H256, + index: usize, + ) -> Option { + self.block_hashes + .write() + .unwrap() + .get(block_hash) + .and_then(|block_number| { + self.get_transaction_by_block_number_and_index(&block_number, index) + }) + } + + pub fn get_transaction_by_block_number_and_index( + &self, + block_number: &U256, + index: usize, + ) -> Option { + self.blocks + .write() + .unwrap() + .get(block_number)? + .transactions + .get(index) + .map(ToOwned::to_owned) + } +} + +// Block impl +impl Cache { + pub fn get_block_by_number(&self, number: &U256) -> Option { + self.blocks + .write() + .unwrap() + .get(number) + .map(ToOwned::to_owned) + } + + pub fn get_block_by_hash(&self, block_hash: &H256) -> Option { + self.block_hashes + .write() + .unwrap() + .get(block_hash) + .and_then(|block_number| self.get_block_by_number(&block_number)) + } + + pub fn put_block(&self, block: BlockAny) { + self.extend_transactions_from_block(&block); + + let block_number = block.header.number; + let hash = block.header.hash(); + self.blocks.write().unwrap().put(block_number, block); + self.block_hashes.write().unwrap().put(hash, block_number); + } +} + +// Latest block impl +impl Cache { + pub fn get_latest_block(&self) -> Option { + self.latest_block + .read() + .unwrap() + .as_ref() + .map(ToOwned::to_owned) + } + + pub fn put_latest_block(&self, block: BlockAny) { + let mut cache = self.latest_block.write().unwrap(); + *cache = Some(block); + } +} diff --git a/lib/ain-evm/src/handler.rs b/lib/ain-evm/src/handler.rs index a9929d1c1bf..6bff6978a30 100644 --- a/lib/ain-evm/src/handler.rs +++ b/lib/ain-evm/src/handler.rs @@ -1,8 +1,9 @@ use crate::block::BlockHandler; use crate::evm::EVMHandler; use crate::executor::AinExecutor; +use crate::storage::Storage; use crate::traits::Executor; -use ethereum::{Block, PartialHeader, TransactionV2}; +use ethereum::{Block, BlockAny, PartialHeader, TransactionV2}; use evm::backend::{MemoryBackend, MemoryVicinity}; use primitive_types::{H160, H256, U256}; use std::error::Error; @@ -11,6 +12,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; pub struct Handlers { pub evm: EVMHandler, pub block: BlockHandler, + pub storage: Storage, } impl Handlers { @@ -18,6 +20,7 @@ impl Handlers { Self { evm: EVMHandler::new(), block: BlockHandler::new(), + storage: Storage::new(), } } @@ -26,7 +29,7 @@ impl Handlers { context: u64, update_state: bool, miner_address: Option, - ) -> Result<(Block, Vec), Box> { + ) -> Result<(BlockAny, Vec), Box> { let mut tx_hashes = Vec::with_capacity(self.evm.tx_queues.len(context)); let mut failed_tx_hashes = Vec::with_capacity(self.evm.tx_queues.len(context)); let vicinity = get_vicinity(None, None); @@ -45,17 +48,13 @@ impl Handlers { self.evm.tx_queues.remove(context); - if update_state { - let mut state = self.evm.state.write().unwrap(); - *state = executor.backend().state().clone(); - } - let (parent_hash, number) = { - let blocks = self.block.blocks.read().unwrap(); - blocks - .first() - .and_then(|first_block| Some((first_block.header.hash(), blocks.len() + 1))) - .unwrap_or((H256::default(), 0)) + self.storage + .get_latest_block() + .and_then(|first_block| { + Some((first_block.header.hash(), first_block.header.number + 1)) + }) + .unwrap_or((H256::default(), U256::zero())) }; let block = Block::new( @@ -83,6 +82,14 @@ impl Handlers { self.block.connect_block(block.clone()); + if update_state { + let mut state = self.evm.state.write().unwrap(); + *state = executor.backend().state().clone(); + + self.storage.put_latest_block(block.clone()); + self.storage.put_block(block.clone()); + } + Ok((block, failed_tx_hashes)) } } diff --git a/lib/ain-evm/src/lib.rs b/lib/ain-evm/src/lib.rs index cc3a0b8acc0..c31cd5a8f13 100644 --- a/lib/ain-evm/src/lib.rs +++ b/lib/ain-evm/src/lib.rs @@ -1,9 +1,11 @@ mod block; +mod cache; mod ecrecover; mod evm; pub mod executor; pub mod handler; pub mod runtime; +pub mod storage; pub mod traits; pub mod transaction; mod tx_queue; diff --git a/lib/ain-evm/src/runtime.rs b/lib/ain-evm/src/runtime.rs index fc9b87d026b..2bfa0fd852c 100644 --- a/lib/ain-evm/src/runtime.rs +++ b/lib/ain-evm/src/runtime.rs @@ -23,6 +23,7 @@ impl Runtime { pub fn new() -> Self { let r = Builder::new_multi_thread().enable_all().build().unwrap(); let (tx, mut rx) = mpsc::channel(1); + Runtime { tx, rt_handle: r.handle().clone(), diff --git a/lib/ain-evm/src/storage.rs b/lib/ain-evm/src/storage.rs new file mode 100644 index 00000000000..3498f9e2c6b --- /dev/null +++ b/lib/ain-evm/src/storage.rs @@ -0,0 +1,68 @@ +use crate::cache::Cache; +use ethereum::{BlockAny, TransactionV2}; +use primitive_types::{H256, U256}; + +#[derive(Debug)] +pub struct Storage { + cache: Cache, +} + +// TODO : Add DB and pull from DB when cache miss +impl Storage { + pub fn new() -> Self { + Self { + cache: Cache::new(None), + } + } +} + +// Block storage +impl Storage { + pub fn get_block_by_number(&self, number: &U256) -> Option { + self.cache.get_block_by_number(number) + } + + pub fn get_block_by_hash(&self, block_hash: &H256) -> Option { + self.cache.get_block_by_hash(block_hash) + } + + pub fn put_block(&self, block: BlockAny) { + self.cache.put_block(block) + } +} + +// Transaction storage +impl Storage { + pub fn get_transaction_by_hash(&self, hash: H256) -> Option { + self.cache.get_transaction_by_hash(&hash) + } + + pub fn get_transaction_by_block_hash_and_index( + &self, + hash: H256, + index: usize, + ) -> Option { + self.cache + .get_transaction_by_block_hash_and_index(&hash, index) + } + + pub fn get_transaction_by_block_number_and_index( + &self, + hash: U256, + index: usize, + ) -> Option { + self.cache + .get_transaction_by_block_number_and_index(&hash, index) + } +} + +// Latest block storage +impl Storage { + pub fn get_latest_block(&self) -> Option { + self.cache.get_latest_block() + } + + pub fn put_latest_block(&self, block: BlockAny) { + self.cache.put_latest_block(block) + } +} diff --git a/lib/ain-grpc/Cargo.toml b/lib/ain-grpc/Cargo.toml index dd1aed9d990..60673ee3d2c 100644 --- a/lib/ain-grpc/Cargo.toml +++ b/lib/ain-grpc/Cargo.toml @@ -23,6 +23,7 @@ tokio = { version = "1.1", features = ["rt-multi-thread"] } tonic = "0.8" primitive-types = "0.12.1" ethereum = "0.14.0" +hex = "0.4.3" [build-dependencies] cxx-gen = "0.7" diff --git a/lib/ain-grpc/src/impls.rs b/lib/ain-grpc/src/impls.rs new file mode 100644 index 00000000000..63cf9720764 --- /dev/null +++ b/lib/ain-grpc/src/impls.rs @@ -0,0 +1,73 @@ +use std::convert::From; +use std::mem::size_of_val; + +use ain_evm::transaction::{SignedTx, TransactionError}; +use ethereum::{BlockAny, TransactionV2}; +use primitive_types::{H160, H256, U256}; + +use crate::codegen::rpc::ffi::{EthBlockInfo, EthTransactionInfo}; + +fn format_hash(hash: H256) -> String { + return format!("{:#x}", hash); +} + +fn format_address(hash: H160) -> String { + return format!("{:#x}", hash); +} + +fn format_number(number: U256) -> String { + return format!("{:#x}", number); +} + +impl From for EthBlockInfo { + fn from(block: BlockAny) -> Self { + EthBlockInfo { + block_number: format!("{:#x}", block.header.number), + hash: format_hash(block.header.hash()), + parent_hash: format_hash(block.header.parent_hash), + nonce: format!("{:#x}", block.header.nonce), + sha3_uncles: format_hash(block.header.ommers_hash), + logs_bloom: format!("{:#x}", block.header.logs_bloom), + transactions_root: format_hash(block.header.transactions_root), + state_root: format_hash(block.header.state_root), + receipt_root: format_hash(block.header.receipts_root), + miner: format!("{:#x}", block.header.beneficiary), + difficulty: format!("{:#x}", block.header.difficulty), + total_difficulty: format_number(block.header.difficulty), + extra_data: format!("{:#x?}", block.header.extra_data.to_ascii_lowercase()), + size: format!("{:#x}", size_of_val(&block)), + gas_limit: format_number(block.header.gas_limit), + gas_used: format_number(block.header.gas_used), + timestamps: format!("0x{:x}", block.header.timestamp), + transactions: block + .transactions + .iter() + .map(|x| x.hash().to_string()) + .collect::>(), + uncles: block + .ommers + .iter() + .map(|x| x.hash().to_string()) + .collect::>(), + } + } +} + +impl TryFrom for EthTransactionInfo { + type Error = TransactionError; + + fn try_from(tx: TransactionV2) -> Result { + let signed_tx: SignedTx = tx.try_into()?; + + println!("signed_tx : {:#?}", signed_tx); + Ok(EthTransactionInfo { + from: format_address(signed_tx.sender), + to: format_address(signed_tx.to().unwrap_or_default()), + gas: signed_tx.gas_limit().as_u64(), + price: signed_tx.gas_price().to_string(), + value: signed_tx.value().to_string(), + data: hex::encode(signed_tx.data()), + nonce: signed_tx.nonce().to_string(), + }) + } +} diff --git a/lib/ain-grpc/src/lib.rs b/lib/ain-grpc/src/lib.rs index 80d87f8d785..74c397476ab 100644 --- a/lib/ain-grpc/src/lib.rs +++ b/lib/ain-grpc/src/lib.rs @@ -3,6 +3,7 @@ extern crate serde; extern crate serde_json; mod codegen; +mod impls; pub mod rpc; use env_logger::{Builder as LogBuilder, Env}; diff --git a/lib/ain-grpc/src/rpc.rs b/lib/ain-grpc/src/rpc.rs index d1c0411ff57..19e0ff95f3d 100644 --- a/lib/ain-grpc/src/rpc.rs +++ b/lib/ain-grpc/src/rpc.rs @@ -4,13 +4,15 @@ use crate::codegen::rpc::{ EthChainIdResult, EthGetBalanceInput, EthGetBalanceResult, EthGetBlockByHashInput, EthGetBlockByNumberInput, EthGetBlockTransactionCountByHashInput, EthGetBlockTransactionCountByHashResult, EthGetBlockTransactionCountByNumberInput, - EthGetBlockTransactionCountByNumberResult, EthMiningResult, EthTransactionInfo, + EthGetBlockTransactionCountByNumberResult, EthGetTransactionByBlockHashAndIndexInput, + EthGetTransactionByBlockNumberAndIndexInput, EthGetTransactionByHashInput, EthMiningResult, + EthTransactionInfo, }, EthService, }; use ain_evm::handler::Handlers; use primitive_types::{H256, U256}; -use std::mem::size_of_val; +use std::convert::Into; use std::sync::Arc; #[allow(non_snake_case)] @@ -48,6 +50,21 @@ pub trait EthServiceApi { fn Eth_Mining(handler: Arc) -> Result; + fn Eth_GetTransactionByHash( + handler: Arc, + input: EthGetTransactionByHashInput, + ) -> Result; + + fn Eth_GetTransactionByBlockHashAndIndex( + handler: Arc, + input: EthGetTransactionByBlockHashAndIndexInput, + ) -> Result; + + fn Eth_GetTransactionByBlockNumberAndIndex( + handler: Arc, + input: EthGetTransactionByBlockNumberAndIndexInput, + ) -> Result; + fn Eth_GetBlockTransactionCountByHash( handler: Arc, input: EthGetBlockTransactionCountByHashInput, @@ -78,9 +95,11 @@ impl EthServiceApi for EthService { let from = from.parse().ok(); let to = to.parse().ok(); + let value: U256 = value.parse().expect("Wrong value format"); + let (_, data) = handler .evm - .call(from, to, value.into(), data.as_bytes(), gas, vec![]); + .call(from, to, value, data.as_bytes(), gas, vec![]); Ok(EthCallResult { data: String::from_utf8_lossy(&*data).to_string(), @@ -98,14 +117,7 @@ impl EthServiceApi for EthService { ) -> Result { let EthGetBalanceInput { address, .. } = input; let address = address.parse().expect("Wrong address"); - let balance = handler - .evm - .state - .read() - .unwrap() - .get(&address) - .map(|addr| addr.balance) - .unwrap_or_default(); + let balance = handler.evm.get_balance(address); Ok(EthGetBalanceResult { balance: balance.to_string(), @@ -118,38 +130,13 @@ impl EthServiceApi for EthService { ) -> Result { let EthGetBlockByHashInput { hash, .. } = input; - let hash = hash.parse().expect("Invalid hash"); - let block = handler.block.get_block_by_hash(hash).unwrap(); - - Ok(EthBlockInfo { - block_number: format!("{:#x}", block.header.number), - hash: format_hash(block.header.hash()), - parent_hash: format_hash(block.header.parent_hash), - nonce: format!("{:#x}", block.header.nonce), - sha3_uncles: format_hash(block.header.ommers_hash), - logs_bloom: format!("{:#x}", block.header.logs_bloom), - transactions_root: format_hash(block.header.transactions_root), - state_root: format_hash(block.header.state_root), - receipt_root: format_hash(block.header.receipts_root), - miner: format!("{:#x}", block.header.beneficiary), - difficulty: format!("{:#x}", block.header.difficulty), - total_difficulty: format_number(block.header.difficulty), - extra_data: format!("{:#x?}", block.header.extra_data.to_ascii_lowercase()), - size: format!("{:#x}", size_of_val(&block)), - gas_limit: format_number(block.header.gas_limit), - gas_used: format_number(block.header.gas_used), - timestamps: format!("0x{:x}", block.header.timestamp), - transactions: block - .transactions - .iter() - .map(|x| x.hash().to_string()) - .collect::>(), - uncles: block - .ommers - .iter() - .map(|x| x.hash().to_string()) - .collect::>(), - }) + let hash: H256 = hash.parse().expect("Invalid hash"); + + handler + .storage + .get_block_by_hash(&hash) + .map(Into::into) + .ok_or(jsonrpsee_core::Error::Custom(String::from("Missing block"))) } fn Eth_ChainId(_handler: Arc) -> Result { @@ -171,7 +158,11 @@ impl EthServiceApi for EthService { fn Eth_BlockNumber( handler: Arc, ) -> Result { - let count = handler.block.blocks.read().unwrap().len(); + let count = handler + .storage + .get_latest_block() + .map(|block| block.header.number) + .unwrap_or_default(); Ok(EthBlockNumberResult { block_number: format!("0x{:x}", count), @@ -184,38 +175,12 @@ impl EthServiceApi for EthService { ) -> Result { let EthGetBlockByNumberInput { number, .. } = input; - let number: usize = number.parse().ok().unwrap(); - let block = handler.block.get_block_by_number(number).unwrap(); - - Ok(EthBlockInfo { - block_number: format!("{:#x}", block.header.number), - hash: format_hash(block.header.hash()), - parent_hash: format_hash(block.header.parent_hash), - nonce: format!("{:#x}", block.header.nonce), - sha3_uncles: format_hash(block.header.ommers_hash), - logs_bloom: format!("{:#x}", block.header.logs_bloom), - transactions_root: format_hash(block.header.transactions_root), - state_root: format_hash(block.header.state_root), - receipt_root: format_hash(block.header.receipts_root), - miner: format!("{:#x}", block.header.beneficiary), - difficulty: format!("{:#x}", block.header.difficulty), - total_difficulty: format_number(block.header.difficulty), - extra_data: format!("{:#x?}", block.header.extra_data.to_ascii_lowercase()), - size: format!("{:#x}", size_of_val(&block)), - gas_limit: format_number(block.header.gas_limit), - gas_used: format_number(block.header.gas_used), - timestamps: format!("{:#x}", block.header.timestamp), - transactions: block - .transactions - .iter() - .map(|x| x.hash().to_string()) - .collect::>(), - uncles: block - .ommers - .iter() - .map(|x| x.hash().to_string()) - .collect::>(), - }) + let number: U256 = number.parse().ok().unwrap(); + handler + .storage + .get_block_by_number(&number) + .map(Into::into) + .ok_or(jsonrpsee_core::Error::Custom(String::from("Missing block"))) } fn Eth_Mining(_handler: Arc) -> Result { @@ -224,19 +189,48 @@ impl EthServiceApi for EthService { Ok(EthMiningResult { is_mining: mining }) } - fn Eth_GetBlockTransactionCountByHash( + fn Eth_GetTransactionByHash( handler: Arc, - input: EthGetBlockTransactionCountByHashInput, - ) -> Result { - let EthGetBlockTransactionCountByHashInput { block_hash } = input; + input: EthGetTransactionByHashInput, + ) -> Result { + let hash: H256 = input.hash.parse().ok().unwrap(); + handler + .storage + .get_transaction_by_hash(hash) + .and_then(|tx| tx.try_into().ok()) + .ok_or(jsonrpsee_core::Error::Custom(String::from( + "Missing transaction", + ))) + } - let block_hash = block_hash.parse().expect("Invalid hash"); - let block = handler.block.get_block_by_hash(block_hash).unwrap(); - let count = block.transactions.len(); + fn Eth_GetTransactionByBlockHashAndIndex( + handler: Arc, + input: EthGetTransactionByBlockHashAndIndexInput, + ) -> Result { + let hash: H256 = input.block_hash.parse().ok().unwrap(); + let index: usize = input.index.parse().ok().unwrap(); + handler + .storage + .get_transaction_by_block_hash_and_index(hash, index) + .and_then(|tx| tx.try_into().ok()) + .ok_or(jsonrpsee_core::Error::Custom(String::from( + "Missing transaction", + ))) + } - Ok(EthGetBlockTransactionCountByHashResult { - number_transaction: format!("{:#x}", count), - }) + fn Eth_GetTransactionByBlockNumberAndIndex( + handler: Arc, + input: EthGetTransactionByBlockNumberAndIndexInput, + ) -> Result { + let number: U256 = input.block_number.parse().ok().unwrap(); + let index: usize = input.index.parse().ok().unwrap(); + handler + .storage + .get_transaction_by_block_number_and_index(number, index) + .and_then(|tx| tx.try_into().ok()) + .ok_or(jsonrpsee_core::Error::Custom(String::from( + "Missing transaction", + ))) } fn Eth_GetBlockTransactionCountByNumber( @@ -253,12 +247,19 @@ impl EthServiceApi for EthService { number_transaction: format!("{:#x}", count), }) } -} -fn format_hash(hash: H256) -> String { - return format!("{:#x}", hash); -} + fn Eth_GetBlockTransactionCountByHash( + handler: Arc, + input: EthGetBlockTransactionCountByHashInput, + ) -> Result { + let EthGetBlockTransactionCountByHashInput { block_hash } = input; -fn format_number(number: U256) -> String { - return format!("{:#x}", number); + let block_hash = block_hash.parse().expect("Invalid hash"); + let block = handler.block.get_block_by_hash(block_hash).unwrap(); + let count = block.transactions.len(); + + Ok(EthGetBlockTransactionCountByHashResult { + number_transaction: format!("{:#x}", count), + }) + } } diff --git a/lib/ain-grpc/src/tests.rs b/lib/ain-grpc/src/tests.rs index 8eee7939703..8d29007904e 100644 --- a/lib/ain-grpc/src/tests.rs +++ b/lib/ain-grpc/src/tests.rs @@ -6,15 +6,19 @@ use std::sync::Arc; use primitive_types::{H160, U256}; -use ain_evm::handler::Handlers; - use crate::{ codegen::{ rpc::EthService, - types::{EthCallInput, EthGetBalanceInput, EthGetBlockByHashInput, EthTransactionInfo}, + types::{ + EthCallInput, EthGetBalanceInput, EthGetBlockByHashInput, + EthGetTransactionByBlockHashAndIndexInput, EthGetTransactionByBlockNumberAndIndexInput, + EthGetTransactionByHashInput, EthTransactionInfo, + }, }, rpc::EthServiceApi, }; +use ain_evm::handler::Handlers; +use ain_evm::transaction::SignedTx; const ALICE: &str = "0x0000000000000000000000000000000000000000"; const BOB: &str = "0x0000000000000000000000000000000000000001"; @@ -85,9 +89,12 @@ fn should_get_block_by_hash() { Vec::new(), ); handler.block.connect_block(block.clone()); + handler.storage.put_block(block.clone()); - let binding = handler.block.block_map.read().unwrap(); - let _bno = binding.get(&block.header.hash()).unwrap(); + let block = handler + .block + .get_block_by_hash(block.header.hash()) + .unwrap(); let input = EthGetBlockByHashInput { hash: format!("{:x}", block.header.hash()), @@ -99,3 +106,165 @@ fn should_get_block_by_hash() { format!("0x{:x}", block.header.hash()) ); } + +#[test] +fn should_get_transaction_by_hash() { + let handler = Arc::new(Handlers::new()); + let signed_tx: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a0b0842b0c78dd7fc33584ec9a81ab5104fe70169878de188ba6c11fe7605e298aa0735dc483f625f17d68d1e1fae779b7160612628e6dde9eecf087892fe60bba4e".try_into().unwrap(); + let tx_hashes = vec![signed_tx.clone().transaction]; + let block = BlockV2::new( + PartialHeader { + parent_hash: Default::default(), + beneficiary: Default::default(), + state_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: 0, + extra_data: vec![], + mix_hash: Default::default(), + nonce: Default::default(), + }, + tx_hashes, + Vec::new(), + ); + + handler.block.connect_block(block.clone()); + handler.storage.put_block(block.clone()); + + let input = EthGetTransactionByHashInput { + hash: format!("{:x}", signed_tx.transaction.hash()), + }; + + let res = EthService::Eth_GetTransactionByHash(handler.clone(), input.clone().into()).unwrap(); + + assert_eq!(res.from.parse::().unwrap(), signed_tx.sender); + assert_eq!(res.to.parse::().ok(), signed_tx.to()); + assert_eq!(res.gas, signed_tx.gas_limit().as_u64()); + assert_eq!( + U256::from_str_radix(&res.price, 10).unwrap(), + signed_tx.gas_price() + ); + assert_eq!( + U256::from_str_radix(&res.value, 10).unwrap(), + signed_tx.value() + ); + assert_eq!(res.data, hex::encode(signed_tx.data())); + assert_eq!( + U256::from_str_radix(&res.nonce, 10).unwrap(), + signed_tx.nonce() + ); +} + +#[test] +fn should_get_transaction_by_block_hash_and_index() { + let handler = Arc::new(Handlers::new()); + let signed_tx: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a0b0842b0c78dd7fc33584ec9a81ab5104fe70169878de188ba6c11fe7605e298aa0735dc483f625f17d68d1e1fae779b7160612628e6dde9eecf087892fe60bba4e".try_into().unwrap(); + let tx_hashes = vec![signed_tx.clone().transaction]; + let block = BlockV2::new( + PartialHeader { + parent_hash: Default::default(), + beneficiary: Default::default(), + state_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: 0, + extra_data: vec![], + mix_hash: Default::default(), + nonce: Default::default(), + }, + tx_hashes, + Vec::new(), + ); + + handler.block.connect_block(block.clone()); + handler.storage.put_block(block.clone()); + + let input = EthGetTransactionByBlockHashAndIndexInput { + block_hash: format!("{:x}", block.header.hash()), + index: String::from("0"), + }; + + let res = + EthService::Eth_GetTransactionByBlockHashAndIndex(handler.clone(), input.clone().into()) + .unwrap(); + + assert_eq!(res.from.parse::().unwrap(), signed_tx.sender); + assert_eq!(res.to.parse::().ok(), signed_tx.to()); + assert_eq!(res.gas, signed_tx.gas_limit().as_u64()); + assert_eq!( + U256::from_str_radix(&res.price, 10).unwrap(), + signed_tx.gas_price() + ); + assert_eq!( + U256::from_str_radix(&res.value, 10).unwrap(), + signed_tx.value() + ); + assert_eq!(res.data, hex::encode(signed_tx.data())); + assert_eq!( + U256::from_str_radix(&res.nonce, 10).unwrap(), + signed_tx.nonce() + ); +} + +#[test] +fn should_get_transaction_by_block_number_and_index() { + let handler = Arc::new(Handlers::new()); + let signed_tx: SignedTx = "f86b02830186a0830186a094a8f7c4c78c36e54c3950ad58dad24ca5e0191b2989056bc75e2d631000008025a0b0842b0c78dd7fc33584ec9a81ab5104fe70169878de188ba6c11fe7605e298aa0735dc483f625f17d68d1e1fae779b7160612628e6dde9eecf087892fe60bba4e".try_into().unwrap(); + let tx_hashes = vec![signed_tx.clone().transaction]; + let block = BlockV2::new( + PartialHeader { + parent_hash: Default::default(), + beneficiary: Default::default(), + state_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: 0, + extra_data: vec![], + mix_hash: Default::default(), + nonce: Default::default(), + }, + tx_hashes, + Vec::new(), + ); + + handler.block.connect_block(block.clone()); + handler.storage.put_block(block.clone()); + + let input = EthGetTransactionByBlockNumberAndIndexInput { + block_number: format!("{:x}", block.header.number), + index: String::from("0"), + }; + + let res = + EthService::Eth_GetTransactionByBlockNumberAndIndex(handler.clone(), input.clone().into()) + .unwrap(); + + assert_eq!(res.from.parse::().unwrap(), signed_tx.sender); + assert_eq!(res.to.parse::().ok(), signed_tx.to()); + assert_eq!(res.gas, signed_tx.gas_limit().as_u64()); + assert_eq!( + U256::from_str_radix(&res.price, 10).unwrap(), + signed_tx.gas_price() + ); + assert_eq!( + U256::from_str_radix(&res.value, 10).unwrap(), + signed_tx.value() + ); + assert_eq!(res.data, hex::encode(signed_tx.data())); + assert_eq!( + U256::from_str_radix(&res.nonce, 10).unwrap(), + signed_tx.nonce() + ); +} diff --git a/lib/proto/rpc/eth.proto b/lib/proto/rpc/eth.proto index 1cf070cf49f..e8f84f117e6 100644 --- a/lib/proto/rpc/eth.proto +++ b/lib/proto/rpc/eth.proto @@ -28,10 +28,18 @@ service eth { rpc Eth_GetBlockByNumber(types.EthGetBlockByNumberInput) returns (types.EthBlockInfo); + /// Returns the information about a transaction from a transaction hash. + rpc Eth_GetTransactionByHash(types.EthGetTransactionByHashInput) returns (types.EthTransactionInfo); + + /// Returns information about a transaction given a blockhash and transaction index position. + rpc Eth_GetTransactionByBlockHashAndIndex(types.EthGetTransactionByBlockHashAndIndexInput) returns (types.EthTransactionInfo); + + /// Returns information about a transaction given a block number and transaction index position. + rpc Eth_GetTransactionByBlockNumberAndIndex(types.EthGetTransactionByBlockNumberAndIndexInput) returns (types.EthTransactionInfo); + rpc Eth_Mining(google.protobuf.Empty) returns (types.EthMiningResult); rpc Eth_GetBlockTransactionCountByHash(types.EthGetBlockTransactionCountByHashInput) returns (types.EthGetBlockTransactionCountByHashResult); rpc Eth_GetBlockTransactionCountByNumber(types.EthGetBlockTransactionCountByNumberInput) returns (types.EthGetBlockTransactionCountByNumberResult); - } diff --git a/lib/proto/types/eth.proto b/lib/proto/types/eth.proto index 8b2d7c2d012..f4d57fc1705 100644 --- a/lib/proto/types/eth.proto +++ b/lib/proto/types/eth.proto @@ -9,10 +9,10 @@ message EthTransactionInfo { optional string from = 1; // The address from which the transaction is sent optional string to = 2; // The address to which the transaction is addressed optional uint64 gas = 3; // The integer of gas provided for the transaction execution - optional uint64 price = 4; // The integer of gas price used for each paid gas encoded as hexadecimal - optional uint64 value = 5; // The integer of value sent with this transaction encoded as hexadecimal + optional string price = 4; // The integer of gas price used for each paid gas encoded as hexadecimal + optional string value = 5; // The integer of value sent with this transaction encoded as hexadecimal optional string data = 6; // The hash of the method signature and encoded parameters. - optional uint64 nonce = 7; // The integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce. + optional string nonce = 7; // The integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce. } message EthChainIdResult {