diff --git a/Cargo.lock b/Cargo.lock index d217d4e565..cfc34fb2ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,6 +199,7 @@ dependencies = [ "codechain-network 0.1.0", "codechain-reactor 0.1.0", "codechain-rpc 0.1.0", + "codechain-state 0.1.0", "codechain-stratum 1.11.0", "codechain-sync 0.1.0", "codechain-types 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index a602defdba..d780ddca90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ codechain-merkle = { path = "util/merkle" } codechain-network = { path = "network" } codechain-reactor = { path = "util/reactor" } codechain-rpc = { path = "rpc" } +codechain-state = { path = "state" } codechain-sync = { path = "sync" } codechain-types = { path = "types" } codechain-vm = { path = "vm" } diff --git a/codechain/config/chain_type.rs b/codechain/config/chain_type.rs index ec10613446..d7f8c9f2f7 100644 --- a/codechain/config/chain_type.rs +++ b/codechain/config/chain_type.rs @@ -15,9 +15,11 @@ // along with this program. If not, see . use std::str::FromStr; +use std::sync::Arc; use std::{fmt, fs}; use ccore::Spec; +use cstate::ActionHandler; #[derive(Debug, PartialEq, Deserialize)] #[serde(rename_all = "snake_case")] @@ -66,17 +68,17 @@ impl fmt::Display for ChainType { } impl ChainType { - pub fn spec<'a>(&self) -> Result { + pub fn spec<'a>(&self, handlers: Vec>) -> Result { match self { - ChainType::Solo => Ok(Spec::new_test_solo()), - ChainType::SoloAuthority => Ok(Spec::new_test_solo_authority()), - ChainType::Tendermint => Ok(Spec::new_test_tendermint()), - ChainType::Cuckoo => Ok(Spec::new_test_cuckoo()), - ChainType::BlakePoW => Ok(Spec::new_test_blake_pow()), + ChainType::Solo => Ok(Spec::new_test_solo(handlers)), + ChainType::SoloAuthority => Ok(Spec::new_test_solo_authority(handlers)), + ChainType::Tendermint => Ok(Spec::new_test_tendermint(handlers)), + ChainType::Cuckoo => Ok(Spec::new_test_cuckoo(handlers)), + ChainType::BlakePoW => Ok(Spec::new_test_blake_pow(handlers)), ChainType::Custom(filename) => { let file = fs::File::open(filename) .map_err(|e| format!("Could not load specification file at {}: {}", filename, e))?; - Spec::load(file) + Spec::load(file, handlers) } } } diff --git a/codechain/main.rs b/codechain/main.rs index f48f94d6b1..5d76db641d 100644 --- a/codechain/main.rs +++ b/codechain/main.rs @@ -35,6 +35,7 @@ extern crate codechain_logger as clogger; extern crate codechain_network as cnetwork; extern crate codechain_reactor as creactor; extern crate codechain_rpc as crpc; +extern crate codechain_state as cstate; extern crate codechain_sync as csync; extern crate codechain_types as ctypes; extern crate ctrlc; @@ -69,6 +70,7 @@ use clap::ArgMatches; use clogger::LoggerConfig; use cnetwork::{NetworkConfig, NetworkControl, NetworkControlError, NetworkService, SocketAddr}; use creactor::EventLoop; +use cstate::{ActionHandler, HitHandler}; use csync::{BlockSyncExtension, ParcelSyncExtension, SnapshotService}; use ctrlc::CtrlC; use fdlimit::raise_fd_limit; @@ -263,7 +265,10 @@ fn run_node(matches: ArgMatches) -> Result<(), String> { let _event_loop = EventLoop::spawn(); let config = load_config(&matches)?; - let spec = config.operating.chain.spec()?; + + // Add handlers here to accept additional custom actions + let custom_action_handlers: Vec> = vec![Arc::new(HitHandler::new())]; + let spec = config.operating.chain.spec(custom_action_handlers)?; let instance_id = config.operating.instance_id.unwrap_or( SystemTime::now() diff --git a/core/src/block.rs b/core/src/block.rs index 68ffc3060e..57fb586ed7 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -453,7 +453,7 @@ mod tests { #[test] fn open_block() { - let spec = Spec::new_test(); + let spec = Spec::new_test(Vec::new()); let genesis_header = spec.genesis_header(); let db = spec.ensure_genesis_state(get_temp_state_db(), &Default::default()).unwrap(); let b = OpenBlock::new(&*spec.engine, Default::default(), db, &genesis_header, Address::zero(), vec![], false) diff --git a/core/src/client/client.rs b/core/src/client/client.rs index 4bdbcd23c7..127d1a66e8 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -22,7 +22,10 @@ use std::time::Instant; use cio::IoChannel; use ckey::{Address, Public}; use cnetwork::NodeId; -use cstate::{Asset, AssetAddress, AssetScheme, AssetSchemeAddress, StateDB, TopLevelState, TopStateInfo}; +use cstate::{ + ActionHandler, Asset, AssetAddress, AssetScheme, AssetSchemeAddress, StateDB, TopBackend, TopLevelState, + TopStateInfo, +}; use ctypes::invoice::ParcelInvoice; use ctypes::parcel::ChangeShard; use ctypes::transaction::Transaction; @@ -99,7 +102,7 @@ impl Client { let trie_factory = TrieFactory::new(trie_spec); let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::Archive, ::db::COL_STATE); - let mut state_db = StateDB::new(journal_db, config.state_cache_size); + let mut state_db = StateDB::new(journal_db, config.state_cache_size, spec.custom_handlers.clone()); if !spec.check_genesis_root(state_db.as_hashdb()) { return Err(SpecError::InvalidState.into()) } @@ -449,6 +452,10 @@ impl BlockChainClient for Client { }) }) } + + fn custom_handlers(&self) -> Vec> { + self.state_db.read().custom_handlers().to_vec() + } } pub struct Importer { diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 155cb73a2d..c96f4ff50c 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -31,7 +31,7 @@ use std::sync::Arc; use ckey::{Address, Public}; use cnetwork::NodeId; -use cstate::{Asset, AssetScheme, AssetSchemeAddress, TopStateInfo}; +use cstate::{ActionHandler, Asset, AssetScheme, AssetSchemeAddress, TopStateInfo}; use ctypes::invoice::{Invoice, ParcelInvoice}; use ctypes::parcel::ChangeShard; use ctypes::transaction::Transaction; @@ -209,6 +209,8 @@ pub trait BlockChainClient: Sync + Send + AccountData + BlockChain + ImportBlock fn parcel_invoice(&self, id: ParcelId) -> Option; fn transaction_invoice(&self, id: TransactionId) -> Option; + + fn custom_handlers(&self) -> Vec>; } /// Result of import block operation. diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index 7c6e3445f6..d34fff83f6 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -38,7 +38,7 @@ use std::sync::Arc; use ckey::{Address, Generator, Random}; use cmerkle::skewed_merkle_root; use cnetwork::NodeId; -use cstate::StateDB; +use cstate::{ActionHandler, StateDB}; use ctypes::invoice::ParcelInvoice; use ctypes::parcel::{Action, Parcel}; use ctypes::BlockNumber; @@ -114,7 +114,7 @@ impl TestBlockChainClient { /// Creates new test client with specified extra data for each block pub fn new_with_extra_data(extra_data: Bytes) -> Self { - let spec = Spec::new_test(); + let spec = Spec::new_test(Vec::new()); TestBlockChainClient::new_with_spec_and_extra(spec, extra_data) } @@ -288,7 +288,7 @@ impl TestBlockChainClient { pub fn get_temp_state_db() -> StateDB { let db = kvdb_memorydb::create(NUM_COLUMNS.unwrap_or(0)); let journal_db = journaldb::new(Arc::new(db), journaldb::Algorithm::Archive, COL_STATE); - StateDB::new(journal_db, 1024 * 1024) + StateDB::new(journal_db, 1024 * 1024, Vec::new()) } impl ReopenBlock for TestBlockChainClient { @@ -508,6 +508,10 @@ impl BlockChainClient for TestBlockChainClient { fn transaction_invoice(&self, _id: TransactionId) -> Option { unimplemented!() } + + fn custom_handlers(&self) -> Vec> { + unimplemented!() + } } impl super::EngineClient for TestBlockChainClient { diff --git a/core/src/consensus/solo/mod.rs b/core/src/consensus/solo/mod.rs index c2bd884572..724061ee3e 100644 --- a/core/src/consensus/solo/mod.rs +++ b/core/src/consensus/solo/mod.rs @@ -88,7 +88,7 @@ mod tests { #[test] fn solo_can_seal() { - let spec = Spec::new_test_solo(); + let spec = Spec::new_test_solo(Vec::new()); let engine = &*spec.engine; let db = spec.ensure_genesis_state(get_temp_state_db(), &Default::default()).unwrap(); let genesis_header = spec.genesis_header(); @@ -104,7 +104,7 @@ mod tests { #[test] fn solo_cant_verify() { - let engine = Spec::new_test_solo().engine; + let engine = Spec::new_test_solo(Vec::new()).engine; let mut header: Header = Header::default(); assert!(engine.verify_block_basic(&header).is_ok()); diff --git a/core/src/consensus/solo_authority/mod.rs b/core/src/consensus/solo_authority/mod.rs index f9c18d0d89..226a0b94b1 100644 --- a/core/src/consensus/solo_authority/mod.rs +++ b/core/src/consensus/solo_authority/mod.rs @@ -207,13 +207,13 @@ mod tests { #[test] fn has_valid_metadata() { - let engine = Spec::new_test_solo_authority().engine; + let engine = Spec::new_test_solo_authority(Vec::new()).engine; assert!(!engine.name().is_empty()); } #[test] fn can_do_signature_verification_fail() { - let engine = Spec::new_test_solo_authority().engine; + let engine = Spec::new_test_solo_authority(Vec::new()).engine; let mut header: Header = Header::default(); header.set_seal(vec![::rlp::encode(&SignatureData::default()).into_vec()]); @@ -223,7 +223,7 @@ mod tests { #[test] fn can_generate_seal() { - let spec = Spec::new_test_solo_authority(); + let spec = Spec::new_test_solo_authority(Vec::new()); let engine = &*spec.engine; let db = spec.ensure_genesis_state(get_temp_state_db(), &Default::default()).unwrap(); let genesis_header = spec.genesis_header(); @@ -239,7 +239,7 @@ mod tests { #[test] fn seals_internally() { - let engine = Spec::new_test_solo_authority().engine; + let engine = Spec::new_test_solo_authority(Vec::new()).engine; assert!(!engine.seals_internally().unwrap()); } } diff --git a/core/src/consensus/tendermint/mod.rs b/core/src/consensus/tendermint/mod.rs index 6d5e74ff78..e3f7989af2 100644 --- a/core/src/consensus/tendermint/mod.rs +++ b/core/src/consensus/tendermint/mod.rs @@ -953,13 +953,13 @@ mod tests { #[test] fn has_valid_metadata() { - let engine = Spec::new_test_tendermint().engine; + let engine = Spec::new_test_tendermint(Vec::new()).engine; assert!(!engine.name().is_empty()); } #[test] fn verification_fails_on_short_seal() { - let engine = Spec::new_test_tendermint().engine; + let engine = Spec::new_test_tendermint(Vec::new()).engine; let header = Header::default(); let verify_result = engine.verify_block_basic(&header); diff --git a/core/src/miner/sealing_queue.rs b/core/src/miner/sealing_queue.rs index a8be90562b..570b4c0fb5 100644 --- a/core/src/miner/sealing_queue.rs +++ b/core/src/miner/sealing_queue.rs @@ -82,7 +82,7 @@ mod tests { const QUEUE_SIZE: usize = 2; fn create_closed_block(address: Address) -> ClosedBlock { - let spec = Spec::new_test(); + let spec = Spec::new_test(Vec::new()); let genesis_header = spec.genesis_header(); let db = spec.ensure_genesis_state(get_temp_state_db(), &Default::default()).unwrap(); let b = OpenBlock::new(&*spec.engine, Default::default(), db, &genesis_header, address, vec![], false).unwrap(); diff --git a/core/src/spec/spec.rs b/core/src/spec/spec.rs index cde1bf0d5c..8cef8b00c1 100644 --- a/core/src/spec/spec.rs +++ b/core/src/spec/spec.rs @@ -20,7 +20,9 @@ use std::sync::Arc; use ccrypto::{blake256, BLAKE_NULL_RLP}; use cjson; use ckey::Address; -use cstate::{Backend, Metadata, MetadataAddress, Shard, ShardAddress, ShardMetadataAddress, StateDB, StateResult}; +use cstate::{ + ActionHandler, Backend, Metadata, MetadataAddress, Shard, ShardAddress, ShardMetadataAddress, StateDB, StateResult, +}; use ctypes::ShardId; use hashdb::HashDB; use parking_lot::RwLock; @@ -103,6 +105,8 @@ pub struct Spec { /// Genesis state as plain old data. genesis_accounts: PodAccounts, genesis_shards: PodShards, + + pub custom_handlers: Vec>, } // helper for formatting errors. @@ -111,8 +115,8 @@ fn fmt_err(f: F) -> String { } macro_rules! load_bundled { - ($e:expr) => { - Spec::load(include_bytes!(concat!("../../res/", $e, ".json")) as &[u8]).expect(concat!( + ($e:expr, $h:expr) => { + Spec::load(include_bytes!(concat!("../../res/", $e, ".json")) as &[u8], $h).expect(concat!( "Chain spec ", $e, " is invalid." @@ -147,6 +151,7 @@ impl Spec { let root = BLAKE_NULL_RLP; let (db, root) = self.initialize_accounts(trie_factory, db, root)?; let (db, root) = self.initialize_shards(trie_factory, db, root)?; + let (db, root) = self.initialize_custom_actions(trie_factory, db, root)?; *self.state_root_memo.write() = root; Ok(db) @@ -229,6 +234,24 @@ impl Spec { Ok((db, root)) } + fn initialize_custom_actions( + &self, + trie_factory: &TrieFactory, + mut db: DB, + mut root: H256, + ) -> StateResult<(DB, H256)> { + // basic accounts in spec. + { + let mut t = trie_factory.from_existing(db.as_hashdb_mut(), &mut root)?; + + for handler in &self.custom_handlers { + handler.init(t.as_mut())?; + } + } + + Ok((db, root)) + } + pub fn check_genesis_root(&self, db: &HashDB) -> bool { if db.keys().is_empty() { return true @@ -273,43 +296,43 @@ impl Spec { /// Loads spec from json file. Provide factories for executing contracts and ensuring /// storage goes to the right place. - pub fn load<'a, R>(reader: R) -> Result + pub fn load<'a, R>(reader: R, handlers: Vec>) -> Result where R: Read, { - cjson::spec::Spec::load(reader).map_err(fmt_err).and_then(|x| load_from(x).map_err(fmt_err)) + cjson::spec::Spec::load(reader).map_err(fmt_err).and_then(|x| load_from(x, handlers).map_err(fmt_err)) } /// Create a new test Spec. - pub fn new_test() -> Self { - load_bundled!("null") + pub fn new_test(handlers: Vec>) -> Self { + load_bundled!("null", handlers) } /// Create a new Spec with Solo consensus which does internal sealing (not requiring /// work). - pub fn new_test_solo() -> Self { - load_bundled!("solo") + pub fn new_test_solo(handlers: Vec>) -> Self { + load_bundled!("solo", handlers) } /// Create a new Spec with SoloAuthority consensus which does internal sealing (not requiring /// work). - pub fn new_test_solo_authority() -> Self { - load_bundled!("solo_authority") + pub fn new_test_solo_authority(handlers: Vec>) -> Self { + load_bundled!("solo_authority", handlers) } /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring /// work). - pub fn new_test_tendermint() -> Self { - load_bundled!("tendermint") + pub fn new_test_tendermint(handlers: Vec>) -> Self { + load_bundled!("tendermint", handlers) } /// Create a new Spec with Cuckoo PoW consensus. - pub fn new_test_cuckoo() -> Self { - load_bundled!("cuckoo") + pub fn new_test_cuckoo(handlers: Vec>) -> Self { + load_bundled!("cuckoo", handlers) } /// Create a new Spec with Blake PoW consensus. - pub fn new_test_blake_pow() -> Self { - load_bundled!("blake_pow") + pub fn new_test_blake_pow(handlers: Vec>) -> Self { + load_bundled!("blake_pow", handlers) } /// Get common blockchain parameters. @@ -350,7 +373,7 @@ impl Spec { } /// Load from JSON object. -fn load_from(s: cjson::spec::Spec) -> Result { +fn load_from(s: cjson::spec::Spec, handlers: Vec>) -> Result { let g = Genesis::from(s.genesis); let GenericSeal(seal_rlp) = g.seal.into(); let params = CommonParams::from(s.params); @@ -371,13 +394,15 @@ fn load_from(s: cjson::spec::Spec) -> Result { state_root_memo: RwLock::new(Default::default()), // will be overwritten right after. genesis_accounts: s.accounts.into(), genesis_shards: s.shards.into(), + + custom_handlers: handlers, }; // use memoized state root if provided. match g.state_root { Some(root) => *s.state_root_memo.get_mut() = root, None => { - let db = StateDB::new_with_memorydb(0); + let db = StateDB::new_with_memorydb(0, s.custom_handlers.clone()); let trie_factory = TrieFactory::new(Default::default()); let _ = s.initialize_state(&trie_factory, db)?; } @@ -394,7 +419,7 @@ mod tests { #[test] fn extra_data_of_genesis_header_is_hash_of_common_params() { - let spec = Spec::new_test(); + let spec = Spec::new_test(Vec::new()); let common_params = spec.params(); let hash_of_common_params = H256::blake(&common_params.rlp_bytes()).to_vec(); diff --git a/core/src/tests/helpers.rs b/core/src/tests/helpers.rs index aa0228c32a..3228d5521d 100644 --- a/core/src/tests/helpers.rs +++ b/core/src/tests/helpers.rs @@ -48,7 +48,7 @@ pub fn get_good_dummy_block() -> Bytes { pub fn get_good_dummy_block_hash() -> (H256, Bytes) { let mut block_header = Header::new(); - let test_spec = Spec::new_test(); + let test_spec = Spec::new_test(Vec::new()); block_header.set_score(U256::from(0x20000)); block_header.set_timestamp(40); block_header.set_number(1); @@ -58,5 +58,5 @@ pub fn get_good_dummy_block_hash() -> (H256, Bytes) { } pub fn get_temp_state_db() -> StateDB { - StateDB::new_with_memorydb(5 * 1024 * 1024) + StateDB::new_with_memorydb(5 * 1024 * 1024, Vec::new()) } diff --git a/core/src/verification/queue/mod.rs b/core/src/verification/queue/mod.rs index c37c480d54..b2e7661ebb 100644 --- a/core/src/verification/queue/mod.rs +++ b/core/src/verification/queue/mod.rs @@ -514,7 +514,7 @@ mod tests { // create a test block queue. // auto_scaling enables verifier adjustment. fn get_test_queue() -> BlockQueue { - let spec = Spec::new_test(); + let spec = Spec::new_test(Vec::new()); let engine = spec.engine; let config = Config::default(); @@ -524,7 +524,7 @@ mod tests { #[test] fn can_be_created() { // TODO better test - let spec = Spec::new_test(); + let spec = Spec::new_test(Vec::new()); let engine = spec.engine; let config = Config::default(); diff --git a/rpc/src/v1/impls/chain.rs b/rpc/src/v1/impls/chain.rs index 773c5bb518..d27804fa8a 100644 --- a/rpc/src/v1/impls/chain.rs +++ b/rpc/src/v1/impls/chain.rs @@ -18,15 +18,16 @@ use std::sync::Arc; use ccore::{ AssetClient, BlockId, ExecuteClient, MinerService, MiningBlockChainClient, RegularKey, Shard, SignedParcel, + UnverifiedParcel, }; use ckey::{Address, Public}; use cstate::{Asset, AssetScheme, AssetSchemeAddress}; use ctypes::invoice::{Invoice, ParcelInvoice}; -use ctypes::parcel::ChangeShard; +use ctypes::parcel::{Action, ChangeShard}; use ctypes::transaction::Transaction; use ctypes::{BlockNumber, ShardId}; use primitives::{H160, H256, U256}; -use rlp::UntrustedRlp; +use rlp::{DecoderError, UntrustedRlp}; use jsonrpc_core::Result; @@ -64,6 +65,17 @@ where UntrustedRlp::new(&raw.into_vec()) .as_val() .map_err(errors::rlp) + .and_then(|parcel: UnverifiedParcel| { + match &parcel.as_unsigned().action { + Action::Custom(bytes) => { + if !self.client.custom_handlers().iter().any(|c| c.is_target(bytes)) { + return Err(errors::rlp(DecoderError::Custom("Invalid custom action!"))) + } + } + _ => {} + } + Ok(parcel) + }) .and_then(|parcel| SignedParcel::new(parcel).map_err(errors::parcel_core)) .and_then(|signed| { let hash = signed.hash(); diff --git a/state/src/action_handler/hit.rs b/state/src/action_handler/hit.rs new file mode 100644 index 0000000000..3eeb02a5a4 --- /dev/null +++ b/state/src/action_handler/hit.rs @@ -0,0 +1,86 @@ +// Copyright 2018 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use ccrypto::blake256; +use ctypes::invoice::Invoice; +use ctypes::parcel::Outcome; +use primitives::{Bytes, H256}; +use rlp::{self, Decodable, DecoderError, Encodable, UntrustedRlp}; +use trie::TrieMut; + +use super::super::{StateResult, TopLevelState, TopState, TopStateInfo}; +use super::ActionHandler; + +const ACTION_ID: u8 = 0; + +pub struct HitAction { + increase: u8, +} + +impl Decodable for HitAction { + fn decode(rlp: &UntrustedRlp) -> Result { + if rlp.item_count()? != 2 { + return Err(DecoderError::RlpIncorrectListLen) + } + if rlp.val_at::(0)? != ACTION_ID { + return Err(DecoderError::Custom("Unknown message id detected")) + } + Ok(Self { + increase: rlp.val_at(1)?, + }) + } +} + +#[derive(Clone)] +pub struct HitHandler {} + +impl HitHandler { + pub fn new() -> Self { + Self {} + } + + fn address(&self) -> H256 { + let mut hash: H256 = blake256(&b"metadata hit"); + hash[0] = b'M'; + hash + } +} + +impl ActionHandler for HitHandler { + fn init(&self, state: &mut TrieMut) -> StateResult<()> { + let r = state.insert(&self.address(), &1u32.rlp_bytes()); + debug_assert_eq!(Ok(None), r); + r?; + Ok(()) + } + + fn is_target(&self, bytes: &Bytes) -> bool { + HitAction::decode(&UntrustedRlp::new(bytes)).is_ok() + } + + /// `bytes` must be valid encoding of HitAction + fn execute(&self, bytes: &Bytes, state: &mut TopLevelState) -> Option> { + HitAction::decode(&UntrustedRlp::new(bytes)).ok().map(|action| { + let prev_counter: u32 = rlp::decode(&state.action_data(&self.address())?); + let increase = action.increase as u32; + state.update_action_data(&self.address(), (prev_counter + increase).rlp_bytes().to_vec())?; + Ok(Outcome::Single { + invoice: Invoice::Success, + error: None, + }) + }) + } +} diff --git a/state/src/action_handler/mod.rs b/state/src/action_handler/mod.rs new file mode 100644 index 0000000000..a2cc60d3ba --- /dev/null +++ b/state/src/action_handler/mod.rs @@ -0,0 +1,31 @@ +// Copyright 2018 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +mod hit; + +use ctypes::parcel::Outcome; +use primitives::Bytes; +use trie::TrieMut; + +use super::{StateResult, TopLevelState}; + +pub trait ActionHandler: Send + Sync { + fn init(&self, state: &mut TrieMut) -> StateResult<()>; + fn is_target(&self, bytes: &Bytes) -> bool; + fn execute(&self, bytes: &Bytes, state: &mut TopLevelState) -> Option>; +} + +pub use self::hit::HitHandler; diff --git a/state/src/backend.rs b/state/src/backend.rs index 057f825686..a44f56a30d 100644 --- a/state/src/backend.rs +++ b/state/src/backend.rs @@ -37,11 +37,15 @@ //! should become general over time to the point where not even a //! merkle trie is strictly necessary. +use std::sync::Arc; + use ckey::Address; use hashdb::HashDB; +use primitives::{Bytes, H256}; use super::{ - Account, Asset, AssetAddress, AssetScheme, AssetSchemeAddress, Metadata, MetadataAddress, Shard, ShardAddress, + Account, ActionHandler, Asset, AssetAddress, AssetScheme, AssetSchemeAddress, Metadata, MetadataAddress, Shard, + ShardAddress, }; @@ -59,12 +63,14 @@ pub trait TopBackend: Send { fn add_to_account_cache(&mut self, addr: Address, data: Option, modified: bool); fn add_to_metadata_cache(&mut self, address: MetadataAddress, item: Option, modified: bool); fn add_to_shard_cache(&mut self, address: ShardAddress, item: Option, modified: bool); + fn add_to_action_data_cache(&mut self, address: H256, item: Option, modified: bool); /// Get basic copy of the cached account. Not required to include storage. /// Returns 'None' if cache is disabled or if the account is not cached. fn get_cached_account(&self, addr: &Address) -> Option>; fn get_cached_metadata(&self, addr: &MetadataAddress) -> Option>; fn get_cached_shard(&self, addr: &ShardAddress) -> Option>; + fn get_cached_action_data(&self, key: &H256) -> Option>; /// Get value from a cached account. /// `None` is passed to the closure if the account entry cached @@ -73,6 +79,8 @@ pub trait TopBackend: Send { fn get_cached_account_with(&self, a: &Address, f: F) -> Option where F: FnOnce(Option<&mut Account>) -> U; + + fn custom_handlers(&self) -> &[Arc]; } pub trait ShardBackend: Send { diff --git a/state/src/db.rs b/state/src/db.rs index a62f655cb7..25f9ef4b49 100644 --- a/state/src/db.rs +++ b/state/src/db.rs @@ -27,12 +27,12 @@ use kvdb::DBTransaction; use kvdb_memorydb; use lru_cache::LruCache; use parking_lot::Mutex; -use primitives::H256; +use primitives::{Bytes, H256}; use util_error::UtilError; use super::{ - Account, Asset, AssetAddress, AssetScheme, AssetSchemeAddress, Backend, CacheableItem, Metadata, MetadataAddress, - Shard, ShardAddress, ShardBackend, TopBackend, + Account, ActionHandler, Asset, AssetAddress, AssetScheme, AssetSchemeAddress, Backend, CacheableItem, Metadata, + MetadataAddress, Shard, ShardAddress, ShardBackend, TopBackend, }; const STATE_CACHE_BLOCKS: usize = 12; @@ -40,9 +40,10 @@ const STATE_CACHE_BLOCKS: usize = 12; // The percentage of supplied cache size to go to accounts. const ACCOUNT_CACHE_RATIO: usize = 40; const METADATA_CACHE_RATIO: usize = 1; -const SHARD_CACHE_RATIO: usize = 9; +const SHARD_CACHE_RATIO: usize = 8; const ASSET_SCHEME_CACHE_RATIO: usize = 10; const ASSET_CACHE_RATIO: usize = 40; +const ACTION_DATA_CACHE_RATIO: usize = 1; /// Shared canonical state cache. struct Cache @@ -108,6 +109,7 @@ pub struct StateDB { shard_cache: Arc>>, asset_scheme_cache: Arc>>, asset_cache: Arc>>, + action_data_cache: Arc>>, /// Local dirty cache. local_account_cache: Vec>, @@ -115,6 +117,7 @@ pub struct StateDB { local_shard_cache: Vec>, local_asset_scheme_cache: Vec>, local_asset_cache: Vec>, + local_action_data_cache: Vec>, /// Hash of the block on top of which this instance was created or /// `None` if cache is disabled parent_hash: Option, @@ -122,6 +125,8 @@ pub struct StateDB { commit_hash: Option, /// Number of the committing block or `None` if not committed yet. commit_number: Option, + + custom_handlers: Vec>, } impl StateDB { @@ -129,7 +134,7 @@ impl StateDB { /// of the LRU cache in bytes. Actual used memory may (read: will) be higher due to bookkeeping. // TODO: make the cache size actually accurate by moving the account storage cache // into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`. - pub fn new(db: Box, cache_size: usize) -> StateDB { + pub fn new(db: Box, cache_size: usize, custom_handlers: Vec>) -> StateDB { assert_eq!( 100, ACCOUNT_CACHE_RATIO @@ -137,6 +142,7 @@ impl StateDB { + SHARD_CACHE_RATIO + ASSET_SCHEME_CACHE_RATIO + ASSET_CACHE_RATIO + + ACTION_DATA_CACHE_RATIO ); let account_cache_size = cache_size * ACCOUNT_CACHE_RATIO / 100; @@ -154,6 +160,9 @@ impl StateDB { let asset_cache_size = cache_size * ASSET_CACHE_RATIO / 100; let asset_cache_items = asset_cache_size / ::std::mem::size_of::>(); + let action_data_cache_size = cache_size * ACTION_DATA_CACHE_RATIO / 100; + let action_data_cache_items = action_data_cache_size / ::std::mem::size_of::>(); + StateDB { db, account_cache: Arc::new(Mutex::new(Cache { @@ -176,21 +185,27 @@ impl StateDB { cache: LruCache::new(asset_cache_items), modifications: VecDeque::new(), })), + action_data_cache: Arc::new(Mutex::new(Cache { + cache: LruCache::new(action_data_cache_items), + modifications: VecDeque::new(), + })), local_account_cache: Vec::new(), local_metadata_cache: Vec::new(), local_shard_cache: Vec::new(), local_asset_scheme_cache: Vec::new(), local_asset_cache: Vec::new(), + local_action_data_cache: Vec::new(), parent_hash: None, commit_hash: None, commit_number: None, + custom_handlers, } } - pub fn new_with_memorydb(cache_size: usize) -> Self { + pub fn new_with_memorydb(cache_size: usize, custom_handlers: Vec>) -> Self { let memorydb = Arc::new(kvdb_memorydb::create(0)); - StateDB::new(journaldb::new(memorydb, Algorithm::Archive, None), cache_size) + StateDB::new(journaldb::new(memorydb, Algorithm::Archive, None), cache_size, custom_handlers) } /// Journal all recent operations under the given era and ID. @@ -271,6 +286,17 @@ impl StateDB { &self.commit_hash, &self.commit_number, ); + + Self::sync_cache_impl( + enacted, + retracted, + is_best, + &mut self.action_data_cache, + &mut self.local_action_data_cache, + &self.parent_hash, + &self.commit_hash, + &self.commit_number, + ); } fn sync_cache_impl( @@ -392,16 +418,19 @@ impl StateDB { shard_cache: self.shard_cache.clone(), asset_scheme_cache: self.asset_scheme_cache.clone(), asset_cache: self.asset_cache.clone(), + action_data_cache: self.action_data_cache.clone(), local_account_cache: Vec::new(), local_metadata_cache: Vec::new(), local_shard_cache: Vec::new(), local_asset_scheme_cache: Vec::new(), local_asset_cache: Vec::new(), + local_action_data_cache: Vec::new(), parent_hash: Some(parent.clone()), commit_hash: None, commit_number: None, + custom_handlers: self.custom_handlers.clone(), } } @@ -425,6 +454,7 @@ impl StateDB { + Self::mem_used_impl(&self.shard_cache.lock()) + Self::mem_used_impl(&self.asset_scheme_cache.lock()) + Self::mem_used_impl(&self.asset_cache.lock()) + + Self::mem_used_impl(&self.action_data_cache.lock()) } /// Returns underlying `JournalDB`. @@ -503,16 +533,20 @@ impl Clone for StateDB { shard_cache: self.shard_cache.clone(), asset_scheme_cache: self.asset_scheme_cache.clone(), asset_cache: self.asset_cache.clone(), + action_data_cache: self.action_data_cache.clone(), local_account_cache: Vec::new(), local_metadata_cache: Vec::new(), local_shard_cache: Vec::new(), local_asset_scheme_cache: Vec::new(), local_asset_cache: Vec::new(), + local_action_data_cache: Vec::new(), parent_hash: None, commit_hash: None, commit_number: None, + + custom_handlers: self.custom_handlers.clone(), } } } @@ -552,6 +586,14 @@ impl TopBackend for StateDB { }) } + fn add_to_action_data_cache(&mut self, address: H256, item: Option, modified: bool) { + self.local_action_data_cache.push(CacheQueueItem { + address, + item, + modified, + }) + } + fn get_cached_account(&self, addr: &Address) -> Option> { self.get_cached(addr, &self.account_cache) } @@ -564,11 +606,19 @@ impl TopBackend for StateDB { self.get_cached(addr, &self.shard_cache) } + fn get_cached_action_data(&self, key: &H256) -> Option> { + self.get_cached(key, &self.action_data_cache) + } + fn get_cached_account_with(&self, a: &Address, f: F) -> Option where F: FnOnce(Option<&mut Account>) -> U, { self.get_cached_with(a, f, &self.account_cache) } + + fn custom_handlers(&self) -> &[Arc] { + &self.custom_handlers + } } impl ShardBackend for StateDB { diff --git a/state/src/impls/top_level.rs b/state/src/impls/top_level.rs index 441d648c29..c2f13b145f 100644 --- a/state/src/impls/top_level.rs +++ b/state/src/impls/top_level.rs @@ -44,7 +44,8 @@ use ctypes::invoice::Invoice; use ctypes::parcel::{Action, ChangeShard, Error as ParcelError, Outcome as ParcelOutcome, Parcel}; use ctypes::transaction::{Error as TransactionError, Outcome as TransactionOutcome, Transaction}; use ctypes::ShardId; -use primitives::{H256, U256}; +use primitives::{Bytes, H256, U256}; +use rlp::NULL_RLP; use trie::{Result as TrieResult, Trie, TrieError, TrieFactory}; use unexpected::Mismatch; @@ -108,6 +109,7 @@ pub struct TopLevelState { account: Cache, metadata: Cache, shard: Cache, + action_data: Cache, id_of_checkpoints: Vec, trie_factory: TrieFactory, } @@ -161,6 +163,11 @@ impl TopStateInfo for TopLevelState { ShardLevelState::from_existing(shard_id, self.db.clone(), shard_root, self.trie_factory)?; shard_level_state.asset(asset_address) } + + fn action_data(&self, key: &H256) -> TrieResult { + let action_data = self.require_action_data(key)?; + Ok(action_data.clone()) + } } const PARCEL_FEE_CHECKPOINT: CheckpointId = 123; @@ -172,6 +179,7 @@ impl StateWithCheckpoint for TopLevelState { self.account.checkpoint(); self.metadata.checkpoint(); self.shard.checkpoint(); + self.action_data.checkpoint(); } fn discard_checkpoint(&mut self, id: CheckpointId) { @@ -181,6 +189,7 @@ impl StateWithCheckpoint for TopLevelState { self.account.discard_checkpoint(); self.metadata.discard_checkpoint(); self.shard.discard_checkpoint(); + self.action_data.discard_checkpoint(); } fn revert_to_checkpoint(&mut self, id: CheckpointId) { @@ -190,6 +199,7 @@ impl StateWithCheckpoint for TopLevelState { self.account.revert_to_checkpoint(); self.metadata.revert_to_checkpoint(); self.shard.revert_to_checkpoint(); + self.action_data.revert_to_checkpoint(); } } @@ -199,6 +209,7 @@ impl StateWithCache for TopLevelState { self.account.commit(&mut trie)?; self.metadata.commit(&mut trie)?; self.shard.commit(&mut trie)?; + self.action_data.commit(&mut trie)?; Ok(()) } @@ -213,12 +224,16 @@ impl StateWithCache for TopLevelState { self.shard.propagate_to_global_cache(|address, item, modified| { db.add_to_shard_cache(address, item, modified); }); + self.action_data.propagate_to_global_cache(|address, item, modified| { + db.add_to_action_data_cache(address, item, modified); + }); } fn clear(&mut self) { self.account.clear(); self.metadata.clear(); self.shard.clear(); + self.action_data.clear(); } } @@ -238,6 +253,7 @@ impl TopLevelState { account: Cache::new(), metadata: Cache::new(), shard: Cache::new(), + action_data: Cache::new(), id_of_checkpoints: Default::default(), trie_factory, } @@ -255,6 +271,7 @@ impl TopLevelState { account: Cache::new(), metadata: Cache::new(), shard: Cache::new(), + action_data: Cache::new(), id_of_checkpoints: Default::default(), trie_factory, }; @@ -393,6 +410,15 @@ impl TopLevelState { error: None, }) } + Action::Custom(bytes) => { + let handlers = self.db.custom_handlers().to_vec(); + for h in handlers { + if let Some(result) = h.execute(bytes, self) { + return result + } + } + panic!("Unknown custom parcel accepted!") + } } } @@ -511,6 +537,13 @@ impl TopLevelState { let from_db = || self.db.get_cached_shard(&shard_address); self.shard.require_item_or_from(&shard_address, default, db, from_db) } + + fn require_action_data<'a>(&'a self, key: &H256) -> TrieResult> { + let default = || NULL_RLP.to_vec(); + let db = self.trie_factory.readonly(self.db.as_hashdb(), &self.root)?; + let from_db = || self.db.get_cached_action_data(key); + self.action_data.require_item_or_from(key, default, db, from_db) + } } impl fmt::Debug for TopLevelState { @@ -518,6 +551,7 @@ impl fmt::Debug for TopLevelState { writeln!(f, "account: {:?}", self.account)?; writeln!(f, "metadata: {:?}", self.metadata)?; writeln!(f, "shard: {:?}", self.shard)?; + writeln!(f, "action_data: {:?}", self.action_data)?; Ok(()) } } @@ -533,6 +567,7 @@ impl Clone for TopLevelState { account: self.account.clone(), metadata: self.metadata.clone(), shard: self.shard.clone(), + action_data: self.action_data.clone(), trie_factory: self.trie_factory.clone(), } } @@ -620,6 +655,12 @@ impl TopState for TopLevelState { shard.set_root(*new_root); Ok(()) } + + fn update_action_data(&mut self, key: &H256, data: Bytes) -> StateResult<()> { + let mut action_data = self.require_action_data(key)?; + *action_data = data; + Ok(()) + } } #[cfg(test)] diff --git a/state/src/item/cache.rs b/state/src/item/cache.rs index ffa56a3e59..ef1a04b7f5 100644 --- a/state/src/item/cache.rs +++ b/state/src/item/cache.rs @@ -22,6 +22,7 @@ use std::fmt; use std::hash::Hash; use std::vec::Vec; +use primitives::{Bytes, H256}; use rlp::{Decodable, Encodable}; use trie::{self, Result as TrieResult, Trie, TrieKinds, TrieMut}; @@ -30,6 +31,13 @@ pub trait CacheableItem: Clone + fmt::Debug + Decodable + Encodable { fn is_null(&self) -> bool; } +impl CacheableItem for Bytes { + type Address = H256; + fn is_null(&self) -> bool { + self.is_empty() + } +} + #[derive(Eq, PartialEq, Clone, Copy, Debug)] /// Account modification state. Used to check if the account was /// Modified in between commits and overall. diff --git a/state/src/lib.rs b/state/src/lib.rs index 7751a26fb2..17f2cfe1c2 100644 --- a/state/src/lib.rs +++ b/state/src/lib.rs @@ -39,6 +39,7 @@ extern crate serde_derive; extern crate unexpected; extern crate util_error; +mod action_handler; mod backend; mod checkpoint; mod db; @@ -50,6 +51,7 @@ mod traits; #[cfg(test)] pub mod tests; +pub use action_handler::{ActionHandler, HitHandler}; pub use backend::{Backend, ShardBackend, TopBackend}; pub use checkpoint::{CheckpointId, StateWithCheckpoint}; pub use db::StateDB; diff --git a/state/src/tests.rs b/state/src/tests.rs index 6b330529d2..9b3a82e051 100644 --- a/state/src/tests.rs +++ b/state/src/tests.rs @@ -19,7 +19,7 @@ pub mod helpers { use super::super::StateDB; pub fn get_temp_state_db() -> StateDB { - StateDB::new_with_memorydb(5 * 1024 * 1024) + StateDB::new_with_memorydb(5 * 1024 * 1024, Vec::new()) } pub fn get_temp_state() -> TopLevelState { diff --git a/state/src/traits.rs b/state/src/traits.rs index 22adf9a59e..53105454a6 100644 --- a/state/src/traits.rs +++ b/state/src/traits.rs @@ -17,7 +17,7 @@ use ckey::{Address, Public}; use ctypes::transaction::{Outcome as TransactionOutcome, Transaction}; use ctypes::ShardId; -use primitives::{H256, U256}; +use primitives::{Bytes, H256, U256}; use trie::Result as TrieResult; use super::backend::{ShardBackend, TopBackend}; @@ -42,6 +42,8 @@ pub trait TopStateInfo { fn asset_scheme(&self, shard_id: ShardId, a: &AssetSchemeAddress) -> TrieResult>; /// Get the asset. fn asset(&self, shard_id: ShardId, a: &AssetAddress) -> TrieResult>; + + fn action_data(&self, key: &H256) -> TrieResult; } pub trait ShardStateInfo { @@ -83,7 +85,10 @@ where fn set_regular_key(&mut self, a: &Address, key: &Public) -> StateResult<()>; fn create_shard(&mut self, shard_creation_cost: &U256, fee_payer: &Address) -> StateResult<()>; + fn set_shard_root(&mut self, shard_id: ShardId, old_root: &H256, new_root: &H256) -> StateResult<()>; + + fn update_action_data(&mut self, key: &H256, data: Bytes) -> StateResult<()>; } pub trait StateWithCache { diff --git a/sync/src/block/extension.rs b/sync/src/block/extension.rs index d239bb643f..f476e1d829 100644 --- a/sync/src/block/extension.rs +++ b/sync/src/block/extension.rs @@ -21,9 +21,11 @@ use std::sync::Arc; use ccore::encoded::Header as EncodedHeader; use ccore::{ - Block, BlockChainClient, BlockId, BlockImportError, ChainNotify, Header, ImportError, Seal, UnverifiedParcel, + Block, BlockChainClient, BlockId, BlockImportError, BlockInfo, ChainInfo, ChainNotify, Client, Header, ImportBlock, + ImportError, Seal, UnverifiedParcel, }; use cnetwork::{Api, NetworkExtension, NodeId, TimerToken}; +use ctypes::parcel::Action; use ctypes::BlockNumber; use primitives::{H256, U256}; use rlp::{Encodable, UntrustedRlp}; @@ -41,13 +43,13 @@ pub struct Extension { requests: RwLock>>, header_downloaders: RwLock>, body_downloader: Mutex, - client: Arc, + client: Arc, api: Mutex>>, last_request: AtomicUsize, } impl Extension { - pub fn new(client: Arc) -> Arc { + pub fn new(client: Arc) -> Arc { Arc::new(Self { requests: RwLock::new(HashMap::new()), header_downloaders: RwLock::new(HashMap::new()), @@ -394,7 +396,20 @@ impl Extension { headers.first().map(|header| header.number()) == Some(*start_number) } - (RequestMessage::Bodies(..), ResponseMessage::Bodies(..)) => true, + (RequestMessage::Bodies(_), ResponseMessage::Bodies(bodies)) => { + for body in bodies { + for parcel in body { + let is_valid = match &parcel.as_unsigned().action { + Action::Custom(bytes) => self.client.custom_handlers().iter().any(|c| c.is_target(bytes)), + _ => true, + }; + if !is_valid { + return false + } + } + } + true + } (RequestMessage::StateHead(..), ResponseMessage::StateHead(..)) => unimplemented!(), ( RequestMessage::StateChunk { diff --git a/types/src/parcel/action.rs b/types/src/parcel/action.rs index 9946540d6f..d992f1486c 100644 --- a/types/src/parcel/action.rs +++ b/types/src/parcel/action.rs @@ -16,7 +16,7 @@ use ccrypto::Blake; use ckey::{Address, Public}; -use primitives::{H256, U256}; +use primitives::{Bytes, H256, U256}; use rlp::{Decodable, DecoderError, Encodable, RlpStream, UntrustedRlp}; use super::super::transaction::Transaction; @@ -26,6 +26,7 @@ const CHANGE_SHARD_STATE: u8 = 1; const PAYMENT: u8 = 2; const SET_REGULAR_KEY: u8 = 3; const CREATE_SHARD: u8 = 4; +const CUSTOM: u8 = 5; #[derive(Debug, Clone, PartialEq, Eq, Serialize, RlpDecodable, RlpEncodable)] #[serde(rename_all = "camelCase")] @@ -52,6 +53,7 @@ pub enum Action { key: Public, }, CreateShard, + Custom(Bytes), } impl Action { @@ -93,6 +95,11 @@ impl Encodable for Action { s.begin_list(1); s.append(&CREATE_SHARD); } + Action::Custom(bytes) => { + s.begin_list(2); + s.append(&CUSTOM); + s.append(bytes); + } } } } @@ -132,6 +139,12 @@ impl Decodable for Action { } Ok(Action::CreateShard) } + CUSTOM => { + if rlp.item_count()? != 2 { + return Err(DecoderError::RlpIncorrectListLen) + } + Ok(Action::Custom(rlp.val_at(1)?)) + } _ => Err(DecoderError::Custom("Unexpected action prefix")), } }