diff --git a/src/rust/crates/ain-evm-ffi/src/lib.rs b/src/rust/crates/ain-evm-ffi/src/lib.rs index 9d73c8ae8ca..22919a1deb3 100644 --- a/src/rust/crates/ain-evm-ffi/src/lib.rs +++ b/src/rust/crates/ain-evm-ffi/src/lib.rs @@ -87,6 +87,6 @@ fn evm_queue_tx(context: u64, raw_tx: &str) -> Result> { use rlp::Encodable; fn evm_finalise(context: u64, update_state: bool) -> Result, Box> { - let (block, _failed_tx) = RUNTIME.handlers.evm.finalize_block(context, update_state)?; + let (block, _failed_tx) = RUNTIME.handlers.finalize_block(context, update_state)?; Ok(block.header.rlp_bytes().into()) } diff --git a/src/rust/crates/ain-evm/src/block.rs b/src/rust/crates/ain-evm/src/block.rs index 89d1a49b939..566b53d5a13 100644 --- a/src/rust/crates/ain-evm/src/block.rs +++ b/src/rust/crates/ain-evm/src/block.rs @@ -12,7 +12,7 @@ use std::sync::{Arc, RwLock}; pub static BLOCK_MAP_PATH: &str = "block_map.bin"; pub static BLOCK_DATA_PATH: &str = "block_data.bin"; -type BlockHashtoBlock = HashMap; +type BlockHashtoBlock = HashMap; type Blocks = Vec; pub struct BlockHandler { @@ -33,7 +33,7 @@ impl PersistentState for BlockHashtoBlock { let mut file = File::open(path)?; let mut data = Vec::new(); file.read_to_end(&mut data)?; - let new_state: HashMap = bincode::deserialize(&data)?; + let new_state: HashMap = bincode::deserialize(&data)?; Ok(new_state) } else { Ok(Self::new()) @@ -79,7 +79,7 @@ impl BlockHandler { blocks.push(block.clone()); let mut blockhash = self.block_map.write().unwrap(); - blockhash.insert(block.header.hash(), block.header.number); + blockhash.insert(block.header.hash(), blocks.len() - 1); } pub fn flush(&self) -> Result<(), PersistentStateError> { @@ -90,7 +90,7 @@ impl BlockHandler { self.blocks.write().unwrap().save_to_disk(BLOCK_DATA_PATH) } - pub fn get_block_hash(&self, hash: H256) -> Result { + pub fn get_block_by_hash(&self, hash: H256) -> Result { let block_map = self.block_map.read().unwrap(); let block_number = *block_map .get(&hash) @@ -98,7 +98,17 @@ impl BlockHandler { let blocks = self.blocks.read().unwrap(); let block = blocks - .get(block_number.as_usize()) + .get(block_number) + .ok_or(BlockHandlerError::BlockNotFound)? + .clone(); + + Ok(block) + } + + pub fn get_block_by_number(&self, count: usize) -> Result { + let blocks = self.blocks.read().unwrap(); + let block = blocks + .get(count) .ok_or(BlockHandlerError::BlockNotFound)? .clone(); diff --git a/src/rust/crates/ain-evm/src/evm.rs b/src/rust/crates/ain-evm/src/evm.rs index 3fdccbfe17a..b57b5abb295 100644 --- a/src/rust/crates/ain-evm/src/evm.rs +++ b/src/rust/crates/ain-evm/src/evm.rs @@ -140,56 +140,6 @@ impl EVMHandler { self.tx_queues.add_signed_tx(context, signed_tx); Ok(()) } - - pub fn finalize_block( - &self, - context: u64, - update_state: bool, - ) -> Result<(Block, Vec), Box> { - let mut tx_hashes = Vec::with_capacity(self.tx_queues.len(context)); - let mut failed_tx_hashes = Vec::with_capacity(self.tx_queues.len(context)); - let vicinity = get_vicinity(None, None); - let state = self.tx_queues.state(context).expect("Wrong context"); - let backend = MemoryBackend::new(&vicinity, state); - let mut executor = AinExecutor::new(backend); - - for signed_tx in self.tx_queues.drain_all(context) { - let tx_response = executor.exec(&signed_tx); - if tx_response.exit_reason.is_succeed() { - tx_hashes.push(signed_tx.transaction); - } else { - failed_tx_hashes.push(signed_tx.transaction) - } - } - - self.tx_queues.remove(context); - - if update_state { - let mut state = self.state.write().unwrap(); - *state = executor.backend().state().clone(); - } - - let block = Block::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: Default::default(), - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: Default::default(), - }, - tx_hashes, - Vec::new(), - ); - Ok((block, failed_tx_hashes)) - } } impl EVMHandler { diff --git a/src/rust/crates/ain-evm/src/handler.rs b/src/rust/crates/ain-evm/src/handler.rs index 14d18e501ef..4dce31336af 100644 --- a/src/rust/crates/ain-evm/src/handler.rs +++ b/src/rust/crates/ain-evm/src/handler.rs @@ -1,5 +1,12 @@ use crate::block::BlockHandler; use crate::evm::EVMHandler; +use crate::executor::AinExecutor; +use crate::traits::Executor; +use ethereum::{Block, PartialHeader, TransactionV2}; +use evm::backend::{MemoryBackend, MemoryVicinity}; +use primitive_types::{H160, H256, U256}; +use std::error::Error; +use std::time::{SystemTime, UNIX_EPOCH}; pub struct Handlers { pub evm: EVMHandler, @@ -13,4 +20,83 @@ impl Handlers { block: BlockHandler::new(), } } + + pub fn finalize_block( + &self, + context: u64, + update_state: bool, + ) -> Result<(Block, 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); + let state = self.evm.tx_queues.state(context).expect("Wrong context"); + let backend = MemoryBackend::new(&vicinity, state); + let mut executor = AinExecutor::new(backend); + + for signed_tx in self.evm.tx_queues.drain_all(context) { + let tx_response = executor.exec(&signed_tx); + if tx_response.exit_reason.is_succeed() { + tx_hashes.push(signed_tx.transaction); + } else { + failed_tx_hashes.push(signed_tx.transaction) + } + } + + 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)) + }; + + let block = Block::new( + PartialHeader { + parent_hash, + beneficiary: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + logs_bloom: Default::default(), + difficulty: Default::default(), + number: U256::from(number), + gas_limit: Default::default(), + gas_used: Default::default(), + timestamp: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + extra_data: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + }, + tx_hashes, + Vec::new(), + ); + + self.block.connect_block(block.clone()); + + Ok((block, failed_tx_hashes)) + } +} + +fn get_vicinity(origin: Option, gas_price: Option) -> MemoryVicinity { + MemoryVicinity { + gas_price: gas_price.unwrap_or(U256::MAX), + origin: origin.unwrap_or_default(), + block_hashes: Vec::new(), + block_number: Default::default(), + block_coinbase: Default::default(), + block_timestamp: Default::default(), + block_difficulty: Default::default(), + block_gas_limit: U256::MAX, + chain_id: U256::one(), + block_base_fee_per_gas: U256::MAX, + } } diff --git a/src/rust/crates/ain-evm/tests/block.rs b/src/rust/crates/ain-evm/tests/block.rs index e05f09e60fd..dbf2cbce552 100644 --- a/src/rust/crates/ain-evm/tests/block.rs +++ b/src/rust/crates/ain-evm/tests/block.rs @@ -21,7 +21,7 @@ fn test_finalize_block_and_do_not_update_state() { handler.evm.tx_queues.add_signed_tx(context, tx1); let old_state = handler.evm.state.read().unwrap(); - let _ = handler.evm.finalize_block(context, false).unwrap(); + let _ = handler.finalize_block(context, false).unwrap(); let new_state = handler.evm.state.read().unwrap(); assert_eq!(*new_state, *old_state); @@ -58,7 +58,7 @@ fn test_finalize_block_and_update_state() { assert_eq!(handler.evm.tx_queues.len(context), 3); assert_eq!(handler.evm.tx_queues.len(handler.evm.get_context()), 0); - let (block, failed_txs) = handler.evm.finalize_block(context, true).unwrap(); + let (block, failed_txs) = handler.finalize_block(context, true).unwrap(); assert_eq!( block.transactions, vec![tx1, tx2] @@ -131,7 +131,7 @@ fn test_deploy_and_call_smart_contract() { .tx_queues .add_signed_tx(context, create_smart_contract_tx); - handler.evm.finalize_block(context, true).unwrap(); + handler.finalize_block(context, true).unwrap(); // Fund caller address handler.evm.add_balance( @@ -150,7 +150,7 @@ fn test_deploy_and_call_smart_contract() { .tx_queues .add_signed_tx(context, call_smart_contract_tx); - handler.evm.finalize_block(context, true).unwrap(); + handler.finalize_block(context, true).unwrap(); let smart_contract_storage = handler.evm.get_storage(smart_contract_address); assert_eq!( diff --git a/src/rust/crates/ain-grpc/build.rs b/src/rust/crates/ain-grpc/build.rs index 6ce034f1b05..8eb18efebb2 100644 --- a/src/rust/crates/ain-grpc/build.rs +++ b/src/rust/crates/ain-grpc/build.rs @@ -406,7 +406,7 @@ fn change_types(file: syn::File) -> (HashMap, TokenStream, T modified.extend(quote!(#s)); map.insert(s.ident.to_string(), s.clone()); - s.attrs = Attr::parse("#[derive(Debug, Default, Serialize , Deserialize)]"); + s.attrs = Attr::parse("#[derive(Debug, Default, Serialize , Deserialize, PartialEq)]"); let fields = match &mut s.fields { Fields::Named(ref mut f) => f, _ => unreachable!(), @@ -642,7 +642,7 @@ fn apply_substitutions( .url .as_ref() .map(String::from) - .unwrap_or_else(|| method.name.to_lower_camel_case()); + .unwrap_or_else(|| method.name.to_lowercase()); if method.client { funcs.extend(quote! { #[allow(non_snake_case)] diff --git a/src/rust/crates/ain-grpc/src/rpc.rs b/src/rust/crates/ain-grpc/src/rpc.rs index 6f63633243e..6ef55911cce 100644 --- a/src/rust/crates/ain-grpc/src/rpc.rs +++ b/src/rust/crates/ain-grpc/src/rpc.rs @@ -1,12 +1,17 @@ use crate::codegen::rpc::{ ffi::{ - EthAccountsResult, EthBlockInfo, EthCallInput, EthCallResult, EthGetBalanceInput, - EthGetBalanceResult, EthGetBlockByHashInput, EthGetBlockByHashResult, EthTransactionInfo, + EthAccountsResult, EthBlockInfo, EthBlockNumberResult, EthCallInput, EthCallResult, + EthGetBalanceInput, EthGetBalanceResult, EthGetBlockByHashInput, EthGetBlockByNumberInput, + EthTransactionInfo, }, EthService, }; use ain_evm::handler::Handlers; +use primitive_types::{H256, U256}; +use prost::Message; +use std::hash::Hash; use std::mem::size_of_val; +use std::str::FromStr; use std::sync::Arc; pub trait EthServiceApi { @@ -26,7 +31,16 @@ pub trait EthServiceApi { fn Eth_GetBlockByHash( handler: Arc, input: EthGetBlockByHashInput, - ) -> Result; + ) -> Result; + + fn Eth_BlockNumber( + handler: Arc, + ) -> Result; + + fn Eth_GetBlockByNumber( + handler: Arc, + input: EthGetBlockByNumberInput, + ) -> Result; } impl EthServiceApi for EthService { @@ -85,42 +99,98 @@ impl EthServiceApi for EthService { fn Eth_GetBlockByHash( handler: Arc, input: EthGetBlockByHashInput, - ) -> Result { + ) -> Result { let EthGetBlockByHashInput { hash, .. } = input; let hash = hash.parse().expect("Invalid hash"); - let block = handler.block.get_block_hash(hash).unwrap(); - - Ok(EthGetBlockByHashResult { - block_info: EthBlockInfo { - block_number: block.header.number.to_string(), - hash: block.header.hash().to_string(), - parent_hash: block.header.parent_hash.to_string(), - nonce: block.header.nonce.to_string(), - sha3_uncles: block.header.ommers_hash.to_string(), - logs_bloom: block.header.logs_bloom.to_string(), - transactions_root: block.header.transactions_root.to_string(), - state_root: block.header.state_root.to_string(), - receipt_root: block.header.receipts_root.to_string(), - miner: block.header.beneficiary.to_string(), - difficulty: block.header.difficulty.to_string(), - total_difficulty: block.header.difficulty.to_string(), - extra_data: String::from_utf8(block.header.extra_data.clone()).unwrap(), - size: size_of_val(&block).to_string(), - gas_limit: block.header.gas_limit.to_string(), - gas_used: block.header.gas_used.to_string(), - timestamps: block.header.timestamp.to_string(), - transactions: block - .transactions - .iter() - .map(|x| x.hash().to_string()) - .collect::>(), - uncles: block - .ommers - .iter() - .map(|x| x.hash().to_string()) - .collect::>(), - }, + 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::>(), + }) + } + + fn Eth_BlockNumber( + handler: Arc, + ) -> Result { + let count = handler.block.blocks.read().unwrap().len(); + + Ok(EthBlockNumberResult { + block_number: format!("0x{:x}", count), }) } + + fn Eth_GetBlockByNumber( + handler: Arc, + input: EthGetBlockByNumberInput, + ) -> 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::>(), + }) + } +} + +fn format_hash(hash: H256) -> String { + return format!("{:#x}", hash); +} + +fn format_number(number: U256) -> String { + return format!("{:#x}", number); } diff --git a/src/rust/crates/ain-grpc/src/tests.rs b/src/rust/crates/ain-grpc/src/tests.rs index cc39028ee5c..7d368f926b0 100644 --- a/src/rust/crates/ain-grpc/src/tests.rs +++ b/src/rust/crates/ain-grpc/src/tests.rs @@ -1,5 +1,6 @@ #![cfg(test)] +use ethereum::{BlockV2, PartialHeader}; use std::str::FromStr; use std::sync::Arc; @@ -10,7 +11,7 @@ use ain_evm::handler::Handlers; use crate::{ codegen::{ rpc::EthService, - types::{EthCallInput, EthGetBalanceInput, EthTransactionInfo}, + types::{EthCallInput, EthGetBalanceInput, EthGetBlockByHashInput, EthTransactionInfo}, }, rpc::EthServiceApi, }; @@ -55,8 +56,46 @@ fn should_get_balance() { .evm .add_balance(ctx, H160::from_str(ALICE).unwrap(), U256::from(1337)); - let _ = handler.evm.finalize_block(ctx, true); + let _ = handler.finalize_block(ctx, true); let res2 = EthService::Eth_GetBalance(handler, input.into()); assert_eq!(res2.unwrap().balance, "1337"); } + +#[test] +fn should_get_block_by_hash() { + let handler = Arc::new(Handlers::new()); + 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(), + }, + Vec::new(), + Vec::new(), + ); + handler.block.connect_block(block.clone()); + + let binding = handler.block.block_map.read().unwrap(); + let bno = binding.get(&block.header.hash()).unwrap(); + + let input = EthGetBlockByHashInput { + hash: format!("{:x}", block.header.hash()), + full_transaction: false, + }; + let res = EthService::Eth_GetBlockByHash(handler.clone(), input.clone().into()); + assert_eq!( + format!("{}", res.unwrap().hash), + format!("0x{:x}", block.header.hash()) + ); +} diff --git a/src/rust/protobuf/rpc/eth.proto b/src/rust/protobuf/rpc/eth.proto index 5e7b4f60d77..25ac6b37033 100644 --- a/src/rust/protobuf/rpc/eth.proto +++ b/src/rust/protobuf/rpc/eth.proto @@ -14,9 +14,13 @@ service eth { /// Returns the balance for the given address. rpc Eth_GetBalance(types.EthGetBalanceInput) returns (types.EthGetBalanceResult); - rpc Eth_GetBlockByHash(types.EthGetBlockByHashInput) returns (types.EthGetBlockByHashResult); + rpc Eth_GetBlockByHash(types.EthGetBlockByHashInput) returns (types.EthBlockInfo); /// [ignore] /// Returns the balance for the given address. rpc Eth_SendTransaction(types.EthSendTransactionInput) returns (types.EthSendTransactionResult); + + rpc Eth_BlockNumber(google.protobuf.Empty) returns (types.EthBlockNumberResult); + + rpc Eth_GetBlockByNumber(types.EthGetBlockByNumberInput) returns (types.EthBlockInfo); }