From 5d73cbcff81b71560b5a9354b80be45cada15898 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Wed, 29 May 2019 13:58:28 +0900 Subject: [PATCH 1/4] Replace MetadataInfo with TermInfo Now it returns the states of the term start not only the metadata. --- core/src/client/client.rs | 25 +++++++++++++++++++------ core/src/client/mod.rs | 11 +++++++---- core/src/client/test_client.rs | 18 +++++++++++++----- core/src/consensus/tendermint/engine.rs | 4 ++-- core/src/lib.rs | 2 +- rpc/src/v1/impls/chain.rs | 15 +++++++++++---- 6 files changed, 53 insertions(+), 22 deletions(-) diff --git a/core/src/client/client.rs b/core/src/client/client.rs index 31193ce59f..bca9d3493c 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -24,8 +24,7 @@ use ckey::{Address, PlatformAddress, Public}; use cmerkle::Result as TrieResult; use cnetwork::NodeId; use cstate::{ - ActionHandler, AssetScheme, FindActionHandler, Metadata, OwnedAsset, StateDB, StateResult, Text, TopLevelState, - TopStateView, + ActionHandler, AssetScheme, FindActionHandler, OwnedAsset, StateDB, StateResult, Text, TopLevelState, TopStateView, }; use ctimer::{TimeoutHandler, TimerApi, TimerScheduleError, TimerToken}; use ctypes::transaction::{AssetTransferInput, PartialHashing, ShardTransaction}; @@ -46,7 +45,7 @@ use super::{ }; use crate::block::{ClosedBlock, IsBlock, OpenBlock, SealedBlock}; use crate::blockchain::{BlockChain, BlockProvider, BodyProvider, HeaderProvider, InvoiceProvider, TransactionAddress}; -use crate::client::{ConsensusClient, MetadataInfo}; +use crate::client::{ConsensusClient, TermInfo}; use crate::consensus::CodeChainEngine; use crate::encoded; use crate::error::{BlockImportError, Error, ImportError, SchemeError}; @@ -781,9 +780,23 @@ impl BlockChainClient for Client { } } -impl MetadataInfo for Client { - fn metadata(&self, id: BlockId) -> Option { - self.state_at(id).and_then(|state| state.metadata().unwrap()) +impl TermInfo for Client { + fn last_term_finished_block_num(&self, id: BlockId) -> Option { + self.state_at(id) + .and_then(|state| state.metadata().unwrap()) + .map(|metadata| metadata.last_term_finished_block_num()) + } + + fn current_term_id(&self, id: BlockId) -> Option { + self.state_at(id).and_then(|state| state.metadata().unwrap()).map(|metadata| metadata.current_term_id()) + } + + fn state_at_term_begin(&self, id: BlockId) -> Option { + if let Some(block_num) = self.last_term_finished_block_num(id) { + self.state_at(block_num.into()) + } else { + None + } } } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index f2a7219f88..3889084750 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -35,7 +35,7 @@ use std::sync::Arc; use ckey::{Address, PlatformAddress, Public}; use cmerkle::Result as TrieResult; use cnetwork::NodeId; -use cstate::{AssetScheme, FindActionHandler, Metadata, OwnedAsset, StateResult, Text, TopLevelState, TopStateView}; +use cstate::{AssetScheme, FindActionHandler, OwnedAsset, StateResult, Text, TopLevelState, TopStateView}; use ctypes::transaction::{AssetTransferInput, PartialHashing, ShardTransaction}; use ctypes::{BlockNumber, CommonParams, ShardId}; use cvm::ChainTimeInfo; @@ -111,10 +111,13 @@ pub trait EngineClient: Sync + Send + BlockChainTrait + ImportBlock { fn get_kvdb(&self) -> Arc; } -pub trait ConsensusClient: BlockChainTrait + EngineClient + MetadataInfo {} +pub trait ConsensusClient: BlockChainTrait + EngineClient + TermInfo {} -pub trait MetadataInfo { - fn metadata(&self, id: BlockId) -> Option; +pub trait TermInfo { + fn last_term_finished_block_num(&self, id: BlockId) -> Option; + fn current_term_id(&self, id: BlockId) -> Option; + + fn state_at_term_begin(&self, id: BlockId) -> Option; } /// Provides methods to access account info diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index 9934a64e83..27c28aa78c 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -39,7 +39,7 @@ use std::sync::Arc; use ckey::{public_to_address, Address, Generator, NetworkId, PlatformAddress, Public, Random}; use cmerkle::skewed_merkle_root; use cnetwork::NodeId; -use cstate::{FindActionHandler, Metadata, StateDB}; +use cstate::{FindActionHandler, StateDB, TopLevelState}; use ctimer::{TimeoutHandler, TimerToken}; use ctypes::transaction::{Action, Transaction}; use ctypes::{BlockNumber, CommonParams, Header as BlockHeader}; @@ -55,8 +55,8 @@ use crate::block::{ClosedBlock, OpenBlock, SealedBlock}; use crate::blockchain_info::BlockChainInfo; use crate::client::ImportResult; use crate::client::{ - AccountData, BlockChainClient, BlockChainTrait, BlockProducer, BlockStatus, EngineInfo, ImportBlock, MetadataInfo, - MiningBlockChainClient, StateOrBlock, + AccountData, BlockChainClient, BlockChainTrait, BlockProducer, BlockStatus, EngineInfo, ImportBlock, + MiningBlockChainClient, StateOrBlock, TermInfo, }; use crate::db::{COL_STATE, NUM_COLUMNS}; use crate::encoded; @@ -603,8 +603,16 @@ impl EngineInfo for TestBlockChainClient { impl ConsensusClient for TestBlockChainClient {} -impl MetadataInfo for TestBlockChainClient { - fn metadata(&self, _id: BlockId) -> Option { +impl TermInfo for TestBlockChainClient { + fn last_term_finished_block_num(&self, _id: BlockId) -> Option { + None + } + + fn current_term_id(&self, _id: BlockId) -> Option { + None + } + + fn state_at_term_begin(&self, _id: BlockId) -> Option { None } } diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index d5c03a40d0..f16dac14cc 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -184,8 +184,8 @@ impl ConsensusEngine for Tendermint { let (start_of_the_current_term, start_of_the_previous_term) = { let end_of_the_one_level_previous_term = block.state().metadata()?.unwrap().last_term_finished_block_num(); - let metadata = client.metadata(end_of_the_one_level_previous_term.into()).unwrap(); - let end_of_the_two_level_previous_term = metadata.last_term_finished_block_num(); + let end_of_the_two_level_previous_term = + client.last_term_finished_block_num(end_of_the_one_level_previous_term.into()).unwrap(); (end_of_the_one_level_previous_term + 1, end_of_the_two_level_previous_term + 1) }; diff --git a/core/src/lib.rs b/core/src/lib.rs index 47315bc834..c011500df2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -84,7 +84,7 @@ pub use crate::block::Block; pub use crate::client::Error::Database; pub use crate::client::{ AccountData, AssetClient, BlockChainClient, BlockChainTrait, ChainNotify, Client, ClientConfig, DatabaseClient, - EngineClient, EngineInfo, ExecuteClient, ImportBlock, MetadataInfo, MiningBlockChainClient, Shard, StateInfo, + EngineClient, EngineInfo, ExecuteClient, ImportBlock, MiningBlockChainClient, Shard, StateInfo, TermInfo, TestBlockChainClient, TextClient, }; pub use crate::consensus::{EngineType, TimeGapParams}; diff --git a/rpc/src/v1/impls/chain.rs b/rpc/src/v1/impls/chain.rs index 56d55898b6..192a9bfb12 100644 --- a/rpc/src/v1/impls/chain.rs +++ b/rpc/src/v1/impls/chain.rs @@ -18,8 +18,7 @@ use std::convert::{TryFrom, TryInto}; use std::sync::Arc; use ccore::{ - AccountData, AssetClient, BlockId, EngineInfo, ExecuteClient, MetadataInfo, MiningBlockChainClient, Shard, - TextClient, + AccountData, AssetClient, BlockId, EngineInfo, ExecuteClient, MiningBlockChainClient, Shard, TermInfo, TextClient, }; use ccrypto::Blake; use cjson::scheme::Params; @@ -63,7 +62,7 @@ where + EngineInfo + FindActionHandler + TextClient - + MetadataInfo + + TermInfo + 'static, { fn get_transaction(&self, transaction_hash: H256) -> Result> { @@ -306,7 +305,15 @@ where fn get_term_metadata(&self, block_number: Option) -> Result> { let block_id = block_number.map(BlockId::Number).unwrap_or(BlockId::Latest); - Ok(self.client.metadata(block_id).map(|m| (m.last_term_finished_block_num(), m.current_term_id()))) + let last_term_finished_block_num = self.client.last_term_finished_block_num(block_id); + let current_term_id = self.client.current_term_id(block_id); + match (last_term_finished_block_num, current_term_id) { + (Some(last_term_finished_block_num), Some(current_term_id)) => { + Ok(Some((last_term_finished_block_num, current_term_id))) + } + (None, None) => Ok(None), + _ => unreachable!(), + } } fn execute_transaction(&self, tx: UnsignedTransaction, sender: PlatformAddress) -> Result> { From fafed55841300fa4a6977e96b6dd22e733640395 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Tue, 4 Jun 2019 12:26:00 +0900 Subject: [PATCH 2/4] Store validators in the states and implement an election --- core/src/consensus/stake/action_data.rs | 160 +++++++++++++++++++++++- core/src/consensus/stake/mod.rs | 42 ++++--- state/src/tests.rs | 23 +++- types/src/common_params.rs | 5 + 4 files changed, 206 insertions(+), 24 deletions(-) diff --git a/core/src/consensus/stake/action_data.rs b/core/src/consensus/stake/action_data.rs index 8ef25528f2..5664834ae8 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/core/src/consensus/stake/action_data.rs @@ -14,17 +14,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -#[cfg(test)] -use std::collections::btree_map; use std::collections::btree_map::{BTreeMap, Entry}; use std::collections::btree_set::{self, BTreeSet}; +use std::collections::{btree_map, HashMap}; use std::mem; use ckey::{public_to_address, Address, Public}; use cstate::{ActionData, ActionDataKeyBuilder, StateResult, TopLevelState, TopState, TopStateView}; use ctypes::errors::RuntimeError; use primitives::{Bytes, H256}; -use rlp::{decode_list, Decodable, Encodable, Rlp, RlpStream}; +use rlp::{decode_list, encode_list, Decodable, Encodable, Rlp, RlpStream}; use super::CUSTOM_ACTION_HANDLER_ID; @@ -40,6 +39,8 @@ 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 = + ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 1).append(&"Validators").into_key(); } pub fn get_delegation_key(address: &Address) -> H256 { @@ -122,6 +123,19 @@ impl Stakeholders { Ok(()) } + fn delegatees(state: &TopLevelState) -> StateResult> { + let stakeholders = Stakeholders::load_from_state(state)?; + let mut result = HashMap::new(); + for stakeholder in stakeholders.iter() { + let delegation = Delegation::load_from_state(state, stakeholder)?; + for (delegatee, quantity) in delegation.iter() { + *result.entry(*delegatee).or_default() += *quantity; + } + } + Ok(result) + } + + #[cfg(test)] pub fn contains(&self, address: &Address) -> bool { self.0.contains(address) @@ -204,7 +218,6 @@ impl<'a> Delegation<'a> { self.delegatees.get(delegatee).cloned().unwrap_or(0) } - #[cfg(test)] pub fn iter(&self) -> btree_map::Iter { self.delegatees.iter() } @@ -214,6 +227,129 @@ impl<'a> Delegation<'a> { } } +pub struct Validators(Vec<(StakeQuantity, Deposit, Public)>); +impl Validators { + pub fn load_from_state(state: &TopLevelState) -> StateResult { + let key = &*VALIDATORS_KEY; + let validators = state.action_data(&key)?.map(|data| decode_list(&data)).unwrap_or_default(); + + Ok(Validators(validators)) + } + + pub fn elect(state: &TopLevelState) -> StateResult { + let (delegation_threshold, max_num_of_validators, min_num_of_validators, min_deposit) = { + let metadata = state.metadata()?.expect("Metadata must exist"); + let common_params = metadata.params().expect("CommonParams must exist in the metadata when elect"); + ( + common_params.delegation_threshold(), + common_params.max_num_of_validators(), + common_params.min_num_of_validators(), + common_params.min_deposit(), + ) + }; + assert!(max_num_of_validators > min_num_of_validators); + + let active_candidates = Candidates::active(&state, min_deposit).unwrap(); + let candidates: HashMap<_, _> = + active_candidates.keys().map(|pubkey| (public_to_address(pubkey), *pubkey)).collect(); + + // FIXME: Remove banned accounts + // step 1 + let mut delegatees: Vec<(StakeQuantity, Public)> = Stakeholders::delegatees(&state)? + .into_iter() + .filter_map(|(address, delegation)| candidates.get(&address).map(|pubkey| (delegation, *pubkey))) + .collect(); + + delegatees.sort_unstable(); + delegatees.reverse(); + let the_highest_score_dropout = delegatees.get(max_num_of_validators).map(|(delegation, _address)| *delegation); + let the_lowest_score_first_class = delegatees.get(min_num_of_validators).map(|(delegation, _address)| *delegation) + // None means there are less than MIN_NUM_OF_VALIDATORS. Allow all remains. + .unwrap_or_default(); + + // step 2 + delegatees.truncate(max_num_of_validators); + + // step 3 + if let Some(the_highest_score_dropout) = the_highest_score_dropout { + delegatees.retain(|(delegation, _address)| *delegation > the_highest_score_dropout); + } + + if delegatees.len() < min_num_of_validators { + cerror!( + ENGINE, + "There must be something wrong. {}, {} < {}", + "delegatees.len() < min_num_of_validators", + delegatees.len(), + min_num_of_validators + ); + } + let validators = delegatees + .into_iter() + .filter(|(delegation, _pubkey)| { + // step 4 + if *delegation >= the_lowest_score_first_class { + true + } else { + // step 5 + *delegation >= delegation_threshold + } + }) + .map(|(delegation, pubkey)| { + let deposit = *active_candidates.get(&pubkey).unwrap(); + (delegation, deposit, pubkey) + }) + .collect(); + + Ok(Self(validators)) + } + + + pub fn save_to_state(&self, state: &mut TopLevelState) -> StateResult<()> { + let key = &*VALIDATORS_KEY; + if !self.is_empty() { + state.update_action_data(&key, encode_list(&self.0).to_vec())?; + } else { + state.remove_action_data(&key); + } + Ok(()) + } + + #[allow(dead_code)] + pub fn update(&mut self, block_author: Address, min_delegation: StakeQuantity) { + for (weight, _deposit, pubkey) in self.0.iter_mut().rev() { + if public_to_address(pubkey) == block_author { + // block author + *weight = weight.saturating_sub(min_delegation); + break + } + // neglecting validators + *weight = weight.saturating_sub(min_delegation * 2); + } + self.0.sort(); + } + + pub fn pubkeys(&self) -> Vec { + self.0.iter().map(|(_weight, _deposit, pubkey)| *pubkey).collect() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn total_weight(&self) -> StakeQuantity { + self.0.iter().map(|(weight, _deposit, _pubkey)| weight).sum() + } + + pub fn weight(&self, pubkey: &Public) -> Option { + self.0.iter().find(|(_weight, _deposit, val)| val == pubkey).map(|(weight, _deposit, _val)| *weight) + } +} + #[derive(Default, Debug, PartialEq)] pub struct IntermediateRewards { previous: BTreeMap, @@ -290,6 +426,12 @@ impl Candidates { Ok(()) } + fn active(state: &TopLevelState, min_deposit: Deposit) -> StateResult> { + let candidates = Self::load_from_state(state)?; + Ok(candidates.filter_active(min_deposit)) + } + + pub fn get_candidate(&self, account: &Address) -> Option<&Candidate> { self.0.get(&account) } @@ -320,6 +462,15 @@ impl Candidates { expired } + + pub fn filter_active(self, min_deposit: Deposit) -> HashMap { + self.0 + .into_iter() + .filter(|(_, candidate)| candidate.deposit >= min_deposit) + .map(|(_, deposit)| (deposit.pubkey, deposit.deposit)) + .collect() + } + pub fn remove(&mut self, address: &Address) -> Option { self.0.remove(address) } @@ -548,7 +699,6 @@ mod tests { use cstate::tests::helpers; use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; - use std::collections::HashMap; fn rng() -> XorShiftRng { let seed: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]; diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index e3dd2ad0a4..6028d9dbbc 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -30,6 +30,7 @@ use ctypes::{CommonParams, Header}; use primitives::{Bytes, H256}; use rlp::{Decodable, UntrustedRlp}; +pub use self::action_data::Validators; use self::action_data::{Candidates, Delegation, IntermediateRewards, Jail, ReleaseResult, StakeAccount, Stakeholders}; use self::actions::Action; pub use self::distribute::fee_distribute; @@ -238,6 +239,10 @@ pub fn get_stakes(state: &TopLevelState) -> StateResult> { Ok(result) } +pub fn get_validators(state: &TopLevelState) -> StateResult { + Validators::load_from_state(state) +} + pub fn add_intermediate_rewards(state: &mut TopLevelState, address: Address, reward: u64) -> StateResult<()> { let mut rewards = IntermediateRewards::load_from_state(state)?; rewards.add_quantity(address, reward); @@ -319,7 +324,8 @@ pub fn on_term_close(state: &mut TopLevelState, last_term_finished_block_num: u6 .collect(); revert_delegations(state, &reverted)?; - // TODO: validators, validator_order = elect() + let validators = Validators::elect(state)?; + validators.save_to_state(state)?; state.increase_term_id(last_term_finished_block_num)?; Ok(()) @@ -391,6 +397,15 @@ mod tests { use cstate::TopStateView; use rlp::Encodable; + fn metadata_for_election() -> TopLevelState { + let mut state = helpers::get_temp_state_with_metadata(); + state.metadata().unwrap().unwrap().set_params(CommonParams::default_for_test()); + let mut params = CommonParams::default_for_test(); + params.set_validator_num_for_test(4, 30); + assert_eq!(Ok(()), state.update_params(0, params)); + state + } + #[test] fn genesis_stakes() { let address1 = Address::random(); @@ -872,10 +887,8 @@ mod tests { let address_pubkey = Public::random(); let address = public_to_address(&address_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); increase_term_id_until(&mut state, 29); - state.add_balance(&address, 1000).unwrap(); let stake = Stake::new(HashMap::new()); @@ -915,8 +928,7 @@ mod tests { let delegator_pubkey = Public::random(); let delegator = public_to_address(&address_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); increase_term_id_until(&mut state, 29); state.add_balance(&address, 1000).unwrap(); @@ -996,8 +1008,7 @@ mod tests { let address_pubkey = Public::random(); let address = public_to_address(&address_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); let stake = Stake::new(HashMap::new()); @@ -1036,8 +1047,7 @@ mod tests { let address_pubkey = Public::random(); let address = public_to_address(&address_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); let stake = Stake::new(HashMap::new()); @@ -1091,8 +1101,7 @@ mod tests { let address_pubkey = Public::random(); let address = public_to_address(&address_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); let stake = Stake::new(HashMap::new()); @@ -1134,8 +1143,7 @@ mod tests { let address = public_to_address(&address_pubkey); let delegator = public_to_address(&delegator_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); let stake = { @@ -1179,8 +1187,7 @@ mod tests { let address = public_to_address(&address_pubkey); let delegator = public_to_address(&delegator_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); let stake = { @@ -1222,8 +1229,7 @@ mod tests { let address = public_to_address(&address_pubkey); let delegator = public_to_address(&delegator_pubkey); - let mut state = helpers::get_temp_state(); - assert_eq!(Ok(()), state.update_params(0, CommonParams::default_for_test())); + let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); let stake = { diff --git a/state/src/tests.rs b/state/src/tests.rs index af4951093f..ce41af4a02 100644 --- a/state/src/tests.rs +++ b/state/src/tests.rs @@ -24,9 +24,11 @@ pub mod helpers { use kvdb::KeyValueDB; use kvdb_memorydb; use primitives::H256; + use rlp::Encodable; + use crate::impls::TopLevelState; - use crate::{FindActionHandler, StateDB}; + use crate::{FindActionHandler, Metadata, MetadataAddress, StateDB}; pub struct TestClient {} @@ -55,6 +57,11 @@ pub mod helpers { empty_top_state(state_db) } + pub fn get_temp_state_with_metadata() -> TopLevelState { + let state_db = get_temp_state_db(); + empty_top_state_with_metadata(state_db) + } + pub fn get_test_client() -> TestClient { TestClient {} } @@ -66,6 +73,20 @@ pub mod helpers { // init trie and reset root too null let _ = TrieFactory::create(db.as_hashdb_mut(), &mut root); + TopLevelState::from_existing(db, root).expect("The empty trie root was initialized") + } + + /// Creates new state with empty state root + /// Used for tests. + fn empty_top_state_with_metadata(mut db: StateDB) -> TopLevelState { + let mut root = H256::new(); + // init trie and reset root too null + { + let mut t = TrieFactory::create(db.as_hashdb_mut(), &mut root); + t.insert(&*MetadataAddress::new(), &Metadata::new(1).rlp_bytes()).unwrap(); + } + + TopLevelState::from_existing(db, root).expect("The empty trie root was initialized") } } diff --git a/types/src/common_params.rs b/types/src/common_params.rs index 88194bfe12..5181f2969a 100644 --- a/types/src/common_params.rs +++ b/types/src/common_params.rs @@ -461,6 +461,11 @@ impl CommonParams { pub fn set_max_text_content_size(&mut self, max_text_content_size: usize) { self.max_text_content_size = max_text_content_size; } + + pub fn set_validator_num_for_test(&mut self, min: usize, max: usize) { + self.min_num_of_validators = min; + self.max_num_of_validators = max; + } } #[cfg(test)] From 608868e758ec255befea5dbde362bfc1f35213e3 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Tue, 4 Jun 2019 12:26:16 +0900 Subject: [PATCH 3/4] Implement the dynamic validator set --- core/src/client/client.rs | 3 + core/src/client/mod.rs | 2 +- core/src/client/test_client.rs | 8 +- core/src/consensus/tendermint/params.rs | 6 +- core/src/consensus/tendermint/worker.rs | 10 +- .../validator_set/dynamic_validator.rs | 195 ++++++++++++++++++ core/src/consensus/validator_set/mod.rs | 8 +- 7 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 core/src/consensus/validator_set/dynamic_validator.rs diff --git a/core/src/client/client.rs b/core/src/client/client.rs index bca9d3493c..a8913d557a 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -793,6 +793,9 @@ impl TermInfo for Client { fn state_at_term_begin(&self, id: BlockId) -> Option { if let Some(block_num) = self.last_term_finished_block_num(id) { + if block_num == 0 { + return None + } self.state_at(block_num.into()) } else { None diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 3889084750..17f0e88d3e 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -111,7 +111,7 @@ pub trait EngineClient: Sync + Send + BlockChainTrait + ImportBlock { fn get_kvdb(&self) -> Arc; } -pub trait ConsensusClient: BlockChainTrait + EngineClient + TermInfo {} +pub trait ConsensusClient: BlockChainTrait + EngineClient + EngineInfo + TermInfo + StateInfo {} pub trait TermInfo { fn last_term_finished_block_num(&self, id: BlockId) -> Option; diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index 27c28aa78c..e4cdeb6373 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -56,7 +56,7 @@ use crate::blockchain_info::BlockChainInfo; use crate::client::ImportResult; use crate::client::{ AccountData, BlockChainClient, BlockChainTrait, BlockProducer, BlockStatus, EngineInfo, ImportBlock, - MiningBlockChainClient, StateOrBlock, TermInfo, + MiningBlockChainClient, StateInfo, StateOrBlock, TermInfo, }; use crate::db::{COL_STATE, NUM_COLUMNS}; use crate::encoded; @@ -616,3 +616,9 @@ impl TermInfo for TestBlockChainClient { None } } + +impl StateInfo for TestBlockChainClient { + fn state_at(&self, _id: BlockId) -> Option { + None + } +} diff --git a/core/src/consensus/tendermint/params.rs b/core/src/consensus/tendermint/params.rs index 3373485f69..041eeb860d 100644 --- a/core/src/consensus/tendermint/params.rs +++ b/core/src/consensus/tendermint/params.rs @@ -21,14 +21,14 @@ use std::time::Duration; use cjson; use ckey::{Address, PlatformAddress}; -use super::super::validator_set::{new_validator_set, ValidatorSet}; +use super::super::validator_set::DynamicValidator; use super::types::View; use super::Step; /// `Tendermint` params. pub struct TendermintParams { /// List of validators. - pub validators: Arc, + pub validators: Arc, /// Timeout durations for different steps. pub timeouts: TimeoutParams, /// Reward per block in base units. @@ -41,7 +41,7 @@ impl From for TendermintParams { fn from(p: cjson::scheme::TendermintParams) -> Self { let dt = TimeoutParams::default(); TendermintParams { - validators: new_validator_set(p.validators), + validators: Arc::new(DynamicValidator::new(p.validators)), timeouts: TimeoutParams { propose: p.timeout_propose.map_or(dt.propose, to_duration), propose_delta: p.timeout_propose_delta.map_or(dt.propose_delta, to_duration), diff --git a/core/src/consensus/tendermint/worker.rs b/core/src/consensus/tendermint/worker.rs index c714576f96..6a12ba595d 100644 --- a/core/src/consensus/tendermint/worker.rs +++ b/core/src/consensus/tendermint/worker.rs @@ -43,7 +43,7 @@ use crate::account_provider::AccountProvider; use crate::block::*; use crate::client::ConsensusClient; use crate::consensus::signer::EngineSigner; -use crate::consensus::validator_set::ValidatorSet; +use crate::consensus::validator_set::{DynamicValidator, ValidatorSet}; use crate::consensus::vote_collector::VoteCollector; use crate::consensus::{EngineError, Seal}; use crate::encoded; @@ -59,7 +59,7 @@ type SpawnResult = ( crossbeam::Sender<()>, ); -pub fn spawn(validators: Arc) -> SpawnResult { +pub fn spawn(validators: Arc) -> SpawnResult { Worker::spawn(validators) } @@ -86,7 +86,7 @@ struct Worker { /// The last confirmed view from the commit step. last_confirmed_view: View, /// Set used to determine the current validators. - validators: Arc, + validators: Arc, /// Channel to the network extension, must be set later. extension: EventSender, time_gap_params: TimeGapParams, @@ -164,7 +164,7 @@ pub enum Event { impl Worker { /// Create a new instance of Tendermint engine fn new( - validators: Arc, + validators: Arc, extension: EventSender, client: Weak, time_gap_params: TimeGapParams, @@ -188,7 +188,7 @@ impl Worker { } } - fn spawn(validators: Arc) -> SpawnResult { + fn spawn(validators: Arc) -> SpawnResult { let (sender, receiver) = crossbeam::unbounded(); let (quit, quit_receiver) = crossbeam::bounded(1); let (external_params_initializer, external_params_receiver) = crossbeam::bounded(1); diff --git a/core/src/consensus/validator_set/dynamic_validator.rs b/core/src/consensus/validator_set/dynamic_validator.rs new file mode 100644 index 0000000000..f55615d7fc --- /dev/null +++ b/core/src/consensus/validator_set/dynamic_validator.rs @@ -0,0 +1,195 @@ +// Copyright 2019 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 std::sync::{Arc, Weak}; + +use ckey::{public_to_address, Address, Public}; +use ctypes::util::unexpected::OutOfBounds; +use parking_lot::RwLock; +use primitives::H256; + +use super::{ValidatorList, ValidatorSet}; +use crate::client::ConsensusClient; +use crate::consensus::bit_set::BitSet; +use crate::consensus::EngineError; +use consensus::stake::{get_validators, Validators}; + +/// Validator set containing a known set of public keys. +pub struct DynamicValidator { + initial_list: ValidatorList, + client: RwLock>>, +} + +impl DynamicValidator { + pub fn new(initial_validators: Vec) -> Self { + DynamicValidator { + initial_list: ValidatorList::new(initial_validators), + client: Default::default(), + } + } + + fn validators_at_term_begin(&self, parent: H256) -> Option { + let client: Arc = self.client.read().as_ref().and_then(Weak::upgrade)?; + let state = client.state_at_term_begin(parent.into())?; + Some(get_validators(&state).unwrap()) + } + + fn validators(&self, parent: H256) -> Option { + let client: Arc = self.client.read().as_ref().and_then(Weak::upgrade)?; + let block_id = parent.into(); + if client.current_term_id(block_id)? == 0 { + return None + } + let state = client.state_at(block_id)?; + Some(get_validators(&state).unwrap()) + } + + fn validators_pubkey(&self, parent: H256) -> Option> { + self.validators(parent).map(|validators| validators.pubkeys()) + } +} + +impl ValidatorSet for DynamicValidator { + fn contains(&self, parent: &H256, public: &Public) -> bool { + if let Some(validators) = self.validators_pubkey(*parent) { + validators.into_iter().any(|pubkey| pubkey == *public) + } else { + self.initial_list.contains(parent, public) + } + } + + fn contains_address(&self, parent: &H256, address: &Address) -> bool { + if let Some(validators) = self.validators_pubkey(*parent) { + validators.into_iter().any(|pubkey| public_to_address(&pubkey) == *address) + } else { + self.initial_list.contains_address(parent, address) + } + } + + fn get(&self, parent: &H256, nonce: usize) -> Public { + if let Some(validators) = self.validators_pubkey(*parent) { + let n_validators = validators.len(); + validators.into_iter().nth(nonce % n_validators).unwrap() + } else { + self.initial_list.get(parent, nonce) + } + } + + fn get_index(&self, parent: &H256, public: &Public) -> Option { + if let Some(validators) = self.validators_pubkey(*parent) { + validators.into_iter().enumerate().find(|(_index, pubkey)| pubkey == public).map(|(index, _)| index) + } else { + self.initial_list.get_index(parent, public) + } + } + + fn get_index_by_address(&self, parent: &H256, address: &Address) -> Option { + if let Some(validators) = self.validators_pubkey(*parent) { + validators + .into_iter() + .enumerate() + .find(|(_index, pubkey)| public_to_address(pubkey) == *address) + .map(|(index, _)| index) + } else { + self.initial_list.get_index_by_address(parent, address) + } + } + + fn next_block_proposer(&self, parent: &H256, view: u64) -> Option
{ + if let Some(validators) = self.validators_pubkey(*parent) { + let n_validators = validators.len(); + let nonce = view as usize % n_validators; + Some(public_to_address(validators.get(n_validators - nonce - 1).unwrap())) + } else { + self.initial_list.next_block_proposer(parent, view) + } + } + + fn count(&self, parent: &H256) -> usize { + if let Some(validators) = self.validators(*parent) { + validators.len() + } else { + self.initial_list.count(parent) + } + } + + fn check_enough_votes(&self, parent: &H256, votes: &BitSet) -> Result<(), EngineError> { + if let Some(validators_at_term_begin) = self.validators_at_term_begin(*parent) { + let validators = + self.validators(*parent).expect("The validator must exist in the middle of term").pubkeys(); + let mut weight = 0; + for index in votes.true_index_iter() { + let pubkey = validators.get(index).ok_or_else(|| { + EngineError::ValidatorNotExist { + height: 0, // FIXME + index, + } + })?; + weight += validators_at_term_begin.weight(pubkey).unwrap() as usize; + } + let total_weight = validators_at_term_begin.total_weight() as usize; + if weight * 3 > total_weight * 2 { + Ok(()) + } else { + let threshold = total_weight * 2 / 3; + Err(EngineError::BadSealFieldSize(OutOfBounds { + min: Some(threshold), + max: Some(total_weight), + found: weight, + })) + } + } else { + self.initial_list.check_enough_votes(parent, votes) + } + } + + /// Allows blockchain state access. + fn register_client(&self, client: Weak) { + self.initial_list.register_client(Weak::clone(&client)); + let mut client_lock = self.client.write(); + assert!(client_lock.is_none()); + *client_lock = Some(client); + } + + fn addresses(&self, parent: &H256) -> Vec
{ + if let Some(validators) = self.validators_pubkey(*parent) { + validators.iter().map(public_to_address).collect() + } else { + self.initial_list.addresses(parent) + } + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use ckey::Public; + + use super::super::ValidatorSet; + use super::DynamicValidator; + + #[test] + fn validator_set() { + let a1 = Public::from_str("34959b60d54703e9dfe36afb1e9950a4abe34d666cbb64c92969013bc9cc74063f9e4680d9d48c4597ee623bd4b507a1b2f43a9c5766a06463f85b73a94c51d1").unwrap(); + let a2 = Public::from_str("8c5a25bfafceea03073e2775cfb233a46648a088c12a1ca18a5865534887ccf60e1670be65b5f8e29643f463fdf84b1cbadd6027e71d8d04496570cb6b04885d").unwrap(); + let set = DynamicValidator::new(vec![a1, a2]); + assert!(set.contains(&Default::default(), &a1)); + assert_eq!(set.get(&Default::default(), 0), a1); + assert_eq!(set.get(&Default::default(), 1), a2); + assert_eq!(set.get(&Default::default(), 2), a1); + } +} diff --git a/core/src/consensus/validator_set/mod.rs b/core/src/consensus/validator_set/mod.rs index 30d0a7f79a..c777fbeba7 100644 --- a/core/src/consensus/validator_set/mod.rs +++ b/core/src/consensus/validator_set/mod.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::sync::{Arc, Weak}; +use std::sync::Weak; use ckey::{Address, Public}; use ctypes::BlockNumber; @@ -25,12 +25,10 @@ use super::BitSet; use crate::client::ConsensusClient; use crate::consensus::EngineError; +mod dynamic_validator; pub mod validator_list; -/// Creates a validator set from validator public keys. -pub fn new_validator_set(validators: Vec) -> Arc { - Arc::new(ValidatorList::new(validators)) -} +pub use self::dynamic_validator::DynamicValidator; /// A validator set. pub trait ValidatorSet: Send + Sync { From c420f4100a51efe3292ece0f1c5e12ebd5e592c0 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Tue, 11 Jun 2019 17:55:57 +0900 Subject: [PATCH 4/4] Rename ValidatorList to RoundRobinValidator --- core/src/consensus/mod.rs | 2 +- core/src/consensus/simple_poa/mod.rs | 4 ++-- .../src/consensus/validator_set/dynamic_validator.rs | 6 +++--- core/src/consensus/validator_set/mod.rs | 2 +- core/src/consensus/validator_set/validator_list.rs | 12 ++++++------ 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 226d8aa639..41a4b62290 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -32,7 +32,7 @@ pub use self::null_engine::NullEngine; pub use self::simple_poa::SimplePoA; pub use self::solo::Solo; pub use self::tendermint::{Tendermint, TendermintParams, TimeGapParams}; -pub use self::validator_set::validator_list::ValidatorList; +pub use self::validator_set::validator_list::RoundRobinValidator; pub use self::validator_set::ValidatorSet; use std::fmt; diff --git a/core/src/consensus/simple_poa/mod.rs b/core/src/consensus/simple_poa/mod.rs index c570adeed8..6e79a6f6bb 100644 --- a/core/src/consensus/simple_poa/mod.rs +++ b/core/src/consensus/simple_poa/mod.rs @@ -24,7 +24,7 @@ use parking_lot::RwLock; use self::params::SimplePoAParams; use super::signer::EngineSigner; -use super::validator_set::validator_list::ValidatorList; +use super::validator_set::validator_list::RoundRobinValidator; use super::validator_set::ValidatorSet; use super::{ConsensusEngine, EngineError, Seal}; use crate::account_provider::AccountProvider; @@ -48,7 +48,7 @@ impl SimplePoA { SimplePoA { machine, signer: Default::default(), - validators: Box::new(ValidatorList::new(params.validators)), + validators: Box::new(RoundRobinValidator::new(params.validators)), block_reward: params.block_reward, } } diff --git a/core/src/consensus/validator_set/dynamic_validator.rs b/core/src/consensus/validator_set/dynamic_validator.rs index f55615d7fc..316da3901b 100644 --- a/core/src/consensus/validator_set/dynamic_validator.rs +++ b/core/src/consensus/validator_set/dynamic_validator.rs @@ -21,7 +21,7 @@ use ctypes::util::unexpected::OutOfBounds; use parking_lot::RwLock; use primitives::H256; -use super::{ValidatorList, ValidatorSet}; +use super::{RoundRobinValidator, ValidatorSet}; use crate::client::ConsensusClient; use crate::consensus::bit_set::BitSet; use crate::consensus::EngineError; @@ -29,14 +29,14 @@ use consensus::stake::{get_validators, Validators}; /// Validator set containing a known set of public keys. pub struct DynamicValidator { - initial_list: ValidatorList, + initial_list: RoundRobinValidator, client: RwLock>>, } impl DynamicValidator { pub fn new(initial_validators: Vec) -> Self { DynamicValidator { - initial_list: ValidatorList::new(initial_validators), + initial_list: RoundRobinValidator::new(initial_validators), client: Default::default(), } } diff --git a/core/src/consensus/validator_set/mod.rs b/core/src/consensus/validator_set/mod.rs index c777fbeba7..61b228b792 100644 --- a/core/src/consensus/validator_set/mod.rs +++ b/core/src/consensus/validator_set/mod.rs @@ -20,7 +20,7 @@ use ckey::{Address, Public}; use ctypes::BlockNumber; use primitives::{Bytes, H256}; -use self::validator_list::ValidatorList; +use self::validator_list::RoundRobinValidator; use super::BitSet; use crate::client::ConsensusClient; use crate::consensus::EngineError; diff --git a/core/src/consensus/validator_set/validator_list.rs b/core/src/consensus/validator_set/validator_list.rs index 74d837f5be..f28c11ca70 100644 --- a/core/src/consensus/validator_set/validator_list.rs +++ b/core/src/consensus/validator_set/validator_list.rs @@ -29,16 +29,16 @@ use crate::consensus::EngineError; use crate::types::BlockId; /// Validator set containing a known set of public keys. -pub struct ValidatorList { +pub struct RoundRobinValidator { validators: Vec, addresses: HashSet
, client: RwLock>>, } -impl ValidatorList { +impl RoundRobinValidator { pub fn new(validators: Vec) -> Self { let addresses = validators.iter().map(public_to_address).collect(); - ValidatorList { + RoundRobinValidator { validators, addresses, client: Default::default(), @@ -46,7 +46,7 @@ impl ValidatorList { } } -impl ValidatorSet for ValidatorList { +impl ValidatorSet for RoundRobinValidator { fn contains(&self, _bh: &H256, public: &Public) -> bool { self.validators.contains(public) } @@ -117,13 +117,13 @@ mod tests { use ckey::Public; use super::super::ValidatorSet; - use super::ValidatorList; + use super::RoundRobinValidator; #[test] fn validator_set() { let a1 = Public::from_str("34959b60d54703e9dfe36afb1e9950a4abe34d666cbb64c92969013bc9cc74063f9e4680d9d48c4597ee623bd4b507a1b2f43a9c5766a06463f85b73a94c51d1").unwrap(); let a2 = Public::from_str("8c5a25bfafceea03073e2775cfb233a46648a088c12a1ca18a5865534887ccf60e1670be65b5f8e29643f463fdf84b1cbadd6027e71d8d04496570cb6b04885d").unwrap(); - let set = ValidatorList::new(vec![a1, a2]); + let set = RoundRobinValidator::new(vec![a1, a2]); assert!(set.contains(&Default::default(), &a1)); assert_eq!(set.get(&Default::default(), 0), a1); assert_eq!(set.get(&Default::default(), 1), a2);