From d55635051e5eedf499a771a2583d05894bb66716 Mon Sep 17 00:00:00 2001 From: Vladimir Motylenko Date: Wed, 8 Dec 2021 22:12:03 +0200 Subject: [PATCH] Fix(rpc): Fix rpc return latest state from bank. --- core/src/evm_rpc_impl/mod.rs | 207 ++++++++++++++++--------- evm-utils/evm-state/src/state.rs | 4 +- evm-utils/evm-state/src/storage/mod.rs | 6 +- multinode-demo/bootstrap-validator.sh | 125 +++++++-------- multinode-demo/setup.sh | 2 +- runtime/src/bank.rs | 3 +- 6 files changed, 209 insertions(+), 138 deletions(-) diff --git a/core/src/evm_rpc_impl/mod.rs b/core/src/evm_rpc_impl/mod.rs index af46debc78..a38ff65f80 100644 --- a/core/src/evm_rpc_impl/mod.rs +++ b/core/src/evm_rpc_impl/mod.rs @@ -14,24 +14,76 @@ use evm_rpc::{ BlockId, BlockRelId, Bytes, Either, Hex, RPCBlock, RPCLog, RPCLogFilter, RPCReceipt, RPCTopicFilter, RPCTransaction, }; -use evm_state::{AccountProvider, Address, Gas, LogFilter, TransactionAction, H256, U256}; +use evm_state::{ + AccountProvider, AccountState, Address, Gas, LogFilter, TransactionAction, H160, H256, U256, +}; use snafu::ResultExt; use solana_runtime::bank::Bank; use std::cell::RefCell; +use std::sync::Arc; const GAS_PRICE: u64 = 3; -fn block_to_state_root(block: Option, meta: &JsonRpcRequestProcessor) -> Option { +pub struct StateRootWithBank { + pub state_root: Option, + pub bank: Option>, +} + +impl StateRootWithBank { + pub fn get_account_state_at( + &self, + meta: &JsonRpcRequestProcessor, + address: H160, + ) -> Result, Error> { + assert!(self.state_root.is_some()); + + let root = *self.state_root.as_ref().unwrap(); + if let Some(bank) = &self.bank { + let evm = bank.evm_state.read().unwrap(); + + assert!(evm.last_root() == root, "we store bank with invalid root"); + return Ok(evm.get_account_state(address)); + } + let archive_evm_state = meta.evm_state_archive().ok_or(Error::ArchiveNotSupported)?; + Ok(archive_evm_state.get_account_state_at(root, address)) + } + + pub fn get_storage_at( + &self, + meta: &JsonRpcRequestProcessor, + address: H160, + idx: H256, + ) -> Result, Error> { + assert!(self.state_root.is_some()); + let root = *self.state_root.as_ref().unwrap(); + if let Some(bank) = &self.bank { + let evm = bank.evm_state.read().unwrap(); + + assert!(evm.last_root() == root, "we store bank with invalid root"); + return Ok(evm.get_storage(address, idx)); + } + let archive_evm_state = meta.evm_state_archive().ok_or(Error::ArchiveNotSupported)?; + Ok(archive_evm_state.get_storage_at(root, address, idx)) + } +} + +fn block_to_state_root( + block: Option, + meta: &JsonRpcRequestProcessor, +) -> StateRootWithBank { let block_id = block.unwrap_or_default(); let mut found_block_hash = None; let block_num = match block_id { BlockId::RelativeId(BlockRelId::Pending) | BlockId::RelativeId(BlockRelId::Latest) => { - meta.get_last_available_evm_block().unwrap_or_else(|| { - let bank = meta.bank(Some(CommitmentConfig::processed())); - let evm = bank.evm_state.read().unwrap(); - evm.block_number().saturating_sub(1) - }) + let bank = meta.bank(Some(CommitmentConfig::processed())); + let evm = bank.evm_state.read().unwrap(); + let last_root = evm.last_root(); + drop(evm); + return StateRootWithBank { + state_root: Some(last_root), + bank: Some(bank), + }; } BlockId::RelativeId(BlockRelId::Earliest) | BlockId::Num(Hex(0)) => { meta.get_frist_available_evm_block() @@ -39,23 +91,29 @@ fn block_to_state_root(block: Option, meta: &JsonRpcRequestProcessor) - BlockId::Num(num) => num.0, BlockId::BlockHash { block_hash } => { found_block_hash = Some(block_hash.0); - meta.get_evm_block_id_by_hash(block_hash.0)? + if let Some(num) = meta.get_evm_block_id_by_hash(block_hash.0) { + num + } else { + return StateRootWithBank { + state_root: None, + bank: None, + }; + } } }; - meta.get_evm_block_by_id(block_num) // TODO: don't request full block. - .filter(|(b, _)| { - // if requested specific block hash, check that block with this hash is not in reorged fork - found_block_hash - .map(|block_hash| b.header.hash() == block_hash) - .unwrap_or(true) - }) - .map(|(b, _)| b.header.state_root) - .filter(|state_root| { - meta.evm_state_archive_storage() - .as_ref() - .map(|s| s.check_root_exist(*state_root)) - == Some(true) - }) + StateRootWithBank { + state_root: meta + .get_evm_block_by_id(block_num) // TODO: don't request full block. + .filter(|(b, _)| { + // if requested specific block hash, check that block with this hash is not in reorged fork + found_block_hash + .map(|block_hash| b.header.hash() == block_hash) + .unwrap_or(true) + }) + .map(|(b, _)| b.header.state_root), + + bank: None, + } } fn block_parse_confirmed_num( @@ -215,13 +273,9 @@ impl BasicERPC for BasicErpcImpl { address: Hex
, block: Option, ) -> Result, Error> { - - let evm_state = meta.evm_state_archive().ok_or(Error::ArchiveNotSupported)?; - let root = block_to_state_root(block, &meta).ok_or(Error::BlockNotFound { - block: block.unwrap_or_default(), - })?; - let account = evm_state - .get_account_state_at(root, address.0) + let state = block_to_state_root(block, &meta); + let account = state + .get_account_state_at(&meta, address.0)? .unwrap_or_default(); Ok(Hex(account.balance)) } @@ -233,13 +287,11 @@ impl BasicERPC for BasicErpcImpl { data: Hex, block: Option, ) -> Result, Error> { - let evm_state = meta.evm_state_archive().ok_or(Error::ArchiveNotSupported)?; - let root = block_to_state_root(block, &meta).ok_or(Error::BlockNotFound { - block: block.unwrap_or_default(), - })?; - Ok(Hex(evm_state - .get_storage_at(root, address.0, data.0) - .unwrap_or_default())) + let state = block_to_state_root(block, &meta); + let storage = state + .get_storage_at(&meta, address.0, data.0)? + .unwrap_or_default(); + Ok(Hex(storage)) } fn transaction_count( @@ -248,12 +300,9 @@ impl BasicERPC for BasicErpcImpl { address: Hex
, block: Option, ) -> Result, Error> { - let evm_state = meta.evm_state_archive().ok_or(Error::ArchiveNotSupported)?; - let root = block_to_state_root(block, &meta).ok_or(Error::BlockNotFound { - block: block.unwrap_or_default(), - })?; - let account = evm_state - .get_account_state_at(root, address.0) + let state = block_to_state_root(block, &meta); + let account = state + .get_account_state_at(&meta, address.0)? .unwrap_or_default(); Ok(Hex(account.nonce)) } @@ -264,12 +313,9 @@ impl BasicERPC for BasicErpcImpl { address: Hex
, block: Option, ) -> Result { - let evm_state = meta.evm_state_archive().ok_or(Error::ArchiveNotSupported)?; - let root = block_to_state_root(block, &meta).ok_or(Error::BlockNotFound { - block: block.unwrap_or_default(), - })?; - let account = evm_state - .get_account_state_at(root, address.0) + let state = block_to_state_root(block, &meta); + let account = state + .get_account_state_at(&meta, address.0)? .unwrap_or_default(); Ok(Bytes(account.code.into())) } @@ -403,11 +449,15 @@ impl BasicERPC for BasicErpcImpl { .map(|s| solana_sdk::pubkey::Pubkey::from_str(&s)) .collect::, _>>() .map_err(|e| into_native_error(e, false))?; - let saved_root = block_to_state_root(block, &meta).ok_or(Error::BlockNotFound { - block: block.unwrap_or_default(), - })?; + let saved_state = block_to_state_root(block, &meta); + + if saved_state.state_root.is_none() { + return Err(Error::BlockNotFound { + block: block.unwrap_or_default(), + }); + } - let result = call(meta, tx, Some(saved_root), meta_keys)?; + let result = call(meta, tx, saved_state, meta_keys)?; Ok(Bytes(result.exit_data)) } @@ -438,9 +488,12 @@ impl BasicERPC for BasicErpcImpl { tx_traces: Vec<(RPCTransaction, Vec, Option)>, block: Option, ) -> Result, Error> { - let saved_root = block_to_state_root(block, &meta).ok_or(Error::BlockNotFound { - block: block.unwrap_or_default(), - })?; + let saved_state = block_to_state_root(block, &meta); + if saved_state.state_root.is_none() { + return Err(Error::BlockNotFound { + block: block.unwrap_or_default(), + }); + } let mut txs = Vec::new(); let mut txs_meta = Vec::new(); @@ -459,7 +512,7 @@ impl BasicERPC for BasicErpcImpl { txs_meta.push(meta); } - let traces = call_many(meta, &txs, Some(saved_root))?.into_iter(); + let traces = call_many(meta, &txs, saved_state)?.into_iter(); let mut result = Vec::new(); for (output, meta_tx) in traces.zip(txs_meta) { @@ -552,10 +605,13 @@ impl BasicERPC for BasicErpcImpl { .map(|s| solana_sdk::pubkey::Pubkey::from_str(&s)) .collect::, _>>() .map_err(|e| into_native_error(e, false))?; - let saved_root = block_to_state_root(block, &meta).ok_or(Error::BlockNotFound { - block: block.unwrap_or_default(), - })?; - let result = call(meta, tx, Some(saved_root), meta_keys)?; + let saved_state = block_to_state_root(block, &meta); + if saved_state.state_root.is_none() { + return Err(Error::BlockNotFound { + block: block.unwrap_or_default(), + }); + } + let result = call(meta, tx, saved_state, meta_keys)?; Ok(Hex(result.used_gas.into())) } @@ -615,10 +671,10 @@ struct TxOutput { fn call( meta: JsonRpcRequestProcessor, tx: RPCTransaction, - saved_root: Option, + saved_state: StateRootWithBank, meta_keys: Vec, ) -> Result { - let outputs = call_many(meta, &[(tx, meta_keys)], saved_root)?; + let outputs = call_many(meta, &[(tx, meta_keys)], saved_state)?; let TxOutput { exit_reason, @@ -643,17 +699,28 @@ fn call( fn call_many( meta: JsonRpcRequestProcessor, txs: &[(RPCTransaction, Vec)], - saved_root: Option, + saved_state: StateRootWithBank, ) -> Result, Error> { - let bank = meta.bank(Some(CommitmentConfig::processed())); - let evm_state = meta.evm_state_archive().ok_or(Error::ArchiveNotSupported)?; - - let evm_state = if let Some(root) = saved_root { - evm_state + // if we already found bank with some root, or we just cannot find state_root - use latest. + let use_latest_state = saved_state.bank.is_some() || saved_state.state_root.is_none(); + let bank = saved_state + .bank + .unwrap_or_else(|| meta.bank(Some(CommitmentConfig::processed()))); + + let evm_state = if use_latest_state { + // keep current bank to allow simulating on latest state without archive + match bank.evm_state.read().unwrap().clone() { + evm_state::EvmState::Incomming(i) => i, + evm_state::EvmState::Committed(c) => { + c.next_incomming(bank.clock().unix_timestamp as u64) + } + } + } else { + let root = saved_state.state_root.unwrap(); + meta.evm_state_archive() + .ok_or(Error::ArchiveNotSupported)? .new_incomming_for_root(root) .ok_or(Error::StateRootNotFound { state: root })? - } else { - evm_state }; let estimate_config = evm_state::EvmConfig { diff --git a/evm-utils/evm-state/src/state.rs b/evm-utils/evm-state/src/state.rs index edc9b4d3fc..3865c50569 100644 --- a/evm-utils/evm-state/src/state.rs +++ b/evm-utils/evm-state/src/state.rs @@ -537,7 +537,7 @@ impl EvmState { fs::create_dir(&evm_state)?; } - Self::load_from(evm_state, Incomming::default(), false) + Self::load_from(evm_state, Incomming::default(), true) } pub fn new_from_genesis( @@ -564,7 +564,7 @@ impl EvmState { Self::load_from( evm_state, Incomming::new(1, root_hash, H256::zero(), timestamp, version), - false, + true, // enable gc in newest version of genesis ) } diff --git a/evm-utils/evm-state/src/storage/mod.rs b/evm-utils/evm-state/src/storage/mod.rs index 85133954eb..15da2eb579 100644 --- a/evm-utils/evm-state/src/storage/mod.rs +++ b/evm-utils/evm-state/src/storage/mod.rs @@ -716,8 +716,10 @@ pub mod cleaner { let mut batch = rocksdb::WriteBatchWithTransaction::::default(); for (key, _data) in db.iterator_cf(counters_cf, rocksdb::IteratorMode::Start) { - let bytes = <&[u8; 32]>::try_from(key.as_ref())?; - let key = H256::from(bytes); + let key = + ::try_from_slice( + &key, + )?; if trie_nodes.trie_keys.contains(&key) { continue; // skip this key } else { diff --git a/multinode-demo/bootstrap-validator.sh b/multinode-demo/bootstrap-validator.sh index 350e953533..5f0dd0be9d 100755 --- a/multinode-demo/bootstrap-validator.sh +++ b/multinode-demo/bootstrap-validator.sh @@ -23,59 +23,60 @@ no_restart=0 args=() while [[ -n $1 ]]; do - if [[ ${1:0:1} = - ]]; then - if [[ $1 = --init-complete-file ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 = --gossip-host ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 = --gossip-port ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 = --dev-halt-at-slot ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 = --dynamic-port-range ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 = --limit-ledger-size ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 = --no-rocksdb-compaction ]]; then - args+=("$1") - shift - elif [[ $1 = --enable-rpc-transaction-history ]]; then - args+=("$1") - shift - elif [[ $1 = --enable-cpi-and-log-storage ]]; then - args+=("$1") - shift - elif [[ $1 = --enable-rpc-bigtable-ledger-storage ]]; then - args+=("$1") - shift - elif [[ $1 = --skip-poh-verify ]]; then - args+=("$1") - shift - elif [[ $1 = --log ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 = --no-restart ]]; then - no_restart=1 - shift - elif [[ $1 == --wait-for-supermajority ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 == --expected-bank-hash ]]; then - args+=("$1" "$2") - shift 2 - elif [[ $1 == --accounts ]]; then - args+=("$1" "$2") - shift 2 - else - echo "Unknown argument: $1" - $program --help - exit 1 + if [[ ${1:0:1} = - ]]; then + if [[ $1 = --init-complete-file ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 = --gossip-host ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 = --gossip-port ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 = --dev-halt-at-slot ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 = --dynamic-port-range ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 = --limit-ledger-size ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 = --no-rocksdb-compaction ]]; then + args+=("$1") + shift + elif [[ $1 = --enable-rpc-transaction-history ]]; then + args+=("$1") + shift + elif [[ $1 = --enable-cpi-and-log-storage ]]; then + args+=("$1") + shift + elif [[ $1 = --enable-rpc-bigtable-ledger-storage ]]; then + args+=("$1") + shift + elif [[ $1 = --skip-poh-verify ]]; then + args+=("$1") + shift + elif [[ $1 = --log ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 = --no-restart ]]; then + no_restart=1 + shift + elif [[ $1 == --wait-for-supermajority ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 == --expected-bank-hash ]]; then + args+=("$1" "$2") + shift 2 + elif [[ $1 == --accounts ]]; then + args+=("$1" "$2") + shift 2 + else + echo "Unknown argument: $1" + $program --help + exit 1 + fi fi done @@ -92,15 +93,15 @@ ledger_dir="$SOLANA_CONFIG_DIR"/bootstrap-validator } args+=( - --require-tower - --ledger "$ledger_dir" - --rpc-port 8899 - --snapshot-interval-slots 200 - --identity "$identity" - --vote-account "$vote_account" - --rpc-faucet-address 127.0.0.1:9900 - --no-poh-speed-test - --no-wait-for-vote-to-start-leader + --require-tower + --ledger "$ledger_dir" + --rpc-port 8899 + --snapshot-interval-slots 200 + --identity "$identity" + --vote-account "$vote_account" + --rpc-faucet-address 127.0.0.1:9900 + --no-poh-speed-test + --no-wait-for-vote-to-start-leader ) default_arg --gossip-port 8001 default_arg --log - diff --git a/multinode-demo/setup.sh b/multinode-demo/setup.sh index 645c68bc53..f854f04011 100755 --- a/multinode-demo/setup.sh +++ b/multinode-demo/setup.sh @@ -46,6 +46,6 @@ default_arg --ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator default_arg --faucet-pubkey "$SOLANA_CONFIG_DIR"/faucet.json default_arg --faucet-lamports 500000000000000000 default_arg --hashes-per-tick auto -default_arg --cluster-type development +default_arg --cluster-type devnet $velas_genesis "${args[@]}" diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 0bfe5eeb01..e3a3ee3c60 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2639,13 +2639,14 @@ impl Bank { batch } pub fn take_evm_state_cloned(&self) -> Option> { + let is_frozen = self.is_frozen(); match &*self.evm_state.read().expect("bank evm state was poisoned") { evm_state::EvmState::Incomming(i) => Some(i.clone()), evm_state::EvmState::Committed(_) => { warn!( "Take evm after freeze, bank_slot={}, bank_is_freeze={}", self.slot(), - self.is_frozen() + is_frozen ); None // Return None, so this transaction will fail to execute, // this transaction will be marked as retriable after bank realise that PoH is reached it's max height.