Skip to content

Implement dynamic validator set #1574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions core/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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};
Expand Down Expand Up @@ -781,9 +780,26 @@ impl BlockChainClient for Client {
}
}

impl MetadataInfo for Client {
fn metadata(&self, id: BlockId) -> Option<Metadata> {
self.state_at(id).and_then(|state| state.metadata().unwrap())
impl TermInfo for Client {
fn last_term_finished_block_num(&self, id: BlockId) -> Option<BlockNumber> {
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<u64> {
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<TopLevelState> {
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
}
}
}

Expand Down
11 changes: 7 additions & 4 deletions core/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -111,10 +111,13 @@ pub trait EngineClient: Sync + Send + BlockChainTrait + ImportBlock {
fn get_kvdb(&self) -> Arc<KeyValueDB>;
}

pub trait ConsensusClient: BlockChainTrait + EngineClient + MetadataInfo {}
pub trait ConsensusClient: BlockChainTrait + EngineClient + EngineInfo + TermInfo + StateInfo {}

pub trait MetadataInfo {
fn metadata(&self, id: BlockId) -> Option<Metadata>;
pub trait TermInfo {
fn last_term_finished_block_num(&self, id: BlockId) -> Option<BlockNumber>;
fn current_term_id(&self, id: BlockId) -> Option<u64>;

fn state_at_term_begin(&self, id: BlockId) -> Option<TopLevelState>;
}

/// Provides methods to access account info
Expand Down
24 changes: 19 additions & 5 deletions core/src/client/test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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, StateInfo, StateOrBlock, TermInfo,
};
use crate::db::{COL_STATE, NUM_COLUMNS};
use crate::encoded;
Expand Down Expand Up @@ -603,8 +603,22 @@ impl EngineInfo for TestBlockChainClient {

impl ConsensusClient for TestBlockChainClient {}

impl MetadataInfo for TestBlockChainClient {
fn metadata(&self, _id: BlockId) -> Option<Metadata> {
impl TermInfo for TestBlockChainClient {
fn last_term_finished_block_num(&self, _id: BlockId) -> Option<BlockNumber> {
None
}

fn current_term_id(&self, _id: BlockId) -> Option<u64> {
None
}

fn state_at_term_begin(&self, _id: BlockId) -> Option<TopLevelState> {
None
}
}

impl StateInfo for TestBlockChainClient {
fn state_at(&self, _id: BlockId) -> Option<TopLevelState> {
None
}
}
2 changes: 1 addition & 1 deletion core/src/consensus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions core/src/consensus/simple_poa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
}
}
Expand Down
160 changes: 155 additions & 5 deletions core/src/consensus/stake/action_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#[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;

Expand All @@ -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 {
Expand Down Expand Up @@ -122,6 +123,19 @@ impl Stakeholders {
Ok(())
}

fn delegatees(state: &TopLevelState) -> StateResult<HashMap<Address, u64>> {
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)
Expand Down Expand Up @@ -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<Address, StakeQuantity> {
self.delegatees.iter()
}
Expand All @@ -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<Self> {
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<Self> {
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<Public> {
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<StakeQuantity> {
self.0.iter().find(|(_weight, _deposit, val)| val == pubkey).map(|(weight, _deposit, _val)| *weight)
}
}

#[derive(Default, Debug, PartialEq)]
pub struct IntermediateRewards {
previous: BTreeMap<Address, u64>,
Expand Down Expand Up @@ -290,6 +426,12 @@ impl Candidates {
Ok(())
}

fn active(state: &TopLevelState, min_deposit: Deposit) -> StateResult<HashMap<Public, Deposit>> {
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)
}
Expand Down Expand Up @@ -320,6 +462,15 @@ impl Candidates {
expired
}


pub fn filter_active(self, min_deposit: Deposit) -> HashMap<Public, Deposit> {
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<Candidate> {
self.0.remove(address)
}
Expand Down Expand Up @@ -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];
Expand Down
Loading