diff --git a/lib/ain-evm/src/handler.rs b/lib/ain-evm/src/handler.rs index 0bed2d4c3f..203524dd04 100644 --- a/lib/ain-evm/src/handler.rs +++ b/lib/ain-evm/src/handler.rs @@ -2,6 +2,7 @@ use crate::backend::{EVMBackend, Vicinity}; use crate::block::BlockHandler; use crate::evm::EVMHandler; use crate::executor::{AinExecutor, TxResponse}; +use crate::log::LogHandler; use crate::receipt::ReceiptHandler; use crate::storage::traits::BlockStorage; use crate::storage::Storage; @@ -23,6 +24,7 @@ pub struct Handlers { pub evm: EVMHandler, pub block: BlockHandler, pub receipt: ReceiptHandler, + pub logs: LogHandler, pub storage: Arc, } @@ -54,6 +56,7 @@ impl Handlers { evm: EVMHandler::new_from_json(Arc::clone(&storage), PathBuf::from(path)), block: BlockHandler::new(Arc::clone(&storage)), receipt: ReceiptHandler::new(Arc::clone(&storage)), + logs: LogHandler::new(Arc::clone(&storage)), storage, }) } else { @@ -62,6 +65,7 @@ impl Handlers { evm: EVMHandler::restore(Arc::clone(&storage)), block: BlockHandler::new(Arc::clone(&storage)), receipt: ReceiptHandler::new(Arc::clone(&storage)), + logs: LogHandler::new(Arc::clone(&storage)), storage, }) } @@ -221,6 +225,8 @@ impl Handlers { let base_fee = self.block.calculate_base_fee(parent_hash); self.block.connect_block(block.clone(), base_fee); + self.logs + .generate_logs_from_receipts(&receipts, block.header.number); self.receipt.put_receipts(receipts); } diff --git a/lib/ain-evm/src/lib.rs b/lib/ain-evm/src/lib.rs index 1a59caaa68..f191f9fe07 100644 --- a/lib/ain-evm/src/lib.rs +++ b/lib/ain-evm/src/lib.rs @@ -6,6 +6,7 @@ pub mod executor; mod fee; mod genesis; pub mod handler; +pub mod log; pub mod receipt; pub mod runtime; pub mod storage; diff --git a/lib/ain-evm/src/log.rs b/lib/ain-evm/src/log.rs new file mode 100644 index 0000000000..2db8484bfa --- /dev/null +++ b/lib/ain-evm/src/log.rs @@ -0,0 +1,98 @@ +use crate::receipt::Receipt; +use crate::storage::traits::LogStorage; +use crate::storage::Storage; +use ethereum::ReceiptV3; +use log::debug; +use primitive_types::{H160, H256, U256}; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LogIndex { + pub block_hash: H256, + pub topics: Vec, + pub data: Vec, + pub log_index: U256, + pub address: H160, + pub removed: bool, + pub transaction_hash: H256, + pub transaction_index: usize, +} + +pub struct LogHandler { + storage: Arc, +} + +impl LogHandler { + pub fn new(storage: Arc) -> Self { + Self { storage } + } + + pub fn generate_logs_from_receipts(&self, receipts: &Vec, block_number: U256) { + let mut logs_map: HashMap> = HashMap::new(); + let mut log_index = 0; // log index is a block level index + for receipt in receipts { + let logs = match &receipt.receipt { + ReceiptV3::Legacy(r) => &r.logs, + ReceiptV3::EIP2930(r) => &r.logs, + ReceiptV3::EIP1559(r) => &r.logs, + }; + + for log in logs { + let map = logs_map.entry(log.address).or_insert(Vec::new()); + + map.push(LogIndex { + block_hash: receipt.block_hash, + topics: log.clone().topics, + data: log.clone().data, + log_index: U256::from(log_index), + address: log.clone().address, + removed: false, // hardcoded as no reorgs on DeFiChain + transaction_hash: receipt.tx_hash, + transaction_index: receipt.tx_index, + }); + + log_index += 1; + } + } + + logs_map + .into_iter() + .for_each(|(address, logs)| self.storage.put_logs(address, logs, block_number)); + } + + // get logs at a block height and filter for topics + pub fn get_logs( + &self, + address: Option>, + topics: Option>, + block_number: U256, + ) -> Vec { + debug!("Getting logs for block {:#x?}", block_number); + let logs = self.storage.get_logs(&block_number).unwrap_or_default(); + + let logs = match address { + None => logs.into_iter().flat_map(|(_, log)| log).collect(), + Some(addresses) => { + // filter by addresses + logs.into_iter() + .filter(|(address, _)| addresses.contains(address)) + .into_iter() + .flat_map(|(_, log)| log) + .collect() + } + }; + + match topics { + None => logs, + Some(topics) => logs + .into_iter() + .filter(|log| { + let set: HashSet<_> = log.topics.iter().copied().collect(); + topics.iter().any(|item| set.contains(item)) + }) + .collect(), + } + } +} diff --git a/lib/ain-evm/src/storage/data_handler.rs b/lib/ain-evm/src/storage/data_handler.rs index 181a40b6f4..0cd767459a 100644 --- a/lib/ain-evm/src/storage/data_handler.rs +++ b/lib/ain-evm/src/storage/data_handler.rs @@ -1,10 +1,12 @@ use std::{collections::HashMap, sync::RwLock}; +use crate::log::LogIndex; use ethereum::{BlockAny, TransactionV2}; -use primitive_types::{H256, U256}; +use primitive_types::{H160, H256, U256}; use std::borrow::ToOwned; use crate::receipt::Receipt; +use crate::storage::traits::LogStorage; use super::{ code::CodeHistory, @@ -21,6 +23,7 @@ pub static RECEIPT_MAP_PATH: &str = "receipt_map.bin"; pub static CODE_MAP_PATH: &str = "code_map.bin"; pub static TRANSACTION_DATA_PATH: &str = "transaction_data.bin"; pub static BASE_FEE_MAP_PATH: &str = "base_fee_map.bin"; +pub static ADDRESS_LOGS_MAP_PATH: &str = "address_logs_map.bin"; type BlockHashtoBlock = HashMap; type Blocks = HashMap; @@ -28,12 +31,14 @@ type TxHashToTx = HashMap; type LatestBlockNumber = U256; type TransactionHashToReceipt = HashMap; type BlockHashtoBaseFee = HashMap; +type AddressToLogs = HashMap>>; impl PersistentState for BlockHashtoBlock {} impl PersistentState for Blocks {} impl PersistentState for LatestBlockNumber {} impl PersistentState for TransactionHashToReceipt {} impl PersistentState for TxHashToTx {} +impl PersistentState for AddressToLogs {} #[derive(Debug, Default)] pub struct BlockchainDataHandler { @@ -48,6 +53,8 @@ pub struct BlockchainDataHandler { base_fee_map: RwLock, code_map: RwLock, + + address_logs_map: RwLock, } impl BlockchainDataHandler { @@ -75,6 +82,9 @@ impl BlockchainDataHandler { BlockHashtoBaseFee::load_from_disk(BASE_FEE_MAP_PATH).unwrap_or_default(), ), code_map: RwLock::new(CodeHistory::load_from_disk(CODE_MAP_PATH).unwrap_or_default()), + address_logs_map: RwLock::new( + AddressToLogs::load_from_disk(ADDRESS_LOGS_MAP_PATH).unwrap_or_default(), + ), } } } @@ -203,6 +213,25 @@ impl ReceiptStorage for BlockchainDataHandler { } } +impl LogStorage for BlockchainDataHandler { + fn get_logs(&self, block_number: &U256) -> Option>> { + self.address_logs_map + .read() + .unwrap() + .get(block_number) + .map(ToOwned::to_owned) + } + + fn put_logs(&self, address: H160, logs: Vec, block_number: U256) { + let mut address_logs_map = self.address_logs_map.write().unwrap(); + + let address_map = address_logs_map + .entry(block_number) + .or_insert(HashMap::new()); + address_map.insert(address, logs); + } +} + impl FlushableStorage for BlockchainDataHandler { fn flush(&self) -> Result<(), PersistentStateError> { self.block_map @@ -227,7 +256,11 @@ impl FlushableStorage for BlockchainDataHandler { self.base_fee_map .write() .unwrap() - .save_to_disk(BASE_FEE_MAP_PATH) + .save_to_disk(BASE_FEE_MAP_PATH)?; + self.address_logs_map + .write() + .unwrap() + .save_to_disk(ADDRESS_LOGS_MAP_PATH) } } diff --git a/lib/ain-evm/src/storage/mod.rs b/lib/ain-evm/src/storage/mod.rs index 622527f001..83b1b1b14b 100644 --- a/lib/ain-evm/src/storage/mod.rs +++ b/lib/ain-evm/src/storage/mod.rs @@ -3,10 +3,13 @@ mod code; mod data_handler; pub mod traits; +use crate::log::LogIndex; use ethereum::{BlockAny, TransactionV2}; -use primitive_types::{H256, U256}; +use primitive_types::{H160, H256, U256}; +use std::collections::HashMap; use crate::receipt::Receipt; +use crate::storage::traits::LogStorage; use self::{ cache::Cache, @@ -174,6 +177,17 @@ impl ReceiptStorage for Storage { } } +impl LogStorage for Storage { + fn get_logs(&self, block_number: &U256) -> Option>> { + self.blockchain_data_handler.get_logs(block_number) + } + + fn put_logs(&self, address: H160, logs: Vec, block_number: U256) { + self.blockchain_data_handler + .put_logs(address, logs, block_number) + } +} + impl FlushableStorage for Storage { fn flush(&self) -> Result<(), PersistentStateError> { self.blockchain_data_handler.flush() diff --git a/lib/ain-evm/src/storage/traits.rs b/lib/ain-evm/src/storage/traits.rs index e43d86625b..e17bf81696 100644 --- a/lib/ain-evm/src/storage/traits.rs +++ b/lib/ain-evm/src/storage/traits.rs @@ -3,9 +3,11 @@ use ethereum::BlockAny; use ethereum::TransactionV2; use keccak_hash::H256; use log::debug; -use primitive_types::U256; +use primitive_types::{H160, U256}; +use std::collections::HashMap; use std::fs::File; +use crate::log::LogIndex; use std::fmt; use std::io; use std::io::Write; @@ -44,6 +46,11 @@ pub trait ReceiptStorage { fn put_receipts(&self, receipts: Vec); } +pub trait LogStorage { + fn get_logs(&self, block_number: &U256) -> Option>>; + fn put_logs(&self, address: H160, logs: Vec, block_number: U256); +} + pub trait FlushableStorage { fn flush(&self) -> Result<(), PersistentStateError>; } diff --git a/lib/ain-grpc/src/lib.rs b/lib/ain-grpc/src/lib.rs index ed8ef1fc37..0f18a89f01 100644 --- a/lib/ain-grpc/src/lib.rs +++ b/lib/ain-grpc/src/lib.rs @@ -10,6 +10,7 @@ mod impls; mod receipt; pub mod rpc; mod transaction; +mod transaction_log; mod transaction_request; mod utils; diff --git a/lib/ain-grpc/src/rpc/eth.rs b/lib/ain-grpc/src/rpc/eth.rs index 402d735d0f..643808feec 100644 --- a/lib/ain-grpc/src/rpc/eth.rs +++ b/lib/ain-grpc/src/rpc/eth.rs @@ -10,6 +10,7 @@ use ain_cpp_imports::get_eth_priv_key; use ain_evm::executor::TxResponse; use ain_evm::handler::Handlers; +use crate::transaction_log::{GetLogsRequest, LogResult}; use ain_evm::storage::traits::{BlockStorage, ReceiptStorage, TransactionStorage}; use ain_evm::transaction::{SignedTx, TransactionError}; use ethereum::{EnvelopedEncodable, TransactionV2}; @@ -204,6 +205,9 @@ pub trait MetachainRPC { #[method(name = "maxPriorityFeePerGas")] fn max_priority_fee_per_gas(&self) -> RpcResult; + + #[method(name = "getLogs")] + fn get_logs(&self, input: GetLogsRequest) -> RpcResult>; } pub struct MetachainRPCModule { @@ -721,6 +725,59 @@ impl MetachainRPCServer for MetachainRPCModule { fn max_priority_fee_per_gas(&self) -> RpcResult { Ok(self.handler.block.suggested_priority_fee()) } + + fn get_logs(&self, input: GetLogsRequest) -> RpcResult> { + if let (Some(_), Some(_)) = (input.block_hash, input.to_block.or(input.from_block)) { + return Err(Error::Custom(String::from( + "cannot specify both blockHash and fromBlock/toBlock, choose one or the other", + ))); + } + + let block_numbers = match input.block_hash { + None => { + // use fromBlock-toBlock + let mut block_number = self.block_number_to_u256(input.from_block); + let to_block_number = self.block_number_to_u256(input.to_block); + let mut block_numbers = Vec::new(); + + if block_number > to_block_number { + return Err(Error::Custom(format!( + "fromBlock ({}) > toBlock ({})", + format_u256(block_number), + format_u256(to_block_number) + ))); + } + + while block_number <= to_block_number { + block_numbers.push(block_number); + block_number += U256::one(); + } + + block_numbers + } + Some(block_hash) => { + vec![ + self.handler + .storage + .get_block_by_hash(&block_hash) + .ok_or_else(|| Error::Custom(String::from("Unable to find block hash")))? + .header + .number, + ] + } + }; + + Ok(block_numbers + .into_iter() + .flat_map(|block_number| { + self.handler + .logs + .get_logs(input.clone().address, input.clone().topics, block_number) + .into_iter() + .map(LogResult::from) + }) + .collect()) + } } fn sign( diff --git a/lib/ain-grpc/src/transaction_log.rs b/lib/ain-grpc/src/transaction_log.rs new file mode 100644 index 0000000000..f10392e18e --- /dev/null +++ b/lib/ain-grpc/src/transaction_log.rs @@ -0,0 +1,44 @@ +use crate::block::BlockNumber; +use crate::bytes::Bytes; +use ain_evm::log::LogIndex; +use primitive_types::{H160, H256, U256}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LogResult { + pub block_hash: H256, + pub log_index: U256, + pub removed: bool, + pub transaction_hash: H256, + pub transaction_index: usize, + pub address: H160, + pub data: Bytes, + pub topics: Vec, +} + +impl From for LogResult { + fn from(log: LogIndex) -> Self { + Self { + block_hash: log.block_hash, + log_index: log.log_index, + removed: log.removed, + transaction_hash: log.transaction_hash, + transaction_index: log.transaction_index, + address: log.address, + data: Bytes::from(log.data), + topics: log.topics, + } + } +} + +/// Call request +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct GetLogsRequest { + pub address: Option>, + pub block_hash: Option, + pub from_block: Option, + pub to_block: Option, + pub topics: Option>, +}