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 e5b0782679..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,6 +46,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::consensus::CodeChainEngine; use crate::encoded; use crate::error::{BlockImportError, Error, ImportError, SchemeError}; @@ -563,6 +565,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(); @@ -777,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 655d6cb380..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,6 +111,12 @@ pub trait EngineClient: Sync + Send + BlockChainTrait + ImportBlock { fn get_kvdb(&self) -> Arc; } +pub trait ConsensusClient: BlockChainTrait + EngineClient + MetadataInfo {} + +pub trait MetadataInfo { + fn metadata(&self, id: BlockId) -> Option; +} + /// 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..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}; @@ -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,11 @@ impl EngineInfo for TestBlockChainClient { unimplemented!() } } + +impl ConsensusClient for TestBlockChainClient {} + +impl MetadataInfo for TestBlockChainClient { + fn metadata(&self, _id: BlockId) -> Option { + None + } +} diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 4e99808d91..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. @@ -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/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/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 729b89f6d9..af0089e1ef 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/core/src/consensus/stake/action_data.rs @@ -17,6 +17,7 @@ #[cfg(test)] use std::collections::btree_map; use std::collections::{btree_set, BTreeMap, BTreeSet}; +use std::mem; use ckey::Address; use cstate::{ActionData, ActionDataKeyBuilder, StateResult, TopLevelState, TopState, TopStateView}; @@ -39,6 +40,10 @@ pub fn get_delegation_key(address: &Address) -> H256 { ActionDataKeyBuilder::new(CUSTOM_ACTION_HANDLER_ID, 2).append(&"Delegation").append(address).into_key() } +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 +189,54 @@ impl<'a> Delegation<'a> { } } +#[derive(Default, Debug, PartialEq)] +pub struct IntermediateRewards { + previous: BTreeMap, + current: BTreeMap, +} + +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 +265,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 +291,46 @@ 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); } +} + +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() + } +} + +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 +597,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/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..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, EngineClient}; +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; @@ -146,16 +151,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,11 +173,41 @@ impl ConsensusEngine for Tendermint { } (header.number(), term_id) }; + let rewards = stake::drain_previous_rewards(&mut block.state_mut())?; + 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)?; + } + 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(()) } - fn register_client(&self, client: Weak) { + fn register_client(&self, client: Weak) { *self.client.write() = Some(Weak::clone(&client)); } @@ -254,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 c06a5e69c1..1fbdea070c 100644 --- a/core/src/consensus/tendermint/mod.rs +++ b/core/src/consensus/tendermint/mod.rs @@ -36,8 +36,8 @@ 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 crate::client::EngineClient; +use super::{stake, ValidatorSet}; +use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; use ChainNotify; @@ -55,13 +55,14 @@ 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<()>, 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, @@ -140,8 +143,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/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..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") } @@ -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 { @@ -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..e57c2fe610 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,7 @@ 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) {} + + 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)] diff --git a/spec/Dynamic-Validator.md b/spec/Dynamic-Validator.md index cb66579668..7bfaa37cda 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 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 {