diff --git a/codechain/run_node.rs b/codechain/run_node.rs index 1e93f1a9d6..56b13af6ad 100644 --- a/codechain/run_node.rs +++ b/codechain/run_node.rs @@ -21,8 +21,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use ccore::snapshot_notify; use ccore::{ - AccountProvider, AccountProviderError, BlockId, ChainNotify, Client, ClientConfig, ClientService, EngineInfo, - EngineType, Miner, MinerService, Scheme, Stratum, StratumConfig, StratumError, NUM_COLUMNS, + AccountProvider, AccountProviderError, ChainNotify, Client, ClientConfig, ClientService, EngineInfo, EngineType, + Miner, MinerService, Scheme, Stratum, StratumConfig, StratumError, NUM_COLUMNS, }; use cdiscovery::{Config, Discovery}; use ckey::{Address, NetworkId, PlatformAddress}; @@ -286,7 +286,7 @@ pub fn run_node(matches: &ArgMatches) -> Result<(), String> { let network_config = config.network_config()?; // XXX: What should we do if the network id has been changed. let c = client.client(); - let network_id = c.common_params(BlockId::Number(0)).unwrap().network_id(); + let network_id = c.network_id(); let routing_table = RoutingTable::new(); let service = network_start(network_id, timer_loop, &network_config, Arc::clone(&routing_table))?; diff --git a/core/src/client/client.rs b/core/src/client/client.rs index 22c6192684..bd6e8c7140 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -491,6 +491,10 @@ impl StateInfo for Client { } impl EngineInfo for Client { + fn network_id(&self) -> NetworkId { + self.common_params(BlockId::Earliest).expect("Genesis state should exist").network_id() + } + fn common_params(&self, block_id: BlockId) -> Option { self.state_info(block_id.into()).map(|state| { state @@ -528,7 +532,7 @@ impl EngineInfo for Client { } fn possible_authors(&self, block_number: Option) -> Result>, EngineError> { - let network_id = self.common_params(BlockId::Latest).unwrap().network_id(); + let network_id = self.network_id(); if block_number == Some(0) { let genesis_author = self.block_header(&0.into()).expect("genesis block").author(); return Ok(Some(vec![PlatformAddress::new_v1(network_id, genesis_author)])) @@ -594,8 +598,7 @@ impl BlockChainTrait for Client { } fn genesis_accounts(&self) -> Vec { - // XXX: What should we do if the network id has been changed - let network_id = self.common_params(BlockId::Latest).unwrap().network_id(); + let network_id = self.network_id(); self.genesis_accounts.iter().map(|addr| PlatformAddress::new_v1(network_id, *addr)).collect() } @@ -945,10 +948,6 @@ impl MiningBlockChainClient for Client { fn register_immune_users(&self, immune_user_vec: Vec
) { self.importer.miner.register_immune_users(immune_user_vec) } - - fn get_network_id(&self) -> NetworkId { - self.common_params(BlockId::Latest).unwrap().network_id() - } } impl ChainTimeInfo for Client { diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 02c71223f4..66e7435f3c 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -89,6 +89,7 @@ pub trait BlockChainTrait { } pub trait EngineInfo: Send + Sync { + fn network_id(&self) -> NetworkId; fn common_params(&self, block_id: BlockId) -> Option; fn metadata_seq(&self, block_id: BlockId) -> Option; fn block_reward(&self, block_number: u64) -> u64; @@ -300,9 +301,6 @@ pub trait MiningBlockChainClient: BlockChainClient + BlockProducer + FindActionH /// Append designated users to the immune user list. fn register_immune_users(&self, immune_user_vec: Vec
); - - /// Returns network id. - fn get_network_id(&self) -> NetworkId; } /// Provides methods to access database. diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index b2b0514778..75131faa48 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -59,7 +59,7 @@ use crate::client::{ AccountData, BlockChainClient, BlockChainTrait, BlockProducer, BlockStatus, ConsensusClient, EngineInfo, ImportBlock, ImportResult, MiningBlockChainClient, StateInfo, StateOrBlock, TermInfo, }; -use crate::consensus::stake::{Validator, Validators}; +use crate::consensus::stake::{NextValidators, Validator}; use crate::consensus::EngineError; use crate::db::{COL_STATE, NUM_COLUMNS}; use crate::encoded; @@ -106,7 +106,7 @@ pub struct TestBlockChainClient { /// Fixed validator keys pub validator_keys: RwLock>, /// Fixed validators - pub validators: Validators, + pub validators: NextValidators, } impl Default for TestBlockChainClient { @@ -160,7 +160,7 @@ impl TestBlockChainClient { history: RwLock::new(None), term_id: Some(1), validator_keys: RwLock::new(HashMap::new()), - validators: Validators::from_vector_to_test(vec![]), + validators: NextValidators::from_vector_to_test(vec![]), }; // insert genesis hash. @@ -325,14 +325,14 @@ impl TestBlockChainClient { self.validator_keys.write().insert(*key_pair.public(), *key_pair.private()); pubkeys.push(*key_pair.public()); } - let fixed_validators: Validators = Validators::from_vector_to_test( + let fixed_validators: NextValidators = NextValidators::from_vector_to_test( pubkeys.into_iter().map(|pubkey| Validator::new_for_test(0, 0, pubkey)).collect(), ); self.validators = fixed_validators; } - pub fn get_validators(&self) -> &Validators { + pub fn get_validators(&self) -> &NextValidators { &self.validators } } @@ -381,10 +381,6 @@ impl MiningBlockChainClient for TestBlockChainClient { fn register_immune_users(&self, immune_user_vec: Vec
) { self.miner.register_immune_users(immune_user_vec) } - - fn get_network_id(&self) -> NetworkId { - NetworkId::default() - } } impl AccountData for TestBlockChainClient { @@ -657,6 +653,10 @@ impl super::EngineClient for TestBlockChainClient { } impl EngineInfo for TestBlockChainClient { + fn network_id(&self) -> NetworkId { + self.scheme.engine.machine().genesis_common_params().network_id() + } + fn common_params(&self, _block_id: BlockId) -> Option { Some(*self.scheme.engine.machine().genesis_common_params()) } diff --git a/core/src/consensus/stake/action_data.rs b/core/src/consensus/stake/action_data.rs index af14c8b33e..6272b0559a 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/core/src/consensus/stake/action_data.rs @@ -41,8 +41,10 @@ lazy_static! { pub static ref JAIL_KEY: H256 = ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 1).append(&"Jail").into_key(); pub static ref BANNED_KEY: H256 = ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 1).append(&"Banned").into_key(); - pub static ref VALIDATORS_KEY: H256 = + pub static ref NEXT_VALIDATORS_KEY: H256 = ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 1).append(&"Validators").into_key(); + pub static ref CURRENT_VALIDATORS_KEY: H256 = + ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 1).append(&"CurrentValidators").into_key(); } pub fn get_delegation_key(address: &Address) -> H256 { @@ -274,17 +276,17 @@ impl Validator { } #[derive(Debug)] -pub struct Validators(Vec); -impl Validators { +pub struct NextValidators(Vec); +impl NextValidators { pub fn from_vector_to_test(vec: Vec) -> Self { - Validators(vec) + Self(vec) } pub fn load_from_state(state: &TopLevelState) -> StateResult { - let key = &*VALIDATORS_KEY; + let key = &*NEXT_VALIDATORS_KEY; let validators = state.action_data(&key)?.map(|data| decode_list(&data)).unwrap_or_default(); - Ok(Validators(validators)) + Ok(Self(validators)) } pub fn elect(state: &TopLevelState) -> StateResult { @@ -335,7 +337,7 @@ impl Validators { pub fn save_to_state(&self, state: &mut TopLevelState) -> StateResult<()> { - let key = &*VALIDATORS_KEY; + let key = &*NEXT_VALIDATORS_KEY; if !self.is_empty() { state.update_action_data(&key, encode_list(&self.0).to_vec())?; } else { @@ -384,7 +386,7 @@ impl Validators { } } -impl Deref for Validators { +impl Deref for NextValidators { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -392,13 +394,13 @@ impl Deref for Validators { } } -impl From for Vec { - fn from(val: Validators) -> Self { +impl From for Vec { + fn from(val: NextValidators) -> Self { val.0 } } -impl IntoIterator for Validators { +impl IntoIterator for NextValidators { type Item = Validator; type IntoIter = vec::IntoIter; @@ -407,6 +409,49 @@ impl IntoIterator for Validators { } } +#[derive(Debug)] +pub struct CurrentValidators(Vec); +impl CurrentValidators { + pub fn load_from_state(state: &TopLevelState) -> StateResult { + let key = &*CURRENT_VALIDATORS_KEY; + let validators = state.action_data(&key)?.map(|data| decode_list(&data)).unwrap_or_default(); + + Ok(Self(validators)) + } + + pub fn save_to_state(&self, state: &mut TopLevelState) -> StateResult<()> { + let key = &*CURRENT_VALIDATORS_KEY; + if !self.is_empty() { + state.update_action_data(&key, encode_list(&self.0).to_vec())?; + } else { + state.remove_action_data(&key); + } + Ok(()) + } + + pub fn update(&mut self, validators: Vec) { + self.0 = validators; + } + + pub fn addresses(&self) -> Vec
{ + self.0.iter().rev().map(|v| public_to_address(&v.pubkey)).collect() + } + + pub fn get_validator(&self, index: usize) -> &Validator { + let len = self.0.len(); + // NOTE: validator list is reversed when reading a validator by index + self.0.iter().nth_back(index % len).unwrap() + } +} + +impl Deref for CurrentValidators { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + pub mod v0 { use std::mem; @@ -603,7 +648,7 @@ impl Candidates { pub fn renew_candidates( &mut self, - validators: &Validators, + validators: &NextValidators, nomination_ends_at: u64, inactive_validators: &[Address], banned: &Banned, @@ -1868,7 +1913,7 @@ mod tests { } candidates.save_to_state(&mut state).unwrap(); - let dummy_validators = Validators( + let dummy_validators = NextValidators( pubkeys[0..5] .iter() .map(|pubkey| Validator { diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index 5134367362..771013c7eb 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -33,7 +33,7 @@ use parking_lot::RwLock; use primitives::{Bytes, H256}; use rlp::{Decodable, Rlp}; -pub use self::action_data::{Banned, Validator, Validators}; +pub use self::action_data::{Banned, CurrentValidators, NextValidators, Validator}; use self::action_data::{Candidates, Delegation, Jail, ReleaseResult, StakeAccount, Stakeholders}; pub use self::actions::Action; pub use self::distribute::fee_distribute; @@ -317,8 +317,8 @@ pub fn get_stakes(state: &TopLevelState) -> StateResult> { Ok(result) } -pub fn get_validators(state: &TopLevelState) -> StateResult { - Validators::load_from_state(state) +pub fn get_validators(state: &TopLevelState) -> StateResult { + NextValidators::load_from_state(state) } pub mod v0 { @@ -379,7 +379,7 @@ pub mod v1 { } pub fn update_validator_weights(state: &mut TopLevelState, block_author: &Address) -> StateResult<()> { - let mut validators = Validators::load_from_state(state)?; + let mut validators = NextValidators::load_from_state(state)?; validators.update_weight(block_author); validators.save_to_state(state) } @@ -451,7 +451,7 @@ pub fn on_term_close( jail(state, inactive_validators, custody_until, kick_at)?; - let validators = Validators::elect(state)?; + let validators = NextValidators::elect(state)?; validators.save_to_state(state)?; state.increase_term_id(last_term_finished_block_num)?; @@ -469,7 +469,7 @@ fn update_candidates( let mut candidates = Candidates::load_from_state(state)?; let nomination_ends_at = current_term + nomination_expiration; - let current_validators = Validators::load_from_state(state)?; + let current_validators = NextValidators::load_from_state(state)?; candidates.renew_candidates(¤t_validators, nomination_ends_at, &inactive_validators, &banned); let expired = candidates.drain_expired_candidates(current_term); @@ -519,7 +519,7 @@ pub fn ban(state: &mut TopLevelState, informant: &Public, criminal: Address) -> let mut candidates = Candidates::load_from_state(state)?; let mut jailed = Jail::load_from_state(state)?; - let mut validators = Validators::load_from_state(state)?; + let mut validators = NextValidators::load_from_state(state)?; let deposit = match (candidates.remove(&criminal), jailed.remove(&criminal)) { (Some(_), Some(_)) => unreachable!("A candidate that are jailed cannot exist"), diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 4404cdaef2..69bc34b565 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -142,6 +142,17 @@ impl ConsensusEngine for Tendermint { let block_number = block.header().number(); let metadata = block.state().metadata()?.expect("Metadata must exist"); let era = metadata.term_params().map_or(0, |p| p.era()); + + match era { + 0 => {} + 1 => { + let mut validators = stake::CurrentValidators::load_from_state(block.state())?; + validators.update(stake::NextValidators::load_from_state(block.state())?.clone()); + validators.save_to_state(block.state_mut())?; + } + _ => unimplemented!(), + } + if block_number == metadata.last_term_finished_block_num() + 1 { match era { 0 => {} @@ -274,7 +285,7 @@ impl ConsensusEngine for Tendermint { stake::v0::move_current_to_previous_intermediate_rewards(block.state_mut())?; - let validators = stake::Validators::load_from_state(block.state())? + let validators = stake::NextValidators::load_from_state(block.state())? .into_iter() .map(|val| public_to_address(val.pubkey())) .collect(); @@ -286,7 +297,7 @@ impl ConsensusEngine for Tendermint { } let start_of_the_current_term = metadata.last_term_finished_block_num() + 1; - let validators = stake::Validators::load_from_state(block.state())? + let validators = stake::NextValidators::load_from_state(block.state())? .into_iter() .map(|val| public_to_address(val.pubkey())) .collect(); @@ -448,10 +459,30 @@ fn calculate_pending_rewards_of_the_previous_term( let mut missed_signatures = HashMap::::with_capacity(MAX_NUM_OF_VALIDATORS); let mut signed_blocks = HashMap::::with_capacity(MAX_NUM_OF_VALIDATORS); + let era = { + let end_of_the_current_term_header = chain + .block_header(&start_of_the_current_term_header.parent_hash().into()) + .expect("The parent of the term end block must exist"); + let state = chain + .state_at(end_of_the_current_term_header.parent_hash().into()) + .expect("The state at parent of the term end block must exist"); + let metadata = state.metadata()?.expect("Metadata of the term end block should exist"); + metadata.term_params().map_or(0, |p| p.era()) + }; + let mut header = start_of_the_current_term_header; let mut parent_validators = { - let grand_parent_header = chain.block_header(&header.parent_hash().into()).unwrap(); - validators.addresses(&grand_parent_header.parent_hash()) + match era { + 0 => { + let grand_parent_header = chain.block_header(&header.parent_hash().into()).unwrap(); + validators.addresses(&grand_parent_header.parent_hash()) + } + 1 => { + let state = chain.state_at(header.parent_hash().into()).expect("The block's state must exist"); + stake::CurrentValidators::load_from_state(&state)?.addresses() + } + _ => unimplemented!(), + } }; while start_of_the_previous_term != header.number() { for index in TendermintSealView::new(&header.seal()).bitset()?.true_index_iter() { @@ -461,10 +492,17 @@ fn calculate_pending_rewards_of_the_previous_term( header = chain.block_header(&header.parent_hash().into()).unwrap(); parent_validators = { - // The seal of the current block has the signatures of the parent block. - // It needs the hash of the grand parent block to find the validators of the parent block. - let grand_parent_header = chain.block_header(&header.parent_hash().into()).unwrap(); - validators.addresses(&grand_parent_header.parent_hash()) + match era { + 0 => { + let grand_parent_header = chain.block_header(&header.parent_hash().into()).unwrap(); + validators.addresses(&grand_parent_header.parent_hash()) + } + 1 => { + let state = chain.state_at(header.hash().into()).expect("The block's state must exist"); + stake::CurrentValidators::load_from_state(&state)?.addresses() + } + _ => unimplemented!(), + } }; let author = header.author(); diff --git a/core/src/consensus/tendermint/worker.rs b/core/src/consensus/tendermint/worker.rs index f9abe27922..7cb4aabcb8 100644 --- a/core/src/consensus/tendermint/worker.rs +++ b/core/src/consensus/tendermint/worker.rs @@ -35,7 +35,7 @@ use super::backup::{backup, restore, BackupView}; use super::message::*; use super::network; use super::params::TimeGapParams; -use super::stake::CUSTOM_ACTION_HANDLER_ID; +use super::stake::{CurrentValidators, CUSTOM_ACTION_HANDLER_ID}; use super::types::{Height, Proposal, Step, TendermintSealView, TendermintState, TwoThirdsMajority, View}; use super::vote_collector::{DoubleVote, VoteCollector}; use super::vote_regression_checker::VoteRegressionChecker; @@ -1244,13 +1244,19 @@ impl Worker { }; let mut voted_validators = BitSet::new(); - let grand_parent_hash = self - .client() - .block_header(&(*header.parent_hash()).into()) - .expect("The parent block must exist") - .parent_hash(); + let parent = self.client().block_header(&(*header.parent_hash()).into()).expect("The parent block must exist"); + let grand_parent_hash = parent.parent_hash(); for (bitset_index, signature) in seal_view.signatures()? { - let public = self.validators.get(&grand_parent_hash, bitset_index); + let public = { + let state = self.client().state_at(parent.hash().into()).expect("The parent state must exist"); + let validators = CurrentValidators::load_from_state(&state)?; + // This happens when era == 0 + if validators.is_empty() { + self.validators.get(&grand_parent_hash, bitset_index) + } else { + *validators.get_validator(bitset_index).pubkey() + } + }; if !verify_schnorr(&public, &signature, &precommit_vote_on.hash())? { let address = public_to_address(&public); return Err(EngineError::BlockNotAuthorized(address.to_owned()).into()) @@ -1263,7 +1269,7 @@ impl Worker { if header.number() == 1 { return Ok(()) } - self.validators.check_enough_votes(&grand_parent_hash, &voted_validators)?; + self.validators.check_enough_votes_with_header(&parent.decode(), &voted_validators)?; Ok(()) } @@ -1471,7 +1477,7 @@ impl Worker { } fn report_double_vote(&self, double: &DoubleVote) { - let network_id = self.client().common_params(BlockId::Latest).unwrap().network_id(); + let network_id = self.client().network_id(); let seq = match self.signer.address() { Some(address) => self.client().latest_seq(address), None => { diff --git a/core/src/consensus/validator_set/dynamic_validator.rs b/core/src/consensus/validator_set/dynamic_validator.rs index 829e9cd5df..7091333e06 100644 --- a/core/src/consensus/validator_set/dynamic_validator.rs +++ b/core/src/consensus/validator_set/dynamic_validator.rs @@ -18,13 +18,13 @@ use std::sync::{Arc, Weak}; use ckey::{public_to_address, Address, Public}; use ctypes::util::unexpected::OutOfBounds; -use ctypes::BlockHash; +use ctypes::{BlockHash, Header}; use parking_lot::RwLock; use super::{RoundRobinValidator, ValidatorSet}; use crate::client::ConsensusClient; use crate::consensus::bit_set::BitSet; -use crate::consensus::stake::{get_validators, Validator}; +use crate::consensus::stake::{get_validators, CurrentValidators, Validator}; use crate::consensus::EngineError; /// Validator set containing a known set of public keys. @@ -77,6 +77,58 @@ impl DynamicValidator { (prev_proposer_index + proposed_view + 1) % num_validators } } + + pub fn check_enough_votes_with_validators( + &self, + validators: &[Validator], + votes: &BitSet, + ) -> Result<(), EngineError> { + let mut voted_delegation = 0u64; + let n_validators = validators.len(); + for index in votes.true_index_iter() { + assert!(index < n_validators); + let validator = validators.get(index).ok_or_else(|| { + EngineError::ValidatorNotExist { + height: 0, // FIXME + index, + } + })?; + voted_delegation += validator.delegation(); + } + let total_delegation: u64 = validators.iter().map(Validator::delegation).sum(); + if voted_delegation * 3 > total_delegation * 2 { + Ok(()) + } else { + let threshold = total_delegation as usize * 2 / 3; + Err(EngineError::BadSealFieldSize(OutOfBounds { + min: Some(threshold), + max: Some(total_delegation as usize), + found: voted_delegation as usize, + })) + } + } + + pub fn check_enough_votes_with_header(&self, header: &Header, votes: &BitSet) -> Result<(), EngineError> { + let client: Arc = + self.client.read().as_ref().and_then(Weak::upgrade).expect("Client is not initialized"); + + let validators = { + match client.state_at(header.hash().into()).map(|s| CurrentValidators::load_from_state(&s)) { + Some(Ok(current_validators)) if !current_validators.is_empty() => { + let mut result = (*current_validators).clone(); + result.reverse(); + Some(result) + } + _ => self.validators(*header.parent_hash()), + } + }; + + if let Some(validators) = validators { + self.check_enough_votes_with_validators(&validators, votes) + } else { + self.initial_list.check_enough_votes(header.parent_hash(), votes) + } + } } impl ValidatorSet for DynamicValidator { @@ -145,29 +197,7 @@ impl ValidatorSet for DynamicValidator { fn check_enough_votes(&self, parent: &BlockHash, votes: &BitSet) -> Result<(), EngineError> { if let Some(validators) = self.validators(*parent) { - let mut voted_delegation = 0u64; - let n_validators = validators.len(); - for index in votes.true_index_iter() { - assert!(index < n_validators); - let validator = validators.get(index).ok_or_else(|| { - EngineError::ValidatorNotExist { - height: 0, // FIXME - index, - } - })?; - voted_delegation += validator.delegation(); - } - let total_delegation: u64 = validators.iter().map(Validator::delegation).sum(); - if voted_delegation * 3 > total_delegation * 2 { - Ok(()) - } else { - let threshold = total_delegation as usize * 2 / 3; - Err(EngineError::BadSealFieldSize(OutOfBounds { - min: Some(threshold), - max: Some(total_delegation as usize), - found: voted_delegation as usize, - })) - } + self.check_enough_votes_with_validators(&validators, votes) } else { self.initial_list.check_enough_votes(parent, votes) } diff --git a/rpc/src/v1/impls/account.rs b/rpc/src/v1/impls/account.rs index a4c16207e9..fa10fbc063 100644 --- a/rpc/src/v1/impls/account.rs +++ b/rpc/src/v1/impls/account.rs @@ -18,8 +18,8 @@ use std::convert::TryInto; use std::sync::Arc; use std::time::Duration; -use ccore::{AccountData, AccountProvider, BlockId, EngineInfo, MinerService, MiningBlockChainClient, TermInfo}; -use ckey::{NetworkId, Password, PlatformAddress, Signature}; +use ccore::{AccountData, AccountProvider, EngineInfo, MinerService, MiningBlockChainClient, TermInfo}; +use ckey::{Password, PlatformAddress, Signature}; use ctypes::transaction::IncompleteTransaction; use jsonrpc_core::Result; use parking_lot::Mutex; @@ -46,11 +46,6 @@ where miner, } } - - fn network_id(&self) -> NetworkId { - // XXX: What should we do if the network id has been changed - self.client.common_params(BlockId::Latest).unwrap().network_id() - } } impl Account for AccountClient @@ -62,7 +57,10 @@ where self.account_provider .get_list() .map(|addresses| { - addresses.into_iter().map(|address| PlatformAddress::new_v1(self.network_id(), address)).collect() + addresses + .into_iter() + .map(|address| PlatformAddress::new_v1(self.client.network_id(), address)) + .collect() }) .map_err(account_provider) } @@ -70,13 +68,13 @@ where fn create_account(&self, passphrase: Option) -> Result { let (address, _) = self.account_provider.new_account_and_public(&passphrase.unwrap_or_default()).map_err(account_provider)?; - Ok(PlatformAddress::new_v1(self.network_id(), address)) + Ok(PlatformAddress::new_v1(self.client.network_id(), address)) } fn create_account_from_secret(&self, secret: H256, passphrase: Option) -> Result { self.account_provider .insert_account(secret.into(), &passphrase.unwrap_or_default()) - .map(|address| PlatformAddress::new_v1(self.network_id(), address)) + .map(|address| PlatformAddress::new_v1(self.client.network_id(), address)) .map_err(account_provider) } diff --git a/rpc/src/v1/impls/chain.rs b/rpc/src/v1/impls/chain.rs index 576e571d00..d9a3797f0c 100644 --- a/rpc/src/v1/impls/chain.rs +++ b/rpc/src/v1/impls/chain.rs @@ -128,10 +128,11 @@ where return Ok(None) } let block_id = block_number.map(BlockId::from).unwrap_or(BlockId::Latest); - Ok(self.client.get_text(transaction_hash, block_id).map_err(errors::transaction_state)?.map(|text| { - let parent_block_id = block_number.map(|n| (n - 1).into()).unwrap_or(BlockId::ParentOfLatest); - Text::from_core(text, self.client.common_params(parent_block_id).unwrap().network_id()) - })) + Ok(self + .client + .get_text(transaction_hash, block_id) + .map_err(errors::transaction_state)? + .map(|text| Text::from_core(text, self.client.network_id()))) } fn get_asset( @@ -178,8 +179,7 @@ where fn get_regular_key_owner(&self, public: Public, block_number: Option) -> Result> { let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); Ok(self.client.regular_key_owner(&public_to_address(&public), block_id.into()).and_then(|address| { - let parent_block_id = block_number.map(|n| (n - 1).into()).unwrap_or(BlockId::ParentOfLatest); - let network_id = self.client.common_params(parent_block_id).unwrap().network_id(); + let network_id = self.client.network_id(); Some(PlatformAddress::new_v1(network_id, address)) })) } @@ -206,8 +206,7 @@ where fn get_shard_owners(&self, shard_id: ShardId, block_number: Option) -> Result>> { let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); Ok(self.client.shard_owners(shard_id, block_id.into()).map(|owners| { - let parent_block_id = block_number.map(|n| (n - 1).into()).unwrap_or(BlockId::ParentOfLatest); - let network_id = self.client.common_params(parent_block_id).unwrap().network_id(); + let network_id = self.client.network_id(); owners.into_iter().map(|owner| PlatformAddress::new_v1(network_id, owner)).collect() })) } @@ -215,8 +214,7 @@ where fn get_shard_users(&self, shard_id: ShardId, block_number: Option) -> Result>> { let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); Ok(self.client.shard_users(shard_id, block_id.into()).map(|users| { - let parent_block_id = block_number.map(|n| (n - 1).into()).unwrap_or(BlockId::ParentOfLatest); - let network_id = self.client.common_params(parent_block_id).unwrap().network_id(); + let network_id = self.client.network_id(); users.into_iter().map(|user| PlatformAddress::new_v1(network_id, user)).collect() })) } @@ -239,26 +237,14 @@ where fn get_block_by_number(&self, block_number: u64) -> Result> { let id = BlockId::Number(block_number); - Ok(self.client.block(&id).map(|block| { - let block_id_to_read_params = if block_number == 0 { - 0.into() - } else { - (block_number - 1).into() - }; - Block::from_core(block.decode(), self.client.common_params(block_id_to_read_params).unwrap().network_id()) - })) + Ok(self.client.block(&id).map(|block| Block::from_core(block.decode(), self.client.network_id()))) } fn get_block_by_hash(&self, block_hash: BlockHash) -> Result> { let id = BlockId::Hash(block_hash); Ok(self.client.block(&id).map(|block| { let block = block.decode(); - let block_id_to_read_params = if block.header.number() == 0 { - 0.into() - } else { - (*block.header.parent_hash()).into() - }; - Block::from_core(block, self.client.common_params(block_id_to_read_params).unwrap().network_id()) + Block::from_core(block, self.client.network_id()) })) } @@ -301,7 +287,7 @@ where } fn get_network_id(&self) -> Result { - Ok(self.client.common_params(BlockId::Latest).unwrap().network_id()) + Ok(self.client.network_id()) } fn get_common_params(&self, block_number: Option) -> Result> { diff --git a/rpc/src/v1/impls/engine.rs b/rpc/src/v1/impls/engine.rs index f106f29346..730ec09073 100644 --- a/rpc/src/v1/impls/engine.rs +++ b/rpc/src/v1/impls/engine.rs @@ -62,7 +62,7 @@ where Ok(None) } else { // XXX: What should we do if the network id has been changed - let network_id = self.client.common_params(BlockId::Latest).unwrap().network_id(); + let network_id = self.client.network_id(); Ok(Some(PlatformAddress::new_v1(network_id, author))) } } diff --git a/rpc/src/v1/impls/mempool.rs b/rpc/src/v1/impls/mempool.rs index ed021f1c8d..ab78c740e8 100644 --- a/rpc/src/v1/impls/mempool.rs +++ b/rpc/src/v1/impls/mempool.rs @@ -16,7 +16,7 @@ use std::sync::Arc; -use ccore::{BlockChainClient, MiningBlockChainClient, SignedTransaction}; +use ccore::{BlockChainClient, EngineInfo, MiningBlockChainClient, SignedTransaction}; use cjson::bytes::Bytes; use ckey::{Address, PlatformAddress}; use ctypes::{Tracker, TxHash}; @@ -42,7 +42,7 @@ impl MempoolClient { impl Mempool for MempoolClient where - C: BlockChainClient + MiningBlockChainClient + 'static, + C: BlockChainClient + MiningBlockChainClient + EngineInfo + 'static, { fn send_signed_transaction(&self, raw: Bytes) -> Result { Rlp::new(&raw.into_vec()) @@ -82,7 +82,7 @@ where fn get_banned_accounts(&self) -> Result> { let malicious_user_vec = self.client.get_malicious_users(); - let network_id = self.client.get_network_id(); + let network_id = self.client.network_id(); Ok(malicious_user_vec.into_iter().map(|address| PlatformAddress::new_v1(network_id, address)).collect()) } @@ -102,7 +102,7 @@ where fn get_immune_accounts(&self) -> Result> { let immune_user_vec = self.client.get_immune_users(); - let network_id = self.client.get_network_id(); + let network_id = self.client.network_id(); Ok(immune_user_vec.into_iter().map(|address| PlatformAddress::new_v1(network_id, address)).collect()) } diff --git a/test/src/e2e.dynval/2/snapshot.test.ts b/test/src/e2e.dynval/2/snapshot.test.ts index bb548d4aef..746b878147 100644 --- a/test/src/e2e.dynval/2/snapshot.test.ts +++ b/test/src/e2e.dynval/2/snapshot.test.ts @@ -17,6 +17,7 @@ import * as chai from "chai"; import { expect } from "chai"; import * as chaiAsPromised from "chai-as-promised"; +import { SDK } from "codechain-sdk"; import * as stake from "codechain-stakeholder-sdk"; import * as fs from "fs"; import "mocha"; @@ -24,8 +25,9 @@ import * as path from "path"; import mkdirp = require("mkdirp"); import { validators } from "../../../tendermint.dynval/constants"; +import { faucetAddress, faucetSecret } from "../../helper/constants"; import { PromiseExpect } from "../../helper/promise"; -import CodeChain from "../../helper/spawn"; +import CodeChain, { Signer } from "../../helper/spawn"; import { setTermTestTimeout, withNodes } from "../setup"; chai.use(chaiAsPromised); @@ -36,6 +38,7 @@ const SNAPSHOT_PATH = `${__dirname}/../../../../snapshot/`; describe("Snapshot for Tendermint with Dynamic Validator", function() { const promiseExpect = new PromiseExpect(); const snapshotValidators = validators.slice(0, 3); + const freshNodeValidator = validators[3]; const { nodes } = withNodes(this, { promiseExpect, overrideParams: { @@ -82,9 +85,77 @@ describe("Snapshot for Tendermint with Dynamic Validator", function() { ).to.satisfy(fs.existsSync); }); + it("should be able to boot with the snapshot", async function() { + const termWaiter = setTermTestTimeout(this, { + terms: 3 + }); + const termMetadata1 = await termWaiter.waitNodeUntilTerm(nodes[0], { + target: 2, + termPeriods: 1 + }); + const snapshotBlock = await getSnapshotBlock(nodes[0], termMetadata1); + await makeItValidator(nodes[0], freshNodeValidator); + const snapshotPath = fs.mkdtempSync(SNAPSHOT_PATH); + const node = new CodeChain({ + chain: `${__dirname}/../../scheme/tendermint-dynval.json`, + argv: [ + "--engine-signer", + freshNodeValidator.platformAddress.toString(), + "--password-path", + `test/tendermint.dynval/${freshNodeValidator.platformAddress.value}/password.json`, + "--force-sealing", + "--snapshot-path", + snapshotPath, + "--config", + SNAPSHOT_CONFIG, + "--snapshot-hash", + snapshotBlock.hash.toString(), + "--snapshot-number", + snapshotBlock.number.toString() + ], + additionalKeysPath: `tendermint.dynval/${freshNodeValidator.platformAddress.value}/keys` + }); + try { + await node.start(); + await node.connect(nodes[0]); + await termWaiter.waitNodeUntilTerm(node, { + target: 4, + termPeriods: 2 + }); + + await freshValidatorCheck(nodes[0].sdk); + + expect(await node.sdk.rpc.chain.getBlock(snapshotBlock.number - 1)) + .to.be.null; + expect(await node.sdk.rpc.chain.getBlock(snapshotBlock.number)).not + .to.be.null; + // Check that the freshNodeValidator is still a validator & make sure it doesn't have a block/header before termMetadata1. + } catch (e) { + node.keepLogs(); + throw e; + } finally { + await node.clean(); + } + }); + afterEach(async function() { promiseExpect.checkFulfilled(); }); + + async function freshValidatorCheck(sdk: SDK) { + const blockNumber = await sdk.rpc.chain.getBestBlockNumber(); + const termMedata = await stake.getTermMetadata(sdk, blockNumber); + const currentTermInitialBlockNumber = + termMedata!.lastTermFinishedBlockNumber + 1; + const validatorsAfter = (await stake.getPossibleAuthors( + sdk, + currentTermInitialBlockNumber + ))!.map(platformAddr => platformAddr.toString()); + + expect(validatorsAfter).and.contains( + freshNodeValidator.platformAddress.toString() + ); + } }); async function getSnapshotBlock( @@ -95,3 +166,44 @@ async function getSnapshotBlock( await node.waitBlockNumber(blockNumber); return (await node.sdk.rpc.chain.getBlock(blockNumber))!; } + +async function makeItValidator(node: CodeChain, freshNodeValidator: Signer) { + const faucetSeq = await node.sdk.rpc.chain.getSeq(faucetAddress); + const payTx = node.sdk.core + .createPayTransaction({ + recipient: freshNodeValidator.platformAddress, + quantity: 200000000 + }) + .sign({ + secret: faucetSecret, + seq: faucetSeq, + fee: 10 + }); + await node.waitForTx(await node.sdk.rpc.chain.sendSignedTransaction(payTx)); + const selfNominateTx = stake + .createSelfNominateTransaction(node.sdk, 10000000, "") + .sign({ + secret: freshNodeValidator.privateKey, + seq: await node.sdk.rpc.chain.getSeq( + freshNodeValidator.platformAddress + ), + fee: 10 + }); + await node.waitForTx( + await node.sdk.rpc.chain.sendSignedTransaction(selfNominateTx) + ); + const delegateTx = stake + .createDelegateCCSTransaction( + node.sdk, + freshNodeValidator.platformAddress, + 10000 + ) + .sign({ + secret: faucetSecret, + seq: faucetSeq + 1, + fee: 10 + }); + await node.waitForTx( + await node.sdk.rpc.chain.sendSignedTransaction(delegateTx) + ); +} diff --git a/test/src/e2e/snapshot.test.ts b/test/src/e2e/snapshot.test.ts index d0b81f614d..195cf00c8a 100644 --- a/test/src/e2e/snapshot.test.ts +++ b/test/src/e2e/snapshot.test.ts @@ -54,6 +54,60 @@ describe("Snapshot", async function() { ).to.satisfies(fs.existsSync); }); + it("can restore from snapshot", async function() { + for (let i = 0; i < 10; i++) { + const tx = await node.sendPayTx({ + quantity: 100, + recipient: aliceAddress + }); + await node.waitForTx(tx.hash()); + } + + const pay = await node.sendPayTx({ + quantity: 100, + recipient: aliceAddress + }); + + const blockHash = (await node.sdk.rpc.chain.getTransaction(pay.hash()))! + .blockHash!; + + const block = (await node.sdk.rpc.chain.getBlock(blockHash))!; + await node.sdk.rpc.sendRpcRequest("devel_snapshot", [ + blockHash.toJSON() + ]); + // Wait for 1 secs + await new Promise(resolve => setTimeout(resolve, 1000)); + + const newNode = new CodeChain({ + argv: [ + "--snapshot-hash", + block.hash.toString(), + "--snapshot-number", + block.number.toString() + ] + }); + + try { + await newNode.start(); + await newNode.connect(node); + await newNode.waitBlockNumber(block.number); + await node.sdk.rpc.devel.stopSealing(); + // New node creates block + const newPay = await newNode.sendPayTx({ + quantity: 100, + recipient: aliceAddress + }); + await newNode.waitForTx(newPay.hash()); + await node.sdk.rpc.devel.startSealing(); + await node.waitForTx(newPay.hash()); + } catch (e) { + newNode.keepLogs(); + throw e; + } finally { + await newNode.clean(); + } + }); + afterEach(function() { if (this.currentTest!.state === "failed") { node.keepLogs();