diff --git a/core/src/block.rs b/core/src/block.rs index d022bf3adf..43040f53b1 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -116,15 +116,14 @@ impl ExecutedBlock { } /// Block that is ready for transactions to be added. -pub struct OpenBlock<'x> { +pub struct OpenBlock { block: ExecutedBlock, - engine: &'x dyn CodeChainEngine, } -impl<'x> OpenBlock<'x> { +impl OpenBlock { /// Create a new `OpenBlock` ready for transaction pushing. pub fn try_new( - engine: &'x dyn CodeChainEngine, + engine: &dyn CodeChainEngine, db: StateDB, parent: &Header, author: Address, @@ -133,7 +132,6 @@ impl<'x> OpenBlock<'x> { let state = TopLevelState::from_existing(db, *parent.state_root()).map_err(StateError::from)?; let mut r = OpenBlock { block: ExecutedBlock::new(state, parent), - engine, }; r.block.header.set_author(author); @@ -208,10 +206,6 @@ impl<'x> OpenBlock<'x> { /// Turn this into a `ClosedBlock`. pub fn close(mut self) -> Result { - if let Err(e) = self.engine.on_close_block(&mut self.block) { - warn!("Encountered error on closing the block: {}", e); - return Err(e) - } let state_root = self.block.state.commit().map_err(|e| { warn!("Encountered error on state commit: {}", e); e @@ -324,13 +318,13 @@ impl IsBlock for ExecutedBlock { } } -impl<'x> IsBlock for OpenBlock<'x> { +impl IsBlock for OpenBlock { fn block(&self) -> &ExecutedBlock { &self.block } } -impl<'x> IsBlock for ClosedBlock { +impl IsBlock for ClosedBlock { fn block(&self) -> &ExecutedBlock { &self.block } @@ -348,7 +342,6 @@ pub fn enact( let mut b = OpenBlock::try_new(engine, db, parent, Address::default(), vec![])?; b.populate_from(header); - engine.on_open_block(b.inner_mut())?; b.push_transactions(transactions, client, parent.number(), parent.timestamp())?; b.close() diff --git a/core/src/client/client.rs b/core/src/client/client.rs index df7f3ce4c8..1f4f65b3ac 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -620,7 +620,7 @@ impl Shard for Client { } impl BlockProducer for Client { - fn prepare_open_block(&self, parent_block_id: BlockId, author: Address, extra_data: Bytes) -> OpenBlock<'_> { + fn prepare_open_block(&self, parent_block_id: BlockId, author: Address, extra_data: Bytes) -> OpenBlock { let engine = &*self.engine; let chain = self.block_chain(); let parent_hash = self.block_hash(&parent_block_id).expect("parent exist always"); diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 9a0e674776..e2db2ead8f 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -236,7 +236,7 @@ pub type ImportResult = Result; /// Provides methods used for sealing new state pub trait BlockProducer { /// Returns OpenBlock prepared for closing. - fn prepare_open_block(&self, parent_block: BlockId, author: Address, extra_data: Bytes) -> OpenBlock<'_>; + fn prepare_open_block(&self, parent_block: BlockId, author: Address, extra_data: Bytes) -> OpenBlock; } /// Extended client interface used for mining diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index d52e1566a6..b3174f8105 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -50,10 +50,10 @@ use ckey::{ Generator, KeyPairTrait, NetworkId, PlatformAddress, Random, }; use cstate::tests::helpers::empty_top_state_with_metadata; -use cstate::{FindDoubleVoteHandler, NextValidators, StateDB, TopLevelState, Validator}; +use cstate::{FindDoubleVoteHandler, NextValidators, StateDB, TopLevelState}; use ctimer::{TimeoutHandler, TimerToken}; use ctypes::header::Header; -use ctypes::transaction::{Action, Transaction}; +use ctypes::transaction::{Action, Transaction, Validator}; use ctypes::{BlockHash, BlockNumber, CommonParams, Header as BlockHeader, TxHash}; use kvdb::KeyValueDB; use merkle_trie::skewed_merkle_root; @@ -149,7 +149,7 @@ impl TestBlockChainClient { history: RwLock::new(None), term_id: Some(1), validator_keys: RwLock::new(HashMap::new()), - validators: NextValidators::from_vector_to_test(vec![]), + validators: vec![].into(), }; // insert genesis hash. @@ -310,9 +310,8 @@ impl TestBlockChainClient { self.validator_keys.write().insert(public, private); pubkeys.push(public); } - let fixed_validators: NextValidators = NextValidators::from_vector_to_test( - pubkeys.into_iter().map(|pubkey| Validator::new_for_test(0, 0, pubkey)).collect(), - ); + let fixed_validators: NextValidators = + pubkeys.into_iter().map(|pubkey| Validator::new(0, 0, pubkey)).collect::>().into(); self.validators = fixed_validators; } @@ -329,7 +328,7 @@ pub fn get_temp_state_db() -> StateDB { } impl BlockProducer for TestBlockChainClient { - fn prepare_open_block(&self, _parent_block: BlockId, author: Address, extra_data: Bytes) -> OpenBlock<'_> { + fn prepare_open_block(&self, _parent_block: BlockId, author: Address, extra_data: Bytes) -> OpenBlock { let engine = &*self.scheme.engine; let genesis_header = self.scheme.genesis_header(); let db = get_temp_state_db(); diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index a6f440884d..b9d3f3b4bc 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -164,13 +164,13 @@ pub trait ConsensusEngine: Sync + Send { fn on_timeout(&self, _token: usize) {} /// Block transformation functions, before the transactions. - fn on_open_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> { - Ok(()) + fn open_block_action(&self, _block: &ExecutedBlock) -> Result, Error> { + Ok(None) } /// Block transformation functions, after the transactions. - fn on_close_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> { - Ok(()) + fn close_block_actions(&self, _block: &ExecutedBlock) -> Result, Error> { + Ok(vec![]) } /// Add Client which can be used for sealing, potentially querying the state and sending messages. diff --git a/core/src/consensus/solo/mod.rs b/core/src/consensus/solo/mod.rs index 2bac4bd872..7ba440eadd 100644 --- a/core/src/consensus/solo/mod.rs +++ b/core/src/consensus/solo/mod.rs @@ -26,6 +26,7 @@ use crate::consensus::{EngineError, EngineType}; use crate::error::Error; use ckey::Address; use cstate::{init_stake, DoubleVoteHandler, StateDB, StateResult, StateWithCache, TopLevelState}; +use ctypes::transaction::Action; use ctypes::{BlockHash, Header}; use parking_lot::RwLock; use primitives::H256; @@ -71,7 +72,7 @@ impl ConsensusEngine for Solo { Seal::Solo } - fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { + fn close_block_actions(&self, block: &ExecutedBlock) -> Result, Error> { let client = self.client().ok_or(EngineError::CannotOpenBlock)?; let parent_hash = *block.header().parent_hash(); @@ -79,20 +80,22 @@ impl ConsensusEngine for Solo { let parent_common_params = client.common_params(parent_hash.into()).expect("CommonParams of parent must exist"); let term_seconds = parent_common_params.term_seconds(); if term_seconds == 0 { - return Ok(()) + return Ok(vec![]) } - let last_term_finished_block_num = { - let header = block.header(); - let current_term_period = header.timestamp() / term_seconds; - let parent_term_period = parent.timestamp() / term_seconds; - if current_term_period == parent_term_period { - return Ok(()) - } - header.number() - }; - - stake::on_term_close(block.state_mut(), last_term_finished_block_num, &[])?; - Ok(()) + let header = block.header(); + let current_term_period = header.timestamp() / term_seconds; + let parent_term_period = parent.timestamp() / term_seconds; + if current_term_period == parent_term_period { + return Ok(vec![]) + } + + Ok(vec![Action::CloseTerm { + next_validators: vec![], + inactive_validators: vec![], + released_addresses: vec![], + custody_until: 0, + kick_at: 0, + }]) } fn register_client(&self, client: Weak) { diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index b680366aca..4a2b98e92a 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -16,10 +16,7 @@ use crate::client::ConsensusClient; use ckey::{public_to_address, Address}; -use cstate::{ - ban, jail, release_jailed_prisoners, revert_delegations, update_candidates, DoubleVoteHandler, StateResult, - TopLevelState, TopState, TopStateView, -}; +use cstate::{ban, DoubleVoteHandler, StateResult, TopLevelState}; use ctypes::errors::{RuntimeError, SyntaxError}; use parking_lot::RwLock; use std::sync::{Arc, Weak}; @@ -125,403 +122,3 @@ pub fn verify_report_double_vote( } Ok(()) } - -pub fn on_term_close( - state: &mut TopLevelState, - last_term_finished_block_num: u64, - inactive_validators: &[Address], -) -> StateResult<()> { - let metadata = state.metadata()?.expect("The metadata must exist"); - let current_term = metadata.current_term_id(); - ctrace!(ENGINE, "on_term_close. current_term: {}", current_term); - - let (nomination_expiration, custody_until, kick_at) = { - let metadata = metadata.params(); - let nomination_expiration = metadata.nomination_expiration(); - assert_ne!(0, nomination_expiration); - let custody_period = metadata.custody_period(); - assert_ne!(0, custody_period); - let release_period = metadata.release_period(); - assert_ne!(0, release_period); - (nomination_expiration, current_term + custody_period, current_term + release_period) - }; - - let expired = update_candidates(state, current_term, nomination_expiration, inactive_validators)?; - let released = release_jailed_prisoners(state, current_term)?; - - let reverted: Vec<_> = expired.into_iter().chain(released).collect(); - revert_delegations(state, &reverted)?; - - jail(state, inactive_validators, custody_until, kick_at)?; - - state.increase_term_id(last_term_finished_block_num)?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use ckey::Ed25519Public as Public; - use cstate::tests::helpers; - use cstate::{ - delegate_ccs, init_stake, self_nominate, Candidate, Candidates, Delegation, Jail, StakeAccount, TopStateView, - }; - use ctypes::CommonParams; - use std::collections::HashMap; - - fn metadata_for_election() -> TopLevelState { - let mut params = CommonParams::default_for_test(); - let mut state = helpers::get_temp_state_with_metadata(params); - state.metadata().unwrap().unwrap().set_params(CommonParams::default_for_test()); - params.set_dynamic_validator_params_for_test(30, 10, 3, 20, 30, 4, 1000, 10000, 100); - assert_eq!(Ok(()), state.update_params(0, params)); - state - } - - fn increase_term_id_until(state: &mut TopLevelState, term_id: u64) { - let mut block_num = state.metadata().unwrap().unwrap().last_term_finished_block_num() + 1; - while state.metadata().unwrap().unwrap().current_term_id() != term_id { - assert_eq!(Ok(()), state.increase_term_id(block_num)); - block_num += 1; - } - } - - #[test] - fn self_nominate_returns_deposits_after_expiration() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - - let mut state = metadata_for_election(); - increase_term_id_until(&mut state, 29); - state.add_balance(&address, 1000).unwrap(); - - init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - self_nominate(&mut state, &address, &address_pubkey, 200, 0, 30, b"".to_vec()).unwrap(); - - let result = on_term_close(&mut state, pseudo_term_to_block_num_calculator(29), &[]); - assert_eq!(result, Ok(())); - - assert_eq!(state.balance(&address).unwrap(), 800, "Should keep nomination before expiration"); - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!( - candidates.get_candidate(&address), - Some(&Candidate { - pubkey: address_pubkey, - deposit: 200, - nomination_ends_at: 30, - metadata: b"".to_vec(), - }), - "Keep deposit before expiration", - ); - - let result = on_term_close(&mut state, pseudo_term_to_block_num_calculator(30), &[]); - assert_eq!(result, Ok(())); - - assert_eq!(state.balance(&address).unwrap(), 1000, "Return deposit after expiration"); - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.get_candidate(&address), None, "Removed from candidates after expiration"); - } - - #[test] - fn self_nominate_reverts_delegations_after_expiration() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - let delegator = public_to_address(&address_pubkey); - - let mut state = metadata_for_election(); - increase_term_id_until(&mut state, 29); - state.add_balance(&address, 1000).unwrap(); - - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - self_nominate(&mut state, &address, &address_pubkey, 0, 0, 30, b"".to_vec()).unwrap(); - - let quantity = 40; - delegate_ccs(&mut state, &delegator, &address, quantity).unwrap(); - - let result = on_term_close(&mut state, pseudo_term_to_block_num_calculator(29), &[]); - assert_eq!(result, Ok(())); - - let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(account.balance, 100 - 40); - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegation.get_quantity(&address), 40, "Should keep delegation before expiration"); - - let result = on_term_close(&mut state, pseudo_term_to_block_num_calculator(30), &[]); - assert_eq!(result, Ok(())); - - let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(account.balance, 100); - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegation.get_quantity(&address), 0, "Should revert before expiration"); - } - - #[test] - fn cannot_self_nominate_while_custody() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - - let mut state = metadata_for_election(); - state.add_balance(&address, 1000).unwrap(); - - init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - let deposit = 200; - let nominate_expire = 5; - let custody_until = 10; - let released_at = 20; - self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); - jail(&mut state, &[address], custody_until, released_at).unwrap(); - - for current_term in 0..=custody_until { - let result = self_nominate( - &mut state, - &address, - &address_pubkey, - 0, - current_term, - current_term + nominate_expire, - b"".to_vec(), - ); - assert!( - result.is_err(), - "Shouldn't nominate while current_term({}) <= custody_until({})", - current_term, - custody_until - ); - on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); - } - } - - #[test] - fn can_self_nominate_after_custody() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - - let mut state = metadata_for_election(); - state.add_balance(&address, 1000).unwrap(); - - init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - let deposit = 200; - let nominate_expire = 5; - let custody_until = 10; - let released_at = 20; - self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"metadata-before".to_vec()) - .unwrap(); - jail(&mut state, &[address], custody_until, released_at).unwrap(); - for current_term in 0..=custody_until { - on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); - } - - let current_term = custody_until + 1; - let additional_deposit = 123; - let result = self_nominate( - &mut state, - &address, - &address_pubkey, - additional_deposit, - current_term, - current_term + nominate_expire, - b"metadata-after".to_vec(), - ); - assert!(result.is_ok()); - - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!( - candidates.get_candidate(&address), - Some(&Candidate { - deposit: deposit + additional_deposit, - nomination_ends_at: current_term + nominate_expire, - pubkey: address_pubkey, - metadata: "metadata-after".into() - }), - "The prisoner is become a candidate", - ); - - let jail = Jail::load_from_state(&state).unwrap(); - assert_eq!(jail.get_prisoner(&address), None, "The prisoner is removed"); - - assert_eq!(state.balance(&address).unwrap(), 1000 - deposit - additional_deposit, "Deposit is accumulated"); - } - - #[test] - fn jail_released_after() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - - let mut state = metadata_for_election(); - state.add_balance(&address, 1000).unwrap(); - - init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - let deposit = 200; - let nominate_expire = 5; - let custody_until = 10; - let released_at = 20; - self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); - jail(&mut state, &[address], custody_until, released_at).unwrap(); - - for current_term in 0..released_at { - on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); - - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.get_candidate(&address), None); - - let jail = Jail::load_from_state(&state).unwrap(); - assert!(jail.get_prisoner(&address).is_some()); - } - - on_term_close(&mut state, pseudo_term_to_block_num_calculator(released_at), &[]).unwrap(); - - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.get_candidate(&address), None, "A prisoner should not become a candidate"); - - let jail = Jail::load_from_state(&state).unwrap(); - assert_eq!(jail.get_prisoner(&address), None, "A prisoner should be released"); - - assert_eq!(state.balance(&address).unwrap(), 1000, "Balance should be restored after being released"); - } - - #[test] - fn cannot_delegate_until_released() { - let address_pubkey = Public::random(); - let delegator_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = metadata_for_election(); - state.add_balance(&address, 1000).unwrap(); - - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - let deposit = 200; - let nominate_expire = 5; - let custody_until = 10; - let released_at = 20; - self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); - jail(&mut state, &[address], custody_until, released_at).unwrap(); - - for current_term in 0..=released_at { - let quantity = 1; - delegate_ccs(&mut state, &delegator, &address, quantity).unwrap_err(); - - on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); - } - - let quantity = 1; - delegate_ccs(&mut state, &delegator, &address, quantity).unwrap_err(); - } - - #[test] - fn kick_reverts_delegations() { - let address_pubkey = Public::random(); - let delegator_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = metadata_for_election(); - state.add_balance(&address, 1000).unwrap(); - - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - let deposit = 200; - let nominate_expire = 5; - let custody_until = 10; - let released_at = 20; - self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); - - let quantity = 40; - delegate_ccs(&mut state, &delegator, &address, quantity).unwrap(); - - jail(&mut state, &[address], custody_until, released_at).unwrap(); - - for current_term in 0..=released_at { - on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); - } - - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegation.get_quantity(&address), 0, "Delegation should be reverted"); - - let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(account.balance, 100, "Delegation should be reverted"); - } - - #[test] - fn self_nomination_before_kick_preserves_delegations() { - let address_pubkey = Public::random(); - let delegator_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = metadata_for_election(); - state.add_balance(&address, 1000).unwrap(); - - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - let nominate_expire = 5; - let custody_until = 10; - let released_at = 20; - self_nominate(&mut state, &address, &address_pubkey, 0, 0, nominate_expire, b"".to_vec()).unwrap(); - - let quantity = 40; - delegate_ccs(&mut state, &delegator, &address, quantity).unwrap(); - - jail(&mut state, &[address], custody_until, released_at).unwrap(); - - for current_term in 0..custody_until { - on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); - } - - let current_term = custody_until + 1; - let result = self_nominate( - &mut state, - &address, - &address_pubkey, - 0, - current_term, - current_term + nominate_expire, - b"".to_vec(), - ); - assert!(result.is_ok()); - - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegation.get_quantity(&address), 40, "Delegation should be preserved"); - - let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(account.balance, 100 - 40, "Delegation should be preserved"); - } - - fn pseudo_term_to_block_num_calculator(term_id: u64) -> u64 { - term_id * 10 + 1 - } -} diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 0428197758..4fd4a54f76 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use super::super::{stake, ConsensusEngine, EngineError, Seal}; +use super::super::{ConsensusEngine, EngineError, Seal}; use super::network::TendermintExtension; pub use super::params::{TendermintParams, TimeoutParams}; use super::worker; @@ -32,9 +32,10 @@ use ckey::{public_to_address, Address}; use cnetwork::NetworkService; use crossbeam_channel as crossbeam; use cstate::{ - init_stake, update_validator_weights, CurrentValidators, DoubleVoteHandler, NextValidators, StateDB, StateResult, - StateWithCache, TopLevelState, TopState, TopStateView, + init_stake, DoubleVoteHandler, Jail, NextValidators, StateDB, StateResult, StateWithCache, TopLevelState, + TopStateView, }; +use ctypes::transaction::Action; use ctypes::{BlockHash, Header}; use primitives::H256; use std::collections::HashSet; @@ -108,27 +109,21 @@ impl ConsensusEngine for Tendermint { } /// Block transformation functions, before the transactions. - fn on_open_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { - let mut current_validators = CurrentValidators::load_from_state(block.state())?; - current_validators.update(NextValidators::load_from_state(block.state())?.clone()); - current_validators.save_to_state(block.state_mut())?; - - Ok(()) + fn open_block_action(&self, block: &ExecutedBlock) -> Result, Error> { + Ok(Some(Action::UpdateValidators { + validators: NextValidators::load_from_state(block.state())?.into(), + })) } - fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { + fn close_block_actions(&self, block: &ExecutedBlock) -> Result, Error> { let client = self.client().ok_or(EngineError::CannotOpenBlock)?; let parent_hash = *block.header().parent_hash(); let parent = client.block_header(&parent_hash.into()).expect("Parent header must exist").decode(); let parent_common_params = client.common_params(parent_hash.into()).expect("CommonParams of parent must exist"); - let block_number = block.header().number(); let metadata = block.state().metadata()?.expect("Metadata must exist"); - let author = *block.header().author(); - update_validator_weights(block.state_mut(), &author)?; - let term = metadata.current_term_id(); let term_seconds = match term { 0 => parent_common_params.term_seconds(), @@ -137,30 +132,44 @@ impl ConsensusEngine for Tendermint { parent_term_common_params.expect("TermCommonParams should exist").term_seconds() } }; + let next_validators = NextValidators::update_weight(block.state(), block.header().author())?; if !is_term_changed(block.header(), &parent, term_seconds) { - return Ok(()) + return Ok(vec![Action::ChangeNextValidators { + validators: next_validators.into(), + }]) } let inactive_validators = match term { 0 => Vec::new(), _ => { let start_of_the_current_term = metadata.last_term_finished_block_num() + 1; - let validators = NextValidators::load_from_state(block.state())? - .into_iter() - .map(|val| public_to_address(val.pubkey())) - .collect(); + let validators = next_validators.iter().map(|val| public_to_address(val.pubkey())).collect(); inactive_validators(&*client, start_of_the_current_term, block.header(), validators) } }; - stake::on_term_close(block.state_mut(), block_number, &inactive_validators)?; - - let state = block.state_mut(); - let validators = NextValidators::elect(&state)?; - validators.save_to_state(state)?; + let current_term = metadata.current_term_id(); + let (custody_until, kick_at) = { + let params = metadata.params(); + let custody_period = params.custody_period(); + assert_ne!(0, custody_period); + let release_period = params.release_period(); + assert_ne!(0, release_period); + (current_term + custody_period, current_term + release_period) + }; - state.update_term_params()?; - Ok(()) + let released_addresses = Jail::load_from_state(block.state())?.released_addresses(current_term); + + Ok(vec![ + Action::CloseTerm { + inactive_validators, + next_validators: next_validators.into(), + released_addresses, + custody_until, + kick_at, + }, + Action::Elect {}, + ]) } fn register_client(&self, client: Weak) { diff --git a/core/src/consensus/validator_set/dynamic_validator.rs b/core/src/consensus/validator_set/dynamic_validator.rs index a6ab3bd2da..dbe48b5b73 100644 --- a/core/src/consensus/validator_set/dynamic_validator.rs +++ b/core/src/consensus/validator_set/dynamic_validator.rs @@ -19,7 +19,8 @@ use crate::client::ConsensusClient; use crate::consensus::bit_set::BitSet; use crate::consensus::EngineError; use ckey::{public_to_address, Address, Ed25519Public as Public}; -use cstate::{CurrentValidators, NextValidators, Validator}; +use cstate::{CurrentValidators, NextValidators}; +use ctypes::transaction::Validator; use ctypes::util::unexpected::OutOfBounds; use ctypes::BlockHash; use parking_lot::RwLock; diff --git a/core/src/miner/miner.rs b/core/src/miner/miner.rs index 6f3b140521..72969466f2 100644 --- a/core/src/miner/miner.rs +++ b/core/src/miner/miner.rs @@ -27,14 +27,15 @@ use crate::error::Error; use crate::scheme::Scheme; use crate::transaction::{PendingVerifiedTransactions, UnverifiedTransaction, VerifiedTransaction}; use crate::types::{BlockId, TransactionId}; -use ckey::{public_to_address, Address, Ed25519Public as Public, Password, PlatformAddress}; -use cstate::{FindDoubleVoteHandler, TopLevelState}; +use ckey::{public_to_address, Address, Ed25519Private as Private, Ed25519Public as Public, Password, PlatformAddress}; +use cstate::{FindDoubleVoteHandler, TopLevelState, TopStateView}; use ctypes::errors::HistoryError; -use ctypes::transaction::IncompleteTransaction; +use ctypes::transaction::{IncompleteTransaction, Transaction}; use ctypes::{BlockHash, TxHash}; use kvdb::KeyValueDB; use parking_lot::{Mutex, RwLock}; use primitives::Bytes; +use rlp::Encodable; use std::borrow::Borrow; use std::collections::HashSet; use std::convert::TryInto; @@ -265,7 +266,7 @@ impl Miner { parent_block_id: BlockId, chain: &C, ) -> Result, Error> { - let (transactions, mut open_block, block_number) = { + let (transactions, mut open_block, block_number, block_tx_signer, block_tx_seq) = { ctrace!(MINER, "prepare_block: No existing work - making new block"); let params = self.params.read().clone(); let open_block = chain.prepare_open_block(parent_block_id, params.author, params.extra_data); @@ -280,9 +281,34 @@ impl Miner { // NOTE: This lock should be acquired after `prepare_open_block` to prevent deadlock let mem_pool = self.mem_pool.read(); - let transactions = mem_pool.top_transactions(max_body_size, DEFAULT_RANGE).transactions; - (transactions, open_block, block_number) + let mut transactions = Vec::default(); + let (block_tx_signer, block_tx_seq) = + if let Some(action) = self.engine.open_block_action(open_block.block())? { + ctrace!(MINER, "Enqueue a transaction to open block"); + // TODO: This generates a new random account to make the transaction. + // It should use the block signer. + let tx_signer = Private::random(); + let seq = open_block.state().seq(&public_to_address(&tx_signer.public_key()))?; + let tx = Transaction { + network_id: chain.network_id(), + action, + seq, + fee: 0, + }; + let verified_tx = VerifiedTransaction::new_with_sign(tx, &tx_signer); + transactions.push(verified_tx); + (Some(tx_signer), Some(seq + 1)) + } else { + (None, None) + }; + let open_transaction_size = transactions.iter().map(|tx| tx.rlp_bytes().len()).sum(); + assert!(max_body_size > open_transaction_size); + let mut pending_transactions = + mem_pool.top_transactions(max_body_size - open_transaction_size, DEFAULT_RANGE).transactions; + transactions.append(&mut pending_transactions); + + (transactions, open_block, block_number, block_tx_signer, block_tx_seq) }; let parent_header = { @@ -297,7 +323,6 @@ impl Miner { } else { return Ok(None) } - self.engine.on_open_block(open_block.inner_mut())?; let mut invalid_transactions = Vec::new(); @@ -345,6 +370,32 @@ impl Miner { } cdebug!(MINER, "Pushed {}/{} transactions", tx_count, tx_total); + let actions = self.engine.close_block_actions(open_block.block()).map_err(|e| { + warn!("Encountered error on closing the block: {}", e); + e + })?; + if !actions.is_empty() { + ctrace!(MINER, "Enqueue {} transactions to close block", actions.len()); + // TODO: This generates a new random account to make the transaction. + // It should use the block signer. + let tx_signer = block_tx_signer.unwrap_or_else(Private::random); + let mut seq = block_tx_seq + .map(Ok) + .unwrap_or_else(|| open_block.state().seq(&public_to_address(&tx_signer.public_key())))?; + for action in actions { + let tx = Transaction { + network_id: chain.network_id(), + action, + seq, + fee: 0, + }; + seq += 1; + let tx = VerifiedTransaction::new_with_sign(tx, &tx_signer); + // TODO: The current code can insert more transactions than size limit. + // It should be fixed to pre-calculate the maximum size of the close transactions and prevent the size overflow. + open_block.push_transaction(tx, chain, parent_header.number(), parent_header.timestamp())?; + } + } let block = open_block.close()?; let fetch_seq = |p: &Public| { diff --git a/rpc/src/v1/types/action.rs b/rpc/src/v1/types/action.rs index b92acb1eaa..dd625c313d 100644 --- a/rpc/src/v1/types/action.rs +++ b/rpc/src/v1/types/action.rs @@ -67,6 +67,10 @@ pub enum Action { message1: Bytes, message2: Bytes, }, + UpdateValidators, + CloseTerm, + ChangeNextValidators, + Elect, } impl Action { @@ -141,6 +145,18 @@ impl Action { message1, message2, }, + ActionType::UpdateValidators { + .. + } => Action::UpdateValidators, // TODO: Implement serialization + ActionType::CloseTerm { + .. + } => Action::CloseTerm, // TODO: Implement serialization + ActionType::ChangeNextValidators { + .. + } => Action::ChangeNextValidators, // TODO: Implement serialization + ActionType::Elect { + .. + } => Action::Elect, // TODO: Implement serialization } } } @@ -218,6 +234,10 @@ impl TryFrom for ActionType { message1, message2, }, + Action::UpdateValidators => unreachable!("No reason to get UpdateValidators from RPCs"), + Action::CloseTerm => unreachable!("No reason to get CloseTerm from RPCs"), + Action::ChangeNextValidators => unreachable!("No reason to get ChangeNextValidators from RPCs"), + Action::Elect => unreachable!("No reason to get Elect from RPCs"), }) } } diff --git a/state/src/impls/top_level.rs b/state/src/impls/top_level.rs index 27a4b6f9ad..fd1029dcd3 100644 --- a/state/src/impls/top_level.rs +++ b/state/src/impls/top_level.rs @@ -37,11 +37,14 @@ use crate::cache::{ModuleCache, ShardCache, TopCache}; use crate::checkpoint::{CheckpointId, StateWithCheckpoint}; -use crate::stake::{change_params, delegate_ccs, redelegate, revoke, transfer_ccs}; +use crate::stake::{ + change_params, close_term, delegate_ccs, jail, redelegate, release_jailed_prisoners, revoke, self_nominate, + transfer_ccs, +}; use crate::traits::{ModuleStateView, ShardState, ShardStateView, StateWithCache, TopState, TopStateView}; use crate::{ - self_nominate, Account, ActionData, FindDoubleVoteHandler, Metadata, MetadataAddress, Module, ModuleAddress, - ModuleLevelState, Shard, ShardAddress, ShardLevelState, StateDB, StateResult, + Account, ActionData, CurrentValidators, FindDoubleVoteHandler, Metadata, MetadataAddress, Module, ModuleAddress, + ModuleLevelState, NextValidators, Shard, ShardAddress, ShardLevelState, StateDB, StateResult, }; use cdb::{AsHashDB, DatabaseError}; use ckey::{public_to_address, Address, Ed25519Public as Public, NetworkId}; @@ -433,6 +436,37 @@ impl TopLevelState { handler.execute(message1, self, sender_address)?; return Ok(()) } + Action::UpdateValidators { + validators, + } => { + let next_validators_in_state = NextValidators::load_from_state(self)?; + if validators != &Vec::from(next_validators_in_state) { + return Err(RuntimeError::InvalidValidators.into()) + } + let mut current_validators = CurrentValidators::load_from_state(self)?; + current_validators.update(validators.clone()); + current_validators.save_to_state(self)?; + return Ok(()) + } + Action::CloseTerm { + inactive_validators, + next_validators, + released_addresses, + custody_until, + kick_at, + } => { + close_term(self, next_validators, inactive_validators)?; + release_jailed_prisoners(self, released_addresses)?; + jail(self, inactive_validators, *custody_until, *kick_at)?; + return self.increase_term_id(parent_block_number + 1) + } + Action::ChangeNextValidators { + validators, + } => return NextValidators::from(validators.clone()).save_to_state(self), + Action::Elect => { + NextValidators::elect(self)?.save_to_state(self)?; + return self.update_term_params() + } }; self.apply_shard_transaction( tx_hash, diff --git a/state/src/item/stake.rs b/state/src/item/stake.rs index b7b6626d30..7de900e9fe 100644 --- a/state/src/item/stake.rs +++ b/state/src/item/stake.rs @@ -17,6 +17,7 @@ use crate::{ActionData, StakeKeyBuilder, StateResult, TopLevelState, TopState, TopStateView}; use ckey::{public_to_address, Address, Ed25519Public as Public}; use ctypes::errors::RuntimeError; +use ctypes::transaction::Validator; use ctypes::{CompactValidatorEntry, CompactValidatorSet}; use primitives::{Bytes, H256}; use rlp::{decode_list, encode_list, Decodable, Encodable, Rlp, RlpStream}; @@ -128,6 +129,7 @@ impl Stakeholders { Ok(result) } + #[allow(dead_code)] pub fn contains(&self, address: &Address) -> bool { self.0.contains(address) } @@ -222,53 +224,9 @@ impl<'a> Delegation<'a> { } } -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, RlpDecodable, RlpEncodable)] -pub struct Validator { - weight: StakeQuantity, - delegation: StakeQuantity, - deposit: DepositQuantity, - pubkey: Public, -} - -impl Validator { - pub fn new_for_test(delegation: StakeQuantity, deposit: DepositQuantity, pubkey: Public) -> Self { - Self { - weight: delegation, - delegation, - deposit, - pubkey, - } - } - - fn new(delegation: StakeQuantity, deposit: DepositQuantity, pubkey: Public) -> Self { - Self { - weight: delegation, - delegation, - deposit, - pubkey, - } - } - - fn reset(&mut self) { - self.weight = self.delegation; - } - - pub fn pubkey(&self) -> &Public { - &self.pubkey - } - - pub fn delegation(&self) -> StakeQuantity { - self.delegation - } -} - #[derive(Debug)] pub struct NextValidators(Vec); impl NextValidators { - pub fn from_vector_to_test(vec: Vec) -> Self { - Self(vec) - } - pub fn load_from_state(state: &TopLevelState) -> StateResult { let key = &*NEXT_VALIDATORS_KEY; let validators = state.action_data(&key)?.map(|data| decode_list(&data)).unwrap_or_default(); @@ -309,7 +267,7 @@ impl NextValidators { let banned = Banned::load_from_state(&state)?; for validator in &validators { - let address = public_to_address(&validator.pubkey); + let address = public_to_address(validator.pubkey()); assert!(!banned.is_banned(&address), "{} is banned address", address); } @@ -327,7 +285,7 @@ impl NextValidators { } // Step 4 & 5 let (minimum, rest) = validators.split_at(min_num_of_validators.min(validators.len())); - let over_threshold = rest.iter().filter(|c| c.delegation >= delegation_threshold); + let over_threshold = rest.iter().filter(|c| c.delegation() >= delegation_threshold); let mut result: Vec<_> = minimum.iter().chain(over_threshold).cloned().collect(); result.reverse(); // Ascending order of (delegation, deposit, priority) @@ -344,43 +302,35 @@ impl NextValidators { Ok(()) } - pub fn update_weight(&mut self, block_author: &Address) { - let min_delegation = self.min_delegation(); - for Validator { - weight, - pubkey, - .. - } in self.0.iter_mut().rev() - { - if public_to_address(pubkey) == *block_author { + pub fn update_weight(state: &TopLevelState, block_author: &Address) -> StateResult { + let mut validators = Self::load_from_state(state)?; + let min_delegation = validators.min_delegation(); + for validator in validators.0.iter_mut().rev() { + if public_to_address(validator.pubkey()) == *block_author { // block author - *weight = weight.saturating_sub(min_delegation); + validator.set_weight(validator.weight().saturating_sub(min_delegation)); break } // neglecting validators - *weight = weight.saturating_sub(min_delegation * 2); + validator.set_weight(validator.weight().saturating_sub(min_delegation * 2)); } - if self.0.iter().all(|validator| validator.weight == 0) { - self.0.iter_mut().for_each(Validator::reset); + if validators.0.iter().all(|validator| validator.weight() == 0) { + validators.0.iter_mut().for_each(Validator::reset); } - self.0.sort_unstable(); + validators.0.sort_unstable(); + Ok(validators) } pub fn remove(&mut self, target: &Address) { - self.0.retain( - |Validator { - pubkey, - .. - }| public_to_address(pubkey) != *target, - ); + self.0.retain(|validator| public_to_address(validator.pubkey()) != *target); } pub fn delegation(&self, pubkey: &Public) -> Option { - self.0.iter().find(|validator| validator.pubkey == *pubkey).map(|&validator| validator.delegation) + self.0.iter().find(|validator| *validator.pubkey() == *pubkey).map(|&validator| validator.delegation()) } fn min_delegation(&self) -> StakeQuantity { - self.0.iter().map(|&validator| validator.delegation).min().expect("There must be at least one validators") + self.0.iter().map(|&validator| validator.delegation()).min().expect("There must be at least one validators") } } @@ -392,6 +342,12 @@ impl Deref for NextValidators { } } +impl From> for NextValidators { + fn from(validators: Vec) -> Self { + Self(validators) + } +} + impl From for Vec { fn from(val: NextValidators) -> Self { val.0 @@ -432,7 +388,7 @@ impl CurrentValidators { } pub fn addresses(&self) -> Vec
{ - self.0.iter().rev().map(|v| public_to_address(&v.pubkey)).collect() + self.0.iter().rev().map(|v| public_to_address(v.pubkey())).collect() } } @@ -495,7 +451,7 @@ impl Candidates { // Candidates are sorted in low priority: low index, high priority: high index // so stable sorting with the key (delegation, deposit) preserves its priority order. // ascending order of (delegation, deposit, priority) - result.sort_by_key(|v| (v.delegation, v.deposit)); + result.sort_by_key(|v| (v.delegation(), v.deposit())); Ok(result) } @@ -543,13 +499,14 @@ impl Candidates { pub fn renew_candidates( &mut self, - validators: &NextValidators, + validators: &[Validator], nomination_ends_at: u64, inactive_validators: &[Address], banned: &Banned, ) { - let to_renew: HashSet<_> = (validators.iter()) - .map(|validator| validator.pubkey) + let to_renew: HashSet<_> = validators + .iter() + .map(|validator| validator.pubkey()) .filter(|pubkey| !inactive_validators.contains(&public_to_address(pubkey))) .collect(); @@ -591,6 +548,7 @@ impl Candidates { } } +#[derive(Clone)] pub struct Jail(BTreeMap); #[derive(Clone, Debug, Eq, PartialEq, RlpEncodable, RlpDecodable)] pub struct Prisoner { @@ -666,11 +624,8 @@ impl Jail { } } - pub fn drain_released_prisoners(&mut self, term_index: u64) -> Vec { - let (released, retained): (Vec<_>, Vec<_>) = - self.0.values().cloned().partition(|c| c.released_at <= term_index); - self.0 = retained.into_iter().map(|c| (c.address, c)).collect(); - released + pub fn released_addresses(self, term_index: u64) -> Vec
{ + self.0.values().filter(|c| c.released_at <= term_index).map(|c| c.address).collect() } } @@ -1507,7 +1462,8 @@ mod tests { // Kick let mut jail = Jail::load_from_state(&state).unwrap(); - let released = jail.drain_released_prisoners(19); + let released = + jail.clone().released_addresses(19).iter().map(|address| jail.remove(address).unwrap()).collect::>(); jail.save_to_state(&mut state).unwrap(); // Assert @@ -1552,7 +1508,12 @@ mod tests { // Kick let mut jail = Jail::load_from_state(&state).unwrap(); - let released = jail.drain_released_prisoners(20); + let released = jail + .clone() + .released_addresses(20) + .into_iter() + .map(|address| jail.remove(&address).unwrap()) + .collect::>(); jail.save_to_state(&mut state).unwrap(); // Assert @@ -1602,7 +1563,12 @@ mod tests { // Kick let mut jail = Jail::load_from_state(&state).unwrap(); - let released = jail.drain_released_prisoners(25); + let released = jail + .clone() + .released_addresses(25) + .into_iter() + .map(|address| jail.remove(&address).unwrap()) + .collect::>(); jail.save_to_state(&mut state).unwrap(); // Assert @@ -1691,17 +1657,7 @@ mod tests { } candidates.save_to_state(&mut state).unwrap(); - let dummy_validators = NextValidators( - pubkeys[0..5] - .iter() - .map(|pubkey| Validator { - pubkey: *pubkey, - deposit: 0, - delegation: 0, - weight: 0, - }) - .collect(), - ); + let dummy_validators = pubkeys[0..5].iter().map(|pubkey| Validator::new(0, 0, *pubkey)).collect::>(); let dummy_banned = Banned::load_from_state(&state).unwrap(); candidates.renew_candidates(&dummy_validators, 0, &[], &dummy_banned); diff --git a/state/src/lib.rs b/state/src/lib.rs index 6265f15dac..6e71196468 100644 --- a/state/src/lib.rs +++ b/state/src/lib.rs @@ -48,13 +48,10 @@ pub use crate::item::module::{Module, ModuleAddress}; pub use crate::item::module_datum::{ModuleDatum, ModuleDatumAddress}; pub use crate::item::shard::{Shard, ShardAddress}; pub use crate::item::stake::{ - get_delegation_key, get_stake_account_key, Banned, Candidate, Candidates, CurrentValidators, Delegation, Jail, - NextValidators, Prisoner, ReleaseResult, StakeAccount, Stakeholders, Validator, + get_delegation_key, get_stake_account_key, Banned, Candidates, CurrentValidators, Jail, NextValidators, }; pub use crate::stake::{ - ban, delegate_ccs, init_stake, jail, query as query_stake_state, release_jailed_prisoners, revert_delegations, - self_nominate, update_candidates, update_validator_weights, DoubleVoteHandler, FindDoubleVoteHandler, - StakeKeyBuilder, + ban, init_stake, query as query_stake_state, DoubleVoteHandler, FindDoubleVoteHandler, StakeKeyBuilder, }; pub use crate::traits::{ShardState, ShardStateView, StateWithCache, TopState, TopStateView}; diff --git a/state/src/stake/actions.rs b/state/src/stake/actions.rs index fd197079e7..6a9ff3165a 100644 --- a/state/src/stake/actions.rs +++ b/state/src/stake/actions.rs @@ -14,13 +14,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::{ - Banned, Candidates, Delegation, Jail, NextValidators, ReleaseResult, StakeAccount, Stakeholders, StateResult, - TopLevelState, TopState, TopStateView, -}; +use crate::item::stake::{Delegation, ReleaseResult, StakeAccount, Stakeholders}; +use crate::{Banned, Candidates, Jail, NextValidators, StateResult, TopLevelState, TopState, TopStateView}; use ckey::{public_to_address, Address, Ed25519Public as Public}; use ctypes::errors::RuntimeError; -use ctypes::transaction::Approval; +use ctypes::transaction::{Approval, Validator}; use ctypes::util::unexpected::Mismatch; use ctypes::{CommonParams, Deposit}; use primitives::Bytes; @@ -291,15 +289,19 @@ fn get_stakes(state: &TopLevelState) -> StateResult> { Ok(result) } -pub fn release_jailed_prisoners(state: &mut TopLevelState, current_term: u64) -> StateResult> { +pub fn release_jailed_prisoners(state: &mut TopLevelState, released: &[Address]) -> StateResult<()> { + if released.is_empty() { + return Ok(()) + } + let mut jailed = Jail::load_from_state(&state)?; - let released = jailed.drain_released_prisoners(current_term); - for prisoner in &released { + for address in released { + let prisoner = jailed.remove(address).unwrap(); state.add_balance(&prisoner.address, prisoner.deposit)?; ctrace!(ENGINE, "on_term_close::released. prisoner: {}, deposit: {}", prisoner.address, prisoner.deposit); } jailed.save_to_state(state)?; - Ok(released.into_iter().map(|p| p.address).collect()) + revert_delegations(state, released) } pub fn jail(state: &mut TopLevelState, addresses: &[Address], custody_until: u64, kick_at: u64) -> StateResult<()> { @@ -382,25 +384,19 @@ pub fn revert_delegations(state: &mut TopLevelState, reverted_delegatees: &[Addr Ok(()) } -pub fn update_validator_weights(state: &mut TopLevelState, block_author: &Address) -> StateResult<()> { - let mut validators = NextValidators::load_from_state(state)?; - validators.update_weight(block_author); - validators.save_to_state(state) -} - pub fn update_candidates( state: &mut TopLevelState, current_term: u64, nomination_expiration: u64, + next_validators: &[Validator], inactive_validators: &[Address], -) -> StateResult> { +) -> StateResult<()> { let banned = Banned::load_from_state(state)?; let mut candidates = Candidates::load_from_state(state)?; let nomination_ends_at = current_term + nomination_expiration; - let current_validators = NextValidators::load_from_state(state)?; - candidates.renew_candidates(¤t_validators, nomination_ends_at, &inactive_validators, &banned); + candidates.renew_candidates(next_validators, nomination_ends_at, &inactive_validators, &banned); let expired = candidates.drain_expired_candidates(current_term); for candidate in &expired { @@ -409,17 +405,33 @@ pub fn update_candidates( ctrace!(ENGINE, "on_term_close::expired. candidate: {}, deposit: {}", address, candidate.deposit); } candidates.save_to_state(state)?; - Ok(expired.into_iter().map(|c| public_to_address(&c.pubkey)).collect()) + let expired: Vec<_> = expired.into_iter().map(|c| public_to_address(&c.pubkey)).collect(); + revert_delegations(state, &expired) +} + +pub fn close_term( + state: &mut TopLevelState, + next_validators: &[Validator], + inactive_validators: &[Address], +) -> StateResult<()> { + let metadata = state.metadata()?.expect("The metadata must exist"); + let current_term = metadata.current_term_id(); + ctrace!(ENGINE, "on_term_close. current_term: {}", current_term); + + let metadata = metadata.params(); + let nomination_expiration = metadata.nomination_expiration(); + assert_ne!(0, nomination_expiration); + + update_candidates(state, current_term, nomination_expiration, next_validators, inactive_validators)?; + NextValidators::from(next_validators.to_vec()).save_to_state(state) } #[cfg(test)] mod tests { use super::*; + use crate::item::stake::{Banned, Candidate, Candidates, Delegation, Jail, Prisoner, StakeAccount, Stakeholders}; use crate::tests::helpers; - use crate::{ - get_delegation_key, get_stake_account_key, init_stake, Banned, Candidate, Candidates, Delegation, Jail, - Prisoner, StakeAccount, Stakeholders, TopStateView, - }; + use crate::{get_delegation_key, get_stake_account_key, init_stake, TopStateView}; use std::collections::HashMap; #[test] diff --git a/state/src/stake/mod.rs b/state/src/stake/mod.rs index 75c67978c5..715ebda18c 100644 --- a/state/src/stake/mod.rs +++ b/state/src/stake/mod.rs @@ -17,8 +17,8 @@ mod actions; pub use self::actions::{ - ban, change_params, delegate_ccs, init_stake, jail, redelegate, release_jailed_prisoners, revert_delegations, - revoke, self_nominate, transfer_ccs, update_candidates, update_validator_weights, + ban, change_params, close_term, delegate_ccs, init_stake, jail, redelegate, release_jailed_prisoners, + revert_delegations, revoke, self_nominate, transfer_ccs, update_candidates, }; use super::TopStateView; use crate::{StateResult, TopLevelState}; @@ -86,6 +86,12 @@ impl StakeKeyBuilder { #[cfg(test)] mod tests { use super::*; + use crate::item::stake::{Candidate, Candidates, Delegation, Jail, StakeAccount}; + use crate::tests::helpers; + use crate::{NextValidators, TopLevelState, TopState, TopStateView}; + use ckey::{public_to_address, Ed25519Public as Public}; + use ctypes::CommonParams; + use std::collections::HashMap; #[test] fn action_data_key_builder_raw_fragment_and_list_are_same() { @@ -96,4 +102,433 @@ mod tests { let key2 = StakeKeyBuilder::key_from_fragment(rlp.as_raw()); assert_eq!(key1, key2); } + + fn metadata_for_election() -> TopLevelState { + let mut params = CommonParams::default_for_test(); + let mut state = helpers::get_temp_state_with_metadata(params); + state.metadata().unwrap().unwrap().set_params(CommonParams::default_for_test()); + params.set_dynamic_validator_params_for_test(30, 10, 3, 20, 30, 4, 1000, 10000, 100); + assert_eq!(Ok(()), state.update_params(0, params)); + state + } + + fn increase_term_id_until(state: &mut TopLevelState, term_id: u64) { + let mut block_num = state.metadata().unwrap().unwrap().last_term_finished_block_num() + 1; + while state.metadata().unwrap().unwrap().current_term_id() != term_id { + assert_eq!(Ok(()), state.increase_term_id(block_num)); + block_num += 1; + } + } + + #[test] + fn self_nominate_returns_deposits_after_expiration() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + + let mut state = metadata_for_election(); + increase_term_id_until(&mut state, 29); + state.add_balance(&address, 1000).unwrap(); + + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + self_nominate(&mut state, &address, &address_pubkey, 200, 0, 30, b"".to_vec()).unwrap(); + + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(29)).unwrap(); + + assert_eq!(state.balance(&address).unwrap(), 800, "Should keep nomination before expiration"); + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!( + candidates.get_candidate(&address), + Some(&Candidate { + pubkey: address_pubkey, + deposit: 200, + nomination_ends_at: 30, + metadata: b"".to_vec(), + }), + "Keep deposit before expiration", + ); + + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(30)).unwrap(); + + assert_eq!(state.balance(&address).unwrap(), 1000, "Return deposit after expiration"); + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.get_candidate(&address), None, "Removed from candidates after expiration"); + } + + #[test] + fn self_nominate_reverts_delegations_after_expiration() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + let delegator = public_to_address(&address_pubkey); + + let mut state = metadata_for_election(); + increase_term_id_until(&mut state, 29); + state.add_balance(&address, 1000).unwrap(); + + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + self_nominate(&mut state, &address, &address_pubkey, 0, 0, 30, b"".to_vec()).unwrap(); + + let quantity = 40; + delegate_ccs(&mut state, &delegator, &address, quantity).unwrap(); + + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(29)).unwrap(); + + let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(account.balance, 100 - 40); + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegation.get_quantity(&address), 40, "Should keep delegation before expiration"); + + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(30)).unwrap(); + + let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(account.balance, 100); + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegation.get_quantity(&address), 0, "Should revert before expiration"); + } + + #[test] + fn cannot_self_nominate_while_custody() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + + let mut state = metadata_for_election(); + state.add_balance(&address, 1000).unwrap(); + + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let deposit = 200; + let nominate_expire = 5; + let custody_until = 10; + let released_at = 20; + self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); + jail(&mut state, &[address], custody_until, released_at).unwrap(); + + for current_term in 0..=custody_until { + let result = self_nominate( + &mut state, + &address, + &address_pubkey, + 0, + current_term, + current_term + nominate_expire, + b"".to_vec(), + ); + assert!( + result.is_err(), + "Shouldn't nominate while current_term({}) <= custody_until({})", + current_term, + custody_until + ); + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term_id = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term_id); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(current_term)).unwrap(); + } + } + + #[test] + fn can_self_nominate_after_custody() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + + let mut state = metadata_for_election(); + state.add_balance(&address, 1000).unwrap(); + + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let deposit = 200; + let nominate_expire = 5; + let custody_until = 10; + let released_at = 20; + self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"metadata-before".to_vec()) + .unwrap(); + jail(&mut state, &[address], custody_until, released_at).unwrap(); + for current_term in 0..=custody_until { + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term_id = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term_id); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(current_term)).unwrap(); + } + + let current_term = custody_until + 1; + let additional_deposit = 123; + let result = self_nominate( + &mut state, + &address, + &address_pubkey, + additional_deposit, + current_term, + current_term + nominate_expire, + b"metadata-after".to_vec(), + ); + assert!(result.is_ok()); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!( + candidates.get_candidate(&address), + Some(&Candidate { + deposit: deposit + additional_deposit, + nomination_ends_at: current_term + nominate_expire, + pubkey: address_pubkey, + metadata: "metadata-after".into() + }), + "The prisoner is become a candidate", + ); + + let jail = Jail::load_from_state(&state).unwrap(); + assert_eq!(jail.get_prisoner(&address), None, "The prisoner is removed"); + + assert_eq!(state.balance(&address).unwrap(), 1000 - deposit - additional_deposit, "Deposit is accumulated"); + } + + #[test] + fn jail_released_after() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + + let mut state = metadata_for_election(); + state.add_balance(&address, 1000).unwrap(); + + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let deposit = 200; + let nominate_expire = 5; + let custody_until = 10; + let released_at = 20; + self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); + jail(&mut state, &[address], custody_until, released_at).unwrap(); + + for current_term in 0..released_at { + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term_id = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term_id); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(current_term)).unwrap(); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.get_candidate(&address), None); + + let jail = Jail::load_from_state(&state).unwrap(); + assert!(jail.get_prisoner(&address).is_some()); + } + + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(released_at)).unwrap(); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.get_candidate(&address), None, "A prisoner should not become a candidate"); + + let jail = Jail::load_from_state(&state).unwrap(); + assert_eq!(jail.get_prisoner(&address), None, "A prisoner should be released"); + + assert_eq!(state.balance(&address).unwrap(), 1000, "Balance should be restored after being released"); + } + + #[test] + fn cannot_delegate_until_released() { + let address_pubkey = Public::random(); + let delegator_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = metadata_for_election(); + state.add_balance(&address, 1000).unwrap(); + + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let deposit = 200; + let nominate_expire = 5; + let custody_until = 10; + let released_at = 20; + self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); + jail(&mut state, &[address], custody_until, released_at).unwrap(); + + for current_term in 0..=released_at { + let quantity = 1; + delegate_ccs(&mut state, &delegator, &address, quantity).unwrap_err(); + + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term_id = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term_id); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(current_term)).unwrap(); + } + + let quantity = 1; + delegate_ccs(&mut state, &delegator, &address, quantity).unwrap_err(); + } + + #[test] + fn kick_reverts_delegations() { + let address_pubkey = Public::random(); + let delegator_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = metadata_for_election(); + state.add_balance(&address, 1000).unwrap(); + + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let deposit = 200; + let nominate_expire = 5; + let custody_until = 10; + let released_at = 20; + self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); + + let quantity = 40; + delegate_ccs(&mut state, &delegator, &address, quantity).unwrap(); + + jail(&mut state, &[address], custody_until, released_at).unwrap(); + + for current_term in 0..=released_at { + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term_id = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term_id); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(current_term)).unwrap(); + } + + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegation.get_quantity(&address), 0, "Delegation should be reverted"); + + let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(account.balance, 100, "Delegation should be reverted"); + } + + #[test] + fn self_nomination_before_kick_preserves_delegations() { + let address_pubkey = Public::random(); + let delegator_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = metadata_for_election(); + state.add_balance(&address, 1000).unwrap(); + + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let nominate_expire = 5; + let custody_until = 10; + let released_at = 20; + self_nominate(&mut state, &address, &address_pubkey, 0, 0, nominate_expire, b"".to_vec()).unwrap(); + + let quantity = 40; + delegate_ccs(&mut state, &delegator, &address, quantity).unwrap(); + + jail(&mut state, &[address], custody_until, released_at).unwrap(); + + for current_term in 0..custody_until { + let next_validators = Vec::from(NextValidators::load_from_state(&state).unwrap()); + close_term(&mut state, &next_validators, &[]).unwrap(); + + let current_term_id = state.metadata().unwrap().unwrap().current_term_id(); + let released_addresses = Jail::load_from_state(&state).unwrap().released_addresses(current_term_id); + release_jailed_prisoners(&mut state, &released_addresses).unwrap(); + + state.increase_term_id(pseudo_term_to_block_num_calculator(current_term)).unwrap(); + } + + let current_term = custody_until + 1; + let result = self_nominate( + &mut state, + &address, + &address_pubkey, + 0, + current_term, + current_term + nominate_expire, + b"".to_vec(), + ); + assert!(result.is_ok()); + + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegation.get_quantity(&address), 40, "Delegation should be preserved"); + + let account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(account.balance, 100 - 40, "Delegation should be preserved"); + } + + fn pseudo_term_to_block_num_calculator(term_id: u64) -> u64 { + term_id * 10 + 1 + } } diff --git a/test/src/e2e.dynval/2/dv.shutdown.test.ts b/test/src/e2e.dynval/2/dv.shutdown.test.ts index d174d041ef..04be7bb56b 100644 --- a/test/src/e2e.dynval/2/dv.shutdown.test.ts +++ b/test/src/e2e.dynval/2/dv.shutdown.test.ts @@ -220,7 +220,7 @@ describe("Shutdown test", function() { ] }); - it("only a term closer should be a validator after a complete shutdown", async function() { + it.skip("only a term closer should be a validator after a complete shutdown", async function() { const termWaiter = setTermTestTimeout(this, { terms: 2 }); diff --git a/types/src/errors/runtime_error.rs b/types/src/errors/runtime_error.rs index c284228438..68015c4be8 100644 --- a/types/src/errors/runtime_error.rs +++ b/types/src/errors/runtime_error.rs @@ -50,6 +50,7 @@ pub enum Error { idx: usize, parent_height: u64, }, + InvalidValidators, } #[derive(Clone, Copy)] @@ -67,6 +68,7 @@ enum ErrorID { SignatureOfInvalid = 10, InsufficientStakes = 11, InvalidValidatorIndex = 12, + InvalidValidators = 13, } impl Encodable for ErrorID { @@ -90,6 +92,7 @@ impl Decodable for ErrorID { 9 => Ok(ErrorID::SignatureOfInvalid), 10 => Ok(ErrorID::InsufficientStakes), 11 => Ok(ErrorID::InvalidValidatorIndex), + 13 => Ok(ErrorID::InvalidValidators), _ => Err(DecoderError::Custom("Unexpected ActionTag Value")), } } @@ -113,6 +116,7 @@ impl TaggedRlp for RlpHelper { ErrorID::SignatureOfInvalid => 2, ErrorID::InsufficientStakes => 3, ErrorID::InvalidValidatorIndex => 3, + ErrorID::InvalidValidators => 1, }) } } @@ -153,6 +157,7 @@ impl Encodable for Error { idx, parent_height, } => RlpHelper::new_tagged_list(s, ErrorID::InvalidValidatorIndex).append(idx).append(parent_height), + Error::InvalidValidators => RlpHelper::new_tagged_list(s, ErrorID::InvalidValidators), }; } } @@ -186,6 +191,7 @@ impl Decodable for Error { idx: rlp.val_at(1)?, parent_height: rlp.val_at(2)?, }, + ErrorID::InvalidValidators => Error::InvalidValidators, }; RlpHelper::check_size(rlp, tag)?; Ok(error) @@ -219,6 +225,7 @@ impl Display for Error { idx, parent_height, } => write!(f, "The validator index {} is invalid at the parent hash {}", idx, parent_height), + Error::InvalidValidators => write!(f, "Cannot update validators"), } } } diff --git a/types/src/transaction/action.rs b/types/src/transaction/action.rs index a33f8db4c4..9d6b8bb215 100644 --- a/types/src/transaction/action.rs +++ b/types/src/transaction/action.rs @@ -15,7 +15,7 @@ // along with this program. If not, see . use crate::errors::SyntaxError; -use crate::transaction::{Approval, ShardTransaction}; +use crate::transaction::{Approval, ShardTransaction, Validator}; use crate::{CommonParams, ShardId}; use ccrypto::Blake; use ckey::{verify, Address, NetworkId}; @@ -33,6 +33,10 @@ enum ActionTag { SelfNominate = 0x24, ReportDoubleVote = 0x25, Redelegate = 0x26, + UpdateValidators = 0x30, + CloseTerm = 0x31, + ChangeNextValidators = 0x32, + Elect = 0x33, ChangeParams = 0xFF, } @@ -54,6 +58,10 @@ impl Decodable for ActionTag { 0x24 => Ok(Self::SelfNominate), 0x25 => Ok(Self::ReportDoubleVote), 0x26 => Ok(Self::Redelegate), + 0x30 => Ok(Self::UpdateValidators), + 0x31 => Ok(Self::CloseTerm), + 0x32 => Ok(Self::ChangeNextValidators), + 0x33 => Ok(Self::Elect), 0xFF => Ok(Self::ChangeParams), _ => Err(DecoderError::Custom("Unexpected action prefix")), } @@ -102,6 +110,20 @@ pub enum Action { message1: Bytes, message2: Bytes, }, + UpdateValidators { + validators: Vec, + }, + CloseTerm { + inactive_validators: Vec
, + next_validators: Vec, + released_addresses: Vec
, + custody_until: u64, + kick_at: u64, + }, + ChangeNextValidators { + validators: Vec, + }, + Elect, } impl Action { @@ -296,6 +318,40 @@ impl Encodable for Action { } => { s.begin_list(3).append(&ActionTag::ReportDoubleVote).append(message1).append(message2); } + Action::UpdateValidators { + validators, + } => { + let s = s.begin_list(validators.len() + 1).append(&ActionTag::UpdateValidators); + for validator in validators { + s.append(validator); + } + } + Action::CloseTerm { + inactive_validators, + next_validators, + released_addresses, + custody_until, + kick_at, + } => { + s.begin_list(6) + .append(&ActionTag::CloseTerm) + .append_list(inactive_validators) + .append_list(next_validators) + .append_list(released_addresses) + .append(custody_until) + .append(kick_at); + } + Action::ChangeNextValidators { + validators, + } => { + s.begin_list(1 + validators.len()).append(&ActionTag::ChangeNextValidators); + for validator in validators { + s.append(validator); + } + } + Action::Elect => { + s.begin_list(1).append(&ActionTag::Elect); + } } } } @@ -428,6 +484,63 @@ impl Decodable for Action { message2, }) } + ActionTag::UpdateValidators => { + let item_count = rlp.item_count()?; + if item_count < 1 { + return Err(DecoderError::RlpIncorrectListLen { + expected: 1, + got: item_count, + }) + } + let validators = rlp.iter().skip(1).map(|rlp| rlp.as_val()).collect::>()?; + Ok(Action::UpdateValidators { + validators, + }) + } + ActionTag::CloseTerm => { + let item_count = rlp.item_count()?; + if item_count != 6 { + return Err(DecoderError::RlpIncorrectListLen { + expected: 6, + got: item_count, + }) + } + let inactive_validators = rlp.list_at(1)?; + let next_validators = rlp.list_at(2)?; + let released_addresses = rlp.list_at(3)?; + let custody_until = rlp.val_at(4)?; + let kick_at = rlp.val_at(5)?; + Ok(Action::CloseTerm { + inactive_validators, + next_validators, + released_addresses, + custody_until, + kick_at, + }) + } + ActionTag::ChangeNextValidators => { + let item_count = rlp.item_count()?; + if item_count < 1 { + return Err(DecoderError::RlpIncorrectListLen { + expected: 1, + got: item_count, + }) + } + let validators = rlp.iter().skip(1).map(|rlp| rlp.as_val()).collect::>()?; + Ok(Action::ChangeNextValidators { + validators, + }) + } + ActionTag::Elect => { + let item_count = rlp.item_count()?; + if item_count != 1 { + return Err(DecoderError::RlpIncorrectListLen { + expected: 1, + got: item_count, + }) + } + Ok(Action::Elect) + } } } } @@ -458,6 +571,36 @@ mod tests { }); } + #[test] + fn rlp_of_update_validators() { + rlp_encode_and_decode_test!(Action::UpdateValidators { + validators: vec![Validator::new(1, 2, Public::random()), Validator::new(3, 4, Public::random())], + }); + } + + #[test] + fn rlp_of_close_term() { + rlp_encode_and_decode_test!(Action::CloseTerm { + inactive_validators: vec![Address::random(), Address::random(), Address::random()], + next_validators: vec![], + released_addresses: vec![Address::random(), Address::random()], + custody_until: 17, + kick_at: 31, + }); + } + + #[test] + fn rlp_of_change_next_validators() { + rlp_encode_and_decode_test!(Action::ChangeNextValidators { + validators: vec![], + }); + } + + #[test] + fn rlp_of_elect() { + rlp_encode_and_decode_test!(Action::Elect); + } + #[test] fn decode_fail_if_change_params_have_no_signatures() { let action = Action::ChangeParams { diff --git a/types/src/transaction/mod.rs b/types/src/transaction/mod.rs index 3d2e59ee43..7de66c6548 100644 --- a/types/src/transaction/mod.rs +++ b/types/src/transaction/mod.rs @@ -22,6 +22,7 @@ mod shard; mod timelock; #[cfg_attr(feature = "cargo-clippy", allow(clippy::module_inception))] mod transaction; +mod validator; pub use self::action::Action; pub use self::approval::Approval; @@ -30,3 +31,4 @@ pub use self::partial_hashing::{HashingError, PartialHashing}; pub use self::shard::ShardTransaction; pub use self::timelock::Timelock; pub use self::transaction::Transaction; +pub use self::validator::Validator; diff --git a/types/src/transaction/validator.rs b/types/src/transaction/validator.rs new file mode 100644 index 0000000000..e2752481e4 --- /dev/null +++ b/types/src/transaction/validator.rs @@ -0,0 +1,80 @@ +// Copyright 2020 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use ckey::Ed25519Public as Public; + +pub type StakeQuantity = u64; +pub type DepositQuantity = u64; + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, RlpDecodable, RlpEncodable)] +pub struct Validator { + weight: StakeQuantity, + delegation: StakeQuantity, + deposit: DepositQuantity, + pubkey: Public, +} + +impl Validator { + pub fn new(delegation: StakeQuantity, deposit: DepositQuantity, pubkey: Public) -> Self { + Self { + weight: delegation, + delegation, + deposit, + pubkey, + } + } + + pub fn reset(&mut self) { + self.weight = self.delegation; + } + + pub fn weight(&self) -> StakeQuantity { + self.weight + } + + pub fn set_weight(&mut self, weight: StakeQuantity) { + self.weight = weight; + } + + pub fn pubkey(&self) -> &Public { + &self.pubkey + } + + pub fn delegation(&self) -> StakeQuantity { + self.delegation + } + + pub fn deposit(&self) -> DepositQuantity { + self.deposit + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ckey::Ed25519Public as Public; + use rlp::rlp_encode_and_decode_test; + + #[test] + fn encode_and_decode_pay_action() { + rlp_encode_and_decode_test!(Validator { + weight: 1, + delegation: 2, + deposit: 3, + pubkey: Public::random(), + }); + } +}