From a91b4f162a628329eb3ea94379bbf1c5b9e0f23b Mon Sep 17 00:00:00 2001 From: Vladimir Motylenko Date: Mon, 4 Jan 2021 12:59:49 +0200 Subject: [PATCH] Feat: Add logs requesting, and receipt logs. --- .dockerignore | 1 + Dockerfile | 2 + client/src/rpc_request.rs | 2 + core/src/evm_rpc_impl/mod.rs | 139 ++++++++++++--------- core/src/rpc.rs | 25 ++++ entrypoint.sh | 11 +- evm-utils/evm-bridge/src/main.rs | 8 +- evm-utils/evm-rpc/src/lib.rs | 119 +++++++++++++++--- evm-utils/evm-state/src/evm_backend.rs | 125 +++++++++--------- evm-utils/evm-state/src/layered_backend.rs | 65 +++++++++- evm-utils/evm-state/src/lib.rs | 24 ++-- evm-utils/evm-state/src/transactions.rs | 5 +- ledger/src/blockstore.rs | 22 ++++ storage-bigtable/src/lib.rs | 9 ++ 14 files changed, 395 insertions(+), 162 deletions(-) diff --git a/.dockerignore b/.dockerignore index e7cab0e8b5..d115ebdd89 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ docs/node_modules/ docs/build/ explorer/node_modules/ programs/bpf/target/ +Dockerfile local-cluster/farf/ core/farf/ diff --git a/Dockerfile b/Dockerfile index 05477f1741..cc8208a6fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,8 @@ COPY ./ /solana #RUN git clone https://github.com/solana-labs/solana WORKDIR /solana RUN cargo build --release +RUN rm /solana/target/release/deps -rf +RUN rm /solana/target/release/build -rf FROM ubuntu:20.04 as dest RUN apt-get update && \ diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index 24d5a8d5f0..14ad5a4710 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -72,6 +72,7 @@ pub enum RpcRequest { EthGetTransactionReceipt, EthCall, EthEstimateGas, + EthGetLogs, } impl fmt::Display for RpcRequest { @@ -142,6 +143,7 @@ impl fmt::Display for RpcRequest { RpcRequest::EthGetTransactionReceipt => "eth_getTransactionReceipt", RpcRequest::EthCall => "eth_call", RpcRequest::EthEstimateGas => "eth_estimateGas", + RpcRequest::EthGetLogs => "eth_getLogs", }; write!(f, "{}", method) diff --git a/core/src/evm_rpc_impl/mod.rs b/core/src/evm_rpc_impl/mod.rs index e5b76b7994..b8453250b8 100644 --- a/core/src/evm_rpc_impl/mod.rs +++ b/core/src/evm_rpc_impl/mod.rs @@ -7,6 +7,7 @@ use sha3::{Digest, Keccak256}; use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel}; use solana_transaction_status::UiTransactionEncoding; use std::convert::TryInto; +use std::str::FromStr; const CHAIN_ID: u64 = 0x77; @@ -32,6 +33,16 @@ fn block_to_commitment(block: Option) -> Option { Some(CommitmentConfig { commitment }) } +fn block_to_confirmed_num(block: Option, meta: &JsonRpcRequestProcessor) -> Option { + let block = block?; + match block.as_str() { + "pending" => None, + "earliest" => Some(meta.get_first_available_block()), + "latest" => Some(meta.get_slot(None)), + v => Hex::::from_hex(&v).ok().map(|f| f.0), + } +} + pub struct ChainMockERPCImpl; impl ChainMockERPC for ChainMockERPCImpl { type Metadata = JsonRpcRequestProcessor; @@ -116,14 +127,9 @@ impl ChainMockERPC for ChainMockERPCImpl { &self, meta: Self::Metadata, block: String, - _full: bool, + full: bool, ) -> Result, Error> { - let num = match block.as_str() { - "pending" => None, - "earliest" => Some(meta.get_first_available_block()), - "latest" => Some(meta.get_slot(None)), - v => Hex::::from_hex(&v).ok().map(|f| f.0), - }; + let num = block_to_confirmed_num(Some(block), &meta); // TODO: Inline evm_state lookups, and request only solana headers. let block_num = num.unwrap_or(0); if block_num == 0 { @@ -133,7 +139,6 @@ impl ChainMockERPC for ChainMockERPCImpl { .get_confirmed_block(block_num, UiTransactionEncoding::Binary.into()) .map_err(|_| Error::NotFound)? .map(|block| { - use std::str::FromStr; let block_hash = solana_sdk::hash::Hash::from_str(&block.blockhash).unwrap(); let block_hash = H256::from_slice(&block_hash.0); let parent_hash = @@ -141,26 +146,24 @@ impl ChainMockERPC for ChainMockERPCImpl { let bank = meta.bank(None); let evm_lock = bank.evm_state.read().expect("Evm lock poisoned"); let tx_hashes = evm_lock.get_txs_in_block(block_num); - let transactions = tx_hashes - .iter() - .flatten() - .map(|tx_hash| { - ( - *tx_hash, - evm_lock - .get_tx_receipt_by_hash(*tx_hash) - .expect("Transaction exist"), - ) - }) - .filter_map(|(tx_hash, tx)| { - let mut rpc_tx: RPCTransaction = tx.transaction.try_into().ok()?; - rpc_tx.hash = Some(tx_hash.into()); - rpc_tx.block_hash = Some(Hex(block_hash.into())); - rpc_tx.block_number = Some(Hex(tx.block_number.into())); - rpc_tx.transaction_index = Some(Hex(tx.index as usize)); - Some(rpc_tx) - }) - .collect(); + let transactions = if full { + let txs = tx_hashes + .iter() + .flatten() + .map(|tx_hash| { + evm_lock + .get_tx_receipt_by_hash(*tx_hash) + .expect("Transaction exist") + }) + .filter_map(|receipt| { + RPCTransaction::new_from_receipt(receipt, block_hash.clone()).ok() + }) + .collect(); + Either::Right(txs) + } else { + let txs = tx_hashes.into_iter().flatten().map(Hex).collect(); + Either::Left(txs) + }; drop(evm_lock); RPCBlock { @@ -171,7 +174,7 @@ impl ChainMockERPC for ChainMockERPCImpl { gas_limit: Hex(0x10000.into()), gas_used: Gas::zero().into(), timestamp: Hex(block.block_time.unwrap_or(0) as u64), - transactions: Either::Right(transactions), + transactions, nonce: 0x7bb9369dcbaec019.into(), sha3_uncles: H256::zero().into(), @@ -324,12 +327,15 @@ impl BasicERPC for BasicERPCImpl { let receipt = evm_state.get_tx_receipt_by_hash(tx_hash.0); Ok(match receipt { - Some(tx) => { - let mut rpc_tx: RPCTransaction = tx.transaction.clone().try_into()?; - rpc_tx.hash = Some(tx_hash); - rpc_tx.block_number = Some(Hex(tx.block_number.into())); - rpc_tx.transaction_index = Some(Hex(tx.index as usize)); - Some(rpc_tx) + Some(receipt) => { + let block_hash = meta + .get_confirmed_block_hash(receipt.block_number) + .map_err(|_| Error::InvalidParams)?; + let block_hash = block_hash.ok_or(Error::InvalidParams)?; + let block_hash = solana_sdk::hash::Hash::from_str(&block_hash).unwrap(); + let block_hash = H256::from_slice(&block_hash.0); + + Some(RPCTransaction::new_from_receipt(receipt, block_hash)?) } None => None, }) @@ -344,25 +350,15 @@ impl BasicERPC for BasicERPCImpl { let evm_state = bank.evm_state.read().unwrap(); let receipt = evm_state.get_tx_receipt_by_hash(tx_hash.0); Ok(match receipt { - Some(tx) => Some(RPCReceipt { - transaction_index: Hex(tx.index as usize), - block_hash: H256::zero().into(), - block_number: Hex(tx.block_number.into()), - cumulative_gas_used: tx.used_gas.into(), - gas_used: tx.used_gas.into(), - transaction_hash: tx_hash, - logs: vec![], - contract_address: Some(Hex(tx - .transaction - .address() - .map_err(|_| Error::InvalidParams)?)), - root: H256::zero().into(), - status: Hex(if let evm_state::ExitReason::Succeed(_) = tx.status { - 1 - } else { - 0 - }), - }), + Some(receipt) => { + let block_hash = meta + .get_confirmed_block_hash(receipt.block_number) + .map_err(|_| Error::InvalidParams)?; + let block_hash = block_hash.ok_or(Error::InvalidParams)?; + let block_hash = solana_sdk::hash::Hash::from_str(&block_hash).unwrap(); + let block_hash = H256::from_slice(&block_hash.0); + Some(RPCReceipt::new_from_receipt(receipt, block_hash)?) + } None => None, }) } @@ -386,6 +382,28 @@ impl BasicERPC for BasicERPCImpl { let result = call(meta, tx, block)?; Ok(Hex(result.2.into())) } + + fn logs(&self, meta: Self::Metadata, log_filter: RPCLogFilter) -> Result, Error> { + let bank = meta.bank(None); + let slot = bank.slot(); + + let evm_lock = bank.evm_state.read().expect("Evm lock poisoned"); + let to = block_to_confirmed_num(log_filter.to_block, &meta).unwrap_or(slot); + let from = block_to_confirmed_num(log_filter.from_block, &meta).unwrap_or(slot); + + let filter = LogFilter { + address: log_filter.address.map(|k| k.0), + topics: vec![], + from_block: from, + to_block: to, + }; + + Ok(evm_lock + .get_logs(filter) + .into_iter() + .map(|l| l.into()) + .collect()) + } } fn call( @@ -400,7 +418,7 @@ fn call( let gas_limit: u64 = tx .gas .map(|a| a.0) - .unwrap_or_else(|| 300000.into()) + .unwrap_or_else(|| 300000000.into()) .try_into() .map_err(|_| Error::InvalidParams)?; let gas_limit = gas_limit as usize; @@ -415,9 +433,14 @@ fn call( let mut executor = evm_state::Executor::with_config(evm_state, Config::istanbul(), gas_limit, bank.slot()); - let address = tx.to.map(|h| h.0).unwrap_or_default(); - let result = - executor.with_executor(|e| e.transact_call(caller, address, value, input, gas_limit)); + + let result = if let Some(address) = tx.to { + let address = address.0; + executor.with_executor(|e| e.transact_call(caller, address, value, input, gas_limit)) + } else { + executor.with_executor(|e| (e.transact_create(caller, value, input, gas_limit), vec![])) + }; + let gas_used = executor.used_gas(); Ok((result.0, result.1, gas_used)) } diff --git a/core/src/rpc.rs b/core/src/rpc.rs index acc7a6c4fd..d269603615 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -632,6 +632,31 @@ impl JsonRpcRequestProcessor { } } + pub fn get_confirmed_block_hash(&self, slot: Slot) -> Result> { + if self.config.enable_rpc_transaction_history + && slot + <= self + .block_commitment_cache + .read() + .unwrap() + .highest_confirmed_root() + { + let result = self.blockstore.get_confirmed_block_hash(slot); + if result.is_err() { + if let Some(bigtable_ledger_storage) = &self.bigtable_ledger_storage { + return Ok(self + .runtime_handle + .block_on(bigtable_ledger_storage.get_confirmed_block_hash(slot)) + .ok()); + } + } + self.check_slot_cleaned_up(&result, slot)?; + Ok(result.ok()) + } else { + Err(RpcCustomError::BlockNotAvailable { slot }.into()) + } + } + pub fn get_confirmed_blocks( &self, start_slot: Slot, diff --git a/entrypoint.sh b/entrypoint.sh index d3204534ea..6325654461 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -13,6 +13,10 @@ run_solana_validator() { declare entrypoint=$2 declare port_range=$3 rpc_url=`solana-gossip rpc-url --timeout 180 --entrypoint $entrypoint` # get rpc url + rpc_url=`solana-gossip rpc-url --timeout 30 --entrypoint $entrypoint` # get rpc url + if [ $? -ne 0 ]; then + rpc_url=http://$(echo $entrypoint | cut -d ':' -f 1):8899 + fi solana-keygen new --no-passphrase -so $datadir/identity.json #try to generate identity solana-keygen new --no-passphrase -so $datadir/vote-account.json #try to generate vote account @@ -32,10 +36,8 @@ run_solana_validator() { --log - \ --enable-rpc-exit \ --enable-rpc-set-log-filter \ - --no-genesis-fetch \ --dynamic-port-range $port_range \ - --no-snapshot-fetch \ - --snapshot-interval-slots 0 # temporary solution while evm is not persistent + --snapshot-interval-slots 100 # temporary solution while evm is not persistent } run_solana_bootstrap() { @@ -46,6 +48,7 @@ run_solana_bootstrap() { solana-validator \ --enable-rpc-exit \ --enable-rpc-set-log-filter \ + --enable_rpc_transaction_history \ --gossip-host $host \ --ledger $datadir \ --dynamic-port-range $port_range \ @@ -53,7 +56,7 @@ run_solana_bootstrap() { --identity $datadir/identity.json \ --vote-account $datadir/vote-account.json \ --log - \ - --snapshot-interval-slots 0 # temporary solution while evm is not persistent + --snapshot-interval-slots 100 # temporary solution while evm is not persistent } run_evm_bridge() { diff --git a/evm-utils/evm-bridge/src/main.rs b/evm-utils/evm-bridge/src/main.rs index cd526b1316..879aafc128 100644 --- a/evm-utils/evm-bridge/src/main.rs +++ b/evm-utils/evm-bridge/src/main.rs @@ -210,10 +210,6 @@ impl BridgeERPC for BridgeERPCImpl { fn compilers(&self, _meta: Self::Metadata) -> EvmResult> { Err(evm_rpc::Error::NotFound) } - - fn logs(&self, _meta: Self::Metadata, _log_filter: RPCLogFilter) -> EvmResult> { - Err(evm_rpc::Error::NotFound) - } } pub struct ChainMockERPCProxy; @@ -453,6 +449,10 @@ impl BasicERPC for BasicERPCProxy { ) -> EvmResult> { proxy_evm_rpc!(meta.rpc_client, EthEstimateGas, tx, block) } + + fn logs(&self, meta: Self::Metadata, log_filter: RPCLogFilter) -> EvmResult> { + proxy_evm_rpc!(meta.rpc_client, EthGetLogs, log_filter) + } } macro_rules! proxy_sol_rpc { diff --git a/evm-utils/evm-rpc/src/lib.rs b/evm-utils/evm-rpc/src/lib.rs index 8ad40bed17..5ff3d0b8bd 100644 --- a/evm-utils/evm-rpc/src/lib.rs +++ b/evm-utils/evm-rpc/src/lib.rs @@ -41,6 +41,7 @@ pub struct RPCLog { pub transaction_hash: Hex, pub block_hash: Hex, pub block_number: Hex, + pub address: Hex
, pub data: Bytes, pub topics: Vec>, } @@ -101,8 +102,8 @@ pub struct RPCReceipt { pub cumulative_gas_used: Hex, pub gas_used: Hex, pub contract_address: Option>, + pub to: Option>, pub logs: Vec, - pub root: Hex, pub status: Hex, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -258,6 +259,13 @@ pub mod basic { tx: RPCTransaction, block: Option, ) -> Result, Error>; + + #[rpc(meta, name = "eth_getLogs")] + fn logs( + &self, + meta: Self::Metadata, + log_filter: RPCLogFilter, + ) -> Result, Error>; } } @@ -413,13 +421,6 @@ pub mod bridge { #[rpc(meta, name = "eth_getCompilers")] fn compilers(&self, meta: Self::Metadata) -> Result, Error>; - - #[rpc(meta, name = "eth_getLogs")] - fn logs( - &self, - meta: Self::Metadata, - log_filter: RPCLogFilter, - ) -> Result, Error>; } } @@ -487,18 +488,19 @@ pub mod bridge { // server.wait(); // } -use std::convert::TryFrom; - -impl TryFrom for RPCTransaction { - type Error = crate::Error; - - fn try_from(tx: evm_state::transactions::Transaction) -> Result { +impl RPCTransaction { + pub fn new_from_receipt( + receipt: evm_state::transactions::TransactionReceipt, + block_hash: H256, + ) -> Result { + let ref tx = receipt.transaction; let address = tx.address().map_err(|_| Error::InvalidParams)?.into(); let (to, creates) = match tx.action { evm_state::transactions::TransactionAction::Call(_) => (Some(address), None), evm_state::transactions::TransactionAction::Create => (None, Some(address)), }; + let hash = tx.signing_hash(); Ok(RPCTransaction { from: Some(tx.caller().map_err(|_| Error::InvalidParams)?.into()), to, @@ -508,10 +510,91 @@ impl TryFrom for RPCTransaction { value: Some(tx.value.into()), data: Some(tx.input.clone().into()), nonce: Some(tx.nonce.into()), - hash: None, - transaction_index: None, - block_hash: None, - block_number: None, + hash: Some(hash.into()), + transaction_index: Some((receipt.index as usize).into()), + block_hash: Some(block_hash.into()), + block_number: Some(Hex(receipt.block_number.into())), }) } } + +impl RPCReceipt { + pub fn new_from_receipt( + receipt: evm_state::transactions::TransactionReceipt, + block_hash: H256, + ) -> Result { + let ref tx = receipt.transaction; + let address = tx.address().map_err(|_| Error::InvalidParams)?.into(); + let (to, contract_address) = match tx.action { + evm_state::transactions::TransactionAction::Call(_) => (Some(address), None), + evm_state::transactions::TransactionAction::Create => (None, Some(address)), + }; + let tx_hash = Hex(tx.signing_hash()); + let tx_index: Hex<_> = (receipt.index as usize).into(); + let block_number = Hex(receipt.block_number.into()); + + let logs = receipt + .logs + .into_iter() + .enumerate() + .map(|(id, log)| RPCLog { + removed: false, + log_index: Hex(id), + transaction_hash: tx_hash.clone(), + transaction_index: tx_index.clone(), + block_hash: block_hash.into(), + block_number: block_number.clone(), + data: log.data.into(), + topics: log.topics.into_iter().map(Hex).collect(), + address: Hex(log.address), + }) + .collect(); + + Ok(RPCReceipt { + to, + contract_address, + gas_used: receipt.used_gas.into(), + cumulative_gas_used: receipt.used_gas.into(), + transaction_hash: tx_hash, + transaction_index: tx_index, + block_hash: block_hash.into(), + block_number: block_number, + logs, + status: Hex(if let evm_state::ExitReason::Succeed(_) = receipt.status { + 1 + } else { + 0 + }), + }) + } +} + +// #[derive(Serialize, Deserialize, Debug, Clone)] +// #[serde(rename_all = "camelCase")] +// pub struct RPCLog { +// pub removed: bool, +// pub log_index: Hex, +// pub transaction_index: Hex, +// pub transaction_hash: Hex, +// pub block_hash: Hex, +// pub block_number: Hex, +// pub address: Hex
, +// pub data: Bytes, +// pub topics: Vec>, +// } + +impl From for RPCLog { + fn from(log: LogWithLocation) -> Self { + RPCLog { + removed: false, + transaction_hash: log.transaction_hash.into(), + transaction_index: (log.transaction_id as usize).into(), + block_number: Hex(log.block_num.into()), + block_hash: Hex(H256::zero()), + log_index: Hex(0), + address: Hex(log.address), + topics: log.topics.into_iter().map(Hex).collect(), + data: Bytes(log.data), + } + } +} diff --git a/evm-utils/evm-state/src/evm_backend.rs b/evm-utils/evm-state/src/evm_backend.rs index d7b9f9379e..45b0243cda 100644 --- a/evm-utils/evm-state/src/evm_backend.rs +++ b/evm-utils/evm-state/src/evm_backend.rs @@ -1,5 +1,5 @@ use crate::EvmState; -use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log}; +use evm::backend::{Apply, Backend, Basic}; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; @@ -19,6 +19,65 @@ impl EvmBackend { fn tx_info(&self) -> &crate::layered_backend::MemoryVicinity { &self.tx_info } + + pub fn apply(&mut self, values: A, delete_empty: bool) + where + A: IntoIterator>, + I: IntoIterator, + { + for apply in values { + match apply { + Apply::Modify { + address, + basic, + code, + storage, + reset_storage: _, + } => { + log::debug!("Apply::Modify address = {}, basic = {:?}", address, basic); + // TODO: rollback on insert fail. + // TODO: clear account storage on delete. + let is_empty = { + let mut account = self.evm_state.get_account(address).unwrap_or_default(); + account.balance = basic.balance; + account.nonce = basic.nonce; + if let Some(code) = code { + account.code = code; + } + let is_empty_state = account.balance == U256::zero() + && account.nonce == U256::zero() + && account.code.is_empty(); + + self.evm_state.accounts.insert(address, account); + + // TODO: Clear storage on reset_storage = true + // if reset_storage { + // account.storage = BTreeMap::new(); + // } + + // TODO: Clear zeros data (H256::default()) + + for (index, value) in storage { + if value == H256::default() { + self.evm_state.storage.remove((address, index)); + } else { + self.evm_state.storage.insert((address, index), value); + } + } + + is_empty_state + }; + + if is_empty && delete_empty { + self.evm_state.accounts.remove(address); + } + } + Apply::Delete { address } => { + self.evm_state.accounts.remove(address); + } + } + } + } } impl Backend for EvmBackend { @@ -99,70 +158,6 @@ impl Backend for EvmBackend { } } -impl ApplyBackend for EvmBackend { - fn apply(&mut self, values: A, logs: L, delete_empty: bool) - where - A: IntoIterator>, - I: IntoIterator, - L: IntoIterator, - { - for apply in values { - match apply { - Apply::Modify { - address, - basic, - code, - storage, - reset_storage: _, - } => { - log::debug!("Apply::Modify address = {}, basic = {:?}", address, basic); - // TODO: rollback on insert fail. - // TODO: clear account storage on delete. - let is_empty = { - let mut account = self.evm_state.get_account(address).unwrap_or_default(); - account.balance = basic.balance; - account.nonce = basic.nonce; - if let Some(code) = code { - account.code = code; - } - let is_empty_state = account.balance == U256::zero() - && account.nonce == U256::zero() - && account.code.is_empty(); - - self.evm_state.accounts.insert(address, account); - - // TODO: Clear storage on reset_storage = true - // if reset_storage { - // account.storage = BTreeMap::new(); - // } - - // TODO: Clear zeros data (H256::default()) - - for (index, value) in storage { - if value == H256::default() { - self.evm_state.storage.remove((address, index)); - } else { - self.evm_state.storage.insert((address, index), value); - } - } - - is_empty_state - }; - - if is_empty && delete_empty { - self.evm_state.accounts.remove(address); - } - } - Apply::Delete { address } => { - self.evm_state.accounts.remove(address); - } - } - } - - self.evm_state.logs.extend(logs); - } -} - #[cfg(test)] mod test { use super::*; diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index fc43ed8795..a80d19a41c 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -1,4 +1,3 @@ -use evm::backend::Log; use primitive_types::{H160, H256, U256}; use serde::{Deserialize, Serialize}; @@ -28,6 +27,22 @@ pub struct MemoryVicinity { pub block_gas_limit: U256, } +pub struct LogWithLocation { + pub transaction_hash: H256, + pub transaction_id: u64, + pub block_num: u64, + pub address: H160, + pub data: Vec, + pub topics: Vec, +} + +pub struct LogFilter { + pub from_block: u64, + pub to_block: u64, + pub address: Option, + pub topics: Vec, +} + #[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct AccountState { /// Account nonce. @@ -52,7 +67,6 @@ pub struct EvmState { pub(crate) txs_in_block: Map>, //TODO: Deadline for storing data. pub(crate) big_transactions: Map, - pub(crate) logs: Vec, } impl Default for EvmState { @@ -69,7 +83,6 @@ impl EvmState { txs_receipts: Map::new(), txs_in_block: Map::new(), big_transactions: Map::new(), - logs: Vec::new(), } } @@ -94,7 +107,6 @@ impl EvmState { txs_receipts, txs_in_block, big_transactions, - logs: vec![], }) } } @@ -108,7 +120,6 @@ impl EvmState { txs_receipts: Map::new(), txs_in_block: Map::new(), big_transactions: Map::new(), - logs: Vec::new(), } } @@ -132,6 +143,50 @@ impl EvmState { self.storage.get(&(address, index)).cloned() } + // transaction_hash: H256, + // transaction_id: u64, + // block_num: u64, + // address: H160, + // data: H256, + // topics: Vec + + // TODO: Optimize, using bloom filters. + // TODO: Check topics query limits <= 4. + // TODO: Filter by address, topics + pub fn get_logs(&self, logs_filter: LogFilter) -> Vec { + let mut result = Vec::new(); + + for (block_id, txs) in (logs_filter.from_block..=logs_filter.to_block) + .filter_map(|b| self.txs_in_block.get(&b).cloned().map(|k| (b, k))) + { + let txs_in_block = txs + .into_iter() + .map(|tx_hash| { + ( + tx_hash, + self.get_tx_receipt_by_hash(tx_hash) + .expect("Transacton not found by hash, while exist by number"), + ) + }) + .enumerate(); + + for (tx_id, (tx_hash, receipt)) in txs_in_block { + for log in receipt.logs { + let log_entry = LogWithLocation { + transaction_hash: tx_hash, + transaction_id: tx_id as u64, + block_num: block_id, + data: log.data, + topics: log.topics, + address: log.address, + }; + result.push(log_entry) + } + } + } + result + } + pub fn swap_commit(&mut self, mut updated: Self) { // TODO: Assert that updated is newer than current state. std::mem::swap(self, &mut updated); diff --git a/evm-utils/evm-state/src/lib.rs b/evm-utils/evm-state/src/lib.rs index 8a144da677..c868377276 100644 --- a/evm-utils/evm-state/src/lib.rs +++ b/evm-utils/evm-state/src/lib.rs @@ -137,8 +137,8 @@ impl Executor { assert!(used_gas + self.used_gas <= self.evm.tx_info.block_gas_limit.as_usize()); let (updates, logs) = executor.deconstruct(); - self.evm.apply(updates, logs, false); - self.register_tx_receipt(evm_tx, used_gas.into(), result.clone()); + self.evm.apply(updates, false); + self.register_tx_receipt(evm_tx, used_gas.into(), logs, result.clone()); self.used_gas += used_gas; Ok(result) @@ -154,9 +154,9 @@ impl Executor { let mut executor = StackExecutor::new(&self.evm, gas_limit, &self.config); let result = func(&mut executor); let used_gas = executor.used_gas(); - let (updates, logs) = executor.deconstruct(); + let (updates, _logs) = executor.deconstruct(); self.used_gas += used_gas; - self.evm.apply(updates, logs, false); + self.evm.apply(updates, false); result } @@ -231,12 +231,15 @@ impl Executor { } // TODO: Handle duplicates, statuses. - fn register_tx_receipt( + fn register_tx_receipt( &mut self, tx: transactions::Transaction, used_gas: U256, + logs: I, result: (evm::ExitReason, Vec), - ) { + ) where + I: IntoIterator, + { let block_num = self.evm.tx_info.block_number.as_u64(); let tx_hash = tx.signing_hash(); @@ -257,7 +260,14 @@ impl Executor { .txs_in_block .insert(block_num, updated_vec); - let tx_receipt = TransactionReceipt::new(tx, used_gas, block_num, index, result); + let tx_receipt = TransactionReceipt::new( + tx, + used_gas, + block_num, + index, + logs.into_iter().collect(), + result, + ); self.evm.evm_state.txs_receipts.insert(tx_hash, tx_receipt); } diff --git a/evm-utils/evm-state/src/transactions.rs b/evm-utils/evm-state/src/transactions.rs index 5381a6e070..057b16e09a 100644 --- a/evm-utils/evm-state/src/transactions.rs +++ b/evm-utils/evm-state/src/transactions.rs @@ -1,3 +1,4 @@ +use evm::backend::Log; use primitive_types::{H160, H256, U256}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; use serde::{Deserialize, Serialize}; @@ -299,7 +300,7 @@ pub struct TransactionReceipt { // pub state_root: H256, pub used_gas: Gas, // pub logs_bloom: LogsBloom, - // pub logs: Vec, + pub logs: Vec, } impl TransactionReceipt { pub fn new( @@ -307,6 +308,7 @@ impl TransactionReceipt { used_gas: Gas, block_number: u64, index: u64, + logs: Vec, result: (evm::ExitReason, Vec), ) -> TransactionReceipt { TransactionReceipt { @@ -315,6 +317,7 @@ impl TransactionReceipt { used_gas, block_number, index, + logs, } } } diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 1a8c3a6fb9..6eabd7d2c9 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -1643,6 +1643,28 @@ impl Blockstore { Ok(root_iterator.next().unwrap_or_default()) } + pub fn get_confirmed_block_hash(&self, slot: Slot) -> Result { + datapoint_info!( + "blockstore-rpc-api", + ("method", "get_confirmed_block_hash".to_string(), String) + ); + let lowest_cleanup_slot = self.lowest_cleanup_slot.read().unwrap(); + // lowest_cleanup_slot is the last slot that was not cleaned up by + // LedgerCleanupService + if *lowest_cleanup_slot > 0 && *lowest_cleanup_slot >= slot { + return Err(BlockstoreError::SlotCleanedUp); + } + if self.is_root(slot) { + let slot_entries = self.get_slot_entries(slot, 0)?; + if !slot_entries.is_empty() { + let blockhash = get_last_hash(slot_entries.iter()) + .unwrap_or_else(|| panic!("Rooted slot {:?} must have blockhash", slot)); + return Ok(blockhash.to_string()); + } + } + Err(BlockstoreError::SlotNotRooted) + } + pub fn get_confirmed_block( &self, slot: Slot, diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index 0d48b62597..67e11b7410 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -308,6 +308,15 @@ impl LedgerStorage { Ok(block.into_confirmed_block(encoding)) } + /// Fetch the confirmed block from the desired slot + pub async fn get_confirmed_block_hash(&self, slot: Slot) -> Result { + let mut bigtable = self.connection.client(); + let block = bigtable + .get_bincode_cell::("blocks", slot_to_key(slot)) + .await?; + Ok(block.blockhash) + } + pub async fn get_signature_status(&self, signature: &Signature) -> Result { let mut bigtable = self.connection.client(); let transaction_info = bigtable