From 5a3911479fca27bd1279236e17c1e35452f49ec8 Mon Sep 17 00:00:00 2001 From: Vladimir Motylenko Date: Sat, 2 Jan 2021 16:12:02 +0200 Subject: [PATCH 01/13] chore: Review comments fixup. --- README.md | 8 ++++---- docs/docusaurus.config.js | 6 +++--- docs/src/evm.md | 15 ++++++++------- docs/src/evm/bridge.md | 4 ++-- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index afb01c2a8d..5bf6da0965 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Solana + Velas chain

# Building @@ -29,8 +29,8 @@ $ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang ## **2. Download the source code.** ```bash -$ git clone https://github.com/velas/velas.git -$ cd velas +$ git clone https://github.com/velas/velas-chain.git +$ cd velas-chain ``` ## **3. Build.** @@ -53,7 +53,7 @@ $ cargo test ``` ### EVM integration -Info about evm integration is at our [docs](https://docs.next.velas.com/evm). +Info about EVM integration is at our [docs](https://docs.next.velas.com/evm). ### Starting a local devnet Start your own devnet locally, instructions are in the [online docs](https://docs.next.velas.com/cluster/bench-tps). diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index ad7f098763..0df4b32672 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -17,7 +17,7 @@ module.exports = { links: [ { to: "evm", - label: "Evm integration", + label: "EVM integration", position: "left", }, { @@ -78,12 +78,12 @@ module.exports = { items: [ { label: "GitHub", - href: "https://github.com/velas", + href: "https://github.com/velas/velas-chain", }, ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} Velas Foundation`, + copyright: `Copyright © ${new Date().getFullYear()} Velas`, }, }, presets: [ diff --git a/docs/src/evm.md b/docs/src/evm.md index de11988060..15f6138773 100644 --- a/docs/src/evm.md +++ b/docs/src/evm.md @@ -1,11 +1,11 @@ --- -title: EVM in solana +title: EVM in Solana --- -[Solana application model](apps/rent.md) is aiming to high performance by spliting its modifiable state on accounts. -While this allows to process transactions in parallel on single shard, it also introduce complication for ordinary DApps developer. +[Solana application model](apps/rent.md) is aiming to high performance by spliting its modifiable state across multiple accounts. +While this allows to process transactions in parallel on single shard, thats also introduce complication for ordinary DApps developer. Also, most of DApps infrastructure is already relies on Solidity, and targeting Ethereum blockchain. -This two reasons can significantly slow down the spread of solana ecosystem. +These two reasons can significantly slow down the spread of solana ecosystem. To make life of DApps developers, and integrators more easier, we at Velas introduce full hybrid of solana and EVM. @@ -20,7 +20,8 @@ Note: EVM store tokens in nano plancks, so when you transfer for example, 5 plan Usage: ``` -/target/debug/evm-utils transfer-to-eth --help +> evm-utils transfer-to-eth --help + evm-utils-transfer-to-eth 0.1.0 Transfer solana token to EVM world @@ -39,11 +40,11 @@ ARGS: Example: ``` -evm-utils transfer-to-eth 5 9Edb9E0B88Dbf2a29aE121a657e1860aEceaA53D +> evm-utils transfer-to-eth 5 9Edb9E0B88Dbf2a29aE121a657e1860aEceaA53D ``` Result after transaction processing: ``` -[2020-12-26T15:03:01Z INFO evm_utils] Loading keypair from: /home/vladimir/.config/solana/id.json +[2020-12-26T15:03:01Z INFO evm_utils] Loading keypair from: /home/user/.config/solana/id.json Transaction signature = 5d3eP741NYgemyM4CLmXuTEcP8f8w7QxfZ5vBxorqenEtNeSHWMFpkwtyi1meFKHVNXzDD3NbvFCExjZH79gEMKk ``` diff --git a/docs/src/evm/bridge.md b/docs/src/evm/bridge.md index bb0301219c..195e46f778 100644 --- a/docs/src/evm/bridge.md +++ b/docs/src/evm/bridge.md @@ -2,7 +2,7 @@ title: Bridge to EVM --- -All Ethereum transaction is wrapped into native format. In order to execute native transaction, +Any Ethereum transaction is wrapped into native format. In order to execute native transaction, someone should pay a fee in native coin. EVM bridge is managing this routine. Its a regular web-server that wrap EVM transaction into native, and take gas price as fee. @@ -16,4 +16,4 @@ For devnet we provide a public evm-bridge, which is located at http://bridge.nex ## Gas price, and gas limit collecting: Every evm-bridge is responsible to set it's own commision, every evm-bridge users is paying this commission by increasing gas price in transaction. -This mechanism provide incentivise to host your own evm bridge, and increase decentralisation. \ No newline at end of file +This mechanism provide incentivise to host your own evm bridge publicly, and increase decentralisation. \ No newline at end of file From 8452cf33b05fb60009d200ccdf429550e4e5efa9 Mon Sep 17 00:00:00 2001 From: Vladimir Motylenko Date: Sat, 2 Jan 2021 15:19:17 +0200 Subject: [PATCH 02/13] chore: Fixups naming, and force evm-state account to avoid rent. --- bench-tps-evm/src/bench_evm.rs | 10 +++++----- evm-utils/evm-bridge/src/main.rs | 4 ++-- evm-utils/programs/evm_loader/src/lib.rs | 16 ++++++++-------- evm-utils/programs/evm_loader/src/processor.rs | 6 +++--- evm-utils/src/main.rs | 4 ++-- runtime/src/bank.rs | 2 +- runtime/src/rent_collector.rs | 1 + 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/bench-tps-evm/src/bench_evm.rs b/bench-tps-evm/src/bench_evm.rs index 7b44f3cd2e..260caa43c1 100644 --- a/bench-tps-evm/src/bench_evm.rs +++ b/bench-tps-evm/src/bench_evm.rs @@ -33,7 +33,7 @@ use solana_evm_loader_program::scope::*; pub const BENCH_SEED: &str = "authority"; -pub fn generate_evm_keypair(seed_keypair: &Keypair) -> evm::SecretKey { +pub fn generate_evm_key(seed_keypair: &Keypair) -> evm::SecretKey { use solana_evm_loader_program::scope::evm::rand::SeedableRng; let mut seed = [0u8; 32]; @@ -54,8 +54,8 @@ pub fn generate_and_fund_evm_keypairs( let mut keypairs: Vec<_> = sources .into_iter() .map(|key| { - let evm_keys = generate_evm_keypair(&key); - (key, evm_keys) + let evm_key = generate_evm_key(&key); + (key, evm_key) }) .collect(); info!("Get lamports..."); @@ -163,7 +163,7 @@ impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, &'a evm::SecretKey, Trans .par_iter() .map(|(k, evm)| { let instructions = solana_evm_loader_program::transfer_native_to_eth_ixs( - &k.pubkey(), + k.pubkey(), to_lamports, evm.to_address(), ); @@ -323,7 +323,7 @@ fn generate_system_txs( let tx_call = tx_call.sign(&from.1, None); - let ix = solana_evm_loader_program::send_raw_tx(&from.0.pubkey(), tx_call); + let ix = solana_evm_loader_program::send_raw_tx(from.0.pubkey(), tx_call); let message = Message::new(&[ix], Some(&from.0.pubkey())); diff --git a/evm-utils/evm-bridge/src/main.rs b/evm-utils/evm-bridge/src/main.rs index e04f22b53c..9d456ab70d 100644 --- a/evm-utils/evm-bridge/src/main.rs +++ b/evm-utils/evm-bridge/src/main.rs @@ -136,7 +136,7 @@ impl BridgeERPC for BridgeERPCImpl { tx.signature.chain_id() ); - let ix = solana_evm_loader_program::send_raw_tx(&meta.key.pubkey(), tx); + let ix = solana_evm_loader_program::send_raw_tx(meta.key.pubkey(), tx); let message = Message::new(&[ix], Some(&meta.key.pubkey())); let mut send_raw_tx: solana_sdk::transaction::Transaction = @@ -176,7 +176,7 @@ impl BridgeERPC for BridgeERPCImpl { tx.signature.chain_id() ); - let ix = solana_evm_loader_program::send_raw_tx(&meta.key.pubkey(), tx); + let ix = solana_evm_loader_program::send_raw_tx(meta.key.pubkey(), tx); let message = Message::new(&[ix], Some(&meta.key.pubkey())); let mut send_raw_tx: solana_sdk::transaction::Transaction = diff --git a/evm-utils/programs/evm_loader/src/lib.rs b/evm-utils/programs/evm_loader/src/lib.rs index ad6ace309f..5dfcf728e7 100644 --- a/evm-utils/programs/evm_loader/src/lib.rs +++ b/evm-utils/programs/evm_loader/src/lib.rs @@ -29,10 +29,10 @@ use instructions::EvmInstruction; use scope::*; use solana_sdk::instruction::{AccountMeta, Instruction}; -pub fn send_raw_tx(signer: &solana::Address, evm_tx: evm::Transaction) -> solana::Instruction { +pub fn send_raw_tx(signer: solana::Address, evm_tx: evm::Transaction) -> solana::Instruction { let account_metas = vec![ AccountMeta::new(solana::evm_state::ID, false), - AccountMeta::new(*signer, true), + AccountMeta::new(signer, true), ]; Instruction::new( @@ -43,13 +43,13 @@ pub fn send_raw_tx(signer: &solana::Address, evm_tx: evm::Transaction) -> solana } pub(crate) fn transfer_native_to_eth( - owner: &solana::Address, + owner: solana::Address, lamports: u64, ether_address: evm::Address, ) -> solana::Instruction { let account_metas = vec![ AccountMeta::new(solana::evm_state::ID, false), - AccountMeta::new(*owner, true), + AccountMeta::new(owner, true), ]; Instruction::new( @@ -62,22 +62,22 @@ pub(crate) fn transfer_native_to_eth( ) } -pub(crate) fn free_ownership(owner: &solana::Address) -> solana::Instruction { +pub(crate) fn free_ownership(owner: solana::Address) -> solana::Instruction { let account_metas = vec![ AccountMeta::new(solana::evm_state::ID, false), - AccountMeta::new(*owner, true), + AccountMeta::new(owner, true), ]; Instruction::new(crate::ID, &EvmInstruction::FreeOwnership {}, account_metas) } pub fn transfer_native_to_eth_ixs( - owner: &solana::Address, + owner: solana::Address, lamports: u64, ether_address: evm::Address, ) -> Vec { vec![ - solana_sdk::system_instruction::assign(owner, &crate::ID), + solana_sdk::system_instruction::assign(&owner, &crate::ID), transfer_native_to_eth(owner, lamports, ether_address), free_ownership(owner), ] diff --git a/evm-utils/programs/evm_loader/src/processor.rs b/evm-utils/programs/evm_loader/src/processor.rs index 3bb110d320..717e9ad2f5 100644 --- a/evm-utils/programs/evm_loader/src/processor.rs +++ b/evm-utils/programs/evm_loader/src/processor.rs @@ -407,9 +407,9 @@ mod test { let signer = solana::Address::new_rand(); vec![ - crate::transfer_native_to_eth(&signer, 1, tx_call.address().unwrap()), - crate::free_ownership(&signer), - crate::send_raw_tx(&signer, tx_call), + crate::transfer_native_to_eth(signer, 1, tx_call.address().unwrap()), + crate::free_ownership(signer), + crate::send_raw_tx(signer, tx_call), ] } diff --git a/evm-utils/src/main.rs b/evm-utils/src/main.rs index b3b7d65337..0371d910c5 100644 --- a/evm-utils/src/main.rs +++ b/evm-utils/src/main.rs @@ -71,7 +71,7 @@ fn main(args: Args) -> Result<(), Box> { solana_sdk::program_utils::limited_deserialize(&buf).unwrap(); debug!("loaded tx = {:?}", tx); - let ix = solana_evm_loader_program::send_raw_tx(&signer.pubkey(), tx); + let ix = solana_evm_loader_program::send_raw_tx(signer.pubkey(), tx); let message = Message::new(&[ix], Some(&signer.pubkey())); let mut create_account_tx = solana::Transaction::new_unsigned(message); @@ -96,7 +96,7 @@ fn main(args: Args) -> Result<(), Box> { ether_address, } => { let ixs = solana_evm_loader_program::transfer_native_to_eth_ixs( - &signer.pubkey(), + signer.pubkey(), amount, ether_address, ); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 5b52b904e6..5aa2405a44 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -5947,7 +5947,7 @@ mod tests { let create_tx = |from_keypair: &Keypair, hash: Hash| { let from_pubkey = from_keypair.pubkey(); let instruction = solana_evm_loader_program::send_raw_tx( - &from_pubkey, + from_pubkey, solana_evm_loader_program::processor::dummy_call(), ); let message = Message::new(&[instruction], Some(&from_pubkey)); diff --git a/runtime/src/rent_collector.rs b/runtime/src/rent_collector.rs index 8d2ad6a6ce..87d2f17eed 100644 --- a/runtime/src/rent_collector.rs +++ b/runtime/src/rent_collector.rs @@ -79,6 +79,7 @@ impl RentCollector { || account.rent_epoch > self.epoch || sysvar::check_id(&account.owner) || *address == incinerator::id() + || *address == solana_sdk::evm_state::id() { 0 } else { From 4aeca75debdb3d731dacfa9f56c548444966e1d7 Mon Sep 17 00:00:00 2001 From: hrls Date: Fri, 8 Jan 2021 16:08:11 +0200 Subject: [PATCH 03/13] chore(dockerfile): run apt update for caches --- Dockerfile | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 05477f1741..0060017980 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,24 @@ FROM ubuntu:20.04 as builder - +RUN apt-get -y update ENV TZ=Europe/Stockholm RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN apt-get -y update && apt-get install && \ - apt-get -y install curl git libssl-dev libudev-dev make pkg-config zlib1g-dev llvm clang +RUN apt-get -y install curl git libssl-dev libudev-dev make pkg-config zlib1g-dev llvm clang RUN curl https://sh.rustup.rs -sSf | bash -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" RUN rustup component add rustfmt && rustup update -#Use own solana, clone for repo or add to existent repo -COPY ./ /solana -#COPY ./solana /solana -#RUN git clone https://github.com/solana-labs/solana +COPY . /solana WORKDIR /solana RUN cargo build --release + FROM ubuntu:20.04 as dest -RUN apt-get update && \ - apt-get -y install libssl-dev libudev-dev curl +RUN apt-get -y update +RUN apt-get -y install libssl-dev libudev-dev curl + COPY --from=builder /solana/target/release/ /usr/local/solana COPY ./entrypoint.sh /entrypoint.sh ENV PATH="/usr/local/solana:$PATH" -#CMD /bin/bash +# CMD /bin/bash From 6963a3a6c37b597e224a62a9fb9e84419869c2dc Mon Sep 17 00:00:00 2001 From: Vladimir Motylenko Date: Sun, 27 Dec 2020 14:01:15 +0200 Subject: [PATCH 04/13] fix: Make evm utils configurable. --- evm-utils/src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/evm-utils/src/main.rs b/evm-utils/src/main.rs index 0371d910c5..8663800c57 100644 --- a/evm-utils/src/main.rs +++ b/evm-utils/src/main.rs @@ -44,6 +44,8 @@ enum SubCommands { #[derive(Debug, structopt::StructOpt)] struct Args { + #[structopt(short = "r", long = "rpc")] + rpc_address: Option, #[structopt(subcommand)] subcommand: SubCommands, } @@ -60,7 +62,8 @@ fn main(args: Args) -> Result<(), Box> { info!("Loading keypair from: {}", keypath); let signer = Box::new(read_keypair_file(&keypath).unwrap()) as Box; - let rpc_client = RpcClient::new("http://127.0.0.1:8899".to_string()); + let address = args.rpc_address.unwrap_or_else(||"https://api.next.velas.com:8899".to_string()); + let rpc_client = RpcClient::new(address); match args.subcommand { SubCommands::SendRawTx { raw_tx } => { From 90a89b64b2731a97621e0a9e412229a516e63aaa Mon Sep 17 00:00:00 2001 From: hrls Date: Thu, 26 Nov 2020 19:12:11 +0300 Subject: [PATCH 05/13] feat(evm-state): versioned storage, tests WIP feat: squash chore(evm-state): fix clippy warns on Copy types refactor(storage): PR related renaming chore(evm-state): state dump; benches refactor: typed storage Also include some run fixes --- Cargo.lock | 34 +- core/src/evm_rpc_impl/mod.rs | 9 +- evm-utils/evm-state/.gitignore | 1 + evm-utils/evm-state/Cargo.toml | 6 +- evm-utils/evm-state/benches/bench_evm.rs | 128 ++- evm-utils/evm-state/src/evm_backend.rs | 6 +- evm-utils/evm-state/src/layered_backend.rs | 230 +++-- evm-utils/evm-state/src/lib.rs | 97 +-- evm-utils/evm-state/src/storage.rs | 790 ++++++++++++++++++ evm-utils/evm-state/src/test_utils.rs | 39 + evm-utils/evm-state/src/transactions.rs | 4 +- evm-utils/evm-state/src/version_map.rs | 203 ++--- .../programs/evm_loader/src/processor.rs | 36 +- evm-utils/src/main.rs | 4 +- runtime/src/bank.rs | 11 +- runtime/src/bank_client.rs | 10 +- 16 files changed, 1364 insertions(+), 244 deletions(-) create mode 100644 evm-utils/evm-state/.gitignore create mode 100644 evm-utils/evm-state/src/storage.rs create mode 100644 evm-utils/evm-state/src/test_utils.rs diff --git a/Cargo.lock b/Cargo.lock index a263052f58..52f96b0a7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1243,7 +1243,10 @@ dependencies = [ "keccak-hash", "lazy_static", "log 0.4.11", + "paste 1.0.4", "primitive-types", + "quickcheck", + "quickcheck_macros", "rand 0.6.1", "rand 0.7.3", "rlp", @@ -2091,7 +2094,7 @@ checksum = "c502a5ff9dd2924f1ed32ba96e3b65735d837b4bfd978d3161b1702e66aca4b7" dependencies = [ "jemalloc-sys", "libc", - "paste", + "paste 0.1.18", ] [[package]] @@ -2887,6 +2890,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "paste" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d65c4d95931acda4498f675e332fcbdc9a06705cd07086c510e9b6009cd1c1" + [[package]] name = "paste-impl" version = "0.1.18" @@ -3199,6 +3208,29 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quickcheck" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" +dependencies = [ + "env_logger 0.7.1", + "log 0.4.11", + "rand 0.7.3", + "rand_core 0.5.1", +] + +[[package]] +name = "quickcheck_macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.54", +] + [[package]] name = "quote" version = "0.6.13" diff --git a/core/src/evm_rpc_impl/mod.rs b/core/src/evm_rpc_impl/mod.rs index 1252d56ebd..1b87bd70d0 100644 --- a/core/src/evm_rpc_impl/mod.rs +++ b/core/src/evm_rpc_impl/mod.rs @@ -275,7 +275,8 @@ impl BasicERPC for BasicERPCImpl { ) -> Result, Error> { let bank = meta.bank(block_to_commitment(block)); let evm_state = bank.evm_state.read().unwrap(); - Ok(Hex(evm_state.basic(address.0).balance)) + let account = evm_state.get_account(address.0).unwrap_or_default(); + Ok(Hex(account.balance)) } fn storage_at( @@ -300,7 +301,8 @@ impl BasicERPC for BasicERPCImpl { ) -> Result, Error> { let bank = meta.bank(block_to_commitment(block)); let evm_state = bank.evm_state.read().unwrap(); - Ok(Hex(evm_state.basic(address.0).nonce)) + let account = evm_state.get_account(address.0).unwrap_or_default(); + Ok(Hex(account.nonce)) } fn code( @@ -311,7 +313,8 @@ impl BasicERPC for BasicERPCImpl { ) -> Result { let bank = meta.bank(block_to_commitment(block)); let evm_state = bank.evm_state.read().unwrap(); - Ok(Bytes(evm_state.basic(address.0).code)) + let account = evm_state.get_account(address.0).unwrap_or_default(); + Ok(Bytes(account.code)) } fn transaction_by_hash( diff --git a/evm-utils/evm-state/.gitignore b/evm-utils/evm-state/.gitignore new file mode 100644 index 0000000000..9f970225ad --- /dev/null +++ b/evm-utils/evm-state/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/evm-utils/evm-state/Cargo.toml b/evm-utils/evm-state/Cargo.toml index 220acd9973..8f3b8c31c8 100644 --- a/evm-utils/evm-state/Cargo.toml +++ b/evm-utils/evm-state/Cargo.toml @@ -31,7 +31,11 @@ bytes = "0.6.0" [dev-dependencies] criterion = "0.3" +quickcheck = "0.9.2" +quickcheck_macros = "0.9.1" +rand = "0.7.3" +paste = "1.0.3" [[bench]] name = "bench_evm" -harness = false \ No newline at end of file +harness = false diff --git a/evm-utils/evm-state/benches/bench_evm.rs b/evm-utils/evm-state/benches/bench_evm.rs index cc3a4d3dfc..3ed8221d1f 100644 --- a/evm-utils/evm-state/benches/bench_evm.rs +++ b/evm-utils/evm-state/benches/bench_evm.rs @@ -1,18 +1,40 @@ +use std::fs; +use std::path::{Path, PathBuf}; + use criterion::{criterion_group, criterion_main, Criterion, Throughput}; +use anyhow::{bail, Context, Result}; use assert_matches::assert_matches; -use evm::{Capture, CreateScheme, ExitReason, ExitSucceed}; +use evm::{Capture, CreateScheme, ExitReason, ExitSucceed, Handler}; use evm_state::*; - use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; -use std::sync::RwLock; + +use evm_state::{layered_backend::*, *}; fn name_to_key(name: &str) -> H160 { let hash = H256::from_slice(Keccak256::digest(name.as_bytes()).as_slice()); hash.into() } +fn prepare_dir>(path: P) -> Result { + let path = path.as_ref(); + if path.exists() { + bail!("Path {} is already exists", path.display()); + } + fs::create_dir_all(path).context("Unable to create for bench data")?; + Ok(path.to_owned()) +} + +fn cleanup_dir>(path: P) -> Result<()> { + let path = path.as_ref(); + if !path.exists() { + bail!("Path {} is not exists", path.display()); + } + fs::remove_dir_all(path).context("Clean-up")?; + Ok(()) +} + fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("Evm"); @@ -22,12 +44,18 @@ fn criterion_benchmark(c: &mut Criterion) { group.bench_function("call_hello", |b| { let code = hex::decode(HELLO_WORLD_CODE).unwrap(); let data = hex::decode(HELLO_WORLD_ABI).unwrap(); + let dir = prepare_dir("call_hello").unwrap(); + let mut state = EvmState::load_from(&dir, 0).unwrap(); - let config = evm::Config::istanbul(); - let backend = EvmState::new(); - - let backend = RwLock::new(backend); + for acc in &accounts { + let account = name_to_key(acc); + let memory = AccountState { + ..Default::default() + }; + state.accounts.insert(account, memory); + } + let config = evm::Config::istanbul(); let mut executor = Executor::with_config( backend.read().unwrap().clone(), config, @@ -67,24 +95,19 @@ fn criterion_benchmark(c: &mut Criterion) { any_other => panic!("Not expected result={:?}", any_other), } }); + + cleanup_dir(dir).unwrap(); }); group.bench_function("call_hello_with_executor_recreate", |b| { - let accounts = ["contract", "caller"]; - let code = hex::decode(HELLO_WORLD_CODE).unwrap(); let data = hex::decode(HELLO_WORLD_ABI).unwrap(); + let dir = prepare_dir("call_hello_with_executor_recreate").unwrap(); + let mut state = EvmState::load_from(&dir, 0).unwrap(); let config = evm::Config::istanbul(); - let backend = EvmState::new(); - let backend = RwLock::new(backend); - let mut executor = Executor::with_config( - backend.read().unwrap().clone(), - config.clone(), - usize::max_value(), - 0, - ); + let mut executor = Executor::with_config(state, config.clone(), usize::max_value(), 0); let exit_reason = match executor.with_executor(|e| { e.create( @@ -108,6 +131,15 @@ fn criterion_benchmark(c: &mut Criterion) { backend.read().unwrap().clone(), config.clone(), usize::max_value(), + 0, + ); + + let exit_reason = executor.rent_executor().transact_call( + name_to_key("contract"), + name_to_key("contract"), + U256::zero(), + data.to_vec(), + usize::max_value(), 1, ); let exit_reason = executor.with_executor(|e| { @@ -126,7 +158,69 @@ fn criterion_benchmark(c: &mut Criterion) { any_other => panic!("Not expected result={:?}", any_other), } }); + + cleanup_dir(dir).unwrap(); }); + + group.bench_function("call_hello_on_dumped_state", |b| { + let accounts = ["contract", "caller"]; + + let code = hex::decode(HELLO_WORLD_CODE).unwrap(); + let data = hex::decode(HELLO_WORLD_ABI).unwrap(); + + let dir = prepare_dir("call_hello_on_dumped_state").unwrap(); + let mut state = EvmState::load_from(&dir, 0).unwrap(); + + for acc in &accounts { + let account = name_to_key(acc); + let memory = AccountState { + ..Default::default() + }; + state.accounts.insert(account, memory); + } + state.dump().unwrap(); + + let config = evm::Config::istanbul(); + + let mut executor = Executor::with_config(state, config.clone(), usize::max_value(), 0); + + let exit_reason = match executor.rent_executor().create( + name_to_key("caller"), + CreateScheme::Fixed(name_to_key("contract")), + U256::zero(), + code, + None, + ) { + Capture::Exit((s, _, v)) => (s, v), + Capture::Trap(_) => unreachable!(), + }; + + assert_matches!(exit_reason, (ExitReason::Succeed(ExitSucceed::Returned), _)); + let patch = executor.deconstruct(); + state.apply(patch); + state.dump_all(); + + b.iter(|| { + let mut executor = + StaticExecutor::with_config(state.clone(), config.clone(), usize::max_value()); + let exit_reason = executor.rent_executor().transact_call( + name_to_key("contract"), + name_to_key("contract"), + U256::zero(), + data.to_vec(), + usize::max_value(), + ); + + let result = hex::decode(HELLO_WORLD_RESULT).unwrap(); + match exit_reason { + (ExitReason::Succeed(ExitSucceed::Returned), res) if res == result => {} + any_other => panic!("Not expected result={:?}", any_other), + } + }); + + cleanup_dir(dir).unwrap(); + }); + group.finish(); } diff --git a/evm-utils/evm-state/src/evm_backend.rs b/evm-utils/evm-state/src/evm_backend.rs index d7b9f9379e..da9ed58d9b 100644 --- a/evm-utils/evm-state/src/evm_backend.rs +++ b/evm-utils/evm-state/src/evm_backend.rs @@ -140,9 +140,11 @@ impl ApplyBackend for EvmBackend { for (index, value) in storage { if value == H256::default() { - self.evm_state.storage.remove((address, index)); + self.evm_state.accounts_storage.remove((address, index)); } else { - self.evm_state.storage.insert((address, index), value); + self.evm_state + .accounts_storage + .insert((address, index), value); } } diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index 68b65587c1..25007c84f9 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -1,12 +1,19 @@ +use std::{borrow::Cow, path::Path}; + use evm::backend::Log; use primitive_types::{H160, H256, U256}; use serde::{Deserialize, Serialize}; -use super::transactions::TransactionReceipt; -use super::version_map::Map; +use crate::{ + persistent_types, + storage::{PersistentAssoc, PersistentMap, VersionedStorage}, + transactions::TransactionReceipt, + version_map::{KeyResult, Map}, + Slot, +}; /// Vivinity value of a memory backend. -#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct MemoryVicinity { /// Gas price. pub gas_price: U256, @@ -28,7 +35,23 @@ pub struct MemoryVicinity { pub block_gas_limit: U256, } -#[derive(Default, Clone, Debug, Eq, PartialEq)] +impl Default for MemoryVicinity { + fn default() -> Self { + Self { + gas_price: U256::zero(), + origin: H160::default(), + chain_id: U256::zero(), + block_hashes: Vec::new(), + block_number: U256::zero(), + block_coinbase: H160::default(), + block_timestamp: U256::zero(), + block_difficulty: U256::zero(), + block_gas_limit: U256::max_value(), + } + } +} + +#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AccountState { /// Account nonce. pub nonce: U256, @@ -38,87 +61,157 @@ pub struct AccountState { pub code: Vec, } -#[derive(Debug, Clone)] +// Store every account storage at single place, to use power of versioned map. +// This allows us to save only changed data. +persistent_types! { + Accounts :: "accounts" => H160 => AccountState, + AccountsStorage :: "accounts_storage" => (H160, H256) => H256, + TransactionReceipts :: "txs_receipts" => H256 => TransactionReceipt, + TransactionsInBlock :: "txs_in_block" => Slot => Vec, +} + +type Mapped = Map::Key, ::Value>; + +#[derive(Clone)] // TODO: Debug pub struct EvmState { - pub(crate) accounts: Map, - // Store every account storage at single place, to use power of versioned map. - // This allows us to save only changed data. - pub(crate) storage: Map<(H160, H256), H256>, - pub(crate) txs_receipts: Map, - pub(crate) txs_in_block: Map>, - pub(crate) logs: Vec, + pub(crate) slot: Slot, + + pub(crate) accounts: Mapped, + pub(crate) accounts_storage: Mapped, + pub(crate) txs_receipts: Mapped, + pub(crate) txs_in_block: Mapped, + pub(crate) logs: Vec, // TODO: migrate into storage + + storage: VersionedStorage, } +// TODO: move this logic outside impl Default for EvmState { fn default() -> Self { - Self::new_not_forget_to_deserialize_later() + let path = std::env::temp_dir().join("evm-state"); + Self::load_from(path, Slot::default()).expect("Unable to instantiate default EVM state") } } impl EvmState { - pub fn new() -> Self { - Self { - accounts: Map::new(), - storage: Map::new(), - txs_receipts: Map::new(), - txs_in_block: Map::new(), - logs: Vec::new(), - } - } - pub fn freeze(&mut self) { self.accounts.freeze(); - self.storage.freeze(); + self.accounts_storage.freeze(); self.txs_receipts.freeze(); self.txs_in_block.freeze(); } - pub fn try_fork(&self) -> Option { - let accounts = self.accounts.try_fork()?; - let storage = self.storage.try_fork()?; - let txs_receipts = self.txs_receipts.try_fork()?; - let txs_in_block = self.txs_in_block.try_fork()?; + pub fn try_fork(&self, new_slot: Slot) -> Option { + let accounts = self.accounts.try_fork(new_slot)?; + let accounts_storage = self.accounts_storage.try_fork(new_slot)?; + let txs_receipts = self.txs_receipts.try_fork(new_slot)?; + let txs_in_block = self.txs_in_block.try_fork(new_slot)?; + + // TODO: save new_slot in new state and refactor memory map as versionless with inlined get w/o layered map proxy Some(Self { + slot: new_slot, + accounts, - storage, + accounts_storage, txs_receipts, txs_in_block, logs: vec![], + storage: self.storage.clone(), }) } + + #[rustfmt::skip] + pub fn dump_all(&mut self) -> anyhow::Result<()> { + dump_into(&self.storage.typed::(), &mut self.accounts)?; + dump_into(&self.storage.typed::(), &mut self.accounts_storage)?; + dump_into(&self.storage.typed::(), &mut self.txs_receipts)?; + Ok(()) + } + + fn lookup<'a, M: PersistentAssoc>( + &'a self, + // TODO: elide this arg, TBD: maybe typemap + map: &'a Mapped, + key: M::Key, + ) -> Option> + where + M::Key: Copy + Ord, + M::Value: Clone, + { + match map.get(&key) { + KeyResult::Found(mb_value) => mb_value.map(Cow::Borrowed), + KeyResult::NotFound(last_version) => self + .storage + .typed::() + .get_for(*last_version, key) + .expect("Internal storage error") + .map(Cow::Owned), + } + } } -impl EvmState { - // TODO: Replace it by persistent storage - pub fn new_not_forget_to_deserialize_later() -> Self { - EvmState { - accounts: Map::new(), - storage: Map::new(), - txs_receipts: Map::new(), - txs_in_block: Map::new(), - logs: Vec::new(), +fn dump_into<'a, M: PersistentAssoc>( + storage: &PersistentMap<'a, Slot, M>, + map: &mut Mapped, +) -> anyhow::Result<()> +where + M::Key: Ord + Copy, + M::Value: Clone, +{ + let mut full_iter = map.full_iter().peekable(); + while let Some((version, kvs)) = full_iter.next() { + for (key, value) in kvs { + storage.insert_with(*version, *key, value.cloned())?; } + + let previous = full_iter + .peek() + .map(|(previous, _)| **previous) + .or_else(|| storage.previous_of(*version).ok().flatten()); + storage.new_version(*version, previous)?; } + drop(full_iter); - pub fn get_tx_receipt_by_hash(&self, tx_hash: H256) -> Option { - self.txs_receipts.get(&tx_hash).cloned() + map.clear(); + Ok(()) +} + +impl EvmState { + pub fn load_from>(path: P, slot: Slot) -> Result { + let storage = VersionedStorage::open(path, COLUMN_NAMES)?; + + Ok(Self { + slot, + logs: vec![], + + accounts: Map::empty(slot), + accounts_storage: Map::empty(slot), + txs_receipts: Map::empty(slot), + txs_in_block: Map::empty(slot), + + storage, + }) } - pub fn get_txs_in_block(&self, block_num: u64) -> Option> { - self.txs_in_block.get(&block_num).cloned() + pub fn get_tx_receipt_by_hash(&self, tx_hash: H256) -> Option { + self.lookup::(&self.txs_receipts, tx_hash) + .map(Cow::into_owned) } - pub fn get_account(&self, address: H160) -> Option { - self.accounts.get(&address).cloned() + pub fn get_txs_in_block(&self, block_num: Slot) -> Option> { + self.lookup::(&self.txs_in_block, block_num) + .map(Cow::into_owned) } - pub fn basic(&self, account: H160) -> AccountState { - self.get_account(account).unwrap_or_default() + pub fn get_account(&self, address: H160) -> Option { + self.lookup::(&self.accounts, address) + .map(Cow::into_owned) } pub fn get_storage(&self, address: H160, index: H256) -> Option { - self.storage.get(&(address, index)).cloned() + self.lookup::(&self.accounts_storage, (address, index)) + .map(Cow::into_owned) } pub fn swap_commit(&mut self, mut updated: Self) { @@ -128,29 +221,22 @@ impl EvmState { } #[cfg(test)] -mod test { - use super::*; +mod tests { + use std::collections::{BTreeMap, BTreeSet}; + use primitive_types::{H160, H256, U256}; use rand::rngs::mock::StepRng; use rand::Rng; - use std::collections::{BTreeMap, BTreeSet}; + + use crate::test_utils::TmpDir; + + use super::*; + const RANDOM_INCR: u64 = 734512; const MAX_SIZE: usize = 32; // Max size of test collections. const SEED: u64 = 123; - impl EvmState { - pub(crate) fn testing_default() -> EvmState { - EvmState { - accounts: Default::default(), - storage: Default::default(), - txs_receipts: Default::default(), - txs_in_block: Default::default(), - logs: Default::default(), - } - } - } - fn generate_account_by_seed(seed: u64) -> AccountState { let mut rng = StepRng::new(seed * RANDOM_INCR + seed, RANDOM_INCR); let nonce: [u8; 32] = rng.gen(); @@ -243,8 +329,8 @@ mod test { for s in storage { match &s.1 { - Some(v) => state.storage.insert(*s.0, *v), - None => state.storage.remove(*s.0), + Some(v) => state.accounts_storage.insert(*s.0, *v), + None => state.accounts_storage.remove(*s.0), } } } @@ -255,11 +341,11 @@ mod test { storage: &BTreeMap<(H160, H256), Option>, ) { for account in accounts { - assert_eq!(state.accounts.get(account.0), account.1.as_ref()) + assert_eq!(state.accounts.get(*account.0).as_ref(), account.1.as_ref()) } for s in storage { - assert_eq!(state.storage.get(s.0), s.1.as_ref()) + assert_eq!(state.accounts_storage.get(*s.0).as_ref(), s.1.as_ref()) } } @@ -272,7 +358,9 @@ mod test { let storage_diff = to_state_diff(storage, BTreeSet::new()); let accounts_state_diff = to_state_diff(accounts_state, BTreeSet::new()); - let mut evm_state = EvmState::testing_default(); + let tmp_dir = TmpDir::new("add_two_accounts_check_helpers"); + let mut evm_state = EvmState::load_from(tmp_dir, Default::default()).unwrap(); + assert_eq!(evm_state.basic(H160::random()).balance, U256::from(0)); save_state(&mut evm_state, &accounts_state_diff, &storage_diff); @@ -288,13 +376,15 @@ mod test { let storage_diff = to_state_diff(storage, BTreeSet::new()); let accounts_state_diff = to_state_diff(accounts_state, BTreeSet::new()); - let mut evm_state = EvmState::testing_default(); + let tmp_dir = TmpDir::new("fork_add_remove_accounts"); + let mut evm_state = EvmState::load_from(tmp_dir, Default::default()).unwrap(); + save_state(&mut evm_state, &accounts_state_diff, &storage_diff); evm_state.freeze(); assert_state(&evm_state, &accounts_state_diff, &storage_diff); - let mut new_evm_state = evm_state.try_fork().unwrap(); + let mut new_evm_state = evm_state.try_fork(1).unwrap(); assert_state(&new_evm_state, &accounts_state_diff, &storage_diff); let new_accounts = generate_accounts_addresses(SEED + 1, 2); diff --git a/evm-utils/evm-state/src/lib.rs b/evm-utils/evm-state/src/lib.rs index 7794186cfc..2d3e4ccac3 100644 --- a/evm-utils/evm-state/src/lib.rs +++ b/evm-utils/evm-state/src/lib.rs @@ -1,24 +1,28 @@ -use std::fmt; - pub use evm::{ backend::{Apply, ApplyBackend, Backend, Log}, executor::StackExecutor, Config, Context, Handler, Transfer, }; pub use evm::{ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed}; -use log::debug; pub use primitive_types::{H256, U256}; pub use secp256k1::rand; -mod evm_backend; -mod layered_backend; pub mod transactions; -mod version_map; - pub use evm_backend::*; pub use layered_backend::*; pub use transactions::*; +use std::fmt; + +use log::debug; + +mod evm_backend; +mod layered_backend; +mod storage; +mod version_map; + +pub(crate) type Slot = u64; // TODO: re-use existing one from sdk package + pub trait FromKey { fn to_public_key(&self) -> secp256k1::PublicKey; fn to_address(&self) -> crate::Address; @@ -167,22 +171,17 @@ impl Executor { let block_num = self.evm.tx_info.block_number.as_u64(); let tx_hash = tx.signing_hash(); - log::debug!("Register tx in evm block={}, tx= {}", block_num, tx_hash); - //TODO: replace by entry api - let updated_vec = match self.evm.evm_state.txs_in_block.get(&block_num) { - None => vec![tx_hash], - Some(v) => { - let mut v = v.clone(); - v.push(tx_hash); - v - } - }; - - let index = updated_vec.len() as u64; - self.evm + debug!("Register tx in evm block={}, tx= {}", block_num, tx_hash); + // TODO: replace by Entry-like api + let mut hashes = self + .evm .evm_state - .txs_in_block - .insert(block_num, updated_vec); + .get_txs_in_block(block_num) + .unwrap_or_default(); + hashes.push(tx_hash); + + let index = hashes.len() as u64; + self.evm.evm_state.txs_in_block.insert(block_num, hashes); let tx_receipt = TransactionReceipt::new(tx, used_gas, block_num, index, result); self.evm.evm_state.txs_receipts.insert(tx_hash, tx_receipt); @@ -197,17 +196,21 @@ pub const HELLO_WORLD_CODE:&str = "608060405234801561001057600080fd5b5061011e806 pub const HELLO_WORLD_ABI: &str = "942ae0a7"; pub const HELLO_WORLD_RESULT:&str = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a68656c6c6f576f726c6400000000000000000000000000000000000000000000"; pub const HELLO_WORLD_CODE_SAVED:&str = "6080604052348015600f57600080fd5b506004361060285760003560e01c8063942ae0a714602d575b600080fd5b603360ab565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101560715780820151818401526020810190506058565b50505050905090810190601f168015609d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60606040518060400160405280600a81526020017f68656c6c6f576f726c640000000000000000000000000000000000000000000081525090509056fea2646970667358221220fa787b95ca91ffe90fdb780b8ee8cb11c474bc63cb8217112c88bc465f7ea7d364736f6c63430007020033"; + #[cfg(test)] -pub mod tests { +mod test_utils; - use super::Executor; - use super::*; +#[cfg(test)] +mod tests { + use anyhow::anyhow; use assert_matches::assert_matches; - use evm::{Capture, CreateScheme, ExitReason, ExitSucceed, Handler}; + use evm::{Capture, CreateScheme, ExitReason, ExitSucceed, Handler}; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; - use std::sync::RwLock; + + use super::*; + use crate::test_utils::TmpDir; fn name_to_key(name: &str) -> H160 { let hash = H256::from_slice(Keccak256::digest(name.as_bytes()).as_slice()); @@ -215,33 +218,31 @@ pub mod tests { } #[test] - fn test_evm_bytecode() { - simple_logger::SimpleLogger::new().init().unwrap(); + fn test_evm_bytecode() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new().init()?; let accounts = ["contract", "caller"]; - let code = hex::decode(HELLO_WORLD_CODE).unwrap(); - let data = hex::decode(HELLO_WORLD_ABI).unwrap(); - - let backend = EvmState::new(); - let backend = RwLock::new(backend); + let code = hex::decode(HELLO_WORLD_CODE)?; + let data = hex::decode(HELLO_WORLD_ABI)?; - { - let mut state = backend.write().unwrap(); + let tmp_dir = TmpDir::new("test_evm_bytecode"); + let mut backend = EvmState::load_from(tmp_dir, Slot::default())?; - for acc in &accounts { - let account = name_to_key(acc); - let memory = AccountState { - ..Default::default() - }; - state.accounts.insert(account, memory); - } + for acc in &accounts { + let account = name_to_key(acc); + let memory = AccountState { + ..Default::default() + }; + backend.accounts.insert(account, memory); } - backend.write().unwrap().freeze(); + backend.freeze(); let config = evm::Config::istanbul(); let mut executor = Executor::with_config( - backend.read().unwrap().try_fork().unwrap(), + backend + .clone() + .ok_or_else(|| anyhow!("Unable to fork backend"))?, config, usize::max_value(), 0, @@ -271,7 +272,7 @@ pub mod tests { ) }); - let result = hex::decode(HELLO_WORLD_RESULT).unwrap(); + let result = hex::decode(HELLO_WORLD_RESULT)?; match exit_reason { (ExitReason::Succeed(ExitSucceed::Returned), res) if res == result => {} any_other => panic!("Not expected result={:?}", any_other), @@ -280,11 +281,11 @@ pub mod tests { let patch = executor.deconstruct(); backend.write().unwrap().swap_commit(patch); - let mutex_lock = backend.read().unwrap(); - let contract = mutex_lock.accounts.get(&name_to_key("contract")); + let contract = backend.accounts.get(name_to_key("contract")); assert_eq!( &contract.unwrap().code, &hex::decode(HELLO_WORLD_CODE_SAVED).unwrap() ); + Ok(()) } } diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs new file mode 100644 index 0000000000..120c030c3d --- /dev/null +++ b/evm-utils/evm-state/src/storage.rs @@ -0,0 +1,790 @@ +use std::array::TryFromSliceError; +use std::convert::{TryFrom, TryInto}; +use std::fmt; +use std::io::Cursor; +use std::marker::PhantomData; +use std::mem::size_of; +use std::ops::Deref; +use std::path::Path; +use std::sync::{Arc, RwLock}; + +use bincode::config::{BigEndian, DefaultOptions, Options as _, WithOtherEndian}; +use lazy_static::lazy_static; +use rocksdb::{self, ColumnFamily, ColumnFamilyDescriptor, IteratorMode, Options, DB}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +pub type Result = std::result::Result; +pub type StdResult = std::result::Result; + +type BincodeOpts = WithOtherEndian; +lazy_static! { + static ref CODER: BincodeOpts = DefaultOptions::new().with_big_endian(); +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + DatabaseErr(#[from] rocksdb::Error), + #[error("Missed Column Familiy for type '{0}'")] + ColumnFamilyErr(String), + #[error(transparent)] + BincodeErr(#[from] bincode::Error), + #[error("Unable to construct key from bytes")] + KeyErr(#[from] TryFromSliceError), +} + +pub struct VersionedStorage { + db: Arc, + squash_guard: Arc>, + _version: PhantomData, +} + +impl Clone for VersionedStorage { + fn clone(&self) -> Self { + Self { + db: Arc::clone(&self.db), + squash_guard: Arc::clone(&self.squash_guard), + _version: PhantomData, + } + } +} + +type Previous = Option; // TODO: Vec + +impl VersionedStorage +where + V: Copy + Serialize + DeserializeOwned, + Previous: Serialize + DeserializeOwned, +{ + pub fn previous_of(&self, version: V) -> Result> { + let version = CODER.serialize(&version)?; + let bytes = self.db.get_pinned(version)?; + let previous = bytes.map(|bytes| CODER.deserialize(&bytes)).transpose()?; + Ok(previous) + } + + pub fn versions(&self) -> impl Iterator)> + '_ { + self.db + .iterator(IteratorMode::End) + .map(move |(key, value)| { + let version = CODER.deserialize(&key).unwrap_or_else(|err| { + panic!("Unable to deserialize version from {:?}: {:?}", key, err) + }); + let previous = CODER.deserialize(&value).unwrap_or_else(|err| { + panic!("Unable to deserialize previous from {:?}: {:?}", value, err) + }); + (version, previous) + }) + } + + pub fn new_version(&self, version: V, previous: Previous) -> Result<()> { + let version = CODER.serialize(&version)?; + debug_assert_eq!(self.db.get(&version)?, None); + let previous = CODER.serialize(&previous)?; + self.db.put(version, previous)?; + Ok(()) + } +} + +pub trait PersistentAssoc { + const COLUMN_NAME: &'static str; + type Key: Serialize + DeserializeOwned; + type Value: Serialize + DeserializeOwned; +} + +#[macro_export] +macro_rules! persistent_types { + ($($Marker:ident :: $Column:expr => $Key:ty => $Value:ty,)+) => { + const COLUMN_NAMES: &[&'static str] = &[$($Column),+]; + + $( + pub(crate) enum $Marker {} + impl PersistentAssoc for $Marker { + const COLUMN_NAME: &'static str = $Column; + type Key = $Key; + type Value = $Value; + } + )+ + }; +} + +impl VersionedStorage { + pub fn open, S: AsRef>( + path: P, + column_names: impl IntoIterator, + ) -> Result + where + V: AsBytePrefix, + { + let db_opts = default_db_opts(); + + let descriptors = column_names + .into_iter() + .map(|type_name| { + let mut cf_opts = Options::default(); + cf_opts.set_prefix_extractor(rocksdb::SliceTransform::create_fixed_prefix(V::SIZE)); + ColumnFamilyDescriptor::new(type_name.as_ref(), cf_opts) + }) + .collect::>(); + + let db = Arc::new(DB::open_cf_descriptors(&db_opts, &path, descriptors)?); + let squash_guard = Arc::new(RwLock::default()); + + Ok(Self { + db, + squash_guard, + _version: PhantomData, + }) + } + + pub fn typed<'a, M: PersistentAssoc>(&'a self) -> PersistentMap<'a, V, M> { + assert!(self.db.cf_handle(M::COLUMN_NAME).is_some()); + + PersistentMap { + storage: &self, + _marker: PhantomData, + } + } +} + +pub struct PersistentMap<'a, V, M: PersistentAssoc> { + // TODO: revise this ref type + storage: &'a VersionedStorage, + _marker: PhantomData, +} + +impl<'a, V, M: PersistentAssoc> Deref for PersistentMap<'a, V, M> { + type Target = VersionedStorage; + fn deref(&self) -> &Self::Target { + &self.storage + } +} + +pub trait AsBytePrefix { + const SIZE: usize; + + type Bytes: AsRef<[u8]>; + fn to_bytes(&self) -> Self::Bytes; + + type FromBytesError; + fn from_bytes(_: &[u8]) -> StdResult + where + Self: Sized; +} + +#[derive(Serialize, Deserialize)] +struct VersionedKey { + version: V, + key: Key, +} + +impl TryInto> for VersionedKey +where + V: AsBytePrefix, + Key: Serialize, +{ + type Error = bincode::Error; + + fn try_into(self) -> StdResult, Self::Error> { + let mut bytes = Vec::from(self.version.to_bytes().as_ref()); + let mut cursor = Cursor::new(&mut bytes); + cursor.set_position(::SIZE as u64); + bincode::serialize_into(&mut cursor, &self.key)?; + Ok(bytes) + } +} + +impl VersionedKey +where + V: AsBytePrefix, +{ + #[allow(dead_code)] + fn version_of(bytes: &[u8]) -> Result + where + Error: From, + { + V::from_bytes(bytes).map_err(Error::from) + } + + fn key_from(bytes: &[u8]) -> Result + where + Key: DeserializeOwned, + { + bincode::deserialize_from(&bytes[::SIZE..]).map_err(Error::from) + } +} + +impl<'a, V, M: PersistentAssoc> PersistentMap<'a, V, M> { + fn db(&self) -> &DB { + self.storage.db.as_ref() + } + + fn cf(&self) -> &ColumnFamily { + self.db() + .cf_handle(M::COLUMN_NAME) + .unwrap_or_else(|| panic!("Missed Column Family '{}'", M::COLUMN_NAME)) + } +} + +impl<'a, V, M: PersistentAssoc> PersistentMap<'a, V, M> +where + V: Copy + AsBytePrefix + Serialize + DeserializeOwned, + M::Key: Copy, +{ + pub fn insert_with(&self, version: V, key: M::Key, value: Option) -> Result<()> { + let versioned_key: Vec = VersionedKey { version, key }.try_into()?; + let value = CODER.serialize(&value)?; + self.db().put_cf(self.cf(), versioned_key, value)?; + Ok(()) + } + + pub fn get_for(&self, version: V, key: M::Key) -> Result> { + let _guard = self + .storage + .squash_guard + .read() + .expect("squash guard was poisoned"); + + let mut next_version = Some(version); + + while let Some(version) = next_version.take() { + let value = self.get_exact_for(version, key)?; + if value.is_some() { + return Ok(value); + } else { + next_version = self.storage.previous_of(version)?; + continue; + } + } + + Ok(None) + } + + fn get_exact_for(&self, version: V, key: M::Key) -> Result> { + let versioned_key: Vec = VersionedKey { version, key }.try_into()?; + let bytes = self.db().get_pinned_cf(self.cf(), versioned_key)?; + let mb_value = bytes.map(|bytes| CODER.deserialize(&bytes)).transpose()?; + Ok(mb_value) + } + + pub fn prefix_iter_for( + &self, + version: V, + ) -> Result + '_> { + Ok(self + .db() + .prefix_iterator_cf(self.cf(), version.to_bytes()) + .map(move |(key, value)| { + let key = VersionedKey::::key_from(&key).unwrap_or_else(|err| { + panic!("Unable to deserialize key from {:?}: {:?}", key, err) + }); + let value = CODER.deserialize(&value).unwrap_or_else(|err| { + panic!("Unable to deserialize value from {:?}: {:?}", value, err) + }); + (key, value) + })) + } + + fn has_value_for(&self, version: V, key: M::Key) -> Result { + let versioned_key: Vec = VersionedKey { version, key }.try_into()?; + let data_ref = self.db().get_pinned_cf(self.cf(), versioned_key)?; + Ok(data_ref.is_some()) + } + + pub fn squash_into_rev_pass(&self, target: V) -> Result<()> + where + Previous: DeserializeOwned, + M::Key: HasMax, + { + let _guard = self + .storage + .squash_guard + .write() + .expect("squash guard was poisoned"); + + let mut track = vec![target]; + while let Some(prev) = self.storage.previous_of(track[track.len() - 1])? { + track.push(prev); + } + + let mut rev_track = track.into_iter().rev().peekable(); + while let (Some(current), Some(parent)) = (rev_track.next(), rev_track.peek().copied()) { + for (key, value) in self.prefix_iter_for(parent)? { + if !self.has_value_for(current, key)? { + self.insert_with(current, key, Some(value))?; + } + } + + self.delete_all_for(parent)?; + + // TODO: cleanup all None's + } + + Ok(()) + } + + pub fn squash_into_tracing(&self, target: V) -> Result<()> + where + Previous: DeserializeOwned, + M::Key: HasMax, + { + let _guard = self + .storage + .squash_guard + .write() + .expect("squash guard was poisoned"); + + let mut next_parent_for = target; + + while let Some(parent) = self.storage.previous_of(next_parent_for)? { + for (key, value) in self.prefix_iter_for(parent)? { + if !self.has_value_for(target, key)? { + self.insert_with(target, key, Some(value))?; + } + } + + self.delete_all_for(parent)?; + + next_parent_for = parent; + } + + Ok(()) + } + + fn delete_all_for(&self, version: V) -> Result<()> + where + M::Key: HasMax, + { + let version_prefix: Vec = version.to_bytes().as_ref().iter().copied().collect(); + let key_max_bytes = bincode::serialized_size(&M::Key::MAX)? as usize + 1; + let mut lexicographic_max: Vec = version_prefix.clone(); + lexicographic_max.extend(std::iter::repeat(0u8).take(key_max_bytes)); + self.db() + .delete_range_cf(self.cf(), version_prefix, lexicographic_max)?; + Ok(()) + } +} + +pub trait HasMax { + const MAX: Self; +} + +// pub type VersionedValue<'a, V, Value> = +// PersistentMap<'a, V, M: PersistentAssoc>; + +impl<'a, V, Value, M: PersistentAssoc> PersistentMap<'a, V, M> +where + V: Copy + AsBytePrefix + Serialize + DeserializeOwned, + Value: Serialize + DeserializeOwned, +{ + // TODO: previous versions as argument + pub fn insert(&self, version: V, value: Value) -> Result<()> { + self.insert_with(version, (), Some(value))?; + self.storage.new_version(version, None)?; + Ok(()) + } + + pub fn get(&self, version: V) -> Result> { + self.get_exact_for(version, ()) + } + + pub fn keys(&self) -> impl Iterator + '_ { + self.storage.versions().map(|(version, _)| version) + } +} + +pub fn default_db_opts() -> Options { + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.create_missing_column_families(true); + opts +} + +macro_rules! nums_as_byte_prefixes { + ($($ty:ty),+) => { + $( + impl AsBytePrefix for $ty { + const SIZE: usize = size_of::<$ty>(); + + type Bytes = [u8; size_of::<$ty>()]; + + fn to_bytes(&self) -> Self::Bytes { + self.to_be_bytes() + } + + type FromBytesError = TryFromSliceError; + + fn from_bytes(bytes: &[u8]) -> StdResult<$ty, Self::FromBytesError> { + Self::Bytes::try_from(bytes) + .map(<$ty>::from_be_bytes) + } + } + )+ + } +} + +nums_as_byte_prefixes! { + u8, u16, u32, u64, u128 +} + +macro_rules! nums_has_max { + ($($ty:ty),+) => { + $( + impl HasMax for $ty { + const MAX: $ty = <$ty>::MAX; + } + )+ + } +} + +nums_has_max! { + u8, u16, u32, u64, u128 +} + +macro_rules! primitive_type_has_max { + ($($ty:ty),+) => { + $( + impl HasMax for $ty { + const MAX: $ty = <$ty>::repeat_byte(u8::MAX); + } + )+ + } + +} + +use primitive_types::{H160, H256, H512}; + +primitive_type_has_max! { + H160, H256, H512 +} + +impl fmt::Debug for VersionedStorage +where + V: 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::any::TypeId; + f.debug_struct("VersionedStorage") + .field("Version", &TypeId::of::()) + .field("database", &self.db.path().display()) + .finish() + } +} + +impl<'a, V, M: PersistentAssoc> fmt::Debug for PersistentMap<'a, V, M> +where + V: 'static, + M::Key: 'static, + M::Value: 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::any::TypeId; + + f.debug_struct("Storage") + .field("Version", &TypeId::of::()) + .field("Key", &TypeId::of::()) + .field("Value", &TypeId::of::()) + .field("database", &self.db().path().display()) + .field("column", &M::COLUMN_NAME) + .finish() + } +} + +#[cfg(test)] +#[allow(clippy::blacklisted_name)] +mod tests { + use std::collections::{BTreeMap, HashMap}; + use std::iter::FromIterator; + use std::thread::{self, JoinHandle}; + + use quickcheck_macros::quickcheck; + + use crate::test_utils::TmpDir; + + use super::*; + + fn open(path: P, type_name: S) -> Result> + where + V: AsBytePrefix, + P: AsRef, + S: AsRef, + { + VersionedStorage::open(path, &[type_name.as_ref()])?.typed(type_name.as_ref()) + } + + impl AsBytePrefix for (A, B) + where + A: AsBytePrefix, + B: AsBytePrefix, + TryFromSliceError: From, + TryFromSliceError: From, + { + const SIZE: usize = A::SIZE + B::SIZE; + type Bytes = Vec; + fn to_bytes(&self) -> Self::Bytes { + let mut bytes = Vec::with_capacity(Self::SIZE); + bytes.extend(self.0.to_bytes().as_ref()); + bytes.extend(self.1.to_bytes().as_ref()); + bytes + } + + type FromBytesError = TryFromSliceError; + fn from_bytes(bytes: &[u8]) -> StdResult<(A, B), Self::FromBytesError> { + let a = A::from_bytes(&bytes[..A::SIZE])?; + let b = B::from_bytes(&bytes[A::SIZE..(A::SIZE + B::SIZE)])?; + Ok((a, b)) + } + } + + #[test] + fn it_handles_full_range_mapping() -> Result<()> { + use rand::Rng; + + type Version = u8; + type Key = u8; + type Value = u64; + type Assoc = HashMap>; + + let mut rng = rand::thread_rng(); + + let mut assoc: Assoc = (0..=Version::MAX) + .map(|version| { + ( + version, + (0..=Key::MAX).map(|key| (key, rng.gen())).collect(), + ) + }) + .collect(); + println!("assoc is ready"); + + let dir = TmpDir::new("it_handles_full_range_mapping"); + { + let s = open(&dir, "vkv")?; + + for (&version, map) in &assoc { + for (&key, &value) in map { + s.insert_with(version, key, value)?; + } + s.storage.new_version(version, None)?; + } + s.db().flush()?; + println!("assoc is stored"); + } + { + let s = open(&dir, "vkv")?; + for (version, _) in s.storage.versions() { + let new_map = HashMap::from_iter(s.prefix_iter_for(version)?); + assert_eq!(assoc.remove(&version), Some(new_map)); + } + assert!(assoc.is_empty()); + } + Ok(()) + } + + type K = u16; + type V = u64; + type ThreadId = u64; + + #[quickcheck] + fn qc_version_precedes_key_in_serialized_data(version: u64, key: u64) -> Result<()> { + let version_data = CODER.serialize(&version)?; + let versioned_key = VersionedKey { version, key }; + let versioned_key_data = CODER.serialize(&versioned_key)?; + assert!(versioned_key_data.starts_with(&version_data)); + Ok(()) + } + + macro_rules! pair_works_as_byte_prefix { + ($foo:ty, $bar:ty) => { + paste::item! { + mod [< $foo _ $bar _ as_version >] { + use super::*; + + type Pair = ($foo, $bar); + type Key = Vec; + type ComplexKey = VersionedKey; + + #[quickcheck] + fn [< qc_pair_of _ $foo _ $bar _works_as_byte_prefix >](version: Pair, key: Key) -> Result<()> { + assert_eq!(version.to_bytes().len(), Pair::SIZE); + + let data: Vec = ComplexKey { version, key: key.clone() }.try_into()?; + + assert_eq!(ComplexKey::version_of(&data)?, version); + assert_eq!(ComplexKey::key_from(&data)?, key); + Ok(()) + } + } + } + }; + } + + // TODO: recursive + // pairs_works_as_byte_prefix! { + // @foo: u8, u16, u32, u64, u128, + // @bar: u8, u16, u32, u64, u128, + // } + pair_works_as_byte_prefix!(u8, u16); + pair_works_as_byte_prefix!(u8, u64); + pair_works_as_byte_prefix!(u64, u16); + + #[quickcheck] + fn qc_keyless_storage_behaves_like_a_map_with_version_as_key( + map: BTreeMap, + ) -> Result<()> { + let dir = TmpDir::new("qc_keyless_storage_behaves_like_a_map_with_version_as_key"); + { + let s = open(&dir, "kv")?; + for (&k, &v) in &map { + s.insert(k, v)?; + } + s.db().flush()?; + } + { + let s = open(&dir, "kv")?; + let mut new_map = BTreeMap::new(); + for key in s.keys() { + new_map.insert(key, s.get(key)?.unwrap()); + } + assert_eq!(map, new_map); + } + Ok(()) + } + + #[quickcheck] + fn qc_two_concurrent_threads_works_on_shared_db_via_version_assoc( + (foo, bar): (BTreeMap, BTreeMap), + ) -> Result<()> { + type Storage = VersionedValue<(ThreadId, K), V>; + + let dir = TmpDir::new("qc_two_concurrent_threads_works_on_shared_db_via_version_assoc"); + let s: Arc = Arc::new(open(&dir, "kv_assoc")?); + + fn spawn_insert(s: &Arc, map: BTreeMap) -> JoinHandle> { + let s = Arc::clone(s); + thread::spawn(move || -> Result { + let id = thread_id(); + for (k, v) in map { + s.insert((id, k), v)?; + } + s.db().flush()?; + Ok(id) + }) + } + + let foo_wh = spawn_insert(&s, foo.clone()); + let bar_wh = spawn_insert(&s, bar.clone()); + + let foo_id = foo_wh.join().unwrap()?; + let bar_id = bar_wh.join().unwrap()?; + + fn spawn_read( + s: &Arc, + id: ThreadId, + keys: impl IntoIterator + Send + 'static, + ) -> JoinHandle>> { + let s = Arc::clone(s); + thread::spawn(move || -> Result> { + keys.into_iter() + .map(|key| s.get((id, key)).map(|value| (key, value.unwrap()))) + .collect::>() + }) + } + + let foo_rh = spawn_read(&s, foo_id, foo.keys().copied().collect::>()); + let bar_rh = spawn_read(&s, bar_id, bar.keys().copied().collect::>()); + + let new_foo = foo_rh.join().unwrap()?; + let new_bar = bar_rh.join().unwrap()?; + + assert_eq!(foo, new_foo); + assert_eq!(bar, new_bar); + + Ok(()) + } + + #[quickcheck] + fn qc_two_concurrent_threads_works_on_shared_db_via_own_version_slot( + (foo, bar): (BTreeMap, BTreeMap), + ) -> Result<()> { + type TStorage = PersistentMap; + let dir = TmpDir::new("qc_two_concurrent_threads_works_on_shared_db_via_own_version_slot"); + let s: Arc = Arc::new(open(&dir, "slot_assoc")?); + + fn spawn_insert(s: &Arc, map: BTreeMap) -> JoinHandle> { + let s = Arc::clone(s); + thread::spawn(move || -> Result { + let id = thread_id(); + for (k, v) in map { + s.insert_with(id, k, v)?; + } + s.storage.new_version(id, None)?; + s.db().flush()?; + Ok(id) + }) + } + + let foo_wh = spawn_insert(&s, foo.clone()); + let bar_wh = spawn_insert(&s, bar.clone()); + + let foo_id = foo_wh.join().unwrap()?; + let bar_id = bar_wh.join().unwrap()?; + + fn spawn_read( + s: &Arc, + id: ThreadId, + keys: impl IntoIterator + Send + 'static, + ) -> JoinHandle>> { + let s = Arc::clone(s); + thread::spawn(move || -> Result> { + keys.into_iter() + .map(|key| s.get_exact_for(id, key).map(|value| (key, value.unwrap()))) + .collect::>() + }) + } + + let foo_rh = spawn_read(&s, foo_id, foo.keys().copied().collect::>()); + let bar_rh = spawn_read(&s, bar_id, bar.keys().copied().collect::>()); + + let new_foo = foo_rh.join().unwrap()?; + let new_bar = bar_rh.join().unwrap()?; + + assert_eq!(foo, new_foo); + assert_eq!(bar, new_bar); + + Ok(()) + } + + #[quickcheck] + fn qc_reads_the_same_as_inserts(assoc: HashMap>) -> Result<()> { + let dir = TmpDir::new("qc_reads_the_same_as_inserts"); + { + let s = open(&dir, "vkv")?; + for (&version, map) in &assoc { + for (&k, &v) in map { + s.insert_with(version, k, v)?; + } + s.storage.new_version(version, None)?; + } + s.db().flush()?; + } + { + let s = open(&dir, "vkv")?; + + let mut new_assoc = HashMap::>::new(); + for (version, _) in s.storage.versions() { + new_assoc.insert(version, s.prefix_iter_for(version).unwrap().collect()); + } + + assert_eq!(assoc, new_assoc); + } + Ok(()) + } + + fn thread_id() -> u64 { + use std::hash::{Hash, Hasher}; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + std::thread::current().id().hash(&mut hasher); + hasher.finish() + } +} diff --git a/evm-utils/evm-state/src/test_utils.rs b/evm-utils/evm-state/src/test_utils.rs new file mode 100644 index 0000000000..da52e3102b --- /dev/null +++ b/evm-utils/evm-state/src/test_utils.rs @@ -0,0 +1,39 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +#[derive(Clone)] +pub struct TmpDir(PathBuf); + +impl TmpDir { + pub fn new>(sub_dir: P) -> Self { + let path = env::temp_dir().join(sub_dir); + let pprint = path.as_path().display(); + if path.exists() { + panic!("Path is {} already exists", pprint); + } + fs::create_dir(&path) + .unwrap_or_else(|err| panic!("Unable to create tmp dir {}: {:?}", pprint, err)); + println!("{}", pprint); + Self(path) + } +} + +impl Drop for TmpDir { + fn drop(&mut self) { + fs::remove_dir_all(self.0.as_path()).unwrap_or_else(|err| { + panic!( + "Unable to remove tmp dir {}: {:?}", + self.0.as_path().display(), + err + ) + }); + } +} + +impl AsRef for TmpDir { + fn as_ref(&self) -> &Path { + self.0.as_path() + } +} diff --git a/evm-utils/evm-state/src/transactions.rs b/evm-utils/evm-state/src/transactions.rs index d3f33e1a5c..14319a66d9 100644 --- a/evm-utils/evm-state/src/transactions.rs +++ b/evm-utils/evm-state/src/transactions.rs @@ -280,8 +280,9 @@ impl From for UnsignedTransaction { } } } + // TODO: Work on logs and state_root. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct TransactionReceipt { pub transaction: Transaction, pub status: evm::ExitReason, @@ -292,6 +293,7 @@ pub struct TransactionReceipt { // pub logs_bloom: LogsBloom, // pub logs: Vec, } + impl TransactionReceipt { pub fn new( transaction: Transaction, diff --git a/evm-utils/evm-state/src/version_map.rs b/evm-utils/evm-state/src/version_map.rs index 99b728a4d9..c4d221e287 100644 --- a/evm-utils/evm-state/src/version_map.rs +++ b/evm-utils/evm-state/src/version_map.rs @@ -1,7 +1,5 @@ -use std::borrow::Borrow; use std::collections::BTreeMap; use std::fmt; -use std::ops::Deref; use std::sync::Arc; /// Represent state of value at current version. @@ -32,186 +30,205 @@ impl From> for Option { } #[derive(Clone)] -pub struct Map>> { - state: BTreeMap>, - parent: Option, +pub struct Map { + pub(crate) version: Version, + state: BTreeMap>, + parent: Option>>, } -impl Default for Map { +impl Default for Map +where + Version: Default, + Key: Ord, +{ fn default() -> Self { Map::new() } } -impl fmt::Debug for Map -where - K: fmt::Debug, - V: fmt::Debug, - Store: MapLike, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Map") - .field("state", &self.state) - .field("parent", &"omited") - .finish() - } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum KeyResult { + /// Value for existing key. + Found(T), + /// Key not found, Value is last looked version. + NotFound(V), } -impl Map +impl Map where - K: Ord, - Store: MapLike, + Key: Ord, { + pub fn empty(version: Version) -> Self { + Self { + version, + state: BTreeMap::new(), + parent: None, + } + } + // Create new versioned map. - pub fn new() -> Map + pub fn new() -> Self where - Store: Sized, + Version: Default, { - Map { + Self { + version: Version::default(), state: BTreeMap::new(), parent: None, } } // Borrow value by key - pub fn get(&self, key: &K) -> Option<&V> { - if let Some(s) = self.state.get(key) { - s.by_ref().into() - } else { - self.parent.as_ref().and_then(|parent| parent.get(key)) + pub fn get(&self, key: &Key) -> KeyResult<&Version, Option<&Value>> { + match (self.state.get(key), self.parent.as_ref()) { + (Some(s), _) => KeyResult::Found(s.by_ref().into()), + (None, Some(parent)) => parent.get(key), + (None, None) => KeyResult::NotFound(&self.version), } } - // Exclusively borrow value by key - pub fn get_mut(&mut self, _key: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Ord, - { - unimplemented!() // TODO: Implement a guard that will save value at drop. + // Insert new key, didn't query key before inserting. + pub fn insert(&mut self, key: Key, value: Value) { + self.push_change(key, State::Changed(value)); + } + + // Remove key, didn't query key before inserting. + pub fn remove(&mut self, key: Key) { + self.push_change(key, State::Removed); + } + + pub fn clear(&mut self) { + self.state.clear(); + self.parent = None; } // Override state of key. - pub(crate) fn push_change(&mut self, key: K, value: State) { + fn push_change(&mut self, key: Key, value: State) { self.state.insert(key, value); } - // Insert new key, didn't query key before inserting. - pub fn insert(&mut self, key: K, value: V) { - self.push_change(key, State::Changed(value)); + pub fn iter(&self) -> (&Version, impl Iterator)> + '_) { + ( + &self.version, + self.state + .iter() + .map(|(key, value)| (key, value.by_ref().into())), + ) } - // Remove key, didn't query key before inserting. - pub fn remove(&mut self, key: K) { - self.push_change(key, State::Removed); + pub fn full_iter( + &self, + ) -> impl Iterator)> + '_)> + '_ + { + std::iter::once(self.iter()).chain(self.parent.as_ref().map(|parent| parent.iter())) } } -impl Map +impl Map where - K: Ord + Send + Sync + 'static, - V: Send + Sync + 'static, + Key: Ord, { - pub fn freeze(&mut self) { - let this = Arc::new(std::mem::take(self)) as Arc>; - self.parent = Some(this); + pub fn freeze(&mut self) + where + Version: Clone, + { + let this = Self { + version: self.version.clone(), + state: std::mem::take(&mut self.state), + parent: self.parent.as_ref().map(Arc::clone), + }; + self.parent = Some(Arc::new(this)); } // Create new version from freezed one - pub fn try_fork(&self) -> Option { + pub fn try_fork(&self, new_version: Version) -> Option + where + Version: Ord, + { + assert!(new_version > self.version); + if !self.state.is_empty() { return None; } Some(Self { + version: new_version, state: BTreeMap::new(), parent: self.parent.clone(), }) } } -/// Map can store it's old version in database or in some other immutable structure. -/// This trait allows you to define your own storage -pub trait MapLike: Sync + Send { - type Key; - type Value; - fn get(&self, key: &Self::Key) -> Option<&Self::Value>; -} - -impl MapLike for Arc { - type Key = Store::Key; - type Value = Store::Value; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - ::get(self.deref(), key) - } -} - -impl MapLike for Map +impl fmt::Debug for Map where - K: Ord + Sync + Send, - V: Sync + Send, - Store: MapLike + Send + Sync, + Version: fmt::Debug, + Key: fmt::Debug, + Value: fmt::Debug, { - type Key = Store::Key; - type Value = Store::Value; - fn get(&self, key: &Self::Key) -> Option<&Self::Value> { - Map::get(self, key) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Map") + .field("version", &self.version) + .field("state", &self.state) + .field("parent", &"omited") + .finish() } } #[cfg(test)] mod test { use super::*; + use KeyResult::*; + #[test] fn store_and_get_simple() { - let mut map: Map<_, _> = Map::new(); + let mut map: Map<(), _, _> = Map::new(); map.insert("first", 1); map.insert("second", 2); - assert_eq!(map.get(&"first"), Some(&1)); - assert_eq!(map.get(&"second"), Some(&2)); + assert_eq!(map.get(&"first"), Found(Some(&1))); + assert_eq!(map.get(&"second"), Found(Some(&2))); } // Test that map can save version, and type of map is always remain the same. #[test] fn new_dynamic_version_insert_remove_test() { - let mut map: Map<_, _> = Map::new(); + let mut map: Map<_, _, _> = Map::new(); map.insert("first", 1); map.insert("second", 2); map.insert("third", 3); - assert_eq!(map.get(&"first"), Some(&1)); - assert_eq!(map.get(&"second"), Some(&2)); - assert_eq!(map.get(&"third"), Some(&3)); + assert_eq!(map.get(&"first"), Found(Some(&1))); + assert_eq!(map.get(&"second"), Found(Some(&2))); + assert_eq!(map.get(&"third"), Found(Some(&3))); map.freeze(); - let mut map: Map<_, _> = map.try_fork().unwrap(); + let mut map: Map<_, _, _> = map.try_fork(1).unwrap(); map.remove("first"); map.insert("third", 1); - assert_eq!(map.get(&"first"), None); - assert_eq!(map.get(&"second"), Some(&2)); - assert_eq!(map.get(&"third"), Some(&1)); + assert_eq!(map.get(&"first"), Found(None)); + assert_eq!(map.get(&"second"), Found(Some(&2))); + assert_eq!(map.get(&"third"), Found(Some(&1))); } // Same as new_dynamic_version_insert_remove_test but dont hide type of store. #[test] fn new_static_version_insert_remove_test() { - let mut map: Map<_, _> = Map::new(); + let mut map: Map<_, _, _> = Map::new(); map.insert("first", 1); map.insert("second", 2); map.insert("third", 3); - assert_eq!(map.get(&"first"), Some(&1)); - assert_eq!(map.get(&"second"), Some(&2)); - assert_eq!(map.get(&"third"), Some(&3)); + assert_eq!(map.get(&"first"), Found(Some(&1))); + assert_eq!(map.get(&"second"), Found(Some(&2))); + assert_eq!(map.get(&"third"), Found(Some(&3))); map.freeze(); - let mut map = map.try_fork().unwrap(); + let mut map = map.try_fork(1).unwrap(); map.remove("first"); map.insert("third", 1); - assert_eq!(map.get(&"first"), None); - assert_eq!(map.get(&"second"), Some(&2)); - assert_eq!(map.get(&"third"), Some(&1)); + assert_eq!(map.get(&"first"), Found(None)); + assert_eq!(map.get(&"second"), Found(Some(&2))); + assert_eq!(map.get(&"third"), Found(Some(&1))); } } diff --git a/evm-utils/programs/evm_loader/src/processor.rs b/evm-utils/programs/evm_loader/src/processor.rs index 717e9ad2f5..0f5402a7f1 100644 --- a/evm-utils/programs/evm_loader/src/processor.rs +++ b/evm-utils/programs/evm_loader/src/processor.rs @@ -259,8 +259,22 @@ mod test { let caller_address = tx_create.caller().unwrap(); let tx_address = tx_create.address().unwrap(); - assert_eq!(state.read().unwrap().basic(caller_address).nonce, 0.into()); - assert_eq!(state.read().unwrap().basic(tx_address).nonce, 0.into()); + assert_eq!( + state + .read() + .unwrap() + .get_account(caller_address) + .map(|account| account.nonce), + Some(0.into()) + ); + assert_eq!( + state + .read() + .unwrap() + .get_account(tx_address) + .map(|account| account.nonce), + Some(0.into()) + ); { let mut locked = state.write().unwrap(); let mut executor_orig = evm_state::Executor::with_config( @@ -288,8 +302,22 @@ mod test { locked.swap_commit(patch); } - assert_eq!(state.read().unwrap().basic(caller_address).nonce, 1.into()); - assert_eq!(state.read().unwrap().basic(tx_address).nonce, 1.into()); + assert_eq!( + state + .read() + .unwrap() + .get_account(caller_address) + .map(|account| account.nonce), + Some(1.into()) + ); + assert_eq!( + state + .read() + .unwrap() + .get_account(tx_address) + .map(|account| account.nonce), + Some(1.into()) + ); let tx_call = evm::UnsignedTransaction { nonce: 1.into(), diff --git a/evm-utils/src/main.rs b/evm-utils/src/main.rs index 8663800c57..aa94299f37 100644 --- a/evm-utils/src/main.rs +++ b/evm-utils/src/main.rs @@ -62,7 +62,9 @@ fn main(args: Args) -> Result<(), Box> { info!("Loading keypair from: {}", keypath); let signer = Box::new(read_keypair_file(&keypath).unwrap()) as Box; - let address = args.rpc_address.unwrap_or_else(||"https://api.next.velas.com:8899".to_string()); + let address = args + .rpc_address + .unwrap_or_else(|| "https://api.next.velas.com:8899".to_string()); let rpc_client = RpcClient::new(address); match args.subcommand { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 5aa2405a44..e9430ae034 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -543,7 +543,7 @@ impl Bank { .expect("parent evm state was poisoned"); evm_wl.freeze(); evm_wl - .try_fork() + .try_fork(slot) .expect("Unable to fork EVM state right after freezing it") }), blockhash_queue: RwLock::new(parent.blockhash_queue.read().unwrap().clone()), @@ -646,10 +646,17 @@ impl Bank { fn new() -> T { T::default() } + + // TODO: get real path from extern configuration + const EVM_STATE_STORAGE: &str = "/tmp/solana/evm-state"; + + let evm_state = evm_state::EvmState::load_from(EVM_STATE_STORAGE, fields.slot) + .expect("Unable to open EVM state storage"); + let mut bank = Self { rc: bank_rc, src: new(), - evm_state: RwLock::new(evm_state::EvmState::new_not_forget_to_deserialize_later()), + evm_state: RwLock::new(evm_state), blockhash_queue: RwLock::new(fields.blockhash_queue), ancestors: fields.ancestors, hash: RwLock::new(fields.hash), diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index 4cd44fb5d5..398d313021 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -269,7 +269,15 @@ impl SyncClient for BankClient { /// Get account balance or 0 if not found. fn get_evm_balance(&self, pubkey: &evm_state::Address) -> Result { - Ok(self.bank.evm_state.read().unwrap().basic(*pubkey).balance) + let account = self + .bank + .evm_state + .read() + .unwrap() + .get_account(*pubkey) + .unwrap_or_default(); + + Ok(account.balance) } } From ef1703b8b0241d53b816b4b559d103ebb63529db Mon Sep 17 00:00:00 2001 From: hrls Date: Thu, 17 Dec 2020 18:02:06 +0200 Subject: [PATCH 06/13] refactor(evm/storage): re-touch macro; actual tests --- evm-utils/evm-state/src/layered_backend.rs | 27 ++-- evm-utils/evm-state/src/lib.rs | 4 +- evm-utils/evm-state/src/storage.rs | 140 +++++++++++++-------- 3 files changed, 105 insertions(+), 66 deletions(-) diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index 25007c84f9..f0797bd818 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -64,10 +64,10 @@ pub struct AccountState { // Store every account storage at single place, to use power of versioned map. // This allows us to save only changed data. persistent_types! { - Accounts :: "accounts" => H160 => AccountState, - AccountsStorage :: "accounts_storage" => (H160, H256) => H256, - TransactionReceipts :: "txs_receipts" => H256 => TransactionReceipt, - TransactionsInBlock :: "txs_in_block" => Slot => Vec, + Accounts in "accounts" => H160 : AccountState, + AccountsStorage in "accounts_storage" => (H160, H256) : H256, + TransactionReceipts in "txs_receipts" => H256 : TransactionReceipt, + TransactionsInBlock in "txs_in_block" => Slot : Vec, } type Mapped = Map::Key, ::Value>; @@ -340,12 +340,15 @@ mod tests { accounts: &BTreeMap>, storage: &BTreeMap<(H160, H256), Option>, ) { - for account in accounts { - assert_eq!(state.accounts.get(*account.0).as_ref(), account.1.as_ref()) + for (address, expected) in accounts { + assert_eq!(state.get_account(*address).as_ref(), expected.as_ref()) } - for s in storage { - assert_eq!(state.accounts_storage.get(*s.0).as_ref(), s.1.as_ref()) + for ((address, index), expected) in storage { + assert_eq!( + state.get_storage(*address, *index).as_ref(), + expected.as_ref() + ) } } @@ -361,7 +364,13 @@ mod tests { let tmp_dir = TmpDir::new("add_two_accounts_check_helpers"); let mut evm_state = EvmState::load_from(tmp_dir, Default::default()).unwrap(); - assert_eq!(evm_state.basic(H160::random()).balance, U256::from(0)); + assert_eq!( + evm_state + .get_account(H160::random()) + .unwrap_or_default() + .balance, + U256::from(0) + ); save_state(&mut evm_state, &accounts_state_diff, &storage_diff); assert_state(&evm_state, &accounts_state_diff, &storage_diff); diff --git a/evm-utils/evm-state/src/lib.rs b/evm-utils/evm-state/src/lib.rs index 2d3e4ccac3..c700dca8e3 100644 --- a/evm-utils/evm-state/src/lib.rs +++ b/evm-utils/evm-state/src/lib.rs @@ -279,9 +279,9 @@ mod tests { } let patch = executor.deconstruct(); - backend.write().unwrap().swap_commit(patch); + backend.swap_commit(patch); - let contract = backend.accounts.get(name_to_key("contract")); + let contract = backend.get_account(name_to_key("contract")); assert_eq!( &contract.unwrap().code, &hex::decode(HELLO_WORLD_CODE_SAVED).unwrap() diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs index 120c030c3d..967d47e474 100644 --- a/evm-utils/evm-state/src/storage.rs +++ b/evm-utils/evm-state/src/storage.rs @@ -94,7 +94,7 @@ pub trait PersistentAssoc { #[macro_export] macro_rules! persistent_types { - ($($Marker:ident :: $Column:expr => $Key:ty => $Value:ty,)+) => { + ($($Marker:ident in $Column:expr => $Key:ty : $Value:ty,)+) => { const COLUMN_NAMES: &[&'static str] = &[$($Column),+]; $( @@ -106,6 +106,9 @@ macro_rules! persistent_types { } )+ }; + ($($Marker:ident in $Column:expr => $Key:ty : $Value:ty),+) => { + persistent_types! { $($Marker in $Column => $Key : $Value,)+ } + } } impl VersionedStorage { @@ -263,14 +266,17 @@ where fn get_exact_for(&self, version: V, key: M::Key) -> Result> { let versioned_key: Vec = VersionedKey { version, key }.try_into()?; let bytes = self.db().get_pinned_cf(self.cf(), versioned_key)?; - let mb_value = bytes.map(|bytes| CODER.deserialize(&bytes)).transpose()?; + let mb_value = bytes + .map(|bytes| CODER.deserialize(&bytes)) + .transpose()? + .flatten(); Ok(mb_value) } pub fn prefix_iter_for( &self, version: V, - ) -> Result + '_> { + ) -> Result)> + '_> { Ok(self .db() .prefix_iterator_cf(self.cf(), version.to_bytes()) @@ -311,7 +317,7 @@ where while let (Some(current), Some(parent)) = (rev_track.next(), rev_track.peek().copied()) { for (key, value) in self.prefix_iter_for(parent)? { if !self.has_value_for(current, key)? { - self.insert_with(current, key, Some(value))?; + self.insert_with(current, key, value)?; } } @@ -339,7 +345,7 @@ where while let Some(parent) = self.storage.previous_of(next_parent_for)? { for (key, value) in self.prefix_iter_for(parent)? { if !self.has_value_for(target, key)? { - self.insert_with(target, key, Some(value))?; + self.insert_with(target, key, value)?; } } @@ -369,9 +375,6 @@ pub trait HasMax { const MAX: Self; } -// pub type VersionedValue<'a, V, Value> = -// PersistentMap<'a, V, M: PersistentAssoc>; - impl<'a, V, Value, M: PersistentAssoc> PersistentMap<'a, V, M> where V: Copy + AsBytePrefix + Serialize + DeserializeOwned, @@ -503,13 +506,17 @@ mod tests { use super::*; - fn open(path: P, type_name: S) -> Result> - where - V: AsBytePrefix, - P: AsRef, - S: AsRef, - { - VersionedStorage::open(path, &[type_name.as_ref()])?.typed(type_name.as_ref()) + impl AsBytePrefix for () { + const SIZE: usize = 0; + type Bytes = [u8; 0]; + fn to_bytes(&self) -> Self::Bytes { + [] + } + + type FromBytesError = TryFromSliceError; + fn from_bytes(bytes: &[u8]) -> StdResult { + Self::Bytes::try_from(bytes).map(|_| ()) + } } impl AsBytePrefix for (A, B) @@ -544,6 +551,8 @@ mod tests { type Key = u8; type Value = u64; type Assoc = HashMap>; + type Storage = VersionedStorage; + persistent_types! { KV in "kv" => Key : Value } let mut rng = rand::thread_rng(); @@ -559,21 +568,25 @@ mod tests { let dir = TmpDir::new("it_handles_full_range_mapping"); { - let s = open(&dir, "vkv")?; + let s = Storage::open(&dir, COLUMN_NAMES)?; for (&version, map) in &assoc { for (&key, &value) in map { - s.insert_with(version, key, value)?; + s.typed::().insert_with(version, key, Some(value))?; } - s.storage.new_version(version, None)?; + s.new_version(version, None)?; } - s.db().flush()?; + s.db.flush()?; println!("assoc is stored"); } { - let s = open(&dir, "vkv")?; - for (version, _) in s.storage.versions() { - let new_map = HashMap::from_iter(s.prefix_iter_for(version)?); + let s = Storage::open(&dir, COLUMN_NAMES)?; + for (version, _) in s.versions() { + let new_map = HashMap::from_iter( + s.typed::() + .prefix_iter_for(version)? + .map(|(key, mb_value)| (key, mb_value.unwrap())), + ); assert_eq!(assoc.remove(&version), Some(new_map)); } assert!(assoc.is_empty()); @@ -581,10 +594,6 @@ mod tests { Ok(()) } - type K = u16; - type V = u64; - type ThreadId = u64; - #[quickcheck] fn qc_version_precedes_key_in_serialized_data(version: u64, key: u64) -> Result<()> { let version_data = CODER.serialize(&version)?; @@ -628,23 +637,29 @@ mod tests { pair_works_as_byte_prefix!(u8, u64); pair_works_as_byte_prefix!(u64, u16); + type K = u16; + type V = u64; + type ThreadId = u64; + #[quickcheck] fn qc_keyless_storage_behaves_like_a_map_with_version_as_key( map: BTreeMap, ) -> Result<()> { + type Storage = VersionedStorage; + persistent_types! { KV in "kv" => () : V } let dir = TmpDir::new("qc_keyless_storage_behaves_like_a_map_with_version_as_key"); { - let s = open(&dir, "kv")?; + let s = Storage::open(&dir, COLUMN_NAMES)?; for (&k, &v) in &map { - s.insert(k, v)?; + s.typed::().insert(k, v)?; } - s.db().flush()?; + s.db.flush()?; } { - let s = open(&dir, "kv")?; + let s = Storage::open(&dir, COLUMN_NAMES)?; let mut new_map = BTreeMap::new(); - for key in s.keys() { - new_map.insert(key, s.get(key)?.unwrap()); + for key in s.typed::().keys() { + new_map.insert(key, s.typed::().get(key)?.unwrap()); } assert_eq!(map, new_map); } @@ -655,19 +670,19 @@ mod tests { fn qc_two_concurrent_threads_works_on_shared_db_via_version_assoc( (foo, bar): (BTreeMap, BTreeMap), ) -> Result<()> { - type Storage = VersionedValue<(ThreadId, K), V>; - + type Storage = VersionedStorage<(ThreadId, K)>; + persistent_types! { TKV in "tkv" => () : V } let dir = TmpDir::new("qc_two_concurrent_threads_works_on_shared_db_via_version_assoc"); - let s: Arc = Arc::new(open(&dir, "kv_assoc")?); + let s: Arc = Arc::new(Storage::open(&dir, COLUMN_NAMES)?); fn spawn_insert(s: &Arc, map: BTreeMap) -> JoinHandle> { let s = Arc::clone(s); thread::spawn(move || -> Result { let id = thread_id(); for (k, v) in map { - s.insert((id, k), v)?; + s.typed::().insert((id, k), v)?; } - s.db().flush()?; + s.db.flush()?; Ok(id) }) } @@ -686,7 +701,11 @@ mod tests { let s = Arc::clone(s); thread::spawn(move || -> Result> { keys.into_iter() - .map(|key| s.get((id, key)).map(|value| (key, value.unwrap()))) + .map(|key| { + s.typed::() + .get((id, key)) + .map(|value| (key, value.unwrap())) + }) .collect::>() }) } @@ -707,19 +726,20 @@ mod tests { fn qc_two_concurrent_threads_works_on_shared_db_via_own_version_slot( (foo, bar): (BTreeMap, BTreeMap), ) -> Result<()> { - type TStorage = PersistentMap; + type Storage = VersionedStorage; + persistent_types! { KV in "kv" => K : V } let dir = TmpDir::new("qc_two_concurrent_threads_works_on_shared_db_via_own_version_slot"); - let s: Arc = Arc::new(open(&dir, "slot_assoc")?); + let s = Arc::new(Storage::open(&dir, COLUMN_NAMES)?); - fn spawn_insert(s: &Arc, map: BTreeMap) -> JoinHandle> { + fn spawn_insert(s: &Arc, map: BTreeMap) -> JoinHandle> { let s = Arc::clone(s); thread::spawn(move || -> Result { let id = thread_id(); for (k, v) in map { - s.insert_with(id, k, v)?; + s.typed::().insert_with(id, k, Some(v))?; } - s.storage.new_version(id, None)?; - s.db().flush()?; + s.new_version(id, None)?; + s.db.flush()?; Ok(id) }) } @@ -731,14 +751,18 @@ mod tests { let bar_id = bar_wh.join().unwrap()?; fn spawn_read( - s: &Arc, + s: &Arc, id: ThreadId, keys: impl IntoIterator + Send + 'static, ) -> JoinHandle>> { let s = Arc::clone(s); thread::spawn(move || -> Result> { keys.into_iter() - .map(|key| s.get_exact_for(id, key).map(|value| (key, value.unwrap()))) + .map(|key| { + s.typed::() + .get_exact_for(id, key) + .map(|value| (key, value.unwrap())) + }) .collect::>() }) } @@ -756,24 +780,30 @@ mod tests { } #[quickcheck] - fn qc_reads_the_same_as_inserts(assoc: HashMap>) -> Result<()> { + fn qc_reads_the_same_as_inserts(assoc: HashMap>>) -> Result<()> { + type Storage = VersionedStorage; + persistent_types! { KV in "kv" => K : V } let dir = TmpDir::new("qc_reads_the_same_as_inserts"); + { - let s = open(&dir, "vkv")?; + let s = Storage::open(&dir, COLUMN_NAMES)?; for (&version, map) in &assoc { for (&k, &v) in map { - s.insert_with(version, k, v)?; + s.typed::().insert_with(version, k, v)?; } - s.storage.new_version(version, None)?; + s.new_version(version, None)?; } - s.db().flush()?; + s.db.flush()?; } { - let s = open(&dir, "vkv")?; + let s = Storage::open(&dir, COLUMN_NAMES)?; - let mut new_assoc = HashMap::>::new(); - for (version, _) in s.storage.versions() { - new_assoc.insert(version, s.prefix_iter_for(version).unwrap().collect()); + let mut new_assoc = HashMap::>>::new(); + for (version, _) in s.versions() { + new_assoc.insert( + version, + s.typed::().prefix_iter_for(version).unwrap().collect(), + ); } assert_eq!(assoc, new_assoc); From d64d90b78a3dbdc1a6e29c8c9935a2452ac8f967 Mon Sep 17 00:00:00 2001 From: hrls Date: Fri, 18 Dec 2020 18:05:07 +0200 Subject: [PATCH 07/13] feat/fix: dump evm state on set root, WIP benches --- Cargo.lock | 1 - evm-utils/evm-state/Cargo.toml | 1 - evm-utils/evm-state/benches/bench_evm.rs | 156 +++++++++------------ evm-utils/evm-state/src/layered_backend.rs | 58 +++++++- evm-utils/evm-state/src/lib.rs | 32 ++--- evm-utils/evm-state/src/mb_value.rs | 44 ++++++ evm-utils/evm-state/src/storage.rs | 118 ++++++++++------ evm-utils/evm-state/src/version_map.rs | 56 +++----- runtime/src/bank_forks.rs | 6 + 9 files changed, 281 insertions(+), 191 deletions(-) create mode 100644 evm-utils/evm-state/src/mb_value.rs diff --git a/Cargo.lock b/Cargo.lock index 52f96b0a7f..8af7052b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1234,7 +1234,6 @@ name = "evm-state" version = "0.1.0" dependencies = [ "anyhow", - "assert_matches", "bincode", "bytes 0.6.0", "criterion", diff --git a/evm-utils/evm-state/Cargo.toml b/evm-utils/evm-state/Cargo.toml index 8f3b8c31c8..e93a982427 100644 --- a/evm-utils/evm-state/Cargo.toml +++ b/evm-utils/evm-state/Cargo.toml @@ -20,7 +20,6 @@ simple_logger = "1.11" hex = "0.4.2" serde = "1.0" sha3 = "0.9.1" -assert_matches = "1.4" rand = "0.7.3" rlp = "0.4.5" thiserror = "1.0.22" diff --git a/evm-utils/evm-state/benches/bench_evm.rs b/evm-utils/evm-state/benches/bench_evm.rs index 3ed8221d1f..b44aacbe2a 100644 --- a/evm-utils/evm-state/benches/bench_evm.rs +++ b/evm-utils/evm-state/benches/bench_evm.rs @@ -4,14 +4,11 @@ use std::path::{Path, PathBuf}; use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use anyhow::{bail, Context, Result}; -use assert_matches::assert_matches; -use evm::{Capture, CreateScheme, ExitReason, ExitSucceed, Handler}; +use evm::{ExitReason, ExitSucceed}; use evm_state::*; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; -use evm_state::{layered_backend::*, *}; - fn name_to_key(name: &str) -> H160 { let hash = H256::from_slice(Keccak256::digest(name.as_bytes()).as_slice()); hash.into() @@ -37,13 +34,13 @@ fn cleanup_dir>(path: P) -> Result<()> { fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("Evm"); - - group.throughput(Throughput::Elements(1 as u64)); - // simple_logger::SimpleLogger::new().init().unwrap(); + group.throughput(Throughput::Elements(1)); group.bench_function("call_hello", |b| { let code = hex::decode(HELLO_WORLD_CODE).unwrap(); let data = hex::decode(HELLO_WORLD_ABI).unwrap(); + let accounts = ["contract", "caller"]; + let dir = prepare_dir("call_hello").unwrap(); let mut state = EvmState::load_from(&dir, 0).unwrap(); @@ -52,37 +49,31 @@ fn criterion_benchmark(c: &mut Criterion) { let memory = AccountState { ..Default::default() }; - state.accounts.insert(account, memory); + state.set_account(account, memory); } let config = evm::Config::istanbul(); - let mut executor = Executor::with_config( - backend.read().unwrap().clone(), - config, - usize::max_value(), - 0, - ); - - let exit_reason = match executor.with_executor(|e| { - e.create( + let mut executor = Executor::with_config(state.clone(), config, usize::max_value(), 0); + + let exit_reason = executor.with_executor(|executor| { + executor.transact_create( name_to_key("caller"), - CreateScheme::Fixed(name_to_key("contract")), U256::zero(), - code.clone(), - None, + code, + usize::max_value(), ) - }) { - Capture::Exit((s, _, v)) => (s, v), - Capture::Trap(_) => unreachable!(), - }; - - assert_matches!(exit_reason, (ExitReason::Succeed(ExitSucceed::Returned), _)); + }); + let contract_address = TransactionAction::Create.address(name_to_key("caller"), 0.into()); + assert!(matches!( + exit_reason, + ExitReason::Succeed(ExitSucceed::Returned) + )); b.iter(|| { - let exit_reason = executor.with_executor(|e| { - e.transact_call( - name_to_key("contract"), - name_to_key("contract"), + let exit_reason = executor.with_executor(|executor| { + executor.transact_call( + name_to_key("caller"), + contract_address, U256::zero(), data.to_vec(), usize::max_value(), @@ -107,45 +98,33 @@ fn criterion_benchmark(c: &mut Criterion) { let mut state = EvmState::load_from(&dir, 0).unwrap(); let config = evm::Config::istanbul(); - let mut executor = Executor::with_config(state, config.clone(), usize::max_value(), 0); + let mut executor = + Executor::with_config(state.clone(), config.clone(), usize::max_value(), 0); - let exit_reason = match executor.with_executor(|e| { - e.create( + let exit_reason = executor.with_executor(|executor| { + executor.transact_create( name_to_key("caller"), - CreateScheme::Fixed(name_to_key("contract")), U256::zero(), - code.clone(), - None, + code, + usize::max_value(), ) - }) { - Capture::Exit((s, _, v)) => (s, v), - Capture::Trap(_) => unreachable!(), - }; + }); + assert!(matches!( + exit_reason, + ExitReason::Succeed(ExitSucceed::Returned) + )); - assert_matches!(exit_reason, (ExitReason::Succeed(ExitSucceed::Returned), _)); + let contract_address = TransactionAction::Create.address(name_to_key("caller"), 0.into()); let patch = executor.deconstruct(); - backend.write().unwrap().swap_commit(patch); + state.swap_commit(patch); b.iter(|| { - let mut executor = Executor::with_config( - backend.read().unwrap().clone(), - config.clone(), - usize::max_value(), - 0, - ); - - let exit_reason = executor.rent_executor().transact_call( - name_to_key("contract"), - name_to_key("contract"), - U256::zero(), - data.to_vec(), - usize::max_value(), - 1, - ); - let exit_reason = executor.with_executor(|e| { - e.transact_call( - name_to_key("contract"), - name_to_key("contract"), + let mut executor = + Executor::with_config(state.clone(), config.clone(), usize::max_value(), 0); + let exit_reason = executor.with_executor(|executor| { + executor.transact_call( + name_to_key("caller"), + contract_address, U256::zero(), data.to_vec(), usize::max_value(), @@ -176,40 +155,45 @@ fn criterion_benchmark(c: &mut Criterion) { let memory = AccountState { ..Default::default() }; - state.accounts.insert(account, memory); + state.set_account(account, memory); } - state.dump().unwrap(); + state.dump_all().unwrap(); let config = evm::Config::istanbul(); + let mut executor = + Executor::with_config(state.clone(), config.clone(), usize::max_value(), 0); - let mut executor = Executor::with_config(state, config.clone(), usize::max_value(), 0); - - let exit_reason = match executor.rent_executor().create( - name_to_key("caller"), - CreateScheme::Fixed(name_to_key("contract")), - U256::zero(), - code, - None, - ) { - Capture::Exit((s, _, v)) => (s, v), - Capture::Trap(_) => unreachable!(), - }; + let exit_reason = executor.with_executor(|executor| { + executor.transact_create( + name_to_key("caller"), + U256::zero(), + code, + usize::max_value(), + ) + }); + assert!(matches!( + exit_reason, + ExitReason::Succeed(ExitSucceed::Returned), + )); - assert_matches!(exit_reason, (ExitReason::Succeed(ExitSucceed::Returned), _)); let patch = executor.deconstruct(); - state.apply(patch); - state.dump_all(); + state.swap_commit(patch); + + state.dump_all().unwrap(); + let contract_address = TransactionAction::Create.address(name_to_key("caller"), 0.into()); b.iter(|| { let mut executor = - StaticExecutor::with_config(state.clone(), config.clone(), usize::max_value()); - let exit_reason = executor.rent_executor().transact_call( - name_to_key("contract"), - name_to_key("contract"), - U256::zero(), - data.to_vec(), - usize::max_value(), - ); + Executor::with_config(state.clone(), config.clone(), usize::max_value(), 0); + let exit_reason = executor.with_executor(|executor| { + executor.transact_call( + name_to_key("caller"), + contract_address, + U256::zero(), + data.to_vec(), + usize::max_value(), + ) + }); let result = hex::decode(HELLO_WORLD_RESULT).unwrap(); match exit_reason { diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index f0797bd818..715399fc53 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, path::Path}; +use std::{any::type_name, borrow::Cow, path::Path}; use evm::backend::Log; use primitive_types::{H160, H256, U256}; @@ -145,7 +145,16 @@ impl EvmState { .storage .typed::() .get_for(*last_version, key) - .expect("Internal storage error") + .unwrap_or_else(|err| { + panic!( + "Storage ({} :: Key {} => Value {}) lookup error: {:?}", + type_name::(), + type_name::(), + type_name::(), + err + ) + }) + .and_then(Option::from) .map(Cow::Owned), } } @@ -162,7 +171,7 @@ where let mut full_iter = map.full_iter().peekable(); while let Some((version, kvs)) = full_iter.next() { for (key, value) in kvs { - storage.insert_with(*version, *key, value.cloned())?; + storage.insert_with(*version, *key, value.clone())?; } let previous = full_iter @@ -204,6 +213,11 @@ impl EvmState { .map(Cow::into_owned) } + // NOTE: currently used in benches only + pub fn set_account(&mut self, address: H160, state: AccountState) { + self.accounts.insert(address, state) + } + pub fn get_account(&self, address: H160) -> Option { self.lookup::(&self.accounts, address) .map(Cow::into_owned) @@ -229,6 +243,7 @@ mod tests { use rand::Rng; use crate::test_utils::TmpDir; + use anyhow::anyhow; use super::*; @@ -409,4 +424,41 @@ mod tests { assert_state(&new_evm_state, &new_accounts_state_diff, &BTreeMap::new()); } + + #[test] + fn reads_the_same_after_dump() -> anyhow::Result<()> { + let accounts = generate_accounts_addresses(SEED, 42_000); + + let storage = generate_storage(SEED, &accounts); + let accounts_state = generate_accounts_state(SEED, &accounts); + let storage_diff = to_state_diff(storage, BTreeSet::new()); + let accounts_state_diff = to_state_diff(accounts_state, BTreeSet::new()); + + let slot = 0; + + let tmp_dir = TmpDir::new("reads_the_same_after_dump"); + let mut evm_state = EvmState::load_from(tmp_dir, slot)?; + + save_state(&mut evm_state, &accounts_state_diff, &storage_diff); + evm_state.freeze(); + evm_state.dump_all()?; + + { + let mut evm_state = evm_state + .try_fork(slot + 1) + .ok_or_else(|| anyhow!("Unable to fork evm state after freezing"))?; + let accounts = generate_accounts_addresses(SEED + 1, 42_000); + + let storage = generate_storage(SEED + 1, &accounts); + let accounts_state = generate_accounts_state(SEED + 1, &accounts); + let storage_diff = to_state_diff(storage, BTreeSet::new()); + let accounts_state_diff = to_state_diff(accounts_state, BTreeSet::new()); + save_state(&mut evm_state, &accounts_state_diff, &storage_diff); + evm_state.dump_all()?; + assert_state(&evm_state, &accounts_state_diff, &storage_diff); + } + + assert_state(&evm_state, &accounts_state_diff, &storage_diff); + Ok(()) + } } diff --git a/evm-utils/evm-state/src/lib.rs b/evm-utils/evm-state/src/lib.rs index c700dca8e3..b65f6dbd7a 100644 --- a/evm-utils/evm-state/src/lib.rs +++ b/evm-utils/evm-state/src/lib.rs @@ -7,20 +7,21 @@ pub use evm::{ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed}; pub use primitive_types::{H256, U256}; pub use secp256k1::rand; +pub mod layered_backend; pub mod transactions; + pub use evm_backend::*; pub use layered_backend::*; pub use transactions::*; -use std::fmt; - -use log::debug; - mod evm_backend; -mod layered_backend; +mod mb_value; mod storage; mod version_map; +use log::debug; +use std::fmt; + pub(crate) type Slot = u64; // TODO: re-use existing one from sdk package pub trait FromKey { @@ -203,15 +204,16 @@ mod test_utils; #[cfg(test)] mod tests { use anyhow::anyhow; - use assert_matches::assert_matches; use evm::{Capture, CreateScheme, ExitReason, ExitSucceed, Handler}; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; - use super::*; use crate::test_utils::TmpDir; + use super::Executor; + use super::*; + fn name_to_key(name: &str) -> H160 { let hash = H256::from_slice(Keccak256::digest(name.as_bytes()).as_slice()); hash.into() @@ -219,7 +221,7 @@ mod tests { #[test] fn test_evm_bytecode() -> anyhow::Result<()> { - simple_logger::SimpleLogger::new().init()?; + let _logger_error = simple_logger::SimpleLogger::new().init(); let accounts = ["contract", "caller"]; let code = hex::decode(HELLO_WORLD_CODE)?; @@ -239,14 +241,7 @@ mod tests { backend.freeze(); let config = evm::Config::istanbul(); - let mut executor = Executor::with_config( - backend - .clone() - .ok_or_else(|| anyhow!("Unable to fork backend"))?, - config, - usize::max_value(), - 0, - ); + let mut executor = Executor::with_config(backend.clone(), config, usize::max_value(), 0); let exit_reason = match executor.with_executor(|e| { e.create( @@ -261,7 +256,10 @@ mod tests { Capture::Trap(_) => unreachable!(), }; - assert_matches!(exit_reason, (ExitReason::Succeed(ExitSucceed::Returned), _)); + assert!(matches!( + exit_reason, + (ExitReason::Succeed(ExitSucceed::Returned), _) + )); let exit_reason = executor.with_executor(|e| { e.transact_call( name_to_key("contract"), diff --git a/evm-utils/evm-state/src/mb_value.rs b/evm-utils/evm-state/src/mb_value.rs new file mode 100644 index 0000000000..72786ba9cc --- /dev/null +++ b/evm-utils/evm-state/src/mb_value.rs @@ -0,0 +1,44 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum MaybeValue { + /// Value exist, and was changed. + Value(V), + /// Value was removed. + Removed, +} + +use MaybeValue::*; + +impl MaybeValue { + pub fn by_ref(&self) -> MaybeValue<&V> { + match self { + Value(ref v) => Value(v), + Removed => Removed, + } + } +} + +impl From for MaybeValue { + fn from(value: T) -> Self { + Value(value) + } +} + +impl From> for MaybeValue { + fn from(value: Option) -> Self { + match value { + Some(value) => Value(value), + None => Removed, + } + } +} + +impl From> for Option { + fn from(mb_value: MaybeValue) -> Option { + match mb_value { + Value(value) => Some(value), + Removed => None, + } + } +} diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs index 967d47e474..53e31af6bb 100644 --- a/evm-utils/evm-state/src/storage.rs +++ b/evm-utils/evm-state/src/storage.rs @@ -1,6 +1,6 @@ use std::array::TryFromSliceError; use std::convert::{TryFrom, TryInto}; -use std::fmt; +use std::fmt::{self, Debug}; use std::io::Cursor; use std::marker::PhantomData; use std::mem::size_of; @@ -13,6 +13,8 @@ use lazy_static::lazy_static; use rocksdb::{self, ColumnFamily, ColumnFamilyDescriptor, IteratorMode, Options, DB}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use super::mb_value::MaybeValue; + pub type Result = std::result::Result; pub type StdResult = std::result::Result; @@ -25,10 +27,8 @@ lazy_static! { pub enum Error { #[error(transparent)] DatabaseErr(#[from] rocksdb::Error), - #[error("Missed Column Familiy for type '{0}'")] - ColumnFamilyErr(String), - #[error(transparent)] - BincodeErr(#[from] bincode::Error), + #[error("Type {1} :: {0}")] + BincodeErr(bincode::Error, &'static str), #[error("Unable to construct key from bytes")] KeyErr(#[from] TryFromSliceError), } @@ -51,16 +51,30 @@ impl Clone for VersionedStorage { type Previous = Option; // TODO: Vec +trait BincodeResultExt { + fn typed_ctx(self) -> Result; +} + +impl BincodeResultExt for StdResult { + fn typed_ctx(self) -> Result { + self.map_err(|err| Error::BincodeErr(err, std::any::type_name::())) + } +} + impl VersionedStorage where V: Copy + Serialize + DeserializeOwned, Previous: Serialize + DeserializeOwned, { pub fn previous_of(&self, version: V) -> Result> { - let version = CODER.serialize(&version)?; - let bytes = self.db.get_pinned(version)?; - let previous = bytes.map(|bytes| CODER.deserialize(&bytes)).transpose()?; - Ok(previous) + let version = CODER.serialize(&version).typed_ctx()?; + + if let Some(bytes) = self.db.get_pinned(version)? { + let previous = CODER.deserialize::>(&bytes).typed_ctx()?; + Ok(previous) + } else { + Ok(None) + } } pub fn versions(&self) -> impl Iterator)> + '_ { @@ -78,10 +92,11 @@ where } pub fn new_version(&self, version: V, previous: Previous) -> Result<()> { - let version = CODER.serialize(&version)?; - debug_assert_eq!(self.db.get(&version)?, None); - let previous = CODER.serialize(&previous)?; - self.db.put(version, previous)?; + let version = CODER.serialize(&version).typed_ctx()?; + if self.db.get(&version)?.is_none() { + let previous = CODER.serialize(&previous).typed_ctx()?; + self.db.put(version, previous)?; + } Ok(()) } } @@ -140,7 +155,7 @@ impl VersionedStorage { }) } - pub fn typed<'a, M: PersistentAssoc>(&'a self) -> PersistentMap<'a, V, M> { + pub fn typed(&self) -> PersistentMap<'_, V, M> { assert!(self.db.cf_handle(M::COLUMN_NAME).is_some()); PersistentMap { @@ -186,13 +201,13 @@ where V: AsBytePrefix, Key: Serialize, { - type Error = bincode::Error; + type Error = Error; fn try_into(self) -> StdResult, Self::Error> { let mut bytes = Vec::from(self.version.to_bytes().as_ref()); let mut cursor = Cursor::new(&mut bytes); cursor.set_position(::SIZE as u64); - bincode::serialize_into(&mut cursor, &self.key)?; + bincode::serialize_into(&mut cursor, &self.key).typed_ctx()?; Ok(bytes) } } @@ -213,7 +228,7 @@ where where Key: DeserializeOwned, { - bincode::deserialize_from(&bytes[::SIZE..]).map_err(Error::from) + bincode::deserialize_from(&bytes[::SIZE..]).typed_ctx() } } @@ -234,14 +249,14 @@ where V: Copy + AsBytePrefix + Serialize + DeserializeOwned, M::Key: Copy, { - pub fn insert_with(&self, version: V, key: M::Key, value: Option) -> Result<()> { + pub fn insert_with(&self, version: V, key: M::Key, value: MaybeValue) -> Result<()> { let versioned_key: Vec = VersionedKey { version, key }.try_into()?; - let value = CODER.serialize(&value)?; + let value = CODER.serialize(&value).typed_ctx()?; self.db().put_cf(self.cf(), versioned_key, value)?; Ok(()) } - pub fn get_for(&self, version: V, key: M::Key) -> Result> { + pub fn get_for(&self, version: V, key: M::Key) -> Result>> { let _guard = self .storage .squash_guard @@ -263,20 +278,23 @@ where Ok(None) } - fn get_exact_for(&self, version: V, key: M::Key) -> Result> { + fn get_exact_for(&self, version: V, key: M::Key) -> Result>> { let versioned_key: Vec = VersionedKey { version, key }.try_into()?; let bytes = self.db().get_pinned_cf(self.cf(), versioned_key)?; let mb_value = bytes - .map(|bytes| CODER.deserialize(&bytes)) - .transpose()? - .flatten(); + .map(|bytes| { + CODER + .deserialize::>(&bytes) + .typed_ctx() + }) + .transpose()?; Ok(mb_value) } pub fn prefix_iter_for( &self, version: V, - ) -> Result)> + '_> { + ) -> Result)> + '_> { Ok(self .db() .prefix_iterator_cf(self.cf(), version.to_bytes()) @@ -362,7 +380,9 @@ where M::Key: HasMax, { let version_prefix: Vec = version.to_bytes().as_ref().iter().copied().collect(); - let key_max_bytes = bincode::serialized_size(&M::Key::MAX)? as usize + 1; + let key_max_bytes = bincode::serialized_size(&M::Key::MAX) + .expect("Unable to calculate serialized len") as usize + + 1; let mut lexicographic_max: Vec = version_prefix.clone(); lexicographic_max.extend(std::iter::repeat(0u8).take(key_max_bytes)); self.db() @@ -382,12 +402,12 @@ where { // TODO: previous versions as argument pub fn insert(&self, version: V, value: Value) -> Result<()> { - self.insert_with(version, (), Some(value))?; + self.insert_with(version, (), value.into())?; self.storage.new_version(version, None)?; Ok(()) } - pub fn get(&self, version: V) -> Result> { + pub fn get(&self, version: V) -> Result>> { self.get_exact_for(version, ()) } @@ -461,14 +481,13 @@ primitive_type_has_max! { H160, H256, H512 } -impl fmt::Debug for VersionedStorage +impl Debug for VersionedStorage where V: 'static, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use std::any::TypeId; f.debug_struct("VersionedStorage") - .field("Version", &TypeId::of::()) + .field("Version", &std::any::type_name::()) .field("database", &self.db.path().display()) .finish() } @@ -497,7 +516,6 @@ where #[allow(clippy::blacklisted_name)] mod tests { use std::collections::{BTreeMap, HashMap}; - use std::iter::FromIterator; use std::thread::{self, JoinHandle}; use quickcheck_macros::quickcheck; @@ -572,7 +590,7 @@ mod tests { for (&version, map) in &assoc { for (&key, &value) in map { - s.typed::().insert_with(version, key, Some(value))?; + s.typed::().insert_with(version, key, value.into())?; } s.new_version(version, None)?; } @@ -582,11 +600,11 @@ mod tests { { let s = Storage::open(&dir, COLUMN_NAMES)?; for (version, _) in s.versions() { - let new_map = HashMap::from_iter( - s.typed::() - .prefix_iter_for(version)? - .map(|(key, mb_value)| (key, mb_value.unwrap())), - ); + let new_map = s + .typed::() + .prefix_iter_for(version)? + .map(|(key, mb_value)| (key, Option::from(mb_value).unwrap())) + .collect::>(); assert_eq!(assoc.remove(&version), Some(new_map)); } assert!(assoc.is_empty()); @@ -596,9 +614,10 @@ mod tests { #[quickcheck] fn qc_version_precedes_key_in_serialized_data(version: u64, key: u64) -> Result<()> { - let version_data = CODER.serialize(&version)?; + let version_data = CODER.serialize(&version).typed_ctx()?; + let versioned_key = VersionedKey { version, key }; - let versioned_key_data = CODER.serialize(&versioned_key)?; + let versioned_key_data = CODER.serialize(&versioned_key).typed_ctx()?; assert!(versioned_key_data.starts_with(&version_data)); Ok(()) } @@ -659,7 +678,10 @@ mod tests { let s = Storage::open(&dir, COLUMN_NAMES)?; let mut new_map = BTreeMap::new(); for key in s.typed::().keys() { - new_map.insert(key, s.typed::().get(key)?.unwrap()); + new_map.insert( + key, + s.typed::().get(key)?.and_then(Option::from).unwrap(), + ); } assert_eq!(map, new_map); } @@ -704,7 +726,7 @@ mod tests { .map(|key| { s.typed::() .get((id, key)) - .map(|value| (key, value.unwrap())) + .map(|value| (key, value.and_then(Option::from).unwrap())) }) .collect::>() }) @@ -736,7 +758,7 @@ mod tests { thread::spawn(move || -> Result { let id = thread_id(); for (k, v) in map { - s.typed::().insert_with(id, k, Some(v))?; + s.typed::().insert_with(id, k, v.into())?; } s.new_version(id, None)?; s.db.flush()?; @@ -761,7 +783,7 @@ mod tests { .map(|key| { s.typed::() .get_exact_for(id, key) - .map(|value| (key, value.unwrap())) + .map(|value| (key, value.and_then(Option::from).unwrap())) }) .collect::>() }) @@ -789,7 +811,7 @@ mod tests { let s = Storage::open(&dir, COLUMN_NAMES)?; for (&version, map) in &assoc { for (&k, &v) in map { - s.typed::().insert_with(version, k, v)?; + s.typed::().insert_with(version, k, v.into())?; } s.new_version(version, None)?; } @@ -802,7 +824,11 @@ mod tests { for (version, _) in s.versions() { new_assoc.insert( version, - s.typed::().prefix_iter_for(version).unwrap().collect(), + s.typed::() + .prefix_iter_for(version) + .unwrap() + .map(|(key, mb_value)| (key, Option::from(mb_value))) + .collect(), ); } diff --git a/evm-utils/evm-state/src/version_map.rs b/evm-utils/evm-state/src/version_map.rs index c4d221e287..4e9da18e3c 100644 --- a/evm-utils/evm-state/src/version_map.rs +++ b/evm-utils/evm-state/src/version_map.rs @@ -2,37 +2,12 @@ use std::collections::BTreeMap; use std::fmt; use std::sync::Arc; -/// Represent state of value at current version. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] -pub enum State { - // Value exist, and was changed. - Changed(V), - // Value was removed. - Removed, -} - -impl State { - fn by_ref(&self) -> State<&V> { - match self { - State::Changed(ref v) => State::Changed(v), - State::Removed => State::Removed, - } - } -} - -impl From> for Option { - fn from(state: State) -> Option { - match state { - State::Changed(value) => Some(value), - State::Removed => None, - } - } -} +use super::mb_value::MaybeValue; #[derive(Clone)] pub struct Map { pub(crate) version: Version, - state: BTreeMap>, + state: BTreeMap>, parent: Option>>, } @@ -89,12 +64,12 @@ where // Insert new key, didn't query key before inserting. pub fn insert(&mut self, key: Key, value: Value) { - self.push_change(key, State::Changed(value)); + self.push_change(key, MaybeValue::Value(value)); } // Remove key, didn't query key before inserting. pub fn remove(&mut self, key: Key) { - self.push_change(key, State::Removed); + self.push_change(key, MaybeValue::Removed); } pub fn clear(&mut self) { @@ -103,23 +78,30 @@ where } // Override state of key. - fn push_change(&mut self, key: Key, value: State) { + fn push_change(&mut self, key: Key, value: MaybeValue) { self.state.insert(key, value); } - pub fn iter(&self) -> (&Version, impl Iterator)> + '_) { + pub fn iter( + &self, + ) -> ( + &Version, + impl Iterator)> + '_, + ) { ( &self.version, - self.state - .iter() - .map(|(key, value)| (key, value.by_ref().into())), + self.state.iter(), //.map(|(key, value)| (key, value.by_ref().into())), ) } pub fn full_iter( &self, - ) -> impl Iterator)> + '_)> + '_ - { + ) -> impl Iterator< + Item = ( + &Version, + impl Iterator)> + '_, + ), + > + '_ { std::iter::once(self.iter()).chain(self.parent.as_ref().map(|parent| parent.iter())) } } @@ -145,7 +127,7 @@ where where Version: Ord, { - assert!(new_version > self.version); + assert!(new_version >= self.version); if !self.state.is_empty() { return None; diff --git a/runtime/src/bank_forks.rs b/runtime/src/bank_forks.rs index ca30bbee19..19a3d6c00d 100644 --- a/runtime/src/bank_forks.rs +++ b/runtime/src/bank_forks.rs @@ -221,6 +221,12 @@ impl BankForks { let parents = root_bank.parents(); banks.extend(parents.iter()); for bank in banks.iter() { + bank.evm_state + .write() + .expect("evm state was poisoned") + .dump_all() + .expect("internal evm state error"); + let bank_slot = bank.slot(); if bank.block_height() % self.accounts_hash_interval_slots == 0 && bank_slot > self.last_accounts_hash_slot From 932036aaad50467e642f65d27b4686306787cbf6 Mon Sep 17 00:00:00 2001 From: hrls Date: Wed, 23 Dec 2020 12:22:06 +0200 Subject: [PATCH 08/13] feat/fix: Fixed try_fork usage. Rewrite version tracking. --- evm-utils/evm-state/Cargo.toml | 2 +- evm-utils/evm-state/src/layered_backend.rs | 237 ++++++++++++++++----- evm-utils/evm-state/src/storage.rs | 179 +++++++++++++++- evm-utils/evm-state/src/test_utils.rs | 3 +- evm-utils/evm-state/src/version_map.rs | 120 +++++++---- runtime/src/bank.rs | 26 ++- runtime/src/bank_forks.rs | 10 +- 7 files changed, 462 insertions(+), 115 deletions(-) diff --git a/evm-utils/evm-state/Cargo.toml b/evm-utils/evm-state/Cargo.toml index e93a982427..bc71716298 100644 --- a/evm-utils/evm-state/Cargo.toml +++ b/evm-utils/evm-state/Cargo.toml @@ -15,7 +15,7 @@ rocksdb = { version = "0.15.0", default-features = false } primitive-types = "0.7.2" keccak-hash = "0.5" -log = "0.4" +log = "0.4.11" simple_logger = "1.11" hex = "0.4.2" serde = "1.0" diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index 715399fc53..c5b1a6c00d 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -1,6 +1,7 @@ use std::{any::type_name, borrow::Cow, path::Path}; use evm::backend::Log; +use log::*; use primitive_types::{H160, H256, U256}; use serde::{Deserialize, Serialize}; @@ -74,7 +75,8 @@ type Mapped = Map::Key, :: #[derive(Clone)] // TODO: Debug pub struct EvmState { - pub(crate) slot: Slot, + pub(crate) current_slot: Slot, + pub(crate) previous_slot: Option, pub(crate) accounts: Mapped, pub(crate) accounts_storage: Mapped, @@ -95,6 +97,7 @@ impl Default for EvmState { impl EvmState { pub fn freeze(&mut self) { + info!(target: "evm_state", "freezing evm state (slot {})", self.current_slot); self.accounts.freeze(); self.accounts_storage.freeze(); self.txs_receipts.freeze(); @@ -102,15 +105,31 @@ impl EvmState { } pub fn try_fork(&self, new_slot: Slot) -> Option { + info!( + "forking evm state (slots: from {} to {})", + self.current_slot, new_slot + ); let accounts = self.accounts.try_fork(new_slot)?; let accounts_storage = self.accounts_storage.try_fork(new_slot)?; let txs_receipts = self.txs_receipts.try_fork(new_slot)?; let txs_in_block = self.txs_in_block.try_fork(new_slot)?; + // XXX: >_< + if new_slot != self.current_slot { + debug!( + "new slot {} with previous {:?}", + self.current_slot, self.previous_slot + ); + self.storage + .new_version(self.current_slot, self.previous_slot) + .unwrap(); + } + // TODO: save new_slot in new state and refactor memory map as versionless with inlined get w/o layered map proxy Some(Self { - slot: new_slot, + current_slot: new_slot, + previous_slot: Some(self.current_slot), accounts, accounts_storage, @@ -126,6 +145,7 @@ impl EvmState { dump_into(&self.storage.typed::(), &mut self.accounts)?; dump_into(&self.storage.typed::(), &mut self.accounts_storage)?; dump_into(&self.storage.typed::(), &mut self.txs_receipts)?; + debug!(target: "evm_state", "all layers have been dumped"); Ok(()) } @@ -136,26 +156,60 @@ impl EvmState { key: M::Key, ) -> Option> where - M::Key: Copy + Ord, - M::Value: Clone, + M::Key: Copy + Ord + std::fmt::Debug, + M::Value: Clone + std::fmt::Debug, { + debug!("lookup {} for key {:?}", type_name::(), &key); match map.get(&key) { - KeyResult::Found(mb_value) => mb_value.map(Cow::Borrowed), - KeyResult::NotFound(last_version) => self - .storage - .typed::() - .get_for(*last_version, key) - .unwrap_or_else(|err| { - panic!( - "Storage ({} :: Key {} => Value {}) lookup error: {:?}", + KeyResult::Found(mb_value) => mb_value.map(|value| { + debug!( + "{}: key {:?} was found in memory layer, value: {:?}", + type_name::(), + key, + &value + ); + Cow::Borrowed(value) + }), + KeyResult::NotFound(last_version) => { + debug!("last looked version in memory was {}", last_version); + + let old_lookup = + if *last_version == self.current_slot && self.previous_slot.is_some() { + self.previous_slot.unwrap() + } else { + *last_version + }; + + if let Some(mb_value) = self + .storage + .typed::() + .get_for(old_lookup, key) + .unwrap_or_else(|err| { + panic!( + "Storage ({} :: Key {} => Value {}) lookup error: {:?}", + type_name::(), + type_name::(), + type_name::(), + err + ) + }) + { + debug!( + "{}: key {:?} was found in storage, value: {:?}", + type_name::(), + key, + &mb_value + ); + Option::from(mb_value).map(Cow::Owned) + } else { + debug!( + "{}: key {:?} was not found in storage", type_name::(), - type_name::(), - type_name::(), - err - ) - }) - .and_then(Option::from) - .map(Cow::Owned), + key + ); + None + } + } } } } @@ -165,40 +219,62 @@ fn dump_into<'a, M: PersistentAssoc>( map: &mut Mapped, ) -> anyhow::Result<()> where - M::Key: Ord + Copy, - M::Value: Clone, + M::Key: Ord + Copy + std::fmt::Debug, + M::Value: Clone + std::fmt::Debug, { - let mut full_iter = map.full_iter().peekable(); + trace!("dump assoc {} ...", type_name::()); + + let mut full_iter = map.iter_full().peekable(); while let Some((version, kvs)) = full_iter.next() { for (key, value) in kvs { + debug!( + "{}: {:?} {:?} migrates from memory into storage...", + version, key, &value + ); storage.insert_with(*version, *key, value.clone())?; + debug!( + "{}: {:?} in storage as {:?}", + version, + key, + storage.get_for(*version, *key) + ); } - let previous = full_iter - .peek() - .map(|(previous, _)| **previous) - .or_else(|| storage.previous_of(*version).ok().flatten()); - storage.new_version(*version, previous)?; + // let previous = full_iter + // .peek() + // .map(|(previous, _)| **previous) + // .or_else(|| storage.previous_of(*version).ok().flatten()); } drop(full_iter); map.clear(); + trace!("dump assoc {} done", type_name::()); Ok(()) } impl EvmState { pub fn load_from>(path: P, slot: Slot) -> Result { + info!( + "open evm state storage {} for slot {}", + path.as_ref().display(), + slot + ); let storage = VersionedStorage::open(path, COLUMN_NAMES)?; + let previous_slot = storage.previous_of(slot)?; + debug!( + "storage reports: previous of {} is {:?}", + slot, previous_slot + ); Ok(Self { - slot, - logs: vec![], + current_slot: slot, + previous_slot, accounts: Map::empty(slot), accounts_storage: Map::empty(slot), txs_receipts: Map::empty(slot), txs_in_block: Map::empty(slot), - + logs: vec![], storage, }) } @@ -247,10 +323,19 @@ mod tests { use super::*; - const RANDOM_INCR: u64 = 734512; + const RANDOM_INCR: u64 = 1; // TODO: replace by rand::SeedableRng implementor const MAX_SIZE: usize = 32; // Max size of test collections. - const SEED: u64 = 123; + const SEED: u64 = 1; + + #[test] + fn it_handles_my_own_expectations() { + let tmp_dir = TmpDir::new("it_handles_my_own_expectations"); + let mut evm_state = EvmState::load_from(&tmp_dir, 0).unwrap(); + assert_eq!(evm_state.current_slot, 0); + assert_eq!(evm_state.previous_slot, None); + // assert_eq!(evm_state.storage.previous_of(0).unwrap(), None); + } fn generate_account_by_seed(seed: u64) -> AccountState { let mut rng = StepRng::new(seed * RANDOM_INCR + seed, RANDOM_INCR); @@ -426,39 +511,77 @@ mod tests { } #[test] - fn reads_the_same_after_dump() -> anyhow::Result<()> { - let accounts = generate_accounts_addresses(SEED, 42_000); + fn reads_the_same_after_consequent_dumps() -> anyhow::Result<()> { + use std::ops::Bound::Included; + let _ = simple_logger::SimpleLogger::from_env().init(); - let storage = generate_storage(SEED, &accounts); - let accounts_state = generate_accounts_state(SEED, &accounts); - let storage_diff = to_state_diff(storage, BTreeSet::new()); - let accounts_state_diff = to_state_diff(accounts_state, BTreeSet::new()); + const N_VERSIONS: usize = 10; + const ACCOUNTS_PER_VERSION: usize = 10; - let slot = 0; + let accounts = generate_accounts_addresses(SEED, ACCOUNTS_PER_VERSION * N_VERSIONS); + let accounts_state = generate_accounts_state(SEED, &accounts); + let accounts_storage = generate_storage(SEED, &accounts); let tmp_dir = TmpDir::new("reads_the_same_after_dump"); - let mut evm_state = EvmState::load_from(tmp_dir, slot)?; + let mut evm_state = EvmState::load_from(tmp_dir, 0)?; + + for accounts_per_version in accounts.chunks(N_VERSIONS) { + for account in accounts_per_version { + log::debug!("working with account: {:?}", account); + evm_state + .accounts + .insert(*account, accounts_state[account].clone()); + + for (account_with_index, data) in accounts_storage.range(( + Included((*account, H256::zero())), + Included((*account, H256::repeat_byte(u8::MAX))), + )) { + evm_state + .accounts_storage + .insert(*account_with_index, *data); + } + } - save_state(&mut evm_state, &accounts_state_diff, &storage_diff); - evm_state.freeze(); - evm_state.dump_all()?; - - { - let mut evm_state = evm_state - .try_fork(slot + 1) - .ok_or_else(|| anyhow!("Unable to fork evm state after freezing"))?; - let accounts = generate_accounts_addresses(SEED + 1, 42_000); - - let storage = generate_storage(SEED + 1, &accounts); - let accounts_state = generate_accounts_state(SEED + 1, &accounts); - let storage_diff = to_state_diff(storage, BTreeSet::new()); - let accounts_state_diff = to_state_diff(accounts_state, BTreeSet::new()); - save_state(&mut evm_state, &accounts_state_diff, &storage_diff); + evm_state.freeze(); evm_state.dump_all()?; - assert_state(&evm_state, &accounts_state_diff, &storage_diff); + + let next_slot = evm_state.current_slot + 1; + evm_state = evm_state + .try_fork(next_slot) + .expect("unable to fork evm state after freeze"); } - assert_state(&evm_state, &accounts_state_diff, &storage_diff); + let accounts_state_diff = to_state_diff(accounts_state, BTreeSet::new()); + let accounts_storage_diff = to_state_diff(accounts_storage, BTreeSet::new()); + + assert_state(&evm_state, &accounts_state_diff, &accounts_storage_diff); + Ok(()) } + + #[test] + fn lookups_thru_forks() { + let _ = simple_logger::SimpleLogger::new().init(); + + let tmp_dir = TmpDir::new("lookups_thru_forks"); + let mut state = EvmState::load_from(tmp_dir, 0).unwrap(); + + let accounts = generate_accounts_addresses(SEED, 1); + let account_states = generate_accounts_state(SEED, &accounts); + + let account = accounts.first().copied().unwrap(); + let account_state = account_states[&account].clone(); + + state.accounts.insert(account, account_state.clone()); + + for _ in 0..42 { + state.freeze(); + state.dump_all().unwrap(); + + let next_slot = state.current_slot + 1; + state = state.try_fork(next_slot).unwrap(); + } + + assert_eq!(state.get_account(account), Some(account_state)); + } } diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs index 53e31af6bb..544c0442ec 100644 --- a/evm-utils/evm-state/src/storage.rs +++ b/evm-utils/evm-state/src/storage.rs @@ -91,11 +91,17 @@ where }) } - pub fn new_version(&self, version: V, previous: Previous) -> Result<()> { + pub fn new_version(&self, version: V, previous: Previous) -> Result<()> + where + V: PartialEq + std::fmt::Debug, + { + assert_ne!(Some(version), previous); let version = CODER.serialize(&version).typed_ctx()?; if self.db.get(&version)?.is_none() { let previous = CODER.serialize(&previous).typed_ctx()?; self.db.put(version, previous)?; + } else { + // TODO: assert the same } Ok(()) } @@ -244,6 +250,136 @@ impl<'a, V, M: PersistentAssoc> PersistentMap<'a, V, M> { } } +mod track { + use std::fmt::{self, Display}; + use std::ops::{Range, Sub}; + + #[derive(Debug, Clone)] + enum RevTrack { + Single(V), + Sequence(std::ops::Range), + } + + use RevTrack::*; + + impl RevTrack { + fn single(v: V) -> Self { + Self::Single(v) + } + + fn is_prev(&self, other: V) -> bool + where + V: Copy + Sub + PartialEq + Stepped, + { + (match self { + Single(v) => *v, + Sequence(range) => range.start, + }) - other + == V::ONE + } + + fn prepend(&mut self, prev: V) + where + V: Copy, + { + match self { + Single(v) => { + *self = Sequence(Range { + start: prev, + end: *v, + }) + } + Sequence(range) => range.start = prev, + } + } + } + + pub(super) struct Checked(Vec>); + + impl Default for Checked { + fn default() -> Self { + Self(vec![]) + } + } + + impl Checked { + pub fn prepend(&mut self, prev: V) + where + V: Copy + Sub + PartialEq + Stepped, + { + match self.0.last_mut() { + Some(ref mut last) if last.is_prev(prev) => last.prepend(prev), + Some(_) | None => self.0.push(Single(prev)), + } + } + } + + impl Display for RevTrack { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Single(v) => write!(f, "{}", v), + Sequence(range) => write!(f, "{}..{}", range.end, range.start), + } + } + } + + impl Display for Checked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[")?; + let mut iter = self.0.iter().peekable(); + while let Some(track) = iter.next() { + write!(f, "{}", track)?; + if iter.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, "]")?; + Ok(()) + } + } + + #[test] + fn it_prints_tracks_as_expected() { + impl Checked { + fn assert_display(&self, s: &str) { + assert_eq!(format!("{}", self), s); + } + } + let mut c = Checked::::default(); + c.assert_display("[]"); + c.prepend(42); + c.assert_display("[42]"); + c.prepend(19); + c.assert_display("[42, 19]"); + c.prepend(18); + c.assert_display("[42, 19..18]"); + c.prepend(17); + c.assert_display("[42, 19..17]"); + c.prepend(15); + c.prepend(14); + c.prepend(13); + c.assert_display("[42, 19..17, 15..13]"); + } + + pub trait Stepped { + const ONE: Self; + } + + macro_rules! nums_as_stepped { + ($($ty:ty),+) => { + $( + impl Stepped for $ty { + const ONE: $ty = 1; + } + )+ + } + } + + nums_as_stepped! { + u8, u16, u32, u64, u128 + } +} + impl<'a, V, M: PersistentAssoc> PersistentMap<'a, V, M> where V: Copy + AsBytePrefix + Serialize + DeserializeOwned, @@ -256,7 +392,16 @@ where Ok(()) } - pub fn get_for(&self, version: V, key: M::Key) -> Result>> { + pub fn get_for(&self, version: V, key: M::Key) -> Result>> + where + V: std::fmt::Debug + + std::fmt::Display + + PartialEq + + track::Stepped + + std::ops::Sub, + M::Key: std::fmt::Debug, + M::Value: std::fmt::Debug, + { let _guard = self .storage .squash_guard @@ -265,16 +410,30 @@ where let mut next_version = Some(version); + let mut track = track::Checked::::default(); + while let Some(version) = next_version.take() { + track.prepend(version); let value = self.get_exact_for(version, key)?; if value.is_some() { + log::debug!( + "get_for: key {:?} found with track {}: value {:?}", + key, + track, + &value + ); return Ok(value); } else { - next_version = self.storage.previous_of(version)?; + let previous = self.storage.previous_of(version)?; + log::trace!("get_for: previous of {:?} is {:?}", version, previous); + assert_ne!(Some(version), previous); + next_version = previous; continue; } } + log::debug!("get_for: not found {}", track); + Ok(None) } @@ -401,7 +560,10 @@ where Value: Serialize + DeserializeOwned, { // TODO: previous versions as argument - pub fn insert(&self, version: V, value: Value) -> Result<()> { + pub fn insert(&self, version: V, value: Value) -> Result<()> + where + V: std::fmt::Debug + PartialEq, + { self.insert_with(version, (), value.into())?; self.storage.new_version(version, None)?; Ok(()) @@ -561,6 +723,15 @@ mod tests { } } + #[test] + fn it_handles_versions_as_expected() -> Result<()> { + persistent_types! { KV in "kv" => u64 : usize } // TODO: rm, can be any + let dir = TmpDir::new("it_handles_versions_as_expected"); + let s = VersionedStorage::::open(&dir, COLUMN_NAMES)?; + assert_eq!(s.previous_of(0)?, None); + Ok(()) + } + #[test] fn it_handles_full_range_mapping() -> Result<()> { use rand::Rng; diff --git a/evm-utils/evm-state/src/test_utils.rs b/evm-utils/evm-state/src/test_utils.rs index da52e3102b..5948eb98f2 100644 --- a/evm-utils/evm-state/src/test_utils.rs +++ b/evm-utils/evm-state/src/test_utils.rs @@ -11,7 +11,8 @@ impl TmpDir { let path = env::temp_dir().join(sub_dir); let pprint = path.as_path().display(); if path.exists() { - panic!("Path is {} already exists", pprint); + // panic!("Path is {} already exists", pprint); + fs::remove_dir_all(&path).expect("Unable to cleanup existing dir"); } fs::create_dir(&path) .unwrap_or_else(|err| panic!("Unable to create tmp dir {}: {:?}", pprint, err)); diff --git a/evm-utils/evm-state/src/version_map.rs b/evm-utils/evm-state/src/version_map.rs index 4e9da18e3c..eb662b037d 100644 --- a/evm-utils/evm-state/src/version_map.rs +++ b/evm-utils/evm-state/src/version_map.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use super::mb_value::MaybeValue; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct Map { pub(crate) version: Version, state: BTreeMap>, @@ -82,27 +82,40 @@ where self.state.insert(key, value); } - pub fn iter( - &self, - ) -> ( - &Version, - impl Iterator)> + '_, - ) { - ( - &self.version, - self.state.iter(), //.map(|(key, value)| (key, value.by_ref().into())), - ) - } - - pub fn full_iter( - &self, - ) -> impl Iterator< - Item = ( - &Version, - impl Iterator)> + '_, - ), - > + '_ { - std::iter::once(self.iter()).chain(self.parent.as_ref().map(|parent| parent.iter())) + pub fn iter_full<'a>(&'a self) -> VMapFullIter<'a, Version, Key, Value> { + VMapFullIter::Map(self) + } +} + +pub enum VMapFullIter<'a, Version, Key, Value> { + Map(&'a Map), + Parent(Option<&'a Map>), +} + +impl<'a, Version, Key, Value> Iterator for VMapFullIter<'a, Version, Key, Value> +where + Key: Ord, +{ + type Item = ( + &'a Version, + std::collections::btree_map::Iter<'a, Key, MaybeValue>, + ); + + fn next(&mut self) -> Option { + use VMapFullIter::*; // Self actually + match self { + Map(map) => { + let item = (&map.version, map.state.iter()); + *self = Parent(map.parent.as_ref().map(Arc::as_ref)); + Some(item) + } + Parent(Some(parent)) => { + let item = (&parent.version, parent.state.iter()); + *self = Parent(parent.parent.as_ref().map(Arc::as_ref)); + Some(item) + } + Parent(None) => None, + } } } @@ -141,21 +154,6 @@ where } } -impl fmt::Debug for Map -where - Version: fmt::Debug, - Key: fmt::Debug, - Value: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Map") - .field("version", &self.version) - .field("state", &self.state) - .field("parent", &"omited") - .finish() - } -} - #[cfg(test)] mod test { use super::*; @@ -213,4 +211,52 @@ mod test { assert_eq!(map.get(&"second"), Found(Some(&2))); assert_eq!(map.get(&"third"), Found(Some(&1))); } + + #[test] + fn try_fork_produces_correct_track_on_freezed_state() { + let mut map: Map<_, _, _> = Map::empty(0); + + // map.insert("default", 0); + map.freeze(); + map = map.try_fork(map.version + 1).unwrap(); + + map.insert("first", 1); + map.freeze(); + map = map.try_fork(map.version + 1).unwrap(); + + map.insert("second", 2); + map.freeze(); + map = map.try_fork(map.version + 1).unwrap(); + + map.insert("third", 3); + // map.freeze(); + + let expected = vec![ + (3, vec![("third", 3)]), + (2, vec![("second", 2)]), + (1, vec![("first", 1)]), + (0, vec![]), + ]; + + let full = dbg!(map) + .iter_full() + .map(|(version, iter)| { + ( + *version, + iter.map(|(k, v)| { + ( + *k, + match v { + MaybeValue::Value(v) => *v, + MaybeValue::Removed => unreachable!(), + }, + ) + }) + .collect::>(), + ) + }) + .collect::>(); + + assert_eq!(full, expected); + } } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e9430ae034..eb75db06c6 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -531,21 +531,19 @@ impl Bank { let fee_rate_governor = FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count()); + let evm_state = parent + .evm_state + .read() + .expect("parent evm state was poisoned") + .try_fork(slot) + .expect("unable to fork evm state from parent bank right after freezing it"); + let mut new = Bank { rc, src, slot, epoch, - evm_state: RwLock::new({ - let mut evm_wl = parent - .evm_state - .write() - .expect("parent evm state was poisoned"); - evm_wl.freeze(); - evm_wl - .try_fork(slot) - .expect("Unable to fork EVM state right after freezing it") - }), + evm_state: RwLock::new(evm_state), blockhash_queue: RwLock::new(parent.blockhash_queue.read().unwrap().clone()), // TODO: clean this up, soo much special-case copying... @@ -1129,6 +1127,14 @@ impl Bank { pub fn freeze(&self) { let mut hash = self.hash.write().unwrap(); + + { + let mut evm_state = self.evm_state.write().expect("evm state was poisoned"); + + evm_state.freeze(); + evm_state.dump_all().expect("unable to dump evm state"); + } + if *hash == Hash::default() { // finish up any deferred changes to account state self.collect_rent_eagerly(); diff --git a/runtime/src/bank_forks.rs b/runtime/src/bank_forks.rs index 19a3d6c00d..fa20506141 100644 --- a/runtime/src/bank_forks.rs +++ b/runtime/src/bank_forks.rs @@ -221,11 +221,11 @@ impl BankForks { let parents = root_bank.parents(); banks.extend(parents.iter()); for bank in banks.iter() { - bank.evm_state - .write() - .expect("evm state was poisoned") - .dump_all() - .expect("internal evm state error"); + // bank.evm_state + // .write() + // .expect("evm state was poisoned") + // .dump_all() + // .expect("internal evm state error"); let bank_slot = bank.slot(); if bank.block_height() % self.accounts_hash_interval_slots == 0 From 0b4a656f10855f46b016ada81887524e3bd92dad Mon Sep 17 00:00:00 2001 From: hrls Date: Wed, 30 Dec 2020 17:54:12 +0200 Subject: [PATCH 09/13] refactor(evm-state): remove version_map --- Cargo.lock | 515 +++++++++++---------- evm-utils/evm-state/Cargo.toml | 1 + evm-utils/evm-state/src/evm_backend.rs | 11 +- evm-utils/evm-state/src/layered_backend.rs | 314 +++++++------ evm-utils/evm-state/src/lib.rs | 33 +- evm-utils/evm-state/src/storage.rs | 27 +- evm-utils/evm-state/src/types.rs | 53 +++ evm-utils/evm-state/src/version_map.rs | 262 ----------- 8 files changed, 512 insertions(+), 704 deletions(-) create mode 100644 evm-utils/evm-state/src/types.rs delete mode 100644 evm-utils/evm-state/src/version_map.rs diff --git a/Cargo.lock b/Cargo.lock index 8af7052b7c..f40bde7fef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,9 +12,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" +checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" dependencies = [ "gimli", ] @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4" +checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86" [[package]] name = "arrayref" @@ -103,8 +103,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -114,8 +114,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -245,7 +245,7 @@ dependencies = [ "log 0.4.11", "peeking_take_while", "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "regex", "rustc-hash", "shlex", @@ -406,9 +406,9 @@ checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" [[package]] name = "bytes" @@ -433,6 +433,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16" +[[package]] +name = "bytes" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f8e949d755f9d79112b5bb46938e0ef9d3804a0b16dfab13aafcaa5f0fa72" + [[package]] name = "bytesize" version = "1.0.1" @@ -589,9 +595,9 @@ dependencies = [ [[package]] name = "console" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08" +checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa" dependencies = [ "encode_unicode", "lazy_static", @@ -600,14 +606,13 @@ dependencies = [ "terminal_size", "unicode-width", "winapi 0.3.9", - "winapi-util", ] [[package]] name = "const_fn" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826" +checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" [[package]] name = "constant_time_eq" @@ -865,9 +870,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "2.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" +checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8" dependencies = [ "byteorder", "digest 0.8.1", @@ -878,13 +883,24 @@ dependencies = [ [[package]] name = "derivative" -version = "2.1.1" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.8", + "syn 1.0.58", +] + +[[package]] +name = "derive_more" +version = "0.99.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -989,9 +1005,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dtoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" [[package]] name = "ed25519" @@ -1054,8 +1070,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -1107,7 +1123,7 @@ dependencies = [ "hash-db", "hash256-std-hasher", "parity-scale-codec", - "rlp", + "rlp 0.4.6", "rlp-derive", "serde", "sha3 0.9.1", @@ -1141,7 +1157,7 @@ dependencies = [ "log 0.4.11", "parity-scale-codec", "primitive-types", - "rlp", + "rlp 0.4.6", "serde", "sha3 0.8.2", ] @@ -1163,7 +1179,7 @@ dependencies = [ "num_cpus", "paw", "primitive-types", - "rlp", + "rlp 0.4.6", "secp256k1", "serde_json", "sha3 0.9.1", @@ -1212,7 +1228,7 @@ dependencies = [ "jsonrpc-http-server", "jsonrpc-pubsub", "primitive-types", - "rlp", + "rlp 0.4.6", "secp256k1", "serde", "serde_json", @@ -1237,6 +1253,7 @@ dependencies = [ "bincode", "bytes 0.6.0", "criterion", + "derive_more", "evm", "hex", "keccak-hash", @@ -1248,7 +1265,7 @@ dependencies = [ "quickcheck_macros", "rand 0.6.1", "rand 0.7.3", - "rlp", + "rlp 0.4.6", "rocksdb 0.15.0", "secp256k1", "serde", @@ -1293,8 +1310,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", "synstructure", ] @@ -1416,9 +1433,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "funty" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "futures" @@ -1428,9 +1445,9 @@ checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed" [[package]] name = "futures" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" +checksum = "c70be434c505aee38639abccb918163b63158a4b4bb791b45b7023044bdc3c9c" dependencies = [ "futures-channel", "futures-core", @@ -1443,9 +1460,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +checksum = "f01c61843314e95f96cc9245702248733a3a3d744e43e2e755e3c7af8348a0a9" dependencies = [ "futures-core", "futures-sink", @@ -1453,9 +1470,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" +checksum = "db8d3b0917ff63a2a96173133c02818fac4a746b0a57569d3baca9ec0e945e08" [[package]] name = "futures-cpupool" @@ -1469,9 +1486,9 @@ dependencies = [ [[package]] name = "futures-executor" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" +checksum = "9ee9ca2f7eb4475772cf39dd1cd06208dce2670ad38f4d9c7262b3e15f127068" dependencies = [ "futures-core", "futures-task", @@ -1480,42 +1497,42 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" +checksum = "e37c1a51b037b80922864b8eed90692c5cd8abd4c71ce49b77146caa47f3253b" [[package]] name = "futures-macro" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" +checksum = "0f8719ca0e1f3c5e34f3efe4570ef2c0610ca6da85ae7990d472e9cbfba13664" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] name = "futures-sink" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" +checksum = "f6adabac1290109cfa089f79192fb6244ad2c3f1cc2281f3e1dd987592b71feb" [[package]] name = "futures-task" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +checksum = "a92a0843a2ff66823a8f7c77bffe9a09be2b64e533562c412d63075643ec0038" dependencies = [ "once_cell", ] [[package]] name = "futures-util" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +checksum = "036a2107cdeb57f6d7322f1b6c363dad67cd63ca3b7d1b925bdf75bd5d96cda9" dependencies = [ "futures-channel", "futures-core", @@ -1524,7 +1541,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project 1.0.2", + "pin-project-lite 0.2.3", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -1573,11 +1590,11 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -1613,7 +1630,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5945153d9c566dd5544eca9da4aa430a16343129221a2064cb600d1e6c307212" dependencies = [ - "futures 0.3.8", + "futures 0.3.9", "log 0.4.11", "reqwest", "serde", @@ -1621,7 +1638,7 @@ dependencies = [ "serde_json", "simpl", "smpl_jwt", - "time 0.2.23", + "time 0.2.24", "tokio 0.2.24", ] @@ -1665,7 +1682,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.2", + "http 0.2.3", "indexmap", "slab", "tokio 0.2.24", @@ -1712,9 +1729,9 @@ checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" [[package]] name = "heck" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" dependencies = [ "unicode-segmentation", ] @@ -1785,11 +1802,11 @@ dependencies = [ [[package]] name = "http" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84129d298a6d57d246960ff8eb831ca4af3f96d29e2e28848dae275408658e26" +checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" dependencies = [ - "bytes 0.5.6", + "bytes 1.0.0", "fnv", "itoa", ] @@ -1813,7 +1830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ "bytes 0.5.6", - "http 0.2.2", + "http 0.2.3", ] [[package]] @@ -1903,12 +1920,12 @@ dependencies = [ "futures-core", "futures-util", "h2 0.2.7", - "http 0.2.2", + "http 0.2.3", "http-body 0.3.1", "httparse", "httpdate", "itoa", - "pin-project 1.0.2", + "pin-project 1.0.4", "socket2", "tokio 0.2.24", "tower-service", @@ -1988,7 +2005,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f7a72f11830b52333f36e3b09a288333888bf54380fd0ac0790a3c31ab0f3c5" dependencies = [ - "rlp", + "rlp 0.4.6", ] [[package]] @@ -2022,7 +2039,7 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" dependencies = [ - "console 0.13.0", + "console 0.14.0", "lazy_static", "number_prefix", "regex", @@ -2081,9 +2098,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "jemalloc-ctl" @@ -2183,8 +2200,8 @@ checksum = "d0e77e8812f02155b85a677a96e1d16b60181950c0636199bc4528524fba98dc" dependencies = [ "proc-macro-crate", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -2307,9 +2324,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" [[package]] name = "libloading" @@ -2361,9 +2378,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" @@ -2560,9 +2577,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcc7939b5edc4e4f86b1b4a04bb1498afaaf871b1a6691838ed06fcb48d3a3f" +checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" dependencies = [ "lazy_static", "libc", @@ -2652,8 +2669,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -2703,8 +2720,8 @@ checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" dependencies = [ "proc-macro-crate", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -2748,9 +2765,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.31" +version = "0.10.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d008f51b1acffa0d3450a68606e6a51c123012edaacb0f4e1426bd978869187" +checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -2768,9 +2785,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.59" +version = "0.9.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de52d8eabd217311538a39bba130d7dea1f1e118010fee7a033d966845e7d5fe" +checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" dependencies = [ "autocfg 1.0.1", "cc", @@ -2800,8 +2817,8 @@ checksum = "198db82bb1c18fc00176004462dd809b2a6d851669550aa17af6dacd21ae0c14" dependencies = [ "proc-macro-crate", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -2833,7 +2850,7 @@ checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" dependencies = [ "instant", "lock_api 0.4.2", - "parking_lot_core 0.8.1", + "parking_lot_core 0.8.2", ] [[package]] @@ -2847,7 +2864,7 @@ dependencies = [ "libc", "redox_syscall", "rustc_version", - "smallvec 0.6.13", + "smallvec 0.6.14", "winapi 0.3.9", ] @@ -2861,21 +2878,21 @@ dependencies = [ "cloudabi", "libc", "redox_syscall", - "smallvec 1.5.1", + "smallvec 1.6.1", "winapi 0.3.9", ] [[package]] name = "parking_lot_core" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0" +checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" dependencies = [ "cfg-if 1.0.0", "instant", "libc", "redox_syscall", - "smallvec 1.5.1", + "smallvec 1.6.1", "winapi 0.3.9", ] @@ -2921,8 +2938,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f35583365be5d148e959284f42526841917b7bfa09e2d1a7ad5dde2cf0eaa39" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -2983,11 +3000,11 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2" dependencies = [ - "pin-project-internal 1.0.2", + "pin-project-internal 1.0.4", ] [[package]] @@ -2997,19 +3014,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] name = "pin-project-internal" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -3020,9 +3037,9 @@ checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" [[package]] name = "pin-project-lite" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" +checksum = "ba36e0a6cc5a4c645073f4984f1ed55d09f5857d4de7c14550baa81a39ef5a17" [[package]] name = "pin-utils" @@ -3062,9 +3079,9 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "predicates" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" +checksum = "73dd9b7b200044694dfede9edf907c1ca19630908443e9447e624993700c6932" dependencies = [ "difference", "predicates-core", @@ -3072,15 +3089,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" +checksum = "fb3dbeaaf793584e29c58c7e3a82bbb3c7c06b63cea68d13b0e3cddc124104dc" [[package]] name = "predicates-tree" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" +checksum = "aee95d988ee893cb35c06b148c80ed2cd52c8eea927f50ba7a0be1a786aeab73" dependencies = [ "predicates-core", "treeline", @@ -3122,8 +3139,8 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", "version_check 0.9.2", ] @@ -3134,7 +3151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "version_check 0.9.2", ] @@ -3187,8 +3204,8 @@ dependencies = [ "anyhow", "itertools 0.8.2", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -3226,8 +3243,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -3241,9 +3258,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2 1.0.24", ] @@ -3470,14 +3487,14 @@ checksum = "a415a013dd7c5d4221382329a5a3482566da675737494935cbbbcdec04662f9d" dependencies = [ "cc", "libc", - "smallvec 1.5.1", + "smallvec 1.6.1", ] [[package]] name = "regex" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" dependencies = [ "aho-corasick", "memchr", @@ -3496,9 +3513,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.21" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" [[package]] name = "remove_dir_all" @@ -3520,7 +3537,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "http 0.2.2", + "http 0.2.3", "http-body 0.3.1", "hyper 0.13.9", "hyper-rustls", @@ -3533,7 +3550,7 @@ dependencies = [ "mime_guess", "native-tls", "percent-encoding 2.1.0", - "pin-project-lite 0.2.0", + "pin-project-lite 0.2.3", "rustls", "serde", "serde_json", @@ -3573,6 +3590,16 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rlp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54369147e3e7796c9b885c7304db87ca3d09a0a98f72843d532868675bbfba8" +dependencies = [ + "bytes 1.0.0", + "rustc-hex", +] + [[package]] name = "rlp-derive" version = "0.1.0" @@ -3580,8 +3607,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -3725,8 +3752,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b12bd20b94c7cdfda8c7ba9b92ad0d9a56e3fa018c25fca83b51aa664c9b4c0d" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -3831,15 +3858,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] name = "serde_json" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" dependencies = [ "itoa", "ryu", @@ -3860,9 +3887,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7baae0a99f1a324984bcdc5f0718384c1f69775f1c7eec8b859b71b443e3fd7" +checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f" dependencies = [ "dtoa", "linked-hash-map", @@ -3888,8 +3915,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d08338d8024b227c62bd68a12c7c9883f5c66780abaef15c550dc56f46ee6515" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -3955,9 +3982,9 @@ checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "signal-hook" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" +checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" dependencies = [ "libc", "signal-hook-registry", @@ -3965,18 +3992,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" dependencies = [ "libc", ] [[package]] name = "signature" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210" +checksum = "0f0242b8e50dd9accdd56170e94ca1ebd223b098eb9c83539a6e367d0f36ae68" [[package]] name = "simpl" @@ -4005,18 +4032,18 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" dependencies = [ "maybe-uninit", ] [[package]] name = "smallvec" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "smpl_jwt" @@ -4031,14 +4058,14 @@ dependencies = [ "serde_derive", "serde_json", "simpl", - "time 0.2.23", + "time 0.2.24", ] [[package]] name = "socket2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97e0e9fd577458a4f61fb91fcb559ea2afecc54c934119421f9f5d3d5b1a1057" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", @@ -4108,7 +4135,7 @@ version = "1.4.0" dependencies = [ "async-trait", "bincode", - "futures 0.3.8", + "futures 0.3.9", "solana-banks-interface", "solana-banks-server", "solana-runtime", @@ -4132,7 +4159,7 @@ name = "solana-banks-server" version = "1.4.0" dependencies = [ "bincode", - "futures 0.3.8", + "futures 0.3.9", "log 0.4.11", "solana-banks-interface", "solana-metrics", @@ -4420,7 +4447,7 @@ dependencies = [ "rayon", "regex", "reqwest", - "rlp", + "rlp 0.4.6", "rustc_version", "secp256k1", "serde", @@ -4484,16 +4511,16 @@ dependencies = [ "reqwest", "serde", "syn 0.15.44", - "syn 1.0.54", + "syn 1.0.58", "tokio 0.1.22", "winapi 0.3.9", ] [[package]] name = "solana-crate-features" -version = "1.4.17" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250a18e04d94d3b115eb72600eed2124f190f2e63411866cb02b3f2faa4c4a4e" +checksum = "1b04080bbd3fac60b60de703dfa5bcd144be6f3a20f06ef7a954bb0213ad69cb" dependencies = [ "backtrace", "bytes 0.4.12", @@ -4508,7 +4535,7 @@ dependencies = [ "reqwest", "serde", "syn 0.15.44", - "syn 1.0.54", + "syn 1.0.58", "tokio 0.1.22", "winapi 0.3.9", ] @@ -4720,7 +4747,7 @@ dependencies = [ "dlopen_derive", "ed25519-dalek", "fs_extra", - "futures 0.3.8", + "futures 0.3.9", "futures-util", "itertools 0.9.0", "lazy_static", @@ -4764,7 +4791,7 @@ dependencies = [ "bs58", "bytecount", "clap", - "futures 0.3.8", + "futures 0.3.9", "futures-util", "histogram", "log 0.4.11", @@ -4841,11 +4868,11 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.4.17" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b97055ab14e7c1f67a3141b066ada44e6dfd1b2424d61ba2411dfd7cab08b69" +checksum = "8f35069c0ec1ce82b9f9351a3e16de23ea98b76cd0d16bc1b64f16de07326b63" dependencies = [ - "env_logger 0.7.1", + "env_logger 0.8.2", "lazy_static", "log 0.4.11", ] @@ -5126,9 +5153,9 @@ dependencies = [ "serde_json", "sha2", "sha3 0.9.1", - "solana-crate-features 1.4.17", - "solana-logger 1.4.17", - "solana-sdk-macro 1.4.17", + "solana-crate-features 1.5.2", + "solana-logger 1.5.2", + "solana-sdk-macro 1.5.2", "solana-sdk-macro-frozen-abi 1.4.1", "thiserror", ] @@ -5178,22 +5205,22 @@ version = "1.4.0" dependencies = [ "bs58", "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "rustversion", - "syn 1.0.54", + "syn 1.0.58", ] [[package]] name = "solana-sdk-macro" -version = "1.4.17" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090e095a5ac39010fa83488dfae422132798e15183d887cc9ab33ed6bb9dab8f" +checksum = "d9874e9b88fb0608cf3ed5d2c293f512cf68cf21c5c111b9222c53bfa3be92e6" dependencies = [ "bs58", "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "rustversion", - "syn 1.0.54", + "syn 1.0.58", ] [[package]] @@ -5202,9 +5229,9 @@ version = "1.4.0" dependencies = [ "lazy_static", "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "rustc_version", - "syn 1.0.54", + "syn 1.0.58", ] [[package]] @@ -5215,9 +5242,9 @@ checksum = "8e47f618ad2d7af7b9c701e9cc9951681f6d6a9c754863f2ab63e1b98507e515" dependencies = [ "lazy_static", "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "rustc_version", - "syn 1.0.54", + "syn 1.0.58", ] [[package]] @@ -5307,7 +5334,7 @@ dependencies = [ "bzip2", "enum-iterator", "flate2", - "futures 0.3.8", + "futures 0.3.9", "goauth", "log 0.4.11", "prost", @@ -5574,9 +5601,9 @@ dependencies = [ [[package]] name = "standback" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" +checksum = "c66a8cff4fa24853fdf6b51f75c6d7f8206d7c75cab4e467bcd7f25c2b1febe0" dependencies = [ "version_check 0.9.2", ] @@ -5608,10 +5635,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "serde", "serde_derive", - "syn 1.0.54", + "syn 1.0.58", ] [[package]] @@ -5622,12 +5649,12 @@ checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "serde", "serde_derive", "serde_json", "sha1", - "syn 1.0.54", + "syn 1.0.58", ] [[package]] @@ -5672,8 +5699,8 @@ dependencies = [ "heck", "proc-macro-error", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -5707,12 +5734,12 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.54" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" +checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "unicode-xid 0.2.1", ] @@ -5723,8 +5750,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", "unicode-xid 0.2.1", ] @@ -5782,7 +5809,7 @@ checksum = "1503e47bfae912674d6f4226c09cb8d2f0271a57eef7e799b6f98a545f89c7a3" dependencies = [ "anyhow", "fnv", - "futures 0.3.8", + "futures 0.3.9", "humantime 1.3.0", "log 0.4.11", "pin-project 0.4.27", @@ -5802,8 +5829,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edbaf92ceea0a2ab555bea18a47a891e46ba2d6f930ec9506771662f4ab82bb7" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -5869,22 +5896,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -5895,9 +5922,9 @@ checksum = "bcbb6aa301e5d3b0b5ef639c9a9c7e2f1c944f177b460c04dc24c69b1fa2bd99" [[package]] name = "thread_local" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447" dependencies = [ "lazy_static", ] @@ -5915,9 +5942,9 @@ dependencies = [ [[package]] name = "time" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" +checksum = "273d3ed44dca264b0d6b3665e8d48fb515042d42466fad93d2a45b90ec4058f7" dependencies = [ "const_fn", "libc", @@ -5946,9 +5973,9 @@ checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", - "quote 1.0.7", + "quote 1.0.8", "standback", - "syn 1.0.54", + "syn 1.0.58", ] [[package]] @@ -5978,9 +6005,9 @@ dependencies = [ [[package]] name = "tinytemplate" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" +checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74" dependencies = [ "serde", "serde_json", @@ -6120,8 +6147,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -6164,7 +6191,7 @@ dependencies = [ "bincode", "bytes 0.5.6", "derivative", - "futures 0.3.8", + "futures 0.3.9", "pin-project 0.4.27", "serde", ] @@ -6292,9 +6319,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] @@ -6311,7 +6338,7 @@ dependencies = [ "bytes 0.5.6", "futures-core", "futures-util", - "http 0.2.2", + "http 0.2.3", "http-body 0.3.1", "hyper 0.13.9", "percent-encoding 2.1.0", @@ -6397,9 +6424,9 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35d656f2638b288b33495d1053ea74c40dc05ec0b92084dd71ca5566c4ed1dc" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-limit" @@ -6516,7 +6543,7 @@ checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" dependencies = [ "cfg-if 1.0.0", "log 0.4.11", - "pin-project-lite 0.2.0", + "pin-project-lite 0.2.3", "tracing-attributes", "tracing-core", ] @@ -6528,8 +6555,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", ] [[package]] @@ -6574,12 +6601,12 @@ dependencies = [ [[package]] name = "triehash" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f490aa7aa4e4d07edeba442c007e42e3e7f43aafb5112c5b047fff0b1aa5449c" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" dependencies = [ "hash-db", - "rlp", + "rlp 0.5.0", ] [[package]] @@ -6597,7 +6624,7 @@ dependencies = [ "base64 0.11.0", "byteorder", "bytes 0.5.6", - "http 0.2.2", + "http 0.2.3", "httparse", "input_buffer", "log 0.4.11", @@ -6857,8 +6884,8 @@ dependencies = [ "lazy_static", "log 0.4.11", "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", "wasm-bindgen-shared", ] @@ -6880,7 +6907,7 @@ version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" dependencies = [ - "quote 1.0.7", + "quote 1.0.8", "wasm-bindgen-macro-support", ] @@ -6891,8 +6918,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7079,9 +7106,9 @@ dependencies = [ [[package]] name = "yaml-rust" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] @@ -7102,25 +7129,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" dependencies = [ "proc-macro2 1.0.24", - "quote 1.0.7", - "syn 1.0.54", + "quote 1.0.8", + "syn 1.0.58", "synstructure", ] [[package]] name = "zstd" -version = "0.5.3+zstd.1.4.5" +version = "0.5.4+zstd.1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8" +checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "2.0.5+zstd.1.4.5" +version = "2.0.6+zstd.1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055" +checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e" dependencies = [ "libc", "zstd-sys", @@ -7128,9 +7155,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.4.17+zstd.1.4.5" +version = "1.4.18+zstd.1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b" +checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81" dependencies = [ "cc", "glob", diff --git a/evm-utils/evm-state/Cargo.toml b/evm-utils/evm-state/Cargo.toml index bc71716298..32cdcfac8b 100644 --- a/evm-utils/evm-state/Cargo.toml +++ b/evm-utils/evm-state/Cargo.toml @@ -27,6 +27,7 @@ anyhow = "1.0.34" bincode = "1.3.1" lazy_static = "1.4.0" bytes = "0.6.0" +derive_more = "0.99.11" [dev-dependencies] criterion = "0.3" diff --git a/evm-utils/evm-state/src/evm_backend.rs b/evm-utils/evm-state/src/evm_backend.rs index da9ed58d9b..dee29e5c13 100644 --- a/evm-utils/evm-state/src/evm_backend.rs +++ b/evm-utils/evm-state/src/evm_backend.rs @@ -3,20 +3,19 @@ use evm::backend::{Apply, ApplyBackend, Backend, Basic, Log}; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; +use crate::types::MemoryVicinity; + pub struct EvmBackend { pub(crate) evm_state: EvmState, - pub(crate) tx_info: crate::layered_backend::MemoryVicinity, + pub(crate) tx_info: MemoryVicinity, } impl EvmBackend { - pub fn new_from_state( - evm_state: EvmState, - tx_info: crate::layered_backend::MemoryVicinity, - ) -> Self { + pub fn new_from_state(evm_state: EvmState, tx_info: MemoryVicinity) -> Self { Self { evm_state, tx_info } } - fn tx_info(&self) -> &crate::layered_backend::MemoryVicinity { + fn tx_info(&self) -> &MemoryVicinity { &self.tx_info } } diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index c5b1a6c00d..02229dd67f 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -1,87 +1,102 @@ -use std::{any::type_name, borrow::Cow, path::Path}; +use std::{ + any::type_name, borrow::Cow, collections::BTreeMap, fmt::Debug, marker::PhantomData, path::Path, +}; -use evm::backend::Log; +use derive_more::Deref; use log::*; -use primitive_types::{H160, H256, U256}; -use serde::{Deserialize, Serialize}; + +use evm::backend::Log; use crate::{ + mb_value::MaybeValue, persistent_types, - storage::{PersistentAssoc, PersistentMap, VersionedStorage}, + storage::{PersistentAssoc, Result as StorageResult, VersionedStorage}, transactions::TransactionReceipt, - version_map::{KeyResult, Map}, - Slot, + types::*, }; -/// Vivinity value of a memory backend. -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct MemoryVicinity { - /// Gas price. - pub gas_price: U256, - /// Origin. - pub origin: H160, - /// Chain ID. - pub chain_id: U256, - /// Environmental block hashes. - pub block_hashes: Vec, - /// Environmental block number. - pub block_number: U256, - /// Environmental coinbase. - pub block_coinbase: H160, - /// Environmental block timestamp. - pub block_timestamp: U256, - /// Environmental block difficulty. - pub block_difficulty: U256, - /// Environmental block gas limit. - pub block_gas_limit: U256, -} - -impl Default for MemoryVicinity { - fn default() -> Self { - Self { - gas_price: U256::zero(), - origin: H160::default(), - chain_id: U256::zero(), - block_hashes: Vec::new(), - block_number: U256::zero(), - block_coinbase: H160::default(), - block_timestamp: U256::zero(), - block_difficulty: U256::zero(), - block_gas_limit: U256::max_value(), - } - } -} - -#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct AccountState { - /// Account nonce. - pub nonce: U256, - /// Account balance. - pub balance: U256, - /// Account code. - pub code: Vec, -} - // Store every account storage at single place, to use power of versioned map. // This allows us to save only changed data. persistent_types! { Accounts in "accounts" => H160 : AccountState, AccountsStorage in "accounts_storage" => (H160, H256) : H256, TransactionReceipts in "txs_receipts" => H256 : TransactionReceipt, - TransactionsInBlock in "txs_in_block" => Slot : Vec, + TransactionsInBlock in "txs_in_block" => Slot : Vec, // TODO: Key is Slot or U256? +} + +#[derive(Deref)] +pub(crate) struct Layer( + #[deref] BTreeMap>, + PhantomData, +) +where + M::Key: Ord; + +impl Layer +where + M: PersistentAssoc, + M::Key: Ord, +{ + pub fn empty() -> Self { + Self(BTreeMap::new(), PhantomData) + } + + pub fn insert(&mut self, key: M::Key, value: M::Value) { + self.0.insert(key, MaybeValue::Value(value)); + } + + pub fn remove(&mut self, key: M::Key) { + self.0.insert(key, MaybeValue::Removed); + } + + fn dump_into(&mut self, storage: &VersionedStorage, version: Slot) -> StorageResult<()> + where + M::Key: Ord + Copy + Debug, + M::Value: Clone + Debug, + { + let storage = storage.typed::(); + + trace!("layer :: {} dumping ...", type_name::()); + for (key, value) in std::mem::replace(self, Self::empty()).0 { + debug!( + "{}: {:?} {:?} migrates from memory into storage", + version, key, &value + ); + + storage.insert_with(version, key, value)?; + + trace!( + "{}: {:?} in storage as {:?}", + version, + key, + storage.get_for(version, key) + ); + } + trace!("layer :: {} dumped", type_name::()); + + Ok(()) + } } -type Mapped = Map::Key, ::Value>; +impl Clone for Layer +where + M::Key: Clone + Ord, + M::Value: Clone, +{ + fn clone(&self) -> Self { + Self(self.0.clone(), PhantomData) + } +} #[derive(Clone)] // TODO: Debug pub struct EvmState { pub(crate) current_slot: Slot, pub(crate) previous_slot: Option, - pub(crate) accounts: Mapped, - pub(crate) accounts_storage: Mapped, - pub(crate) txs_receipts: Mapped, - pub(crate) txs_in_block: Mapped, + pub(crate) accounts: Layer, + pub(crate) accounts_storage: Layer, + pub(crate) txs_receipts: Layer, + pub(crate) txs_in_block: Layer, pub(crate) logs: Vec, // TODO: migrate into storage storage: VersionedStorage, @@ -98,21 +113,24 @@ impl Default for EvmState { impl EvmState { pub fn freeze(&mut self) { info!(target: "evm_state", "freezing evm state (slot {})", self.current_slot); - self.accounts.freeze(); - self.accounts_storage.freeze(); - self.txs_receipts.freeze(); - self.txs_in_block.freeze(); + // self.accounts.freeze(); + // self.accounts_storage.freeze(); + // self.txs_receipts.freeze(); + // self.txs_in_block.freeze(); } + // TODO: dump all pub fn try_fork(&self, new_slot: Slot) -> Option { info!( - "forking evm state (slots: from {} to {})", + "forking evm state from slot {} to slot {}", self.current_slot, new_slot ); - let accounts = self.accounts.try_fork(new_slot)?; - let accounts_storage = self.accounts_storage.try_fork(new_slot)?; - let txs_receipts = self.txs_receipts.try_fork(new_slot)?; - let txs_in_block = self.txs_in_block.try_fork(new_slot)?; + + // TODO: assert that all these maps are empty + let accounts = self.accounts.clone(); + let accounts_storage = self.accounts_storage.clone(); + let txs_receipts = self.txs_receipts.clone(); + let txs_in_block = self.txs_in_block.clone(); // XXX: >_< if new_slot != self.current_slot { @@ -125,8 +143,6 @@ impl EvmState { .unwrap(); } - // TODO: save new_slot in new state and refactor memory map as versionless with inlined get w/o layered map proxy - Some(Self { current_slot: new_slot, previous_slot: Some(self.current_slot), @@ -142,57 +158,48 @@ impl EvmState { #[rustfmt::skip] pub fn dump_all(&mut self) -> anyhow::Result<()> { - dump_into(&self.storage.typed::(), &mut self.accounts)?; - dump_into(&self.storage.typed::(), &mut self.accounts_storage)?; - dump_into(&self.storage.typed::(), &mut self.txs_receipts)?; + self.accounts.dump_into(&self.storage, self.current_slot)?; + self.accounts_storage.dump_into(&self.storage, self.current_slot )?; + self.txs_receipts.dump_into(&self.storage, self.current_slot)?; + self.txs_in_block.dump_into(&self.storage, self.current_slot)?; debug!(target: "evm_state", "all layers have been dumped"); Ok(()) } + // TODO: elide map arg, TBD: maybe typemap fn lookup<'a, M: PersistentAssoc>( &'a self, - // TODO: elide this arg, TBD: maybe typemap - map: &'a Mapped, + map: &'a Layer, key: M::Key, ) -> Option> where - M::Key: Copy + Ord + std::fmt::Debug, - M::Value: Clone + std::fmt::Debug, + M::Key: Copy + Ord + Debug, + M::Value: Clone + Debug, { debug!("lookup {} for key {:?}", type_name::(), &key); - match map.get(&key) { - KeyResult::Found(mb_value) => mb_value.map(|value| { - debug!( - "{}: key {:?} was found in memory layer, value: {:?}", - type_name::(), - key, - &value - ); - Cow::Borrowed(value) - }), - KeyResult::NotFound(last_version) => { - debug!("last looked version in memory was {}", last_version); - - let old_lookup = - if *last_version == self.current_slot && self.previous_slot.is_some() { - self.previous_slot.unwrap() - } else { - *last_version - }; - - if let Some(mb_value) = self - .storage - .typed::() - .get_for(old_lookup, key) - .unwrap_or_else(|err| { - panic!( - "Storage ({} :: Key {} => Value {}) lookup error: {:?}", - type_name::(), - type_name::(), - type_name::(), - err - ) - }) + if let Some(mb_value) = map.0.get(&key) { + Option::from(mb_value.by_ref()).map(Cow::Borrowed) + } else { + let lookup_slot = if self.storage.is_exists(self.current_slot).unwrap() { + Some(self.current_slot) + } else { + self.previous_slot + }; + + if let Some(slot) = lookup_slot { + if let Some(mb_value) = + self.storage + .typed::() + .get_for(slot, key) + .unwrap_or_else(|err| { + panic!( + "Storage ({} :: Key {} => Value {}) lookup error: {:?}", + type_name::(), + type_name::(), + type_name::(), + err + ); + }) { debug!( "{}: key {:?} was found in storage, value: {:?}", @@ -209,49 +216,13 @@ impl EvmState { ); None } + } else { + None } } } } -fn dump_into<'a, M: PersistentAssoc>( - storage: &PersistentMap<'a, Slot, M>, - map: &mut Mapped, -) -> anyhow::Result<()> -where - M::Key: Ord + Copy + std::fmt::Debug, - M::Value: Clone + std::fmt::Debug, -{ - trace!("dump assoc {} ...", type_name::()); - - let mut full_iter = map.iter_full().peekable(); - while let Some((version, kvs)) = full_iter.next() { - for (key, value) in kvs { - debug!( - "{}: {:?} {:?} migrates from memory into storage...", - version, key, &value - ); - storage.insert_with(*version, *key, value.clone())?; - debug!( - "{}: {:?} in storage as {:?}", - version, - key, - storage.get_for(*version, *key) - ); - } - - // let previous = full_iter - // .peek() - // .map(|(previous, _)| **previous) - // .or_else(|| storage.previous_of(*version).ok().flatten()); - } - drop(full_iter); - - map.clear(); - trace!("dump assoc {} done", type_name::()); - Ok(()) -} - impl EvmState { pub fn load_from>(path: P, slot: Slot) -> Result { info!( @@ -270,10 +241,10 @@ impl EvmState { current_slot: slot, previous_slot, - accounts: Map::empty(slot), - accounts_storage: Map::empty(slot), - txs_receipts: Map::empty(slot), - txs_in_block: Map::empty(slot), + accounts: Layer::empty(), + accounts_storage: Layer::empty(), + txs_receipts: Layer::empty(), + txs_in_block: Layer::empty(), logs: vec![], storage, }) @@ -291,7 +262,7 @@ impl EvmState { // NOTE: currently used in benches only pub fn set_account(&mut self, address: H160, state: AccountState) { - self.accounts.insert(address, state) + self.accounts.insert(address, state); } pub fn get_account(&self, address: H160) -> Option { @@ -305,9 +276,28 @@ impl EvmState { } pub fn swap_commit(&mut self, mut updated: Self) { - // TODO: Assert that updated is newer than current state. + // Assert that updated is newer than current state. + // Slot can not change, because we allow multiple commits per block. + // assert!( + // updated.current_slot > self.current_slot + // || (updated.current_slot == self.current_slot && self.is_empty()), + // "Not expected commit: current = slot {}, is_empty {}, updated = slot {}, is_empty {}", + // self.current_slot, + // self.is_empty(), + // updated.current_slot, + // updated.is_empty() + // ); + std::mem::swap(self, &mut updated); } + + /// True if current layer has no any update, false otherwise. + fn is_empty(&self) -> bool { + self.accounts.is_empty() + && self.accounts_storage.is_empty() + && self.txs_receipts.is_empty() + && self.txs_in_block.is_empty() + } } #[cfg(test)] @@ -331,10 +321,16 @@ mod tests { #[test] fn it_handles_my_own_expectations() { let tmp_dir = TmpDir::new("it_handles_my_own_expectations"); - let mut evm_state = EvmState::load_from(&tmp_dir, 0).unwrap(); + let evm_state = EvmState::load_from(&tmp_dir, 0).unwrap(); assert_eq!(evm_state.current_slot, 0); assert_eq!(evm_state.previous_slot, None); - // assert_eq!(evm_state.storage.previous_of(0).unwrap(), None); + assert_eq!( + evm_state + .storage + .previous_of(evm_state.current_slot) + .unwrap(), + None + ); } fn generate_account_by_seed(seed: u64) -> AccountState { diff --git a/evm-utils/evm-state/src/lib.rs b/evm-utils/evm-state/src/lib.rs index b65f6dbd7a..e4e35066b7 100644 --- a/evm-utils/evm-state/src/lib.rs +++ b/evm-utils/evm-state/src/lib.rs @@ -13,17 +13,16 @@ pub mod transactions; pub use evm_backend::*; pub use layered_backend::*; pub use transactions::*; +pub use types::*; mod evm_backend; mod mb_value; mod storage; -mod version_map; +mod types; use log::debug; use std::fmt; -pub(crate) type Slot = u64; // TODO: re-use existing one from sdk package - pub trait FromKey { fn to_public_key(&self) -> secp256k1::PublicKey; fn to_address(&self) -> crate::Address; @@ -62,24 +61,6 @@ impl Executor { gas_limit: usize, block_number: u64, ) -> Self { - //TODO: Request info from solana blockchain for vicinity - - // /// Gas price. - // pub gas_price: U256, - // /// Chain ID. - // pub chain_id: U256, - // /// Environmental block hashes. - // pub block_hashes: Vec, - // /// Environmental block number. - // pub block_number: U256, - // /// Environmental coinbase. - // pub block_coinbase: H160, - // /// Environmental block timestamp. - // pub block_timestamp: U256, - // /// Environmental block difficulty. - // pub block_difficulty: U256, - // /// Environmental block gas limit. - // pub block_gas_limit: U256, let vicinity = MemoryVicinity { block_gas_limit: gas_limit.into(), block_number: block_number.into(), @@ -182,10 +163,16 @@ impl Executor { hashes.push(tx_hash); let index = hashes.len() as u64; - self.evm.evm_state.txs_in_block.insert(block_num, hashes); + self.evm + .evm_state + .txs_in_block + .insert(block_num, hashes.into()); let tx_receipt = TransactionReceipt::new(tx, used_gas, block_num, index, result); - self.evm.evm_state.txs_receipts.insert(tx_hash, tx_receipt); + self.evm + .evm_state + .txs_receipts + .insert(tx_hash, tx_receipt.into()); } pub fn deconstruct(self) -> EvmState { diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs index 544c0442ec..7a38b0b962 100644 --- a/evm-utils/evm-state/src/storage.rs +++ b/evm-utils/evm-state/src/storage.rs @@ -1,19 +1,21 @@ -use std::array::TryFromSliceError; -use std::convert::{TryFrom, TryInto}; -use std::fmt::{self, Debug}; -use std::io::Cursor; -use std::marker::PhantomData; -use std::mem::size_of; -use std::ops::Deref; -use std::path::Path; -use std::sync::{Arc, RwLock}; +use std::{ + array::TryFromSliceError, + convert::{TryFrom, TryInto}, + fmt::{self, Debug}, + io::Cursor, + marker::PhantomData, + mem::size_of, + ops::Deref, + path::Path, + sync::{Arc, RwLock}, +}; use bincode::config::{BigEndian, DefaultOptions, Options as _, WithOtherEndian}; use lazy_static::lazy_static; use rocksdb::{self, ColumnFamily, ColumnFamilyDescriptor, IteratorMode, Options, DB}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use super::mb_value::MaybeValue; +use crate::mb_value::MaybeValue; pub type Result = std::result::Result; pub type StdResult = std::result::Result; @@ -66,6 +68,11 @@ where V: Copy + Serialize + DeserializeOwned, Previous: Serialize + DeserializeOwned, { + pub fn is_exists(&self, version: V) -> Result { + let key = CODER.serialize(&version).typed_ctx()?; + Ok(self.db.get_pinned(key)?.is_some()) + } + pub fn previous_of(&self, version: V) -> Result> { let version = CODER.serialize(&version).typed_ctx()?; diff --git a/evm-utils/evm-state/src/types.rs b/evm-utils/evm-state/src/types.rs new file mode 100644 index 0000000000..357fb4d6df --- /dev/null +++ b/evm-utils/evm-state/src/types.rs @@ -0,0 +1,53 @@ +pub use primitive_types::{H160, H256, U256}; +use serde::{Deserialize, Serialize}; + +pub(crate) type Slot = u64; // TODO: re-use existing one from sdk package + +#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct AccountState { + /// Account nonce. + pub nonce: U256, + /// Account balance. + pub balance: U256, + /// Account code. + pub code: Vec, +} + +/// Vivinity value of a memory backend. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MemoryVicinity { + /// Gas price. + pub gas_price: U256, + /// Origin. + pub origin: H160, + /// Chain ID. + pub chain_id: U256, + /// Environmental block hashes. + pub block_hashes: Vec, + /// Environmental block number. + pub block_number: U256, + /// Environmental coinbase. + pub block_coinbase: H160, + /// Environmental block timestamp. + pub block_timestamp: U256, + /// Environmental block difficulty. + pub block_difficulty: U256, + /// Environmental block gas limit. + pub block_gas_limit: U256, +} + +impl Default for MemoryVicinity { + fn default() -> Self { + Self { + gas_price: U256::zero(), + origin: H160::default(), + chain_id: U256::zero(), + block_hashes: Vec::new(), + block_number: U256::zero(), + block_coinbase: H160::default(), + block_timestamp: U256::zero(), + block_difficulty: U256::zero(), + block_gas_limit: U256::max_value(), + } + } +} diff --git a/evm-utils/evm-state/src/version_map.rs b/evm-utils/evm-state/src/version_map.rs deleted file mode 100644 index eb662b037d..0000000000 --- a/evm-utils/evm-state/src/version_map.rs +++ /dev/null @@ -1,262 +0,0 @@ -use std::collections::BTreeMap; -use std::fmt; -use std::sync::Arc; - -use super::mb_value::MaybeValue; - -#[derive(Debug, Clone)] -pub struct Map { - pub(crate) version: Version, - state: BTreeMap>, - parent: Option>>, -} - -impl Default for Map -where - Version: Default, - Key: Ord, -{ - fn default() -> Self { - Map::new() - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum KeyResult { - /// Value for existing key. - Found(T), - /// Key not found, Value is last looked version. - NotFound(V), -} - -impl Map -where - Key: Ord, -{ - pub fn empty(version: Version) -> Self { - Self { - version, - state: BTreeMap::new(), - parent: None, - } - } - - // Create new versioned map. - pub fn new() -> Self - where - Version: Default, - { - Self { - version: Version::default(), - state: BTreeMap::new(), - parent: None, - } - } - - // Borrow value by key - pub fn get(&self, key: &Key) -> KeyResult<&Version, Option<&Value>> { - match (self.state.get(key), self.parent.as_ref()) { - (Some(s), _) => KeyResult::Found(s.by_ref().into()), - (None, Some(parent)) => parent.get(key), - (None, None) => KeyResult::NotFound(&self.version), - } - } - - // Insert new key, didn't query key before inserting. - pub fn insert(&mut self, key: Key, value: Value) { - self.push_change(key, MaybeValue::Value(value)); - } - - // Remove key, didn't query key before inserting. - pub fn remove(&mut self, key: Key) { - self.push_change(key, MaybeValue::Removed); - } - - pub fn clear(&mut self) { - self.state.clear(); - self.parent = None; - } - - // Override state of key. - fn push_change(&mut self, key: Key, value: MaybeValue) { - self.state.insert(key, value); - } - - pub fn iter_full<'a>(&'a self) -> VMapFullIter<'a, Version, Key, Value> { - VMapFullIter::Map(self) - } -} - -pub enum VMapFullIter<'a, Version, Key, Value> { - Map(&'a Map), - Parent(Option<&'a Map>), -} - -impl<'a, Version, Key, Value> Iterator for VMapFullIter<'a, Version, Key, Value> -where - Key: Ord, -{ - type Item = ( - &'a Version, - std::collections::btree_map::Iter<'a, Key, MaybeValue>, - ); - - fn next(&mut self) -> Option { - use VMapFullIter::*; // Self actually - match self { - Map(map) => { - let item = (&map.version, map.state.iter()); - *self = Parent(map.parent.as_ref().map(Arc::as_ref)); - Some(item) - } - Parent(Some(parent)) => { - let item = (&parent.version, parent.state.iter()); - *self = Parent(parent.parent.as_ref().map(Arc::as_ref)); - Some(item) - } - Parent(None) => None, - } - } -} - -impl Map -where - Key: Ord, -{ - pub fn freeze(&mut self) - where - Version: Clone, - { - let this = Self { - version: self.version.clone(), - state: std::mem::take(&mut self.state), - parent: self.parent.as_ref().map(Arc::clone), - }; - self.parent = Some(Arc::new(this)); - } - - // Create new version from freezed one - pub fn try_fork(&self, new_version: Version) -> Option - where - Version: Ord, - { - assert!(new_version >= self.version); - - if !self.state.is_empty() { - return None; - } - - Some(Self { - version: new_version, - state: BTreeMap::new(), - parent: self.parent.clone(), - }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use KeyResult::*; - - #[test] - fn store_and_get_simple() { - let mut map: Map<(), _, _> = Map::new(); - map.insert("first", 1); - map.insert("second", 2); - assert_eq!(map.get(&"first"), Found(Some(&1))); - assert_eq!(map.get(&"second"), Found(Some(&2))); - } - - // Test that map can save version, and type of map is always remain the same. - #[test] - fn new_dynamic_version_insert_remove_test() { - let mut map: Map<_, _, _> = Map::new(); - map.insert("first", 1); - map.insert("second", 2); - map.insert("third", 3); - assert_eq!(map.get(&"first"), Found(Some(&1))); - assert_eq!(map.get(&"second"), Found(Some(&2))); - assert_eq!(map.get(&"third"), Found(Some(&3))); - - map.freeze(); - let mut map: Map<_, _, _> = map.try_fork(1).unwrap(); - - map.remove("first"); - map.insert("third", 1); - - assert_eq!(map.get(&"first"), Found(None)); - assert_eq!(map.get(&"second"), Found(Some(&2))); - assert_eq!(map.get(&"third"), Found(Some(&1))); - } - - // Same as new_dynamic_version_insert_remove_test but dont hide type of store. - #[test] - fn new_static_version_insert_remove_test() { - let mut map: Map<_, _, _> = Map::new(); - map.insert("first", 1); - map.insert("second", 2); - map.insert("third", 3); - assert_eq!(map.get(&"first"), Found(Some(&1))); - assert_eq!(map.get(&"second"), Found(Some(&2))); - assert_eq!(map.get(&"third"), Found(Some(&3))); - - map.freeze(); - let mut map = map.try_fork(1).unwrap(); - - map.remove("first"); - map.insert("third", 1); - - assert_eq!(map.get(&"first"), Found(None)); - assert_eq!(map.get(&"second"), Found(Some(&2))); - assert_eq!(map.get(&"third"), Found(Some(&1))); - } - - #[test] - fn try_fork_produces_correct_track_on_freezed_state() { - let mut map: Map<_, _, _> = Map::empty(0); - - // map.insert("default", 0); - map.freeze(); - map = map.try_fork(map.version + 1).unwrap(); - - map.insert("first", 1); - map.freeze(); - map = map.try_fork(map.version + 1).unwrap(); - - map.insert("second", 2); - map.freeze(); - map = map.try_fork(map.version + 1).unwrap(); - - map.insert("third", 3); - // map.freeze(); - - let expected = vec![ - (3, vec![("third", 3)]), - (2, vec![("second", 2)]), - (1, vec![("first", 1)]), - (0, vec![]), - ]; - - let full = dbg!(map) - .iter_full() - .map(|(version, iter)| { - ( - *version, - iter.map(|(k, v)| { - ( - *k, - match v { - MaybeValue::Value(v) => *v, - MaybeValue::Removed => unreachable!(), - }, - ) - }) - .collect::>(), - ) - }) - .collect::>(); - - assert_eq!(full, expected); - } -} From 57b4754de872efb130b1297f92accd95ddcbeb02 Mon Sep 17 00:00:00 2001 From: hrls Date: Mon, 4 Jan 2021 16:11:54 +0200 Subject: [PATCH 10/13] fix(emv-state): relax new version on replay stage --- evm-utils/evm-state/src/layered_backend.rs | 142 ++++++++++++--------- evm-utils/evm-state/src/storage.rs | 49 +++---- run.sh | 2 +- runtime/src/bank.rs | 10 +- 4 files changed, 116 insertions(+), 87 deletions(-) diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index 02229dd67f..42dee97c93 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -2,7 +2,6 @@ use std::{ any::type_name, borrow::Cow, collections::BTreeMap, fmt::Debug, marker::PhantomData, path::Path, }; -use derive_more::Deref; use log::*; use evm::backend::Log; @@ -24,13 +23,14 @@ persistent_types! { TransactionsInBlock in "txs_in_block" => Slot : Vec, // TODO: Key is Slot or U256? } -#[derive(Deref)] -pub(crate) struct Layer( - #[deref] BTreeMap>, - PhantomData, -) +pub(crate) struct Layer where - M::Key: Ord; + M::Key: Ord, +{ + map: BTreeMap>, + is_frozen: bool, + _type: PhantomData, +} impl Layer where @@ -38,15 +38,50 @@ where M::Key: Ord, { pub fn empty() -> Self { - Self(BTreeMap::new(), PhantomData) + Self { + map: BTreeMap::new(), + is_frozen: false, + _type: PhantomData, + } } - pub fn insert(&mut self, key: M::Key, value: M::Value) { - self.0.insert(key, MaybeValue::Value(value)); + pub fn is_empty(&self) -> bool { + self.map.is_empty() } - pub fn remove(&mut self, key: M::Key) { - self.0.insert(key, MaybeValue::Removed); + pub fn insert(&mut self, key: M::Key, value: M::Value) + where + M::Key: Debug, + M::Value: Debug, + { + assert!( + !self.is_frozen, + "Modification of frozen layer is prohibited" + ); + trace!( + "layer :: {} inserts {:?} => {:?}", + type_name::(), + key, + value + ); + self.map.insert(key, MaybeValue::Value(value)); + } + + pub fn remove(&mut self, key: M::Key) + where + M::Key: Debug, + { + assert!( + !self.is_frozen, + "Modification of frozen layer is prohibited" + ); + + trace!("layer :: {} removes {:?}", type_name::(), key); + self.map.insert(key, MaybeValue::Removed); + } + + fn freeze(&mut self) { + self.is_frozen = true; } fn dump_into(&mut self, storage: &VersionedStorage, version: Slot) -> StorageResult<()> @@ -56,23 +91,14 @@ where { let storage = storage.typed::(); - trace!("layer :: {} dumping ...", type_name::()); - for (key, value) in std::mem::replace(self, Self::empty()).0 { + for (key, value) in std::mem::replace(self, Self::empty()).map { debug!( "{}: {:?} {:?} migrates from memory into storage", version, key, &value ); storage.insert_with(version, key, value)?; - - trace!( - "{}: {:?} in storage as {:?}", - version, - key, - storage.get_for(version, key) - ); } - trace!("layer :: {} dumped", type_name::()); Ok(()) } @@ -84,7 +110,11 @@ where M::Value: Clone, { fn clone(&self) -> Self { - Self(self.0.clone(), PhantomData) + Self { + map: self.map.clone(), + is_frozen: false, + _type: PhantomData, + } } } @@ -112,11 +142,22 @@ impl Default for EvmState { impl EvmState { pub fn freeze(&mut self) { - info!(target: "evm_state", "freezing evm state (slot {})", self.current_slot); - // self.accounts.freeze(); - // self.accounts_storage.freeze(); - // self.txs_receipts.freeze(); - // self.txs_in_block.freeze(); + debug!("freezing evm state (slot {})", self.current_slot); + self.dump_all() + .expect("Unable to dump EVM state layers into storage"); + + self.accounts.freeze(); + self.accounts_storage.freeze(); + self.txs_receipts.freeze(); + self.txs_in_block.freeze(); + + debug!( + "new slot {} with previous {:?}", + self.current_slot, self.previous_slot + ); + self.storage + .new_version(self.current_slot, self.previous_slot) + .expect("Unable to create new version in storage"); } // TODO: dump all @@ -132,17 +173,6 @@ impl EvmState { let txs_receipts = self.txs_receipts.clone(); let txs_in_block = self.txs_in_block.clone(); - // XXX: >_< - if new_slot != self.current_slot { - debug!( - "new slot {} with previous {:?}", - self.current_slot, self.previous_slot - ); - self.storage - .new_version(self.current_slot, self.previous_slot) - .unwrap(); - } - Some(Self { current_slot: new_slot, previous_slot: Some(self.current_slot), @@ -157,19 +187,18 @@ impl EvmState { } #[rustfmt::skip] - pub fn dump_all(&mut self) -> anyhow::Result<()> { + fn dump_all(&mut self) -> anyhow::Result<()> { self.accounts.dump_into(&self.storage, self.current_slot)?; - self.accounts_storage.dump_into(&self.storage, self.current_slot )?; + self.accounts_storage.dump_into(&self.storage, self.current_slot)?; self.txs_receipts.dump_into(&self.storage, self.current_slot)?; self.txs_in_block.dump_into(&self.storage, self.current_slot)?; - debug!(target: "evm_state", "all layers have been dumped"); Ok(()) } // TODO: elide map arg, TBD: maybe typemap fn lookup<'a, M: PersistentAssoc>( &'a self, - map: &'a Layer, + layer: &'a Layer, key: M::Key, ) -> Option> where @@ -177,7 +206,7 @@ impl EvmState { M::Value: Clone + Debug, { debug!("lookup {} for key {:?}", type_name::(), &key); - if let Some(mb_value) = map.0.get(&key) { + if let Some(mb_value) = layer.map.get(&key) { Option::from(mb_value.by_ref()).map(Cow::Borrowed) } else { let lookup_slot = if self.storage.is_exists(self.current_slot).unwrap() { @@ -250,13 +279,22 @@ impl EvmState { }) } + pub fn get_account(&self, address: H160) -> Option { + self.lookup(&self.accounts, address).map(Cow::into_owned) + } + + pub fn get_storage(&self, address: H160, index: H256) -> Option { + self.lookup(&self.accounts_storage, (address, index)) + .map(Cow::into_owned) + } + pub fn get_tx_receipt_by_hash(&self, tx_hash: H256) -> Option { - self.lookup::(&self.txs_receipts, tx_hash) + self.lookup(&self.txs_receipts, tx_hash) .map(Cow::into_owned) } pub fn get_txs_in_block(&self, block_num: Slot) -> Option> { - self.lookup::(&self.txs_in_block, block_num) + self.lookup(&self.txs_in_block, block_num) .map(Cow::into_owned) } @@ -265,16 +303,6 @@ impl EvmState { self.accounts.insert(address, state); } - pub fn get_account(&self, address: H160) -> Option { - self.lookup::(&self.accounts, address) - .map(Cow::into_owned) - } - - pub fn get_storage(&self, address: H160, index: H256) -> Option { - self.lookup::(&self.accounts_storage, (address, index)) - .map(Cow::into_owned) - } - pub fn swap_commit(&mut self, mut updated: Self) { // Assert that updated is newer than current state. // Slot can not change, because we allow multiple commits per block. @@ -539,7 +567,6 @@ mod tests { } evm_state.freeze(); - evm_state.dump_all()?; let next_slot = evm_state.current_slot + 1; evm_state = evm_state @@ -572,7 +599,6 @@ mod tests { for _ in 0..42 { state.freeze(); - state.dump_all().unwrap(); let next_slot = state.current_slot + 1; state = state.try_fork(next_slot).unwrap(); diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs index 7a38b0b962..d70d18200a 100644 --- a/evm-utils/evm-state/src/storage.rs +++ b/evm-utils/evm-state/src/storage.rs @@ -1,17 +1,18 @@ use std::{ array::TryFromSliceError, convert::{TryFrom, TryInto}, - fmt::{self, Debug}, + fmt::{self, Debug, Display}, io::Cursor, marker::PhantomData, mem::size_of, - ops::Deref, + ops::{Deref, Sub}, path::Path, sync::{Arc, RwLock}, }; use bincode::config::{BigEndian, DefaultOptions, Options as _, WithOtherEndian}; use lazy_static::lazy_static; +use log::*; use rocksdb::{self, ColumnFamily, ColumnFamilyDescriptor, IteratorMode, Options, DB}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -100,16 +101,27 @@ where pub fn new_version(&self, version: V, previous: Previous) -> Result<()> where - V: PartialEq + std::fmt::Debug, + V: PartialEq + Debug, { assert_ne!(Some(version), previous); - let version = CODER.serialize(&version).typed_ctx()?; - if self.db.get(&version)?.is_none() { - let previous = CODER.serialize(&previous).typed_ctx()?; - self.db.put(version, previous)?; - } else { - // TODO: assert the same + let key = CODER.serialize(&version).typed_ctx()?; + + match self.db.get_pinned(&key)? { + None => { + let value = CODER.serialize(&previous).typed_ctx()?; + self.db.put(key, value)?; + } + Some(data) => { + // TODO: assert or do some check + // assert_eq!( + // previous, + // CODER.deserialize(&data).typed_ctx()?, + // "attempt to insert for {:?}", + // version + // ); + } } + Ok(()) } } @@ -401,13 +413,9 @@ where pub fn get_for(&self, version: V, key: M::Key) -> Result>> where - V: std::fmt::Debug - + std::fmt::Display - + PartialEq - + track::Stepped - + std::ops::Sub, - M::Key: std::fmt::Debug, - M::Value: std::fmt::Debug, + V: Display + Debug + PartialEq + Sub + track::Stepped, + M::Key: Debug, + M::Value: Debug, { let _guard = self .storage @@ -423,23 +431,20 @@ where track.prepend(version); let value = self.get_exact_for(version, key)?; if value.is_some() { - log::debug!( + debug!( "get_for: key {:?} found with track {}: value {:?}", - key, - track, - &value + key, track, &value ); return Ok(value); } else { let previous = self.storage.previous_of(version)?; - log::trace!("get_for: previous of {:?} is {:?}", version, previous); assert_ne!(Some(version), previous); next_version = previous; continue; } } - log::debug!("get_for: not found {}", track); + debug!("get_for: key {:?} not found {}", key, track); Ok(None) } diff --git a/run.sh b/run.sh index 8da6be2dff..7722fcdc33 100755 --- a/run.sh +++ b/run.sh @@ -104,7 +104,7 @@ args=( --enable-rpc-exit --enable-rpc-transaction-history --init-complete-file "$dataDir"/init-completed - --snapshot-interval-slots 0 + --snapshot-interval-slots 100 ) solana-validator "${args[@]}" & validator=$! diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index eb75db06c6..596e376d36 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1128,12 +1128,10 @@ impl Bank { pub fn freeze(&self) { let mut hash = self.hash.write().unwrap(); - { - let mut evm_state = self.evm_state.write().expect("evm state was poisoned"); - - evm_state.freeze(); - evm_state.dump_all().expect("unable to dump evm state"); - } + self.evm_state + .write() + .expect("evm state was poisoned") + .freeze(); if *hash == Hash::default() { // finish up any deferred changes to account state From 61b55b07e11e2efec08fcaf6df2cc3e506f59836 Mon Sep 17 00:00:00 2001 From: hrls Date: Fri, 8 Jan 2021 14:13:04 +0200 Subject: [PATCH 11/13] feat(evm-state): backup/restore evm state in snapshot package --- evm-utils/evm-state/src/layered_backend.rs | 17 +++++--- evm-utils/evm-state/src/lib.rs | 1 + evm-utils/evm-state/src/storage.rs | 50 +++++++++++++++++++++- run.sh | 1 + runtime/src/bank.rs | 6 +-- runtime/src/snapshot_utils.rs | 46 +++++++++++++++++++- 6 files changed, 111 insertions(+), 10 deletions(-) diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index 42dee97c93..88cbf4c77a 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -1,5 +1,6 @@ use std::{ - any::type_name, borrow::Cow, collections::BTreeMap, fmt::Debug, marker::PhantomData, path::Path, + any::type_name, borrow::Cow, collections::BTreeMap, fmt::Debug, marker::PhantomData, + ops::Deref, path::Path, }; use log::*; @@ -14,6 +15,8 @@ use crate::{ types::*, }; +pub type Storage = VersionedStorage; + // Store every account storage at single place, to use power of versioned map. // This allows us to save only changed data. persistent_types! { @@ -84,12 +87,16 @@ where self.is_frozen = true; } - fn dump_into(&mut self, storage: &VersionedStorage, version: Slot) -> StorageResult<()> + fn dump_into( + &mut self, + storage: impl Deref, + version: Slot, + ) -> StorageResult<()> where M::Key: Ord + Copy + Debug, M::Value: Clone + Debug, { - let storage = storage.typed::(); + let storage = storage.deref().typed::(); for (key, value) in std::mem::replace(self, Self::empty()).map { debug!( @@ -129,7 +136,7 @@ pub struct EvmState { pub(crate) txs_in_block: Layer, pub(crate) logs: Vec, // TODO: migrate into storage - storage: VersionedStorage, + pub storage: Storage, } // TODO: move this logic outside @@ -259,7 +266,7 @@ impl EvmState { path.as_ref().display(), slot ); - let storage = VersionedStorage::open(path, COLUMN_NAMES)?; + let storage = Storage::open(path, COLUMN_NAMES)?; let previous_slot = storage.previous_of(slot)?; debug!( "storage reports: previous of {} is {:?}", diff --git a/evm-utils/evm-state/src/lib.rs b/evm-utils/evm-state/src/lib.rs index e4e35066b7..fa9b7d93f9 100644 --- a/evm-utils/evm-state/src/lib.rs +++ b/evm-utils/evm-state/src/lib.rs @@ -11,6 +11,7 @@ pub mod layered_backend; pub mod transactions; pub use evm_backend::*; +pub use layered_backend::Storage; pub use layered_backend::*; pub use transactions::*; pub use types::*; diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs index d70d18200a..7bdc4d8b6f 100644 --- a/evm-utils/evm-state/src/storage.rs +++ b/evm-utils/evm-state/src/storage.rs @@ -2,6 +2,7 @@ use std::{ array::TryFromSliceError, convert::{TryFrom, TryInto}, fmt::{self, Debug, Display}, + fs, io::Cursor, marker::PhantomData, mem::size_of, @@ -13,7 +14,11 @@ use std::{ use bincode::config::{BigEndian, DefaultOptions, Options as _, WithOtherEndian}; use lazy_static::lazy_static; use log::*; -use rocksdb::{self, ColumnFamily, ColumnFamilyDescriptor, IteratorMode, Options, DB}; +use rocksdb::{ + self, + backup::{BackupEngine, BackupEngineOptions, RestoreOptions}, + ColumnFamily, ColumnFamilyDescriptor, IteratorMode, Options, DB, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::mb_value::MaybeValue; @@ -126,6 +131,49 @@ where } } +impl VersionedStorage { + pub fn save_into>(&self, path: P) -> Result<()> { + let path = path.as_ref(); + assert!( + path.is_dir() && path.exists(), + "storage can be saved only into some existing directory" + ); + info!( + "saving storage data into {} (as new backup)", + path.display() + ); + let mut engine = BackupEngine::open(&BackupEngineOptions::default(), path)?; + engine.create_new_backup_flush(self.db.as_ref(), true)?; + Ok(()) + } + + pub fn restore_from, P2: AsRef>(path: P1, target: P2) -> Result<()> { + let path = path.as_ref(); + let target = target.as_ref(); + + // TODO: check target dir is empty or doesn't exists at all + fs::create_dir_all(target).expect("Unable to create target dir"); + + assert!( + path.is_dir() && path.exists(), + "storage can be loaded only from existing directory" + ); + info!( + "loading storage data from {} into {} (restore from backup)", + path.display(), + target.display() + ); + let mut engine = BackupEngine::open(&BackupEngineOptions::default(), path)?; + assert!( + target.is_dir(), + "loaded storage data must lays in target dir" + ); + engine.restore_from_latest_backup(&target, &target, &RestoreOptions::default())?; + + Ok(()) + } +} + pub trait PersistentAssoc { const COLUMN_NAME: &'static str; type Key: Serialize + DeserializeOwned; diff --git a/run.sh b/run.sh index 7722fcdc33..9e70e60fc0 100755 --- a/run.sh +++ b/run.sh @@ -105,6 +105,7 @@ args=( --enable-rpc-transaction-history --init-complete-file "$dataDir"/init-completed --snapshot-interval-slots 100 + --snapshot-compression none ) solana-validator "${args[@]}" & validator=$! diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 596e376d36..0673787ce5 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -96,6 +96,9 @@ pub mod inline_spl_token_v2_0 { } } +// TODO: get real path from extern configuration +pub const EVM_STATE_STORAGE: &str = "/tmp/solana/evm-state"; + pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0; pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5; @@ -645,9 +648,6 @@ impl Bank { T::default() } - // TODO: get real path from extern configuration - const EVM_STATE_STORAGE: &str = "/tmp/solana/evm-state"; - let evm_state = evm_state::EvmState::load_from(EVM_STATE_STORAGE, fields.slot) .expect("Unable to open EVM state storage"); diff --git a/runtime/src/snapshot_utils.rs b/runtime/src/snapshot_utils.rs index 6b609dae73..53d073d606 100644 --- a/runtime/src/snapshot_utils.rs +++ b/runtime/src/snapshot_utils.rs @@ -1,5 +1,5 @@ use crate::{ - bank::{Bank, BankSlotDelta}, + bank::{Bank, BankSlotDelta, EVM_STATE_STORAGE}, bank_forks::CompressionType, hardened_unpack::{unpack_snapshot, UnpackError}, serde_snapshot::{ @@ -38,6 +38,8 @@ pub const TAR_SNAPSHOTS_DIR: &str = "snapshots"; pub const TAR_ACCOUNTS_DIR: &str = "accounts"; pub const TAR_VERSION_FILE: &str = "version"; +const EVM_STATE_DIR: &str = "evm-state"; + const MAX_SNAPSHOT_DATA_FILE_SIZE: u64 = 32 * 1024 * 1024 * 1024; // 32 GiB const VERSION_STRING_V1_2_0: &str = "1.2.0"; const DEFAULT_SNAPSHOT_VERSION: SnapshotVersion = SnapshotVersion::V1_2_0; @@ -101,6 +103,7 @@ impl SnapshotVersion { pub struct SlotSnapshotPaths { pub slot: Slot, pub snapshot_file_path: PathBuf, + pub evm_state_backup_path: PathBuf, } #[derive(Error, Debug)] @@ -149,6 +152,14 @@ impl SlotSnapshotPaths { &self.snapshot_file_path, &new_slot_hardlink_dir.join(self.slot.to_string()), )?; + + // Hard link EVM backup folder + let evm_source = fs::canonicalize(&self.evm_state_backup_path)?; + let evm_target = new_slot_hardlink_dir.join(EVM_STATE_DIR); + info!("EVM backup linked {:?} => {:?}", evm_source, evm_target); + symlink::symlink_dir(evm_source, evm_target)?; + // std::os::unix::fs::symlink(evm_source, evm_target)?; + Ok(()) } } @@ -378,6 +389,7 @@ where SlotSnapshotPaths { slot, snapshot_file_path: snapshot_path.join(get_snapshot_file_name(slot)), + evm_state_backup_path: snapshot_path.join(EVM_STATE_DIR), } }) .collect::>(); @@ -521,9 +533,31 @@ pub fn add_snapshot>( bank_serialize, slot, snapshot_bank_file_path, ); + let evm_state_dir = slot_snapshot_dir.join(EVM_STATE_DIR); + fs::create_dir_all(&evm_state_dir)?; + info!( + "Saving EVM state for slot {}, path: {:?}", + slot, evm_state_dir + ); + + let mut evm_state_saving = Measure::start("evm-state-saving-ms"); + bank.evm_state + .write() + .unwrap() + .storage + .save_into(&evm_state_dir) + .expect("Unable to save EVM storage data in new place"); + evm_state_saving.stop(); + inc_new_counter_info!("evm-state-saving-ms", evm_state_saving.as_ms() as usize); + info!( + "{} for slot {} at {:?}", + evm_state_saving, slot, evm_state_dir + ); + Ok(SlotSnapshotPaths { slot, snapshot_file_path: snapshot_bank_file_path, + evm_state_backup_path: slot_snapshot_dir.join(EVM_STATE_DIR), }) } @@ -769,6 +803,16 @@ where .pop() .ok_or_else(|| get_io_error("No snapshots found in snapshots directory"))?; + info!( + "restoring database from storage backup: {:?}", + root_paths.evm_state_backup_path + ); + let mut measure = Measure::start("evm state database restore"); + evm_state::Storage::restore_from(root_paths.evm_state_backup_path, EVM_STATE_STORAGE) + .expect("Unable to restore EVM state underlying database from storage backup"); + measure.stop(); + info!("{}", measure); + info!("Loading bank from {:?}", &root_paths.snapshot_file_path); let bank = deserialize_snapshot_data_file(&root_paths.snapshot_file_path, |mut stream| { Ok(match snapshot_version_enum { From e41070701324077731effd42b5edc4dfc33c0327 Mon Sep 17 00:00:00 2001 From: hrls Date: Fri, 8 Jan 2021 15:46:45 +0200 Subject: [PATCH 12/13] fix(snapshot): support evm-state in package --- runtime/src/hardened_unpack.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/runtime/src/hardened_unpack.rs b/runtime/src/hardened_unpack.rs index a3976554f3..7616b240b1 100644 --- a/runtime/src/hardened_unpack.rs +++ b/runtime/src/hardened_unpack.rs @@ -156,6 +156,8 @@ fn is_valid_snapshot_archive_entry(parts: &[&str], kind: tar::EntryType) -> bool true } (["snapshots", dir], Directory) if like_slot.is_match(dir) => true, + (["snapshots", dir, "evm-state"], Directory) if like_slot.is_match(dir) => true, + (["snapshots", dir, "evm-state", ..], _) if like_slot.is_match(dir) => true, _ => false, } } @@ -267,6 +269,28 @@ mod tests { tar::EntryType::Directory )); + assert!(is_valid_snapshot_archive_entry( + &["snapshots", "3", "evm-state"], + tar::EntryType::Directory + )); + + assert!(is_valid_snapshot_archive_entry( + &["snapshots", "3", "evm-state", "meta"], + tar::EntryType::Directory + )); + assert!(is_valid_snapshot_archive_entry( + &["snapshots", "3", "evm-state", "meta", "1"], + tar::EntryType::Regular + )); + assert!(is_valid_snapshot_archive_entry( + &["snapshots", "3", "evm-state", "shared"], + tar::EntryType::Directory + )); + assert!(is_valid_snapshot_archive_entry( + &["snapshots", "3", "evm-state", "shared", "01.zst"], + tar::EntryType::Regular + )); + assert!(!is_valid_snapshot_archive_entry( &["accounts", "0x0"], tar::EntryType::Regular From 2c121856181cf9405aa883d5f792ac95567cba71 Mon Sep 17 00:00:00 2001 From: Vladimir Motylenko Date: Sun, 10 Jan 2021 14:09:40 +0200 Subject: [PATCH 13/13] fix: Provide path to evm-state db, rather then use /tmp/evm-state. --- core/src/validator.rs | 4 ++++ core/tests/bank_forks.rs | 14 +++++++++++++- evm-utils/evm-state/src/layered_backend.rs | 7 ++++++- evm-utils/evm-state/src/storage.rs | 3 +++ ledger-tool/src/main.rs | 3 +++ ledger/src/bank_forks_utils.rs | 3 +++ ledger/src/blockstore_processor.rs | 4 +++- runtime/src/bank.rs | 15 +++++++-------- runtime/src/serde_snapshot.rs | 7 ++++++- runtime/src/serde_snapshot/tests.rs | 2 ++ runtime/src/snapshot_utils.rs | 8 ++++++-- 11 files changed, 56 insertions(+), 14 deletions(-) diff --git a/core/src/validator.rs b/core/src/validator.rs index 5ffc1e1276..66bba74614 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -672,9 +672,13 @@ fn new_banks_from_ledger( TransactionHistoryServices::default() }; + // TODO: Add evm-state to config. + let evm_state_path = ledger_path.join("evm-state"); + let (mut bank_forks, mut leader_schedule_cache, snapshot_hash) = bank_forks_utils::load( &genesis_config, &blockstore, + evm_state_path, config.account_paths.clone(), config.snapshot_config.as_ref(), process_options, diff --git a/core/tests/bank_forks.rs b/core/tests/bank_forks.rs index a528376669..9639304c9e 100644 --- a/core/tests/bank_forks.rs +++ b/core/tests/bank_forks.rs @@ -65,6 +65,7 @@ mod tests { DEFINE_SNAPSHOT_VERSION_PARAMETERIZED_TEST_FUNCTIONS!(V1_2_0, MainnetBeta, V1_2_0_MainnetBeta); struct SnapshotTestConfig { + evm_state_dir: TempDir, accounts_dir: TempDir, snapshot_dir: TempDir, _snapshot_output_path: TempDir, @@ -79,6 +80,7 @@ mod tests { cluster_type: ClusterType, snapshot_interval_slots: u64, ) -> SnapshotTestConfig { + let evm_state_dir = TempDir::new().unwrap(); let accounts_dir = TempDir::new().unwrap(); let snapshot_dir = TempDir::new().unwrap(); let snapshot_output_path = TempDir::new().unwrap(); @@ -102,6 +104,7 @@ mod tests { }; bank_forks.set_snapshot_config(Some(snapshot_config.clone())); SnapshotTestConfig { + evm_state_dir, accounts_dir, snapshot_dir, _snapshot_output_path: snapshot_output_path, @@ -116,6 +119,7 @@ mod tests { old_bank_forks: &BankForks, old_last_slot: Slot, old_genesis_config: &GenesisConfig, + evm_state_path: &Path, account_paths: &[PathBuf], ) { let (snapshot_path, snapshot_package_output_path) = old_bank_forks @@ -127,6 +131,7 @@ mod tests { let old_last_bank = old_bank_forks.get(old_last_slot).unwrap(); let deserialized_bank = snapshot_utils::bank_from_archive( + evm_state_path, &account_paths, &[], &old_bank_forks @@ -213,9 +218,16 @@ mod tests { snapshot_utils::archive_snapshot_package(&snapshot_package).unwrap(); // Restore bank from snapshot + let evm_state_path = snapshot_test_config.evm_state_dir.path(); let account_paths = &[snapshot_test_config.accounts_dir.path().to_path_buf()]; let genesis_config = &snapshot_test_config.genesis_config_info.genesis_config; - restore_from_snapshot(bank_forks, last_slot, genesis_config, account_paths); + restore_from_snapshot( + evm_state_path, + bank_forks, + last_slot, + genesis_config, + account_paths, + ); } fn run_test_bank_forks_snapshot_n( diff --git a/evm-utils/evm-state/src/layered_backend.rs b/evm-utils/evm-state/src/layered_backend.rs index 88cbf4c77a..7b3911b87c 100644 --- a/evm-utils/evm-state/src/layered_backend.rs +++ b/evm-utils/evm-state/src/layered_backend.rs @@ -143,7 +143,7 @@ pub struct EvmState { impl Default for EvmState { fn default() -> Self { let path = std::env::temp_dir().join("evm-state"); - Self::load_from(path, Slot::default()).expect("Unable to instantiate default EVM state") + Self::new(path).expect("Unable to instantiate default EVM state") } } @@ -260,6 +260,11 @@ impl EvmState { } impl EvmState { + pub fn new>(path: P) -> Result { + //TODO: add flags, to asserts for empty storage. + Self::load_from(path, 0) + } + pub fn load_from>(path: P, slot: Slot) -> Result { info!( "open evm state storage {} for slot {}", diff --git a/evm-utils/evm-state/src/storage.rs b/evm-utils/evm-state/src/storage.rs index 7bdc4d8b6f..5b3d9a0d1f 100644 --- a/evm-utils/evm-state/src/storage.rs +++ b/evm-utils/evm-state/src/storage.rs @@ -117,6 +117,9 @@ where self.db.put(key, value)?; } Some(data) => { + warn!("Found confict previous: previous = {:?}, version = {:?}, previous_in_db = {:?}", previous, + version, + CODER.deserialize::>(&data).typed_ctx() ); // TODO: assert or do some check // assert_eq!( // previous, diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 3ad267ec04..332493cb6c 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -691,9 +691,12 @@ fn load_bank_forks( vec![non_primary_accounts_path] }; + let evm_state_path = ledger_path.join("evm-state"); + bank_forks_utils::load( &genesis_config, &blockstore, + evm_state_path, account_paths, snapshot_config.as_ref(), process_options, diff --git a/ledger/src/bank_forks_utils.rs b/ledger/src/bank_forks_utils.rs index 4002b7898d..9b211793c7 100644 --- a/ledger/src/bank_forks_utils.rs +++ b/ledger/src/bank_forks_utils.rs @@ -32,6 +32,7 @@ fn to_loadresult( pub fn load( genesis_config: &GenesisConfig, blockstore: &Blockstore, + evm_state_path: PathBuf, account_paths: Vec, snapshot_config: Option<&SnapshotConfig>, process_options: ProcessOptions, @@ -59,6 +60,7 @@ pub fn load( } let deserialized_bank = snapshot_utils::bank_from_archive( + &evm_state_path, &account_paths, &process_options.frozen_accounts, &snapshot_config.snapshot_path, @@ -104,6 +106,7 @@ pub fn load( blockstore_processor::process_blockstore( &genesis_config, &blockstore, + &evm_state_path, account_paths, process_options, ), diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index e72721d280..9b0afff3ba 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -36,7 +36,7 @@ use solana_vote_program::vote_state::VoteState; use std::{ cell::RefCell, collections::{BTreeMap, HashMap}, - path::PathBuf, + path::{Path, PathBuf}, result, sync::Arc, time::{Duration, Instant}, @@ -320,6 +320,7 @@ fn initiate_callback(mut bank: &mut Arc, genesis_config: &GenesisConfig) { pub fn process_blockstore( genesis_config: &GenesisConfig, blockstore: &Blockstore, + evm_state_path: &Path, account_paths: Vec, opts: ProcessOptions, ) -> BlockstoreProcessorResult { @@ -335,6 +336,7 @@ pub fn process_blockstore( // Setup bank for slot 0 let mut bank0 = Arc::new(Bank::new_with_paths( &genesis_config, + Some(evm_state_path), account_paths, &opts.frozen_accounts, )); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 0673787ce5..97eead34b7 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -66,7 +66,7 @@ use std::{ convert::TryFrom, mem, ops::RangeInclusive, - path::PathBuf, + path::{Path, PathBuf}, ptr, rc::Rc, sync::atomic::{AtomicBool, AtomicU64, Ordering}, @@ -96,9 +96,6 @@ pub mod inline_spl_token_v2_0 { } } -// TODO: get real path from extern configuration -pub const EVM_STATE_STORAGE: &str = "/tmp/solana/evm-state"; - pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0; pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5; @@ -473,11 +470,12 @@ impl Default for BlockhashQueue { impl Bank { pub fn new(genesis_config: &GenesisConfig) -> Self { - Self::new_with_paths(&genesis_config, Vec::new(), &[]) + Self::new_with_paths(&genesis_config, None, Vec::new(), &[]) } pub fn new_with_paths( genesis_config: &GenesisConfig, + evm_state_path: Option<&Path>, // TODO: Remove option, currently need for Bank::new, that is used for tests paths: Vec, frozen_account_pubkeys: &[Pubkey], ) -> Self { @@ -486,6 +484,9 @@ impl Bank { bank.ancestors.insert(bank.slot(), 0); bank.rc.accounts = Arc::new(Accounts::new(paths, &genesis_config.cluster_type)); + if let Some(evm_state_path) = evm_state_path { + bank.evm_state = RwLock::new(evm_state::EvmState::new(evm_state_path).unwrap()); + } bank.process_genesis_config(genesis_config); bank.finish_init(genesis_config); @@ -640,6 +641,7 @@ impl Bank { /// Create a bank from explicit arguments and deserialized fields from snapshot #[allow(clippy::float_cmp)] pub(crate) fn new_from_fields( + evm_state: evm_state::EvmState, bank_rc: BankRc, genesis_config: &GenesisConfig, fields: BankFieldsToDeserialize, @@ -648,9 +650,6 @@ impl Bank { T::default() } - let evm_state = evm_state::EvmState::load_from(EVM_STATE_STORAGE, fields.slot) - .expect("Unable to open EVM state storage"); - let mut bank = Self { rc: bank_rc, src: new(), diff --git a/runtime/src/serde_snapshot.rs b/runtime/src/serde_snapshot.rs index 39054414ea..848889c910 100644 --- a/runtime/src/serde_snapshot.rs +++ b/runtime/src/serde_snapshot.rs @@ -121,6 +121,7 @@ pub(crate) fn bank_from_stream( serde_style: SerdeStyle, stream: &mut BufReader, append_vecs_path: P, + evm_state_path: &Path, account_paths: &[PathBuf], genesis_config: &GenesisConfig, frozen_account_pubkeys: &[Pubkey], @@ -138,6 +139,7 @@ where accounts_db_fields, genesis_config, frozen_account_pubkeys, + evm_state_path, account_paths, append_vecs_path, )?; @@ -222,6 +224,7 @@ fn reconstruct_bank_from_fields( accounts_db_fields: AccountsDbFields, genesis_config: &GenesisConfig, frozen_account_pubkeys: &[Pubkey], + evm_state_path: &Path, account_paths: &[PathBuf], append_vecs_path: P, ) -> Result @@ -238,7 +241,9 @@ where accounts_db.freeze_accounts(&bank_fields.ancestors, frozen_account_pubkeys); let bank_rc = BankRc::new(Accounts::new_empty(accounts_db), bank_fields.slot); - let bank = Bank::new_from_fields(bank_rc, genesis_config, bank_fields); + let evm_state = evm_state::EvmState::load_from(evm_state_path, bank_fields.slot) + .expect("Unable to open EVM state storage"); + let bank = Bank::new_from_fields(evm_state, bank_rc, genesis_config, bank_fields); Ok(bank) } diff --git a/runtime/src/serde_snapshot/tests.rs b/runtime/src/serde_snapshot/tests.rs index ee21da53ac..dc1bf77781 100644 --- a/runtime/src/serde_snapshot/tests.rs +++ b/runtime/src/serde_snapshot/tests.rs @@ -197,6 +197,7 @@ fn test_bank_serialize_style(serde_style: SerdeStyle) { let rdr = Cursor::new(&buf[..]); let mut reader = std::io::BufReader::new(&buf[rdr.position() as usize..]); + let evm_state_dir = TempDir::new().unwrap(); // Create a new set of directories for this bank's accounts let (_accounts_dir, dbank_paths) = get_temp_accounts_paths(4).unwrap(); let ref_sc = StatusCacheRc::default(); @@ -208,6 +209,7 @@ fn test_bank_serialize_style(serde_style: SerdeStyle) { serde_style, &mut reader, copied_accounts.path(), + &evm_state_dir.path(), &dbank_paths, &genesis_config, &[], diff --git a/runtime/src/snapshot_utils.rs b/runtime/src/snapshot_utils.rs index 53d073d606..88231b8977 100644 --- a/runtime/src/snapshot_utils.rs +++ b/runtime/src/snapshot_utils.rs @@ -1,5 +1,5 @@ use crate::{ - bank::{Bank, BankSlotDelta, EVM_STATE_STORAGE}, + bank::{Bank, BankSlotDelta}, bank_forks::CompressionType, hardened_unpack::{unpack_snapshot, UnpackError}, serde_snapshot::{ @@ -599,6 +599,7 @@ pub fn remove_snapshot>(slot: Slot, snapshot_path: P) -> Result<( } pub fn bank_from_archive>( + evm_state_path: &Path, account_paths: &[PathBuf], frozen_account_pubkeys: &[Pubkey], snapshot_path: &PathBuf, @@ -620,6 +621,7 @@ pub fn bank_from_archive>( let bank = rebuild_bank_from_snapshots( snapshot_version.trim(), + evm_state_path, account_paths, frozen_account_pubkeys, &unpacked_snapshots_dir, @@ -777,6 +779,7 @@ pub fn untar_snapshot_in, Q: AsRef>( fn rebuild_bank_from_snapshots

( snapshot_version: &str, + evm_state_path: &Path, account_paths: &[PathBuf], frozen_account_pubkeys: &[Pubkey], unpacked_snapshots_dir: &PathBuf, @@ -808,7 +811,7 @@ where root_paths.evm_state_backup_path ); let mut measure = Measure::start("evm state database restore"); - evm_state::Storage::restore_from(root_paths.evm_state_backup_path, EVM_STATE_STORAGE) + evm_state::Storage::restore_from(root_paths.evm_state_backup_path, evm_state_path) .expect("Unable to restore EVM state underlying database from storage backup"); measure.stop(); info!("{}", measure); @@ -820,6 +823,7 @@ where SerdeStyle::NEWER, &mut stream, &append_vecs_path, + evm_state_path, account_paths, genesis_config, frozen_account_pubkeys,