diff --git a/lib/Cargo.lock b/lib/Cargo.lock index ddd21e6049..cedfa69a5a 100644 --- a/lib/Cargo.lock +++ b/lib/Cargo.lock @@ -113,6 +113,7 @@ dependencies = [ "rand 0.8.5", "rlp", "serde", + "serde_json", "sha3", "sp-core", "statrs", diff --git a/lib/ain-cpp-imports/src/bridge.rs b/lib/ain-cpp-imports/src/bridge.rs index 1108a186e0..811903d44c 100644 --- a/lib/ain-cpp-imports/src/bridge.rs +++ b/lib/ain-cpp-imports/src/bridge.rs @@ -8,11 +8,13 @@ pub mod ffi { fn publishEthTransaction(data: Vec) -> String; fn getAccounts() -> Vec; fn getDatadir() -> String; + fn getNetwork() -> String; fn getDifficulty(_block_hash: [u8; 32]) -> u32; fn getChainWork(_block_hash: [u8; 32]) -> [u8; 32]; fn getPoolTransactions() -> Vec; fn getNativeTxSize(data: Vec) -> u64; fn getMinRelayTxFee() -> u64; fn getEthPrivKey(key_id: [u8; 20]) -> [u8; 32]; + fn getStateInputJSON() -> String; } } diff --git a/lib/ain-cpp-imports/src/lib.rs b/lib/ain-cpp-imports/src/lib.rs index 352d9f2032..4e81a4d5a7 100644 --- a/lib/ain-cpp-imports/src/lib.rs +++ b/lib/ain-cpp-imports/src/lib.rs @@ -25,6 +25,9 @@ mod ffi { pub fn getDatadir() -> String { unimplemented!("{}", UNIMPL_MSG) } + pub fn getNetwork() -> String { + unimplemented!("{}", UNIMPL_MSG) + } pub fn getDifficulty(_block_hash: [u8; 32]) -> u32 { unimplemented!("{}", UNIMPL_MSG) } @@ -43,6 +46,9 @@ mod ffi { pub fn getEthPrivKey(_key_id: [u8; 20]) -> [u8; 32] { unimplemented!("{}", UNIMPL_MSG) } + pub fn getStateInputJSON() -> String { + unimplemented!("{}", UNIMPL_MSG) + } } pub fn get_chain_id() -> Result> { @@ -70,6 +76,10 @@ pub fn get_datadir() -> Result> { Ok(datadir) } +pub fn get_network() -> String { + ffi::getNetwork() +} + pub fn get_difficulty(block_hash: [u8; 32]) -> Result> { let bits = ffi::getDifficulty(block_hash); Ok(bits) @@ -100,5 +110,14 @@ pub fn get_eth_priv_key(key_id: [u8; 20]) -> Result<[u8; 32], Box> { Ok(eth_key) } +pub fn get_state_input_json() -> Option { + let json_path = ffi::getStateInputJSON(); + if json_path.is_empty() { + None + } else { + Some(json_path) + } +} + #[cfg(test)] mod tests {} diff --git a/lib/ain-evm/Cargo.toml b/lib/ain-evm/Cargo.toml index d0dd4e9bc6..b305c95d96 100644 --- a/lib/ain-evm/Cargo.toml +++ b/lib/ain-evm/Cargo.toml @@ -22,6 +22,7 @@ keccak-hash = "0.10.0" serde = { version = "1.0", features = ["derive"] } ethbloom = "0.13.0" ethereum-types = "0.14.1" +serde_json = "1.0.96" statrs = "0.16.0" # Trie dependencies diff --git a/lib/ain-evm/src/backend.rs b/lib/ain-evm/src/backend.rs index b7b0e10962..593da0b26b 100644 --- a/lib/ain-evm/src/backend.rs +++ b/lib/ain-evm/src/backend.rs @@ -9,10 +9,10 @@ use sp_core::Blake2Hasher; use vsdb_trie_db::MptOnce; use crate::{ - evm::TrieDBStore, storage::{traits::BlockStorage, Storage}, traits::BridgeBackend, transaction::SignedTx, + trie::TrieDBStore, }; type Hasher = Blake2Hasher; diff --git a/lib/ain-evm/src/block.rs b/lib/ain-evm/src/block.rs index 4d8615c882..c465c559cc 100644 --- a/lib/ain-evm/src/block.rs +++ b/lib/ain-evm/src/block.rs @@ -1,12 +1,16 @@ -use ethereum::{BlockAny, TransactionAny}; +use ethereum::{Block, BlockAny, PartialHeader, TransactionAny}; use keccak_hash::H256; use log::debug; use primitive_types::U256; + use statrs::statistics::{Data, OrderStatistics}; use std::cmp::{max, Ordering}; -use std::sync::Arc; +use std::{fs, io::BufReader, path::PathBuf, sync::Arc}; -use crate::storage::{traits::BlockStorage, Storage}; +use crate::{ + genesis::GenesisData, + storage::{traits::BlockStorage, Storage}, +}; pub struct BlockHandler { storage: Arc, @@ -259,3 +263,29 @@ impl BlockHandler { base_fee + priority_fee } } + +pub fn new_block_from_json(path: PathBuf, state_root: H256) -> Result { + let file = fs::File::open(path)?; + let reader = BufReader::new(file); + let genesis: GenesisData = serde_json::from_reader(reader)?; + + Ok(Block::new( + PartialHeader { + state_root, + beneficiary: genesis.coinbase, + timestamp: genesis.timestamp, + difficulty: genesis.difficulty, + extra_data: genesis.extra_data, + number: U256::zero(), + parent_hash: Default::default(), + receipts_root: Default::default(), + logs_bloom: Default::default(), + gas_limit: Default::default(), + gas_used: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + }, + Vec::new(), + Vec::new(), + )) +} diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 9333c305af..1054b11aac 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -1,9 +1,10 @@ use crate::backend::{EVMBackend, EVMBackendError, InsufficientBalance, Vicinity}; use crate::executor::TxResponse; use crate::fee::calculate_prepay_gas; -use crate::storage::traits::{BlockStorage, PersistentState, PersistentStateError}; +use crate::storage::traits::{BlockStorage, PersistentStateError}; use crate::storage::Storage; use crate::transaction::bridge::{BalanceUpdate, BridgeTx}; +use crate::trie::TrieDBStore; use crate::tx_queue::{QueueError, QueueTx, TransactionQueueMap}; use crate::{ executor::AinExecutor, @@ -11,20 +12,15 @@ use crate::{ transaction::SignedTx, }; use anyhow::anyhow; -use ethereum::{AccessList, Account, Log, TransactionV2}; -use ethereum_types::{Bloom, BloomInput}; +use ethereum::{AccessList, Account, Block, Log, PartialHeader, TransactionV2}; +use ethereum_types::{Bloom, BloomInput, H160, U256}; use hex::FromHex; use log::debug; -use primitive_types::{H160, H256, U256}; -use serde::{Deserialize, Serialize}; use std::error::Error; use std::path::PathBuf; use std::sync::Arc; use vsdb_core::vsdb_set_base_dir; -use vsdb_trie_db::MptStore; - -pub static TRIE_DB_STORE: &str = "trie_db_store.bin"; pub type NativeTxHash = [u8; 32]; @@ -34,34 +30,6 @@ pub struct EVMHandler { storage: Arc, } -#[derive(Serialize, Deserialize)] -pub struct TrieDBStore { - pub trie_db: MptStore, -} - -impl Default for TrieDBStore { - fn default() -> Self { - Self::new() - } -} - -impl TrieDBStore { - pub fn new() -> Self { - debug!("Creating new trie store"); - let trie_store = MptStore::new(); - let mut trie = trie_store - .trie_create(&[0], None, false) - .expect("Error creating initial backend"); - let state_root: H256 = trie.commit().into(); - debug!("Initial state_root : {:#x}", state_root); - Self { - trie_db: trie_store, - } - } -} - -impl PersistentState for TrieDBStore {} - fn init_vsdb() { debug!(target: "vsdb", "Initializating VSDB"); let datadir = ain_cpp_imports::get_datadir().expect("Could not get imported datadir"); @@ -75,20 +43,56 @@ fn init_vsdb() { } impl EVMHandler { - pub fn new(storage: Arc) -> Self { + pub fn restore(storage: Arc) -> Self { init_vsdb(); Self { tx_queues: Arc::new(TransactionQueueMap::new()), - trie_store: Arc::new( - TrieDBStore::load_from_disk(TRIE_DB_STORE).expect("Error loading trie db store"), - ), + trie_store: Arc::new(TrieDBStore::restore()), storage, } } + pub fn new_from_json(storage: Arc, path: PathBuf) -> Self { + debug!("Loading genesis state from {}", path.display()); + init_vsdb(); + + let handler = Self { + tx_queues: Arc::new(TransactionQueueMap::new()), + trie_store: Arc::new(TrieDBStore::new()), + storage: Arc::clone(&storage), + }; + let state_root = + TrieDBStore::genesis_state_root_from_json(&handler.trie_store, &handler.storage, path) + .expect("Error getting genesis state root from json"); + + let block: Block = Block::new( + PartialHeader { + state_root, + number: U256::zero(), + parent_hash: Default::default(), + beneficiary: Default::default(), + receipts_root: Default::default(), + logs_bloom: Default::default(), + difficulty: Default::default(), + gas_limit: Default::default(), + gas_used: Default::default(), + timestamp: Default::default(), + extra_data: Default::default(), + mix_hash: Default::default(), + nonce: Default::default(), + }, + Vec::new(), + Vec::new(), + ); + storage.put_latest_block(Some(&block)); + storage.put_block(&block); + + handler + } + pub fn flush(&self) -> Result<(), PersistentStateError> { - self.trie_store.save_to_disk(TRIE_DB_STORE) + self.trie_store.flush() } pub fn call( diff --git a/lib/ain-evm/src/genesis.rs b/lib/ain-evm/src/genesis.rs new file mode 100644 index 0000000000..36693ce997 --- /dev/null +++ b/lib/ain-evm/src/genesis.rs @@ -0,0 +1,33 @@ +use ethereum_types::{H160, H256, H64, U256}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +#[derive(Debug, Serialize, Deserialize)] +struct Config { + chain_id: u32, + homestead_block: u32, + eip150_block: u32, + eip150_hash: String, + eip155_block: u32, + eip158_block: u32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Alloc { + pub balance: U256, + pub code: Option>, + pub storage: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GenesisData { + // config: Config, + pub coinbase: H160, + pub difficulty: U256, + pub extra_data: Vec, + pub gas_limit: U256, + pub nonce: H64, + pub timestamp: u64, + pub alloc: HashMap, +} diff --git a/lib/ain-evm/src/handler.rs b/lib/ain-evm/src/handler.rs index a8f0165217..0bed2d4c3f 100644 --- a/lib/ain-evm/src/handler.rs +++ b/lib/ain-evm/src/handler.rs @@ -7,18 +7,18 @@ use crate::storage::traits::BlockStorage; use crate::storage::Storage; use crate::traits::Executor; use crate::transaction::bridge::{BalanceUpdate, BridgeTx}; +use crate::trie::GENESIS_STATE_ROOT; use crate::tx_queue::QueueTx; +use anyhow::anyhow; use ethereum::{Block, PartialHeader, ReceiptV3}; use ethereum_types::{Bloom, H160, H64, U256}; use log::debug; use primitive_types::H256; use std::error::Error; +use std::path::PathBuf; use std::sync::Arc; -const GENESIS_STATE_ROOT: &str = - "0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"; - pub struct Handlers { pub evm: EVMHandler, pub block: BlockHandler, @@ -26,20 +26,44 @@ pub struct Handlers { pub storage: Arc, } -impl Default for Handlers { - fn default() -> Self { - Self::new() - } -} - impl Handlers { - pub fn new() -> Self { - let storage = Arc::new(Storage::new()); - Self { - evm: EVMHandler::new(Arc::clone(&storage)), - block: BlockHandler::new(Arc::clone(&storage)), - receipt: ReceiptHandler::new(Arc::clone(&storage)), - storage, + /// Constructs a new Handlers instance. Depending on whether the defid -ethstartstate flag is set, + /// it either revives the storage from a previously saved state or initializes new storage using input from a JSON file. + /// This JSON-based initialization is exclusively reserved for regtest environments. + /// + /// # Warning + /// + /// Loading state from JSON will overwrite previous stored state + /// + /// # Errors + /// + /// This method will return an error if an attempt is made to load a genesis state from a JSON file outside of a regtest environment. + /// + /// # Return + /// + /// Returns an instance of the struct, either restored from storage or created from a JSON file. + pub fn new() -> Result { + if let Some(path) = ain_cpp_imports::get_state_input_json() { + if ain_cpp_imports::get_network() != "regtest" { + return Err(anyhow!( + "Loading a genesis from JSON file is restricted to regtest network" + )); + } + let storage = Arc::new(Storage::new()); + Ok(Self { + evm: EVMHandler::new_from_json(Arc::clone(&storage), PathBuf::from(path)), + block: BlockHandler::new(Arc::clone(&storage)), + receipt: ReceiptHandler::new(Arc::clone(&storage)), + storage, + }) + } else { + let storage = Arc::new(Storage::restore()); + Ok(Self { + evm: EVMHandler::restore(Arc::clone(&storage)), + block: BlockHandler::new(Arc::clone(&storage)), + receipt: ReceiptHandler::new(Arc::clone(&storage)), + storage, + }) } } diff --git a/lib/ain-evm/src/lib.rs b/lib/ain-evm/src/lib.rs index e7fab9f3aa..1a59caaa68 100644 --- a/lib/ain-evm/src/lib.rs +++ b/lib/ain-evm/src/lib.rs @@ -4,10 +4,12 @@ mod ecrecover; pub mod evm; pub mod executor; mod fee; +mod genesis; pub mod handler; pub mod receipt; pub mod runtime; pub mod storage; pub mod traits; pub mod transaction; +mod trie; mod tx_queue; diff --git a/lib/ain-evm/src/runtime.rs b/lib/ain-evm/src/runtime.rs index f7091236e0..16b252cb85 100644 --- a/lib/ain-evm/src/runtime.rs +++ b/lib/ain-evm/src/runtime.rs @@ -41,7 +41,7 @@ impl Runtime { }); }))), jrpc_handle: Mutex::new(None), - handlers: Arc::new(Handlers::new()), + handlers: Arc::new(Handlers::new().expect("Error initializating handlers")), } } diff --git a/lib/ain-evm/src/storage/data_handler.rs b/lib/ain-evm/src/storage/data_handler.rs index f4f2ba2cff..181a40b6f4 100644 --- a/lib/ain-evm/src/storage/data_handler.rs +++ b/lib/ain-evm/src/storage/data_handler.rs @@ -35,7 +35,7 @@ impl PersistentState for LatestBlockNumber {} impl PersistentState for TransactionHashToReceipt {} impl PersistentState for TxHashToTx {} -#[derive(Debug)] +#[derive(Debug, Default)] pub struct BlockchainDataHandler { // Improvements: Add transaction_map behind feature flag -txindex or equivalent transactions: RwLock, @@ -51,7 +51,7 @@ pub struct BlockchainDataHandler { } impl BlockchainDataHandler { - pub fn new() -> Self { + pub fn restore() -> Self { BlockchainDataHandler { transactions: RwLock::new( TxHashToTx::load_from_disk(TRANSACTION_DATA_PATH) diff --git a/lib/ain-evm/src/storage/mod.rs b/lib/ain-evm/src/storage/mod.rs index 4bec3b79ed..622527f001 100644 --- a/lib/ain-evm/src/storage/mod.rs +++ b/lib/ain-evm/src/storage/mod.rs @@ -33,7 +33,14 @@ impl Storage { pub fn new() -> Self { Self { cache: Cache::new(None), - blockchain_data_handler: BlockchainDataHandler::new(), + blockchain_data_handler: BlockchainDataHandler::default(), + } + } + + pub fn restore() -> Self { + Self { + cache: Cache::new(None), + blockchain_data_handler: BlockchainDataHandler::restore(), } } } diff --git a/lib/ain-evm/src/trie.rs b/lib/ain-evm/src/trie.rs new file mode 100644 index 0000000000..23f18f9ffd --- /dev/null +++ b/lib/ain-evm/src/trie.rs @@ -0,0 +1,102 @@ +use crate::backend::{EVMBackend, Vicinity}; +use crate::genesis::GenesisData; +use crate::storage::traits::{PersistentState, PersistentStateError}; +use crate::storage::Storage; + +use evm::backend::{Backend, Basic}; +use log::debug; +use primitive_types::H256; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::io::BufReader; +use std::path::PathBuf; +use std::sync::Arc; +use vsdb_trie_db::MptStore; + +pub static TRIE_DB_STORE: &str = "trie_db_store.bin"; +pub static GENESIS_STATE_ROOT: &str = + "0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a"; + +#[derive(Serialize, Deserialize)] +pub struct TrieDBStore { + pub trie_db: MptStore, +} + +impl PersistentState for TrieDBStore {} + +impl Default for TrieDBStore { + fn default() -> Self { + Self::new() + } +} + +impl TrieDBStore { + pub fn new() -> Self { + debug!("Creating new trie store"); + let trie_store = MptStore::new(); + let mut trie = trie_store + .trie_create(&[0], None, false) + .expect("Error creating initial backend"); + let state_root: H256 = trie.commit().into(); + debug!("Initial state_root : {:#x}", state_root); + Self { + trie_db: trie_store, + } + } + + pub fn restore() -> Self { + TrieDBStore::load_from_disk(TRIE_DB_STORE).expect("Error loading trie db store") + } + + /// # Warning + /// + /// This function should only be used in a regtest environment. + /// Can conflict with existing chain state if used on another network + pub fn genesis_state_root_from_json( + trie_store: &Arc, + storage: &Arc, + json_file: PathBuf, + ) -> Result { + let state_root: H256 = GENESIS_STATE_ROOT.parse().unwrap(); + + let mut backend = EVMBackend::from_root( + state_root, + Arc::clone(trie_store), + Arc::clone(storage), + Vicinity::default(), + ) + .expect("Could not restore backend"); + + let file = fs::File::open(json_file)?; + let reader = BufReader::new(file); + let genesis: GenesisData = serde_json::from_reader(reader)?; + + for (address, data) in genesis.alloc { + debug!("Setting data {:#?} for address {:x?}", data, address); + let basic = backend.basic(address); + + let new_basic = Basic { + balance: data.balance, + ..basic + }; + backend + .apply( + address, + new_basic, + data.code, + data.storage.unwrap_or_default(), + false, + ) + .expect("Could not set account data"); + backend.commit(); + } + + let state_root: H256 = backend.commit().into(); + debug!("Loaded genesis state_root : {:#x}", state_root); + Ok(state_root) + } + + pub fn flush(&self) -> Result<(), PersistentStateError> { + self.save_to_disk(TRIE_DB_STORE) + } +} diff --git a/src/ffi/ffiexports.cpp b/src/ffi/ffiexports.cpp index 994bdd0dd7..dd1deecda4 100644 --- a/src/ffi/ffiexports.cpp +++ b/src/ffi/ffiexports.cpp @@ -78,6 +78,10 @@ rust::string getDatadir() { #endif } +rust::string getNetwork() { + return Params().NetworkIDString(); +} + uint32_t getDifficulty(std::array blockHash) { uint256 hash{}; std::copy(blockHash.begin(), blockHash.end(), hash.begin()); @@ -193,3 +197,7 @@ std::array getEthPrivKey(std::array keyID) { } return {}; } + +rust::string getStateInputJSON() { + return gArgs.GetArg("-ethstartstate", ""); +} diff --git a/src/ffi/ffiexports.h b/src/ffi/ffiexports.h index 20e718d5da..1d3a29f038 100644 --- a/src/ffi/ffiexports.h +++ b/src/ffi/ffiexports.h @@ -9,11 +9,13 @@ bool isMining(); rust::string publishEthTransaction(rust::Vec rawTransaction); rust::vec getAccounts(); rust::string getDatadir(); +rust::string getNetwork(); uint32_t getDifficulty(std::array blockHash); std::array getChainWork(std::array blockHash); rust::vec getPoolTransactions(); uint64_t getNativeTxSize(rust::Vec rawTransaction); uint64_t getMinRelayTxFee(); std::array getEthPrivKey(std::array keyID); +rust::string getStateInputJSON(); #endif // DEFI_FFI_FFIEXPORTS_H diff --git a/src/init.cpp b/src/init.cpp index 5190942012..950b192a79 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -517,6 +517,7 @@ void SetupServerArgs() gArgs.AddArg("-dexstats", strprintf("Enable storing live dex data in DB (default: %u)", DEFAULT_DEXSTATS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-blocktimeordering", strprintf("(Deprecated) Whether to order transactions by time, otherwise ordered by fee (default: %u)", false), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-txordering", strprintf("Whether to order transactions by entry time, fee or both randomly (0: mixed, 1: fee based, 2: entry time) (default: %u)", DEFAULT_TX_ORDERING), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-ethstartstate", strprintf("Initialise Ethereum state trie using JSON input"), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #ifdef USE_UPNP #if USE_UPNP gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1555,8 +1556,8 @@ bool AppInitMain(InitInterfaces& interfaces) // ********************************************************* Step 4b: application initialization - /* Start the ETH RPC and gRPC servers. Current API only allows for one ETH - * RPC/gRPC server to bind to one address. By default, we will only take + /* Start the ETH RPC and gRPC servers. Current API only allows for one ETH + * RPC/gRPC server to bind to one address. By default, we will only take * the first address, if multiple addresses are specified. */ int eth_rpc_port = gArgs.GetArg("-ethrpcport", BaseParams().ETHRPCPort());