From 57c67b270fe2ac6083546ddaedacfd48c5e7ecc3 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Mon, 27 May 2019 19:59:10 +0900 Subject: [PATCH 1/5] Make Bitset::count() return usize instead of u32 --- core/src/consensus/tendermint/types.rs | 10 +++++++--- core/src/consensus/tendermint/worker.rs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/src/consensus/tendermint/types.rs b/core/src/consensus/tendermint/types.rs index 29d6ce472a..3c8de40c78 100644 --- a/core/src/consensus/tendermint/types.rs +++ b/core/src/consensus/tendermint/types.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . use std::cmp::PartialEq; +use std::convert::TryFrom; use std::fmt; use std::ops::Sub; @@ -216,8 +217,11 @@ impl BitSet { self.0[array_index] &= 0b1111_1111 ^ (1 << bit_index); } - pub fn count(&self) -> u32 { - self.0.iter().cloned().map(u8::count_ones).sum() + pub fn count(&self) -> usize { + self.0 + .iter() + .map(|v| usize::try_from(v.count_ones()).expect("CodeChain doesn't support 16-bits architecture")) + .sum() } pub fn true_index_iter(&self) -> BitSetIndexIterator { @@ -363,7 +367,7 @@ impl<'a> TendermintSealView<'a> { pub fn signatures(&self) -> Result, Error> { let precommits = self.precommits(); let bitset = self.bitset()?; - debug_assert_eq!(bitset.count() as usize, precommits.item_count()?); + debug_assert_eq!(bitset.count(), precommits.item_count()?); let bitset_iter = bitset.true_index_iter(); diff --git a/core/src/consensus/tendermint/worker.rs b/core/src/consensus/tendermint/worker.rs index a962f318d3..af98ff5d87 100644 --- a/core/src/consensus/tendermint/worker.rs +++ b/core/src/consensus/tendermint/worker.rs @@ -1086,7 +1086,7 @@ impl Worker { self.check_view_proposer(header.parent_hash(), header.number(), consensus_view(header)?, &proposer) .map_err(Error::from)?; let seal_view = TendermintSealView::new(header.seal()); - let bitset_count = seal_view.bitset()?.count() as usize; + let bitset_count = seal_view.bitset()?.count(); let precommits_count = seal_view.precommits().item_count()?; if bitset_count < precommits_count { From 71cd52f69b62d6bf94cb6ecafc85ec0d54663bed Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Fri, 24 May 2019 11:51:38 +0900 Subject: [PATCH 2/5] Add IntermediateRewards in the stake module --- core/src/consensus/stake/action_data.rs | 157 ++++++++++++++++++++++-- spec/Dynamic-Validator.md | 1 + 2 files changed, 151 insertions(+), 7 deletions(-) diff --git a/core/src/consensus/stake/action_data.rs b/core/src/consensus/stake/action_data.rs index 729b89f6d9..3bec081a12 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/core/src/consensus/stake/action_data.rs @@ -17,6 +17,8 @@ #[cfg(test)] use std::collections::btree_map; use std::collections::{btree_set, BTreeMap, BTreeSet}; +#[cfg(test)] +use std::mem; use ckey::Address; use cstate::{ActionData, ActionDataKeyBuilder, StateResult, TopLevelState, TopState, TopStateView}; @@ -39,6 +41,11 @@ pub fn get_delegation_key(address: &Address) -> H256 { ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 2).append(&"Delegation").append(address).into_key() } +#[cfg(test)] +pub fn get_intermediate_rewards_key() -> H256 { + ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 1).append(&"IntermediateRewards").into_key() +} + pub type StakeQuantity = u64; pub struct StakeAccount<'a> { @@ -184,6 +191,56 @@ impl<'a> Delegation<'a> { } } +#[cfg(test)] +#[derive(Default, Debug, PartialEq)] +pub struct IntermediateRewards { + previous: BTreeMap, + current: BTreeMap, +} + +#[cfg(test)] +impl IntermediateRewards { + pub fn load_from_state(state: &TopLevelState) -> StateResult { + let key = get_intermediate_rewards_key(); + let action_data = state.action_data(&key)?; + let (previous, current) = decode_map_tuple(action_data.as_ref()); + + Ok(Self { + previous, + current, + }) + } + + pub fn save_to_state(&self, state: &mut TopLevelState) -> StateResult<()> { + let key = get_intermediate_rewards_key(); + if self.previous.is_empty() && self.current.is_empty() { + state.remove_action_data(&key); + } else { + let encoded = encode_map_tuple(&self.previous, &self.current); + state.update_action_data(&key, encoded)?; + } + Ok(()) + } + + pub fn add_quantity(&mut self, address: Address, quantity: StakeQuantity) { + if quantity == 0 { + return + } + *self.current.entry(address).or_insert(0) += quantity; + } + + pub fn drain_previous(&mut self) -> BTreeMap { + let mut new = BTreeMap::new(); + mem::swap(&mut new, &mut self.previous); + new + } + + pub fn move_current_to_previous(&mut self) { + assert!(self.previous.is_empty()); + mem::swap(&mut self.previous, &mut self.current); + } +} + fn decode_set(data: Option<&ActionData>) -> BTreeSet where V: Ord + Decodable, { @@ -212,14 +269,23 @@ fn decode_map(data: Option<&ActionData>) -> BTreeMap where K: Ord + Decodable, V: Decodable, { - let mut result = BTreeMap::new(); if let Some(rlp) = data.map(|x| Rlp::new(x)) { - for record in rlp.iter() { - let key: K = record.val_at(0); - let value: V = record.val_at(1); - assert_eq!(2, record.item_count()); - result.insert(key, value); - } + decode_map_impl(rlp) + } else { + Default::default() + } +} + +fn decode_map_impl(rlp: Rlp) -> BTreeMap +where + K: Ord + Decodable, + V: Decodable, { + let mut result = BTreeMap::new(); + for record in rlp.iter() { + let key: K = record.val_at(0); + let value: V = record.val_at(1); + assert_eq!(2, record.item_count()); + result.insert(key, value); } result } @@ -229,12 +295,48 @@ where K: Ord + Encodable, V: Encodable, { let mut rlp = RlpStream::new(); + encode_map_impl(&mut rlp, map); + rlp.drain().into_vec() +} + +fn encode_map_impl(rlp: &mut RlpStream, map: &BTreeMap) +where + K: Ord + Encodable, + V: Encodable, { rlp.begin_list(map.len()); for (key, value) in map.iter() { let record = rlp.begin_list(2); record.append(key); record.append(value); } +} + +#[cfg(test)] +fn decode_map_tuple(data: Option<&ActionData>) -> (BTreeMap, BTreeMap) +where + K: Ord + Decodable, + V: Decodable, { + if let Some(rlp) = data.map(|x| Rlp::new(x)) { + assert_eq!(2, rlp.item_count()); + let map0 = decode_map_impl(rlp.at(0)); + let map1 = decode_map_impl(rlp.at(1)); + (map0, map1) + } else { + Default::default() + } +} + +#[cfg(test)] +fn encode_map_tuple(map0: &BTreeMap, map1: &BTreeMap) -> Vec +where + K: Ord + Encodable, + V: Encodable, { + let mut rlp = RlpStream::new(); + rlp.begin_list(2); + + encode_map_impl(&mut rlp, map0); + encode_map_impl(&mut rlp, map1); + rlp.drain().into_vec() } @@ -501,4 +603,45 @@ mod tests { let result = state.action_data(&get_delegation_key(&delegator)).unwrap(); assert_eq!(result, None); } + + #[test] + fn load_and_save_intermediate_rewards() { + let mut state = helpers::get_temp_state(); + let rewards = IntermediateRewards::load_from_state(&state).unwrap(); + rewards.save_to_state(&mut state).unwrap(); + } + + #[test] + fn add_quantity() { + let address1 = Address::random(); + let address2 = Address::random(); + let mut state = helpers::get_temp_state(); + let mut origin_rewards = IntermediateRewards::load_from_state(&state).unwrap(); + origin_rewards.add_quantity(address1, 1); + origin_rewards.add_quantity(address2, 2); + origin_rewards.save_to_state(&mut state).unwrap(); + let recovered_rewards = IntermediateRewards::load_from_state(&state).unwrap(); + assert_eq!(origin_rewards, recovered_rewards); + } + + #[test] + fn drain() { + let address1 = Address::random(); + let address2 = Address::random(); + let mut state = helpers::get_temp_state(); + let mut origin_rewards = IntermediateRewards::load_from_state(&state).unwrap(); + origin_rewards.add_quantity(address1, 1); + origin_rewards.add_quantity(address2, 2); + origin_rewards.save_to_state(&mut state).unwrap(); + let mut recovered_rewards = IntermediateRewards::load_from_state(&state).unwrap(); + assert_eq!(origin_rewards, recovered_rewards); + let _drained = recovered_rewards.drain_previous(); + recovered_rewards.save_to_state(&mut state).unwrap(); + let mut final_rewards = IntermediateRewards::load_from_state(&state).unwrap(); + assert_eq!(BTreeMap::new(), final_rewards.previous); + let current = final_rewards.current.clone(); + final_rewards.move_current_to_previous(); + assert_eq!(BTreeMap::new(), final_rewards.current); + assert_eq!(current, final_rewards.previous); + } } diff --git a/spec/Dynamic-Validator.md b/spec/Dynamic-Validator.md index 7f1e1262c0..3c145771de 100644 --- a/spec/Dynamic-Validator.md +++ b/spec/Dynamic-Validator.md @@ -223,6 +223,7 @@ pending_rewards = [ [withdraw_at, address, quantity]+ ], [withdraw_at, address] banned = [ address+ ], address asc jailed = [ [address, deposits, custody_until, kicked_at]+ ], address asc term_id = [ the last block number of the previous term, the current term id ] +intermediate_rewards = [ [ address, rewards ]+ address asc, [ address, rewards ]+ address asc ] ``` ### on TermEnd events From 408bc36827f025816980e582e545a111429fecb9 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Fri, 24 May 2019 14:16:51 +0900 Subject: [PATCH 3/5] Pay block proposal rewards in a term at once Currently, the block reward is paid per block; this patch makes the reward is paid at the end of the term if the term seconds is determined. Solo and Tendermint are different. Solo pays the fee at the end of the term, but Tendermint pays the fee at the end of the next term. --- core/src/consensus/mod.rs | 2 ++ core/src/consensus/solo/mod.rs | 15 ++++++++++++--- core/src/consensus/stake/action_data.rs | 6 ------ core/src/consensus/stake/distribute.rs | 16 ++++++++++------ core/src/consensus/stake/mod.rs | 25 +++++++++++++++++++++++-- core/src/consensus/tendermint/engine.rs | 15 ++++++++++++--- state/src/item/metadata.rs | 4 ++++ 7 files changed, 63 insertions(+), 20 deletions(-) diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 4e99808d91..f6a5380c1f 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -312,6 +312,7 @@ pub enum EngineError { BadSealFieldSize(OutOfBounds), /// Malformed consensus message. MalformedMessage(String), + CannotOpenBlock, } impl fmt::Display for EngineError { @@ -340,6 +341,7 @@ impl fmt::Display for EngineError { UnexpectedMessage => "This Engine should not be fed messages.".into(), BadSealFieldSize(oob) => format!("Seal field has an unexpected length: {}", oob), MalformedMessage(msg) => format!("Received malformed consensus message: {}", msg), + CannotOpenBlock => "Cannot open a block".to_string(), }; f.write_fmt(format_args!("Engine error ({})", msg)) diff --git a/core/src/consensus/solo/mod.rs b/core/src/consensus/solo/mod.rs index ae0dcbaf29..71be4f9ba2 100644 --- a/core/src/consensus/solo/mod.rs +++ b/core/src/consensus/solo/mod.rs @@ -96,16 +96,20 @@ impl ConsensusEngine for Solo { assert!(total_reward >= total_min_fee, "{} >= {}", total_reward, total_min_fee); let stakes = stake::get_stakes(block.state()).expect("Cannot get Stake status"); - for (address, share) in stake::fee_distribute(total_min_fee, &stakes) { + + let mut distributor = stake::fee_distribute(total_min_fee, &stakes); + for (address, share) in &mut distributor { self.machine.add_balance(block, &address, share)? } - let stakeholders_share = stake::stakeholders_share(total_min_fee, &stakes); - self.machine.add_balance(block, &author, total_reward - stakeholders_share)?; + + let block_author_reward = total_reward - total_min_fee + distributor.remaining_fee(); let term_seconds = parent_common_params.term_seconds(); if term_seconds == 0 { + self.machine.add_balance(block, &author, block_author_reward)?; return Ok(()) } + stake::add_intermediate_rewards(block.state_mut(), author, block_author_reward)?; let (last_term_finished_block_num, current_term_id) = { let header = block.header(); let term_id = header.timestamp() / term_seconds; @@ -115,6 +119,11 @@ impl ConsensusEngine for Solo { } (header.number(), term_id) }; + stake::move_current_to_previous_intermediate_rewards(&mut block.state_mut())?; + let rewards = stake::drain_previous_rewards(&mut block.state_mut())?; + for (address, reward) in rewards { + self.machine.add_balance(block, &address, reward)?; + } self.machine.change_term_id(block, last_term_finished_block_num, current_term_id)?; Ok(()) } diff --git a/core/src/consensus/stake/action_data.rs b/core/src/consensus/stake/action_data.rs index 3bec081a12..af0089e1ef 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/core/src/consensus/stake/action_data.rs @@ -17,7 +17,6 @@ #[cfg(test)] use std::collections::btree_map; use std::collections::{btree_set, BTreeMap, BTreeSet}; -#[cfg(test)] use std::mem; use ckey::Address; @@ -41,7 +40,6 @@ pub fn get_delegation_key(address: &Address) -> H256 { ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 2).append(&"Delegation").append(address).into_key() } -#[cfg(test)] pub fn get_intermediate_rewards_key() -> H256 { ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 1).append(&"IntermediateRewards").into_key() } @@ -191,14 +189,12 @@ impl<'a> Delegation<'a> { } } -#[cfg(test)] #[derive(Default, Debug, PartialEq)] pub struct IntermediateRewards { previous: BTreeMap, current: BTreeMap, } -#[cfg(test)] impl IntermediateRewards { pub fn load_from_state(state: &TopLevelState) -> StateResult { let key = get_intermediate_rewards_key(); @@ -311,7 +307,6 @@ where } } -#[cfg(test)] fn decode_map_tuple(data: Option<&ActionData>) -> (BTreeMap, BTreeMap) where K: Ord + Decodable, @@ -326,7 +321,6 @@ where } } -#[cfg(test)] fn encode_map_tuple(map0: &BTreeMap, map1: &BTreeMap) -> Vec where K: Ord + Encodable, diff --git a/core/src/consensus/stake/distribute.rs b/core/src/consensus/stake/distribute.rs index eec8bc48a2..de703409a0 100644 --- a/core/src/consensus/stake/distribute.rs +++ b/core/src/consensus/stake/distribute.rs @@ -30,10 +30,6 @@ pub fn fee_distribute(total_min_fee: u64, stakes: &HashMap) -> Fee } } -pub fn stakeholders_share(total_min_fee: u64, stakes: &HashMap) -> u64 { - fee_distribute(total_min_fee, stakes).map(|(_, fee)| fee).sum() -} - fn share(total_stakes: u64, stake: u64, total_min_fee: u64) -> u64 { assert!(total_stakes >= stake); u64::try_from((u128::from(total_min_fee) * u128::from(stake)) / u128::from(total_stakes)).unwrap() @@ -46,6 +42,12 @@ pub struct FeeDistributeIter<'a> { stake_holdings: hash_map::Iter<'a, Address, u64>, } +impl<'a> FeeDistributeIter<'a> { + pub fn remaining_fee(&self) -> u64 { + self.remaining_fee + } +} + impl<'a> Iterator for FeeDistributeIter<'a> { type Item = (&'a Address, u64); fn next(&mut self) -> Option<(&'a Address, u64)> { @@ -89,8 +91,10 @@ mod tests { } let total = 100; - let shares: HashMap = fee_distribute(total, &stakes).map(|(k, v)| (*k, v)).collect(); - let author_share = total - stakeholders_share(total, &stakes); + let mut iter = fee_distribute(total, &stakes); + let shares: HashMap = (&mut iter).map(|(k, v)| (*k, v)).collect(); + + let author_share = iter.remaining_fee(); assert_eq!(49, author_share); assert_eq!(shares, { diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index 6017f50889..3a8645b3cd 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -18,6 +18,7 @@ mod action_data; mod actions; mod distribute; +use std::collections::btree_map::BTreeMap; use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; @@ -31,9 +32,9 @@ use ctypes::{CommonParams, Header}; use primitives::H256; use rlp::{Decodable, UntrustedRlp}; -use self::action_data::{Delegation, StakeAccount, Stakeholders}; +use self::action_data::{Delegation, IntermediateRewards, StakeAccount, Stakeholders}; use self::actions::Action; -pub use self::distribute::{fee_distribute, stakeholders_share}; +pub use self::distribute::fee_distribute; use consensus::ValidatorSet; const CUSTOM_ACTION_HANDLER_ID: u64 = 2; @@ -218,6 +219,26 @@ pub fn get_stakes(state: &TopLevelState) -> StateResult> { Ok(result) } +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); + rewards.save_to_state(state)?; + Ok(()) +} + +pub fn drain_previous_rewards(state: &mut TopLevelState) -> StateResult> { + let mut rewards = IntermediateRewards::load_from_state(state)?; + let drained = rewards.drain_previous(); + rewards.save_to_state(state)?; + Ok(drained) +} + +pub fn move_current_to_previous_intermediate_rewards(state: &mut TopLevelState) -> StateResult<()> { + let mut rewards = IntermediateRewards::load_from_state(state)?; + rewards.move_current_to_previous(); + rewards.save_to_state(state) +} + fn change_params( state: &mut TopLevelState, metadata_seq: u64, diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 2ac9170027..963f175451 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -146,16 +146,19 @@ impl ConsensusEngine for Tendermint { assert!(total_reward >= total_min_fee, "{} >= {}", total_reward, total_min_fee); let stakes = stake::get_stakes(block.state()).expect("Cannot get Stake status"); - for (address, share) in stake::fee_distribute(total_min_fee, &stakes) { + let mut distributor = stake::fee_distribute(total_min_fee, &stakes); + for (address, share) in &mut distributor { self.machine.add_balance(block, &address, share)? } - let stakeholders_share = stake::stakeholders_share(total_min_fee, &stakes); - self.machine.add_balance(block, &author, total_reward - stakeholders_share)?; + + let block_author_reward = total_reward - total_min_fee + distributor.remaining_fee(); let term_seconds = parent_common_params.term_seconds(); if term_seconds == 0 { + self.machine.add_balance(block, &author, block_author_reward)?; return Ok(()) } + stake::add_intermediate_rewards(block.state_mut(), author, block_author_reward)?; let (last_term_finished_block_num, current_term_id) = { let header = block.header(); let term_id = header.timestamp() / term_seconds; @@ -165,7 +168,13 @@ impl ConsensusEngine for Tendermint { } (header.number(), term_id) }; + let rewards = stake::drain_previous_rewards(&mut block.state_mut())?; + for (address, reward) in rewards { + self.machine.add_balance(block, &address, reward)?; + } + self.machine.change_term_id(block, last_term_finished_block_num, current_term_id)?; + stake::move_current_to_previous_intermediate_rewards(&mut block.state_mut())?; Ok(()) } diff --git a/state/src/item/metadata.rs b/state/src/item/metadata.rs index cad3ea466a..5c7f119b76 100644 --- a/state/src/item/metadata.rs +++ b/state/src/item/metadata.rs @@ -99,6 +99,10 @@ impl Metadata { self.term.last_term_finished_block_num = last_term_finished_block_num; self.term.current_term_id = current_term_id; } + + pub fn last_term_finished_block_num(&self) -> u64 { + self.term.last_term_finished_block_num + } } impl Default for Metadata { From 9698d695f2fc19b5860683c4059828a958605f69 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Mon, 27 May 2019 13:51:11 +0900 Subject: [PATCH 4/5] Replace EngineClient with ConsensusClient --- core/src/client/client.rs | 3 +++ core/src/client/mod.rs | 2 ++ core/src/client/test_client.rs | 3 +++ core/src/consensus/mod.rs | 4 ++-- core/src/consensus/simple_poa/mod.rs | 4 ++-- core/src/consensus/tendermint/engine.rs | 4 ++-- core/src/consensus/tendermint/mod.rs | 10 +++++----- core/src/consensus/tendermint/worker.rs | 14 +++++++------- core/src/consensus/validator_set/mod.rs | 4 ++-- 9 files changed, 28 insertions(+), 20 deletions(-) diff --git a/core/src/client/client.rs b/core/src/client/client.rs index e5b0782679..6e52de263a 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -45,6 +45,7 @@ use super::{ }; use crate::block::{ClosedBlock, IsBlock, OpenBlock, SealedBlock}; use crate::blockchain::{BlockChain, BlockProvider, BodyProvider, HeaderProvider, InvoiceProvider, TransactionAddress}; +use crate::client::ConsensusClient; use crate::consensus::CodeChainEngine; use crate::encoded; use crate::error::{BlockImportError, Error, ImportError, SchemeError}; @@ -563,6 +564,8 @@ impl EngineClient for Client { } } +impl ConsensusClient for Client {} + impl BlockChainTrait for Client { fn chain_info(&self) -> BlockChainInfo { let mut chain_info = self.block_chain().chain_info(); diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 655d6cb380..e295af0456 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -111,6 +111,8 @@ pub trait EngineClient: Sync + Send + BlockChainTrait + ImportBlock { fn get_kvdb(&self) -> Arc; } +pub trait ConsensusClient: BlockChainTrait + EngineClient {} + /// Provides methods to access account info pub trait AccountData { /// Attempt to get address seq at given block. diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index f0db2d5758..653abe5fb2 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -65,6 +65,7 @@ use crate::miner::{Miner, MinerService, TransactionImportResult}; use crate::scheme::Scheme; use crate::transaction::{LocalizedTransaction, PendingSignedTransactions, SignedTransaction}; use crate::types::{BlockId, TransactionId, VerificationQueueInfo as QueueInfo}; +use client::ConsensusClient; /// Test client. pub struct TestBlockChainClient { @@ -599,3 +600,5 @@ impl EngineInfo for TestBlockChainClient { unimplemented!() } } + +impl ConsensusClient for TestBlockChainClient {} diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index f6a5380c1f..e05f77f5f4 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -49,7 +49,7 @@ use primitives::{Bytes, H256, U256}; use self::tendermint::types::{BitSet, View}; use crate::account_provider::AccountProvider; use crate::block::{ExecutedBlock, SealedBlock}; -use crate::client::EngineClient; +use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; use crate::encoded; use crate::error::Error; @@ -222,7 +222,7 @@ pub trait ConsensusEngine: Sync + Send { } /// Add Client which can be used for sealing, potentially querying the state and sending messages. - fn register_client(&self, _client: Weak) {} + fn register_client(&self, _client: Weak) {} /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. diff --git a/core/src/consensus/simple_poa/mod.rs b/core/src/consensus/simple_poa/mod.rs index 0fa54a4956..c570adeed8 100644 --- a/core/src/consensus/simple_poa/mod.rs +++ b/core/src/consensus/simple_poa/mod.rs @@ -29,7 +29,7 @@ use super::validator_set::ValidatorSet; use super::{ConsensusEngine, EngineError, Seal}; use crate::account_provider::AccountProvider; use crate::block::ExecutedBlock; -use crate::client::EngineClient; +use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; use crate::consensus::EngineType; use crate::error::{BlockError, Error}; @@ -125,7 +125,7 @@ impl ConsensusEngine for SimplePoA { self.machine.add_balance(block, &author, total_reward) } - fn register_client(&self, client: Weak) { + fn register_client(&self, client: Weak) { self.validators.register_client(client); } diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 963f175451..4849849ec3 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -33,7 +33,7 @@ use super::worker; use super::{ChainNotify, Tendermint, SEAL_FIELDS}; use crate::account_provider::AccountProvider; use crate::block::*; -use crate::client::{Client, EngineClient}; +use crate::client::{Client, ConsensusClient}; use crate::codechain_machine::CodeChainMachine; use crate::consensus::EngineType; use crate::error::Error; @@ -178,7 +178,7 @@ impl ConsensusEngine for Tendermint { Ok(()) } - fn register_client(&self, client: Weak) { + fn register_client(&self, client: Weak) { *self.client.write() = Some(Weak::clone(&client)); } diff --git a/core/src/consensus/tendermint/mod.rs b/core/src/consensus/tendermint/mod.rs index c06a5e69c1..d0de272f20 100644 --- a/core/src/consensus/tendermint/mod.rs +++ b/core/src/consensus/tendermint/mod.rs @@ -37,7 +37,7 @@ use self::chain_notify::TendermintChainNotify; pub use self::params::{TendermintParams, TimeGapParams, TimeoutParams}; use self::types::{Height, Step, View}; use super::stake; -use crate::client::EngineClient; +use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; use ChainNotify; @@ -55,9 +55,9 @@ pub type BlockHash = H256; /// ConsensusEngine using `Tendermint` consensus algorithm pub struct Tendermint { - client: RwLock>>, + client: RwLock>>, external_params_initializer: crossbeam::Sender, - extension_initializer: crossbeam::Sender<(crossbeam::Sender, Weak)>, + extension_initializer: crossbeam::Sender<(crossbeam::Sender, Weak)>, timeouts: TimeoutParams, join: Option>, quit_tendermint: crossbeam::Sender<()>, @@ -140,8 +140,8 @@ mod tests { let test = TestBlockChainClient::new_with_scheme(Scheme::new_test_tendermint()); let test_client: Arc = Arc::new(test); - let engine_client = Arc::clone(&test_client) as Arc; - scheme.engine.register_client(Arc::downgrade(&engine_client)); + let consensus_client = Arc::clone(&test_client) as Arc; + scheme.engine.register_client(Arc::downgrade(&consensus_client)); (scheme, tap, test_client) } diff --git a/core/src/consensus/tendermint/worker.rs b/core/src/consensus/tendermint/worker.rs index af98ff5d87..2ae5edea72 100644 --- a/core/src/consensus/tendermint/worker.rs +++ b/core/src/consensus/tendermint/worker.rs @@ -40,7 +40,7 @@ use super::{ }; use crate::account_provider::AccountProvider; use crate::block::*; -use crate::client::EngineClient; +use crate::client::ConsensusClient; use crate::consensus::signer::EngineSigner; use crate::consensus::validator_set::ValidatorSet; use crate::consensus::vote_collector::VoteCollector; @@ -53,7 +53,7 @@ use crate::BlockId; type SpawnResult = ( JoinHandle<()>, crossbeam::Sender, - crossbeam::Sender<(crossbeam::Sender, Weak)>, + crossbeam::Sender<(crossbeam::Sender, Weak)>, crossbeam::Sender, crossbeam::Sender<()>, ); @@ -63,7 +63,7 @@ pub fn spawn(validators: Arc) -> SpawnResult { } struct Worker { - client: Weak, + client: Weak, /// Blockchain height. height: Height, /// Consensus view. @@ -137,7 +137,7 @@ pub enum Event { signature: SchnorrSignature, view: View, message: Bytes, - result: crossbeam::Sender>>, + result: crossbeam::Sender>>, }, StepState { token: NodeId, @@ -165,7 +165,7 @@ impl Worker { fn new( validators: Arc, extension: EventSender, - client: Weak, + client: Weak, time_gap_params: TimeGapParams, ) -> Self { Worker { @@ -347,7 +347,7 @@ impl Worker { } /// The client is a thread-safe struct. Using it in multi-threads is safe. - fn client(&self) -> Arc { + fn client(&self) -> Arc { self.client.upgrade().expect("Client lives longer than consensus") } @@ -1473,7 +1473,7 @@ impl Worker { signature: SchnorrSignature, proposed_view: View, bytes: Bytes, - ) -> Option> { + ) -> Option> { let c = match self.client.upgrade() { Some(c) => c, None => return None, diff --git a/core/src/consensus/validator_set/mod.rs b/core/src/consensus/validator_set/mod.rs index a706b1197b..baf94a1068 100644 --- a/core/src/consensus/validator_set/mod.rs +++ b/core/src/consensus/validator_set/mod.rs @@ -21,7 +21,7 @@ use ctypes::BlockNumber; use primitives::{Bytes, H256}; use self::validator_list::ValidatorList; -use crate::client::EngineClient; +use crate::client::ConsensusClient; pub mod null_validator; pub mod validator_list; @@ -62,5 +62,5 @@ pub trait ValidatorSet: Send + Sync { /// Notifies about benign misbehaviour. fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber) {} /// Allows blockchain state access. - fn register_client(&self, _client: Weak) {} + fn register_client(&self, _client: Weak) {} } From 7b879f40a6f118e9400f1aaccf7e89c8999eb859 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Mon, 27 May 2019 16:42:23 +0900 Subject: [PATCH 5/5] Penalty disloyal validators and give additional rewards to diligent validators --- Cargo.lock | 22 ++ core/Cargo.toml | 1 + core/src/client/client.rs | 11 +- core/src/client/mod.rs | 8 +- core/src/client/test_client.rs | 10 +- core/src/consensus/tendermint/engine.rs | 289 +++++++++++++++++- core/src/consensus/tendermint/mod.rs | 7 +- core/src/consensus/validator_set/mod.rs | 2 + .../consensus/validator_set/null_validator.rs | 4 + .../consensus/validator_set/validator_list.rs | 4 + core/src/lib.rs | 1 + 11 files changed, 348 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d0a673f3f..e7ef0c5b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,7 @@ dependencies = [ "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "memorydb 0.1.1", + "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", @@ -1644,6 +1645,15 @@ name = "nodrop" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-bigint" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.39" @@ -1652,6 +1662,16 @@ dependencies = [ "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num-rational" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.2.5" @@ -3187,7 +3207,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0" "checksum never-type 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c026cbdf8890d6cab75bd177f36e948bf3a512c61f3e93898fd288f571da6551" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" +"checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" "checksum num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "630de1ef5cc79d0cdd78b7e33b81f083cbfe90de0f4b2b2f07f905867c70e9fe" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c" diff --git a/core/Cargo.toml b/core/Cargo.toml index 3dc6b6f4ab..8b0043cd0d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -29,6 +29,7 @@ kvdb = { path = "../util/kvdb" } kvdb-rocksdb = { path = "../util/kvdb-rocksdb" } kvdb-memorydb = { path = "../util/kvdb-memorydb" } memorydb = { path = "../util/memorydb" } +num-rational = "0.2.1" parking_lot = "0.6.0" primitives = { git = "https://github.com/CodeChain-io/rust-codechain-primitives.git", version = "0.4" } rand = "0.6.1" diff --git a/core/src/client/client.rs b/core/src/client/client.rs index 6e52de263a..31193ce59f 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -24,7 +24,8 @@ use ckey::{Address, PlatformAddress, Public}; use cmerkle::Result as TrieResult; use cnetwork::NodeId; use cstate::{ - ActionHandler, AssetScheme, FindActionHandler, OwnedAsset, StateDB, StateResult, Text, TopLevelState, TopStateView, + ActionHandler, AssetScheme, FindActionHandler, Metadata, OwnedAsset, StateDB, StateResult, Text, TopLevelState, + TopStateView, }; use ctimer::{TimeoutHandler, TimerApi, TimerScheduleError, TimerToken}; use ctypes::transaction::{AssetTransferInput, PartialHashing, ShardTransaction}; @@ -45,7 +46,7 @@ use super::{ }; use crate::block::{ClosedBlock, IsBlock, OpenBlock, SealedBlock}; use crate::blockchain::{BlockChain, BlockProvider, BodyProvider, HeaderProvider, InvoiceProvider, TransactionAddress}; -use crate::client::ConsensusClient; +use crate::client::{ConsensusClient, MetadataInfo}; use crate::consensus::CodeChainEngine; use crate::encoded; use crate::error::{BlockImportError, Error, ImportError, SchemeError}; @@ -780,6 +781,12 @@ 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 AccountData for Client { fn seq(&self, address: &Address, id: BlockId) -> Option { self.state_at(id).and_then(|s| s.seq(address).ok()) diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index e295af0456..f2a7219f88 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, OwnedAsset, StateResult, Text, TopLevelState, TopStateView}; +use cstate::{AssetScheme, FindActionHandler, Metadata, OwnedAsset, StateResult, Text, TopLevelState, TopStateView}; use ctypes::transaction::{AssetTransferInput, PartialHashing, ShardTransaction}; use ctypes::{BlockNumber, CommonParams, ShardId}; use cvm::ChainTimeInfo; @@ -111,7 +111,11 @@ pub trait EngineClient: Sync + Send + BlockChainTrait + ImportBlock { fn get_kvdb(&self) -> Arc; } -pub trait ConsensusClient: BlockChainTrait + EngineClient {} +pub trait ConsensusClient: BlockChainTrait + EngineClient + MetadataInfo {} + +pub trait MetadataInfo { + fn metadata(&self, id: BlockId) -> Option; +} /// Provides methods to access account info pub trait AccountData { diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index 653abe5fb2..9934a64e83 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, StateDB}; +use cstate::{FindActionHandler, Metadata, StateDB}; use ctimer::{TimeoutHandler, TimerToken}; use ctypes::transaction::{Action, Transaction}; use ctypes::{BlockNumber, CommonParams, Header as BlockHeader}; @@ -55,7 +55,7 @@ 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, + AccountData, BlockChainClient, BlockChainTrait, BlockProducer, BlockStatus, EngineInfo, ImportBlock, MetadataInfo, MiningBlockChainClient, StateOrBlock, }; use crate::db::{COL_STATE, NUM_COLUMNS}; @@ -602,3 +602,9 @@ impl EngineInfo for TestBlockChainClient { } impl ConsensusClient for TestBlockChainClient {} + +impl MetadataInfo for TestBlockChainClient { + fn metadata(&self, _id: BlockId) -> Option { + None + } +} diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 4849849ec3..19050fdd24 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -14,6 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use std::collections::btree_map::BTreeMap; +use std::collections::HashMap; +use std::convert::TryFrom; use std::iter::Iterator; use std::sync::atomic::Ordering as AtomicOrdering; use std::sync::{Arc, Weak}; @@ -21,21 +24,23 @@ use std::sync::{Arc, Weak}; use ckey::Address; use cnetwork::NetworkService; use crossbeam_channel as crossbeam; -use cstate::ActionHandler; +use cstate::{ActionHandler, TopStateView}; use ctypes::{CommonParams, Header}; +use num_rational::Ratio; use primitives::H256; use super::super::stake; use super::super::{ConsensusEngine, EngineError, Seal}; use super::network::TendermintExtension; pub use super::params::{TendermintParams, TimeoutParams}; +use super::types::TendermintSealView; use super::worker; use super::{ChainNotify, Tendermint, SEAL_FIELDS}; use crate::account_provider::AccountProvider; use crate::block::*; use crate::client::{Client, ConsensusClient}; use crate::codechain_machine::CodeChainMachine; -use crate::consensus::EngineType; +use crate::consensus::{EngineType, ValidatorSet}; use crate::error::Error; use crate::views::HeaderView; use consensus::tendermint::params::TimeGapParams; @@ -169,7 +174,31 @@ impl ConsensusEngine for Tendermint { (header.number(), term_id) }; let rewards = stake::drain_previous_rewards(&mut block.state_mut())?; - for (address, reward) in rewards { + let client = self + .client + .read() + .as_ref() + .ok_or(EngineError::CannotOpenBlock)? + .upgrade() + .ok_or(EngineError::CannotOpenBlock)?; + + 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(); + + (end_of_the_one_level_previous_term + 1, end_of_the_two_level_previous_term + 1) + }; + + let pending_rewards = calculate_pending_rewards_of_the_previous_term( + &*client, + &*self.validators, + rewards, + start_of_the_current_term, + start_of_the_previous_term, + )?; + + for (address, reward) in pending_rewards { self.machine.add_balance(block, &address, reward)?; } @@ -263,3 +292,257 @@ impl ConsensusEngine for Tendermint { &self.action_handlers } } + +fn calculate_pending_rewards_of_the_previous_term( + chain: &ConsensusClient, + validators: &ValidatorSet, + rewards: BTreeMap, + start_of_the_current_term: u64, + start_of_the_previous_term: u64, +) -> Result, Error> { + let authors = { + let header = chain.block_header(&start_of_the_previous_term.into()).unwrap(); + validators.addresses(&header.parent_hash()) + }; + let mut pending_rewards: HashMap = authors.iter().map(|author| (*author, 0)).collect(); + + let mut missed_signatures = HashMap::::with_capacity(30); + let mut signed_blocks = HashMap::::with_capacity(30); + + let mut header = chain.block_header(&start_of_the_current_term.into()).unwrap(); + while start_of_the_previous_term != header.number() { + for index in TendermintSealView::new(&header.seal()).bitset()?.true_index_iter() { + // FIXME: Change it after implementing ban + *signed_blocks.entry(authors[index]).or_default() += 1; + } + + header = chain.block_header(&header.parent_hash().into()).unwrap(); + + let author = header.author(); + let (proposed, missed) = missed_signatures.entry(author).or_default(); + *proposed += 1; + // FIXME: Consider banned accounts + *missed += authors.len() - TendermintSealView::new(&header.seal()).bitset()?.count(); + } + + let mut reduced_rewards = 0; + + // Penalty disloyal validators + let number_of_blocks_in_term = start_of_the_current_term - start_of_the_previous_term; + for (address, intermediate_reward) in rewards { + // FIXME: Consider banned accounts + let number_of_signatures = u64::try_from(*signed_blocks.get(&address).unwrap()).unwrap(); + let final_block_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + reduced_rewards += intermediate_reward - final_block_rewards; + pending_rewards.insert(address, final_block_rewards); + } + + // Give additional rewards + give_additional_rewards(reduced_rewards, missed_signatures, |address, reward| { + pending_rewards.insert(*address, reward); + Ok(()) + })?; + + Ok(pending_rewards) +} + +/// reward = floor(intermediate_rewards * (a * number_of_signatures / number_of_blocks_in_term + b) / 10) +fn final_rewards(intermediate_reward: u64, number_of_signatures: u64, number_of_blocks_in_term: u64) -> u64 { + let (a, b) = if number_of_signatures * 3 >= number_of_blocks_in_term * 2 { + // number_of_signatures / number_of_blocks_in_term >= 2 / 3 + // x * 3/10 + 7/10 + (3, 7) + } else if number_of_signatures * 3 >= number_of_blocks_in_term { + // number_of_signatures / number_of_blocks_in_term >= 1 / 3 + // x * 24/10 - 7/10 + (24, -7) + } else { + // 1 / 3 > number_of_signatures / number_of_blocks_in_term + // x * 3/10 + 0 + assert!( + number_of_blocks_in_term > 3 * number_of_signatures, + "number_of_signatures / number_of_blocks_in_term = {}", + (number_of_signatures as f64) / (number_of_blocks_in_term as f64) + ); + (3, 0) + }; + let numerator = i128::from(intermediate_reward) + * (a * i128::from(number_of_signatures) + b * i128::from(number_of_blocks_in_term)); + assert!(numerator >= 0); + let denominator = 10 * i128::from(number_of_blocks_in_term); + // Rust's division rounds towards zero. + u64::try_from(numerator / denominator).unwrap() +} + +fn give_additional_rewards Result<(), Error>>( + mut reduced_rewards: u64, + missed_signatures: HashMap, + mut f: F, +) -> Result<(), Error> { + let sorted_validators = missed_signatures + .into_iter() + .map(|(address, (proposed, missed))| (address, Ratio::new(missed, proposed))) + .fold(BTreeMap::, Vec
>::new(), |mut map, (address, average_missed)| { + map.entry(average_missed).or_default().push(address); + map + }); + for validators in sorted_validators.values() { + let reward = reduced_rewards / (u64::try_from(validators.len()).unwrap() + 1); + if reward == 0 { + break + } + for validator in validators { + f(validator, reward)?; + reduced_rewards -= reward; + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::iter::FromIterator; + + use super::*; + + #[test] + fn test_final_rewards() { + let intermediate_reward = 1000; + { + let number_of_signatures = 300; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(1000, final_rewards); + } + + { + let number_of_signatures = 250; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(950, final_rewards); + } + + { + let number_of_signatures = 200; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(900, final_rewards); + } + + { + let number_of_signatures = 150; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(500, final_rewards); + } + + { + let number_of_signatures = 100; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(100, final_rewards); + } + + { + let number_of_signatures = 50; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(50, final_rewards); + } + + { + let number_of_signatures = 0; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(0, final_rewards); + } + } + + #[test] + fn final_rewards_are_rounded_towards_zero() { + let intermediate_reward = 4321; + { + let number_of_signatures = 300; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(4321, final_rewards); + } + + { + let number_of_signatures = 250; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(4104, final_rewards); + } + + { + let number_of_signatures = 200; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(3888, final_rewards); + } + + { + let number_of_signatures = 150; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(2160, final_rewards); + } + + { + let number_of_signatures = 100; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(432, final_rewards); + } + + { + let number_of_signatures = 50; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(216, final_rewards); + } + + { + let number_of_signatures = 0; + let number_of_blocks_in_term = 300; + let final_rewards = final_rewards(intermediate_reward, number_of_signatures, number_of_blocks_in_term); + assert_eq!(0, final_rewards); + } + } + + #[test] + fn test_additional_rewards() { + let reduced_rewards = 100; + let addr00 = Address::random(); + let addr10 = Address::random(); + let addr11 = Address::random(); + let addr12 = Address::random(); + let addr20 = Address::random(); + let addr21 = Address::random(); + let missed_signatures = HashMap::from_iter( + vec![ + (addr00, (30, 28)), + (addr10, (60, 59)), + (addr11, (120, 118)), + (addr12, (120, 118)), + (addr20, (60, 60)), + (addr21, (120, 120)), + ] + .into_iter(), + ); + + let mut result = HashMap::with_capacity(7); + give_additional_rewards(reduced_rewards, missed_signatures, |address, reward| { + assert_eq!(None, result.insert(*address, reward)); + Ok(()) + }) + .unwrap(); + assert_eq!( + result, + HashMap::from_iter( + vec![(addr00, 50), (addr10, 12), (addr11, 12), (addr12, 12), (addr20, 4), (addr21, 4)].into_iter() + ) + ); + } +} diff --git a/core/src/consensus/tendermint/mod.rs b/core/src/consensus/tendermint/mod.rs index d0de272f20..1fbdea070c 100644 --- a/core/src/consensus/tendermint/mod.rs +++ b/core/src/consensus/tendermint/mod.rs @@ -36,7 +36,7 @@ use primitives::H256; use self::chain_notify::TendermintChainNotify; pub use self::params::{TendermintParams, TimeGapParams, TimeoutParams}; use self::types::{Height, Step, View}; -use super::stake; +use super::{stake, ValidatorSet}; use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; use ChainNotify; @@ -62,6 +62,7 @@ pub struct Tendermint { join: Option>, quit_tendermint: crossbeam::Sender<()>, inner: crossbeam::Sender, + validators: Arc, /// Reward per block, in base units. block_reward: u64, /// codechain machine descriptor @@ -85,7 +86,8 @@ impl Drop for Tendermint { impl Tendermint { /// Create a new instance of Tendermint engine pub fn new(our_params: TendermintParams, machine: CodeChainMachine) -> Arc { - let stake = stake::Stake::new(our_params.genesis_stakes, Arc::clone(&our_params.validators)); + let validators = Arc::clone(&our_params.validators); + let stake = stake::Stake::new(our_params.genesis_stakes, Arc::clone(&validators)); let timeouts = our_params.timeouts; let machine = Arc::new(machine); @@ -102,6 +104,7 @@ impl Tendermint { join: Some(join), quit_tendermint, inner, + validators, block_reward: our_params.block_reward, machine, action_handlers, diff --git a/core/src/consensus/validator_set/mod.rs b/core/src/consensus/validator_set/mod.rs index baf94a1068..e57c2fe610 100644 --- a/core/src/consensus/validator_set/mod.rs +++ b/core/src/consensus/validator_set/mod.rs @@ -63,4 +63,6 @@ pub trait ValidatorSet: Send + Sync { fn report_benign(&self, _validator: &Address, _set_block: BlockNumber, _block: BlockNumber) {} /// Allows blockchain state access. fn register_client(&self, _client: Weak) {} + + fn addresses(&self, _parent: &H256) -> Vec
; } diff --git a/core/src/consensus/validator_set/null_validator.rs b/core/src/consensus/validator_set/null_validator.rs index 4345c7d758..cd5aeedfb4 100644 --- a/core/src/consensus/validator_set/null_validator.rs +++ b/core/src/consensus/validator_set/null_validator.rs @@ -48,4 +48,8 @@ impl ValidatorSet for NullValidator { fn count(&self, _parent: &H256) -> usize { unimplemented!() } + + fn addresses(&self, _parent: &H256) -> Vec
{ + vec![] + } } diff --git a/core/src/consensus/validator_set/validator_list.rs b/core/src/consensus/validator_set/validator_list.rs index f71d115741..77bc0bf66f 100644 --- a/core/src/consensus/validator_set/validator_list.rs +++ b/core/src/consensus/validator_set/validator_list.rs @@ -63,6 +63,10 @@ impl ValidatorSet for ValidatorList { fn count(&self, _bh: &H256) -> usize { self.validators.len() } + + fn addresses(&self, _parent: &H256) -> Vec
{ + self.validators.iter().map(public_to_address).collect() + } } #[cfg(test)] diff --git a/core/src/lib.rs b/core/src/lib.rs index f8654afae1..63b2909d1f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -40,6 +40,7 @@ extern crate kvdb_rocksdb; extern crate linked_hash_map; extern crate memorydb; extern crate num_cpus; +extern crate num_rational; extern crate primitives; extern crate rand; #[cfg(test)]