From fc394fb83d8057a4e76cbcf0906984ea2b4d7144 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Thu, 26 Mar 2020 23:46:44 +0900 Subject: [PATCH 1/5] Move StakeAction to the types crate --- core/src/consensus/stake/mod.rs | 393 +++++++++++++++++- .../consensus/tendermint/vote_collector.rs | 2 +- foundry/auto_self_nominate.rs | 2 +- types/src/lib.rs | 2 + types/src/transaction/mod.rs | 4 +- .../src/transaction/stake.rs | 356 +--------------- 6 files changed, 396 insertions(+), 363 deletions(-) rename core/src/consensus/stake/actions.rs => types/src/transaction/stake.rs (50%) diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index 623f35b823..f03f2371ca 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -15,12 +15,12 @@ // along with this program. If not, see . mod action_data; -mod actions; use crate::client::ConsensusClient; use ckey::{public_to_address, Address, Ed25519Public as Public}; use cstate::{StakeHandler, StateResult, TopLevelState, TopState, TopStateView}; use ctypes::errors::{RuntimeError, SyntaxError}; +use ctypes::transaction::{Approval, StakeAction}; use ctypes::util::unexpected::Mismatch; use ctypes::CommonParams; use parking_lot::RwLock; @@ -31,7 +31,6 @@ use std::sync::{Arc, Weak}; pub use self::action_data::{Banned, Candidates, CurrentValidators, Jail, NextValidators, Validator}; use self::action_data::{Delegation, ReleaseResult, StakeAccount, Stakeholders}; -pub use self::actions::{Approval, StakeAction}; use super::tendermint::Deposit; use super::ValidatorSet; use crate::consensus::ConsensusMessage; @@ -60,6 +59,21 @@ impl StakeHandler for Stake { sender_public: &Public, ) -> StateResult<()> { let action = StakeAction::decode(&Rlp::new(bytes)).expect("Verification passed"); + + if let StakeAction::ReportDoubleVote { + message1, + .. + } = &action + { + let message1: ConsensusMessage = + rlp::decode(message1).map_err(|err| RuntimeError::FailedToHandleCustomAction(err.to_string()))?; + let validators = + self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet must be initialized"); + let client = self.client.read().as_ref().and_then(Weak::upgrade).expect("Client must be initialized"); + + execute_report_double_vote(message1, state, sender_address, &*client, &*validators)?; + } + match action { StakeAction::TransferCCS { address, @@ -97,32 +111,95 @@ impl StakeHandler for Stake { approvals, } => change_params(state, metadata_seq, *params, &approvals), StakeAction::ReportDoubleVote { - message1, .. - } => { - let message1: ConsensusMessage = - rlp::decode(&message1).map_err(|err| RuntimeError::FailedToHandleCustomAction(err.to_string()))?; - let validator_set = - self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet must be initialized"); - let client = self.client.read().as_ref().and_then(Weak::upgrade).expect("Client must be initialized"); - let parent_hash = - client.block_header(&(message1.height() - 1).into()).expect("Parent header verified").hash(); - let malicious_user_public = validator_set.get(&parent_hash, message1.signer_index()); - - ban(state, sender_address, public_to_address(&malicious_user_public)) - } + } => Ok(()), } } fn verify(&self, bytes: &[u8], current_params: &CommonParams) -> Result<(), SyntaxError> { let action = StakeAction::decode(&Rlp::new(bytes)).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - let client: Option> = self.client.read().as_ref().and_then(Weak::upgrade); - let validators: Option> = self.validators.read().as_ref().and_then(Weak::upgrade); - action.verify(current_params, client, validators) + action.verify(current_params)?; + if let StakeAction::ReportDoubleVote { + message1, + message2, + } = action + { + let client: Arc = + self.client.read().as_ref().and_then(Weak::upgrade).expect("Client should be initialized"); + let validators: Arc = + self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet should be initialized"); + + let message1: ConsensusMessage = + rlp::decode(&message1).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; + let message2: ConsensusMessage = + rlp::decode(&message2).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; + + verify_report_double_vote(message1, message2, &*client, &*validators)?; + } + Ok(()) } } +fn execute_report_double_vote( + message1: ConsensusMessage, + state: &mut TopLevelState, + sender_address: &Address, + client: &dyn ConsensusClient, + validators: &dyn ValidatorSet, +) -> StateResult<()> { + let parent_hash = client.block_header(&(message1.height() - 1).into()).expect("Parent header verified").hash(); + let malicious_user_public = validators.get(&parent_hash, message1.signer_index()); + + ban(state, sender_address, public_to_address(&malicious_user_public)) +} + +pub fn verify_report_double_vote( + message1: ConsensusMessage, + message2: ConsensusMessage, + client: &dyn ConsensusClient, + validators: &dyn ValidatorSet, +) -> Result<(), SyntaxError> { + if message1.round() != message2.round() { + return Err(SyntaxError::InvalidCustomAction(String::from("The messages are from two different voting rounds"))) + } + + let signer_idx1 = message1.signer_index(); + let signer_idx2 = message2.signer_index(); + + if signer_idx1 != signer_idx2 { + return Err(SyntaxError::InvalidCustomAction(format!( + "Two messages have different signer indexes: {}, {}", + signer_idx1, signer_idx2 + ))) + } + + assert_eq!( + message1.height(), + message2.height(), + "Heights of both messages must be same because message1.round() == message2.round()" + ); + + let signed_block_height = message1.height(); + if signed_block_height == 0 { + return Err(SyntaxError::InvalidCustomAction(String::from( + "Double vote on the genesis block does not make sense", + ))) + } + let parent_hash = client + .block_header(&(signed_block_height - 1).into()) + .ok_or_else(|| { + SyntaxError::InvalidCustomAction(format!("Cannot get header from the height {}", signed_block_height)) + })? + .hash(); + let signer_idx1 = message1.signer_index(); + let signer = validators.get(&parent_hash, signer_idx1); + if !message1.verify(&signer) || !message2.verify(&signer) { + return Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))) + } + Ok(()) +} + fn transfer_ccs(state: &mut TopLevelState, sender: &Address, receiver: &Address, quantity: u64) -> StateResult<()> { let mut stakeholders = Stakeholders::load_from_state(state)?; let mut sender_account = StakeAccount::load_from_state(state, sender)?; @@ -1753,3 +1830,283 @@ mod tests { term_id * 10 + 1 } } + +#[cfg(test)] +mod tests_double_vote { + use super::*; + use crate::client::{ConsensusClient, TestBlockChainClient}; + use crate::consensus::{DynamicValidator, Step, VoteOn, VoteStep}; + use ckey::sign; + use ctypes::BlockHash; + use primitives::H256; + use rlp::Encodable; + + struct ConsensusMessageInfo { + pub height: u64, + pub view: u64, + pub step: Step, + pub block_hash: Option, + pub signer_index: usize, + } + + fn create_consensus_message( + info: ConsensusMessageInfo, + client: &TestBlockChainClient, + vote_step_twister: &F, + block_hash_twister: &G, + ) -> ConsensusMessage + where + F: Fn(VoteStep) -> VoteStep, + G: Fn(Option) -> Option, { + let ConsensusMessageInfo { + height, + view, + step, + block_hash, + signer_index, + } = info; + let vote_step = VoteStep::new(height, view, step); + let on = VoteOn { + step: vote_step, + block_hash, + }; + let twisted = VoteOn { + step: vote_step_twister(vote_step), + block_hash: block_hash_twister(block_hash), + }; + let reversed_idx = client.get_validators().len() - 1 - signer_index; + let pubkey = *client.get_validators().get(reversed_idx).unwrap().pubkey(); + let validator_keys = client.validator_keys.read(); + let privkey = validator_keys.get(&pubkey).unwrap(); + let signature = sign(&twisted.hash(), privkey); + + ConsensusMessage { + signature, + signer_index, + on, + } + } + + fn double_vote_verification_result( + message_info1: ConsensusMessageInfo, + message_info2: ConsensusMessageInfo, + vote_step_twister: &F, + block_hash_twister: &G, + ) -> Result<(), SyntaxError> + where + F: Fn(VoteStep) -> VoteStep, + G: Fn(Option) -> Option, { + let mut test_client = TestBlockChainClient::default(); + test_client.add_blocks(10, 1); + test_client.set_random_validators(10); + let validator_set = DynamicValidator::default(); + + let consensus_message1 = + create_consensus_message(message_info1, &test_client, vote_step_twister, block_hash_twister); + let consensus_message2 = + create_consensus_message(message_info2, &test_client, vote_step_twister, block_hash_twister); + let action = StakeAction::ReportDoubleVote { + message1: consensus_message1.rlp_bytes(), + message2: consensus_message2.rlp_bytes(), + }; + let arced_client: Arc = Arc::new(test_client); + validator_set.register_client(Arc::downgrade(&arced_client)); + action.verify(&CommonParams::default_for_test())?; + verify_report_double_vote(consensus_message1, consensus_message2, &*arced_client, &validator_set) + } + + #[test] + fn double_vote_verify_desirable_report() { + let result = double_vote_verification_result( + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: None, + signer_index: 0, + }, + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: Some(H256::random().into()), + signer_index: 0, + }, + &|v| v, + &|v| v, + ); + assert!(result.is_ok()); + } + + #[test] + fn double_vote_verify_same_message() { + let block_hash = Some(H256::random().into()); + let result = double_vote_verification_result( + ConsensusMessageInfo { + height: 3, + view: 1, + step: Step::Precommit, + block_hash, + signer_index: 2, + }, + ConsensusMessageInfo { + height: 3, + view: 1, + step: Step::Precommit, + block_hash, + signer_index: 2, + }, + &|v| v, + &|v| v, + ); + let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Messages are duplicated"))); + assert_eq!(result, expected_err); + } + + #[test] + fn double_vote_verify_different_height() { + let block_hash = Some(H256::random().into()); + let result = double_vote_verification_result( + ConsensusMessageInfo { + height: 3, + view: 1, + step: Step::Precommit, + block_hash, + signer_index: 2, + }, + ConsensusMessageInfo { + height: 2, + view: 1, + step: Step::Precommit, + block_hash, + signer_index: 2, + }, + &|v| v, + &|v| v, + ); + let expected_err = + Err(SyntaxError::InvalidCustomAction(String::from("The messages are from two different voting rounds"))); + assert_eq!(result, expected_err); + } + + #[test] + fn double_vote_verify_different_signer() { + let result = double_vote_verification_result( + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: None, + signer_index: 1, + }, + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: Some(H256::random().into()), + signer_index: 0, + }, + &|v| v, + &|v| v, + ); + match result { + Err(SyntaxError::InvalidCustomAction(ref s)) + if s.contains("Two messages have different signer indexes") => {} + _ => panic!(), + } + } + + #[test] + fn double_vote_verify_different_message_and_signer() { + let hash1 = Some(H256::random().into()); + let mut hash2 = Some(H256::random().into()); + while hash1 == hash2 { + hash2 = Some(H256::random().into()); + } + let result = double_vote_verification_result( + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: hash1, + signer_index: 1, + }, + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: hash2, + signer_index: 0, + }, + &|v| v, + &|v| v, + ); + match result { + Err(SyntaxError::InvalidCustomAction(ref s)) + if s.contains("Two messages have different signer indexes") => {} + _ => panic!(), + } + } + + #[test] + fn double_vote_verify_strange_sig1() { + let vote_step_twister = |original: VoteStep| VoteStep { + height: original.height + 1, + view: original.height + 1, + step: original.step, + }; + let result = double_vote_verification_result( + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: None, + signer_index: 0, + }, + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: Some(H256::random().into()), + signer_index: 0, + }, + &vote_step_twister, + &|v| v, + ); + let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))); + assert_eq!(result, expected_err); + } + + #[test] + fn double_vote_verify_strange_sig2() { + let block_hash_twister = |original: Option| { + original.map(|hash| { + let mut twisted = H256::random(); + while twisted == *hash { + twisted = H256::random(); + } + BlockHash::from(twisted) + }) + }; + let result = double_vote_verification_result( + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: None, + signer_index: 0, + }, + ConsensusMessageInfo { + height: 2, + view: 0, + step: Step::Precommit, + block_hash: Some(H256::random().into()), + signer_index: 0, + }, + &|v| v, + &block_hash_twister, + ); + let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))); + assert_eq!(result, expected_err); + } +} diff --git a/core/src/consensus/tendermint/vote_collector.rs b/core/src/consensus/tendermint/vote_collector.rs index 595084de31..6f8d08f4d1 100644 --- a/core/src/consensus/tendermint/vote_collector.rs +++ b/core/src/consensus/tendermint/vote_collector.rs @@ -14,10 +14,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use super::stake::StakeAction; use super::{ConsensusMessage, VoteStep}; use crate::consensus::BitSet; use ckey::Signature; +use ctypes::transaction::StakeAction; use ctypes::BlockHash; use rlp::{Encodable, RlpStream}; use std::collections::{BTreeMap, HashMap}; diff --git a/foundry/auto_self_nominate.rs b/foundry/auto_self_nominate.rs index 80bff4b9ee..2dddb75e6c 100644 --- a/foundry/auto_self_nominate.rs +++ b/foundry/auto_self_nominate.rs @@ -15,13 +15,13 @@ // along with this program. If not, see . use crate::config::load_config; -use ccore::stake::StakeAction::SelfNominate; use ccore::stake::{Banned, Candidates, Jail, CUSTOM_ACTION_HANDLER_ID}; use ccore::{AccountProvider, AccountProviderError, BlockId, ConsensusClient, Encodable, UnverifiedTransaction}; use ckey::PlatformAddress; use ckey::{Address, Ed25519Public as Public, Signature}; use ckeystore::DecryptedAccount; use clap::ArgMatches; +use ctypes::transaction::StakeAction::SelfNominate; use ctypes::transaction::{Action, Transaction}; use primitives::{Bytes, H256}; use std::convert::TryInto; diff --git a/types/src/lib.rs b/types/src/lib.rs index 9945918647..d45020e0e2 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -16,6 +16,8 @@ #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate rlp_derive; mod block_hash; mod common_params; diff --git a/types/src/transaction/mod.rs b/types/src/transaction/mod.rs index d8bd0c7b47..7c84767764 100644 --- a/types/src/transaction/mod.rs +++ b/types/src/transaction/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2019 Kodebox, Inc. +// Copyright 2018-2020 Kodebox, Inc. // This file is part of CodeChain. // // This program is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ mod action; mod incomplete_transaction; mod partial_hashing; mod shard; +mod stake; mod timelock; #[cfg_attr(feature = "cargo-clippy", allow(clippy::module_inception))] mod transaction; @@ -26,5 +27,6 @@ pub use self::action::Action; pub use self::incomplete_transaction::IncompleteTransaction; pub use self::partial_hashing::{HashingError, PartialHashing}; pub use self::shard::ShardTransaction; +pub use self::stake::{Approval, StakeAction}; pub use self::timelock::Timelock; pub use self::transaction::Transaction; diff --git a/core/src/consensus/stake/actions.rs b/types/src/transaction/stake.rs similarity index 50% rename from core/src/consensus/stake/actions.rs rename to types/src/transaction/stake.rs index 168fd9aecc..cd403692a4 100644 --- a/core/src/consensus/stake/actions.rs +++ b/types/src/transaction/stake.rs @@ -14,15 +14,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::client::ConsensusClient; -use crate::consensus::{ConsensusMessage, ValidatorSet}; +use crate::errors::SyntaxError; +use crate::CommonParams; use ccrypto::Blake; use ckey::{verify, Address, Ed25519Public as Public, Signature}; -use ctypes::errors::SyntaxError; -use ctypes::CommonParams; use primitives::{Bytes, H256}; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; -use std::sync::Arc; #[derive(Clone, Copy)] #[repr(u8)] @@ -109,12 +106,7 @@ pub enum StakeAction { } impl StakeAction { - pub fn verify( - &self, - current_params: &CommonParams, - client: Option>, - validators: Option>, - ) -> Result<(), SyntaxError> { + pub fn verify(&self, current_params: &CommonParams) -> Result<(), SyntaxError> { match self { StakeAction::TransferCCS { .. @@ -169,54 +161,6 @@ impl StakeAction { if message1 == message2 { return Err(SyntaxError::InvalidCustomAction(String::from("Messages are duplicated"))) } - let message1: ConsensusMessage = - rlp::decode(&message1).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - let message2: ConsensusMessage = - rlp::decode(&message2).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - if message1.round() != message2.round() { - return Err(SyntaxError::InvalidCustomAction(String::from( - "The messages are from two different voting rounds", - ))) - } - - let signer_idx1 = message1.signer_index(); - let signer_idx2 = message2.signer_index(); - - if signer_idx1 != signer_idx2 { - return Err(SyntaxError::InvalidCustomAction(format!( - "Two messages have different signer indexes: {}, {}", - signer_idx1, signer_idx2 - ))) - } - - assert_eq!( - message1.height(), - message2.height(), - "Heights of both messages must be same because message1.round() == message2.round()" - ); - let signed_block_height = message1.height(); - let (client, validators) = ( - client.expect("Client should be initialized"), - validators.expect("ValidatorSet should be initialized"), - ); - if signed_block_height == 0 { - return Err(SyntaxError::InvalidCustomAction(String::from( - "Double vote on the genesis block does not make sense", - ))) - } - let parent_hash = client - .block_header(&(signed_block_height - 1).into()) - .ok_or_else(|| { - SyntaxError::InvalidCustomAction(format!( - "Cannot get header from the height {}", - signed_block_height - )) - })? - .hash(); - let signer = validators.get(&parent_hash, signer_idx1); - if !message1.verify(&signer) || !message2.verify(&signer) { - return Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))) - } } } Ok(()) @@ -393,28 +337,8 @@ impl Decodable for StakeAction { #[cfg(test)] mod tests { use super::*; - use crate::client::TestBlockChainClient; - use crate::consensus::{ConsensusMessage, DynamicValidator, Step, VoteOn, VoteStep}; - use ckey::sign; - use ctypes::BlockHash; use rlp::rlp_encode_and_decode_test; - #[test] - fn decode_fail_if_change_params_have_no_signatures() { - let action = StakeAction::ChangeParams { - metadata_seq: 3, - params: CommonParams::default_for_test().into(), - approvals: vec![], - }; - assert_eq!( - Err(DecoderError::RlpIncorrectListLen { - expected: 4, - got: 3, - }), - Rlp::new(&rlp::encode(&action)).as_val::() - ); - } - #[test] fn rlp_of_change_params() { rlp_encode_and_decode_test!(StakeAction::ChangeParams { @@ -433,271 +357,19 @@ mod tests { }); } - struct ConsensusMessageInfo { - pub height: u64, - pub view: u64, - pub step: Step, - pub block_hash: Option, - pub signer_index: usize, - } - - fn create_consensus_message( - info: ConsensusMessageInfo, - client: &TestBlockChainClient, - vote_step_twister: &F, - block_hash_twister: &G, - ) -> ConsensusMessage - where - F: Fn(VoteStep) -> VoteStep, - G: Fn(Option) -> Option, { - let ConsensusMessageInfo { - height, - view, - step, - block_hash, - signer_index, - } = info; - let vote_step = VoteStep::new(height, view, step); - let on = VoteOn { - step: vote_step, - block_hash, - }; - let twisted = VoteOn { - step: vote_step_twister(vote_step), - block_hash: block_hash_twister(block_hash), - }; - let reversed_idx = client.get_validators().len() - 1 - signer_index; - let pubkey = *client.get_validators().get(reversed_idx).unwrap().pubkey(); - let validator_keys = client.validator_keys.read(); - let privkey = validator_keys.get(&pubkey).unwrap(); - let signature = sign(&twisted.hash(), privkey); - - ConsensusMessage { - signature, - signer_index, - on, - } - } - - fn double_vote_verification_result( - message_info1: ConsensusMessageInfo, - message_info2: ConsensusMessageInfo, - vote_step_twister: &F, - block_hash_twister: &G, - ) -> Result<(), SyntaxError> - where - F: Fn(VoteStep) -> VoteStep, - G: Fn(Option) -> Option, { - let mut test_client = TestBlockChainClient::default(); - test_client.add_blocks(10, 1); - test_client.set_random_validators(10); - let validator_set = DynamicValidator::default(); - - let consensus_message1 = - create_consensus_message(message_info1, &test_client, vote_step_twister, block_hash_twister); - let consensus_message2 = - create_consensus_message(message_info2, &test_client, vote_step_twister, block_hash_twister); - let action = StakeAction::ReportDoubleVote { - message1: consensus_message1.rlp_bytes(), - message2: consensus_message2.rlp_bytes(), - }; - let arced_client: Arc = Arc::new(test_client); - validator_set.register_client(Arc::downgrade(&arced_client)); - action.verify(&CommonParams::default_for_test(), Some(Arc::clone(&arced_client)), Some(Arc::new(validator_set))) - } - #[test] - fn double_vote_verify_desirable_report() { - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 0, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &|v| v, - &|v| v, - ); - assert!(result.is_ok()); - } - - #[test] - fn double_vote_verify_same_message() { - let block_hash = Some(H256::random().into()); - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 3, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - ConsensusMessageInfo { - height: 3, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - &|v| v, - &|v| v, - ); - let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Messages are duplicated"))); - assert_eq!(result, expected_err); - } - - #[test] - fn double_vote_verify_different_height() { - let block_hash = Some(H256::random().into()); - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 3, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - ConsensusMessageInfo { - height: 2, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - &|v| v, - &|v| v, - ); - let expected_err = - Err(SyntaxError::InvalidCustomAction(String::from("The messages are from two different voting rounds"))); - assert_eq!(result, expected_err); - } - - #[test] - fn double_vote_verify_different_signer() { - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 1, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &|v| v, - &|v| v, - ); - match result { - Err(SyntaxError::InvalidCustomAction(ref s)) - if s.contains("Two messages have different signer indexes") => {} - _ => panic!(), - } - } - - #[test] - fn double_vote_verify_different_message_and_signer() { - let hash1 = Some(H256::random().into()); - let mut hash2 = Some(H256::random().into()); - while hash1 == hash2 { - hash2 = Some(H256::random().into()); - } - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: hash1, - signer_index: 1, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: hash2, - signer_index: 0, - }, - &|v| v, - &|v| v, - ); - match result { - Err(SyntaxError::InvalidCustomAction(ref s)) - if s.contains("Two messages have different signer indexes") => {} - _ => panic!(), - } - } - - #[test] - fn double_vote_verify_strange_sig1() { - let vote_step_twister = |original: VoteStep| VoteStep { - height: original.height + 1, - view: original.height + 1, - step: original.step, - }; - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 0, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &vote_step_twister, - &|v| v, - ); - let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))); - assert_eq!(result, expected_err); - } - - #[test] - fn double_vote_verify_strange_sig2() { - let block_hash_twister = |original: Option| { - original.map(|hash| { - let mut twisted = H256::random(); - while twisted == *hash { - twisted = H256::random(); - } - BlockHash::from(twisted) - }) + fn decode_fail_if_change_params_have_no_signatures() { + let action = StakeAction::ChangeParams { + metadata_seq: 3, + params: CommonParams::default_for_test().into(), + approvals: vec![], }; - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 0, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &|v| v, - &block_hash_twister, + assert_eq!( + Err(DecoderError::RlpIncorrectListLen { + expected: 4, + got: 3, + }), + Rlp::new(&rlp::encode(&action)).as_val::() ); - let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))); - assert_eq!(result, expected_err); } } From bde6c5360213ebe373f83c2b984f0a16ce529957 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Fri, 27 Mar 2020 00:21:14 +0900 Subject: [PATCH 2/5] Move states of stakes to the state crate --- Cargo.lock | 3 +++ Cargo.toml | 1 + core/src/block.rs | 5 ++--- core/src/client/test_client.rs | 3 +-- core/src/consensus/stake/mod.rs | 16 ++++------------ core/src/consensus/tendermint/engine.rs | 14 ++++++++------ core/src/consensus/tendermint/worker.rs | 3 +-- .../consensus/validator_set/dynamic_validator.rs | 2 +- foundry/auto_self_nominate.rs | 4 ++-- state/Cargo.toml | 4 ++++ state/src/item/mod.rs | 1 + .../action_data.rs => state/src/item/stake.rs | 15 +++++++-------- state/src/lib.rs | 6 ++++++ 13 files changed, 41 insertions(+), 36 deletions(-) rename core/src/consensus/stake/action_data.rs => state/src/item/stake.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 5d4ee3c2e1..cd11eda9d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,6 +587,8 @@ dependencies = [ "merkle-trie", "parking_lot 0.6.4", "primitives", + "rand 0.6.1", + "rand_xorshift", "rlp", "rlp_derive", "rustc-hex 1.0.0", @@ -1036,6 +1038,7 @@ dependencies = [ "codechain-logger", "codechain-network", "codechain-rpc", + "codechain-state", "codechain-sync", "codechain-timer", "codechain-types", diff --git a/Cargo.toml b/Cargo.toml index 452e1a6fc6..b2467a8c99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ ccore = { package = "codechain-core", path = "core" } cdiscovery = { package = "codechain-discovery", path = "discovery" } codechain-logger = { path = "util/logger" } ckey = { package = "codechain-key", path = "key" } +cstate = { package = "codechain-state", path = "state" } ctypes = { package = "codechain-types", path = "types" } ckeystore = { package = "codechain-keystore", path = "keystore" } cnetwork = { package = "codechain-network", path = "network" } diff --git a/core/src/block.rs b/core/src/block.rs index 39eec848af..c9c4cac90c 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -18,11 +18,10 @@ use super::invoice::Invoice; use crate::client::{EngineInfo, TermInfo}; use crate::consensus::CodeChainEngine; use crate::error::{BlockError, Error}; -use crate::stake; use crate::transaction::{UnverifiedTransaction, VerifiedTransaction}; use ccrypto::BLAKE_NULL_RLP; use ckey::Address; -use cstate::{FindStakeHandler, StateDB, StateError, StateWithCache, TopLevelState}; +use cstate::{FindStakeHandler, NextValidators, StateDB, StateError, StateWithCache, TopLevelState}; use ctypes::errors::HistoryError; use ctypes::header::{Header, Seal}; use ctypes::util::unexpected::Mismatch; @@ -226,7 +225,7 @@ impl<'x> OpenBlock<'x> { It doesn't cause a direct error since we use unwrap_or_default() here, but should be aware of such problem. Remove this comment after we completely omit static-validator-set-mode. */ - let vset_raw = stake::NextValidators::load_from_state(self.block.state())?; + let vset_raw = NextValidators::load_from_state(self.block.state())?; let vset = vset_raw.create_compact_validator_set(); self.block.header.set_next_validator_set_hash(vset.hash()); diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index 49bbe7c197..e5ec60874f 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -36,7 +36,6 @@ use crate::client::{ AccountData, BlockChainClient, BlockChainTrait, BlockProducer, BlockStatus, ConsensusClient, EngineInfo, ImportBlock, ImportResult, MiningBlockChainClient, StateInfo, StateOrBlock, TermInfo, }; -use crate::consensus::stake::{NextValidators, Validator}; use crate::consensus::EngineError; use crate::db::{COL_STATE, NUM_COLUMNS}; use crate::encoded; @@ -51,7 +50,7 @@ use ckey::{ Generator, KeyPairTrait, NetworkId, PlatformAddress, Random, }; use cstate::tests::helpers::empty_top_state_with_metadata; -use cstate::{FindStakeHandler, StateDB, TopLevelState}; +use cstate::{FindStakeHandler, NextValidators, StateDB, TopLevelState, Validator}; use ctimer::{TimeoutHandler, TimerToken}; use ctypes::header::Header; use ctypes::transaction::{Action, Transaction}; diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index f03f2371ca..37e3583504 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -14,10 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -mod action_data; - use crate::client::ConsensusClient; use ckey::{public_to_address, Address, Ed25519Public as Public}; +use cstate::{Banned, Candidates, Delegation, Jail, NextValidators, ReleaseResult, StakeAccount, Stakeholders}; use cstate::{StakeHandler, StateResult, TopLevelState, TopState, TopStateView}; use ctypes::errors::{RuntimeError, SyntaxError}; use ctypes::transaction::{Approval, StakeAction}; @@ -29,14 +28,10 @@ use rlp::{Decodable, Rlp}; use std::collections::HashMap; use std::sync::{Arc, Weak}; -pub use self::action_data::{Banned, Candidates, CurrentValidators, Jail, NextValidators, Validator}; -use self::action_data::{Delegation, ReleaseResult, StakeAccount, Stakeholders}; use super::tendermint::Deposit; use super::ValidatorSet; use crate::consensus::ConsensusMessage; -pub const CUSTOM_ACTION_HANDLER_ID: u64 = 2; - #[derive(Default)] pub struct Stake { client: RwLock>>, @@ -614,12 +609,9 @@ pub(super) fn init( #[cfg(test)] mod tests { - use super::action_data::get_account_key; use super::*; - - use crate::consensus::stake::action_data::{get_delegation_key, Candidate, Prisoner}; use cstate::tests::helpers; - use cstate::TopStateView; + use cstate::{get_delegation_key, get_stake_account_key, Candidate, Prisoner, TopStateView}; use rlp::Encodable; fn metadata_for_election() -> TopLevelState { @@ -702,7 +694,7 @@ mod tests { let account1 = StakeAccount::load_from_state(&state, &address1).unwrap(); assert_eq!(account1.balance, 0); - assert_eq!(state.action_data(&get_account_key(&address1)).unwrap(), None, "Should clear state"); + assert_eq!(state.action_data(&get_stake_account_key(&address1)).unwrap(), None, "Should clear state"); let account2 = StakeAccount::load_from_state(&state, &address2).unwrap(); assert_eq!(account2.balance, 100); @@ -782,7 +774,7 @@ mod tests { let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); assert_eq!(delegator_account.balance, 0); - assert_eq!(state.action_data(&get_account_key(&delegator)).unwrap(), None, "Should clear state"); + assert_eq!(state.action_data(&get_stake_account_key(&delegator)).unwrap(), None, "Should clear state"); let delegatee_account = StakeAccount::load_from_state(&state, &delegatee).unwrap(); assert_eq!(delegatee_account.balance, 100, "Shouldn't be touched"); diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 3c061a6bfe..5a6127eeb5 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -14,8 +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::{self, NextValidators}; -use super::super::{ConsensusEngine, EngineError, Seal}; +use super::super::{stake, ConsensusEngine, EngineError, Seal}; use super::network::TendermintExtension; pub use super::params::{TendermintParams, TimeoutParams}; use super::worker; @@ -33,7 +32,10 @@ use crate::BlockId; use ckey::{public_to_address, Address}; use cnetwork::NetworkService; use crossbeam_channel as crossbeam; -use cstate::{StakeHandler, StateDB, StateResult, StateWithCache, TopLevelState, TopState, TopStateView}; +use cstate::{ + CurrentValidators, NextValidators, StakeHandler, StateDB, StateResult, StateWithCache, TopLevelState, TopState, + TopStateView, +}; use ctypes::{BlockHash, Header}; use primitives::H256; use std::collections::HashSet; @@ -118,8 +120,8 @@ impl ConsensusEngine for Tendermint { /// Block transformation functions, before the transactions. fn on_open_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { - let mut current_validators = stake::CurrentValidators::load_from_state(block.state())?; - current_validators.update(stake::NextValidators::load_from_state(block.state())?.clone()); + 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(()) @@ -154,7 +156,7 @@ impl ConsensusEngine for Tendermint { 0 => Vec::new(), _ => { let start_of_the_current_term = metadata.last_term_finished_block_num() + 1; - let validators = stake::NextValidators::load_from_state(block.state())? + let validators = NextValidators::load_from_state(block.state())? .into_iter() .map(|val| public_to_address(val.pubkey())) .collect(); diff --git a/core/src/consensus/tendermint/worker.rs b/core/src/consensus/tendermint/worker.rs index b2eb3252ac..66cfe4b709 100644 --- a/core/src/consensus/tendermint/worker.rs +++ b/core/src/consensus/tendermint/worker.rs @@ -19,7 +19,6 @@ use super::backup::{backup, restore, BackupView}; use super::message::*; use super::network; use super::params::TimeGapParams; -use super::stake::CUSTOM_ACTION_HANDLER_ID; use super::types::{Height, Proposal, Step, TendermintSealView, TendermintState, TwoThirdsMajority, View}; use super::vote_collector::{DoubleVote, VoteCollector}; use super::vote_regression_checker::VoteRegressionChecker; @@ -1434,7 +1433,7 @@ impl Worker { fee: 0, network_id, action: Action::Custom { - handler_id: CUSTOM_ACTION_HANDLER_ID, + handler_id: 0, bytes: double.to_action().rlp_bytes(), }, }; diff --git a/core/src/consensus/validator_set/dynamic_validator.rs b/core/src/consensus/validator_set/dynamic_validator.rs index 47e476afe0..a6ab3bd2da 100644 --- a/core/src/consensus/validator_set/dynamic_validator.rs +++ b/core/src/consensus/validator_set/dynamic_validator.rs @@ -17,9 +17,9 @@ use super::ValidatorSet; use crate::client::ConsensusClient; use crate::consensus::bit_set::BitSet; -use crate::consensus::stake::{CurrentValidators, NextValidators, Validator}; use crate::consensus::EngineError; use ckey::{public_to_address, Address, Ed25519Public as Public}; +use cstate::{CurrentValidators, NextValidators, Validator}; use ctypes::util::unexpected::OutOfBounds; use ctypes::BlockHash; use parking_lot::RwLock; diff --git a/foundry/auto_self_nominate.rs b/foundry/auto_self_nominate.rs index 2dddb75e6c..8514939b5c 100644 --- a/foundry/auto_self_nominate.rs +++ b/foundry/auto_self_nominate.rs @@ -15,12 +15,12 @@ // along with this program. If not, see . use crate::config::load_config; -use ccore::stake::{Banned, Candidates, Jail, CUSTOM_ACTION_HANDLER_ID}; use ccore::{AccountProvider, AccountProviderError, BlockId, ConsensusClient, Encodable, UnverifiedTransaction}; use ckey::PlatformAddress; use ckey::{Address, Ed25519Public as Public, Signature}; use ckeystore::DecryptedAccount; use clap::ArgMatches; +use cstate::{Banned, Candidates, Jail}; use ctypes::transaction::StakeAction::SelfNominate; use ctypes::transaction::{Action, Transaction}; use primitives::{Bytes, H256}; @@ -179,7 +179,7 @@ impl AutoSelfNomination { fee: 0, network_id, action: Action::Custom { - handler_id: CUSTOM_ACTION_HANDLER_ID, + handler_id: 0, bytes: selfnominate.rlp_bytes(), }, }; diff --git a/state/Cargo.toml b/state/Cargo.toml index cfffb13af6..d3c6a49c55 100644 --- a/state/Cargo.toml +++ b/state/Cargo.toml @@ -20,3 +20,7 @@ primitives = { git = "https://github.com/CodeChain-io/rust-codechain-primitives. rlp = { git = "https://github.com/CodeChain-io/rlp.git", version = "0.4" } rlp_derive = { git = "https://github.com/CodeChain-io/rlp.git", version = "0.2" } rustc-hex = "1.0" + +[dev-dependencies] +rand = "0.6.1" +rand_xorshift = "0.1.0" diff --git a/state/src/item/mod.rs b/state/src/item/mod.rs index 1c7c120def..19c434d9a3 100644 --- a/state/src/item/mod.rs +++ b/state/src/item/mod.rs @@ -22,6 +22,7 @@ pub mod action_data; pub mod dummy_shard_text; pub mod metadata; pub mod shard; +pub mod stake; #[derive(Clone, Copy)] #[repr(u8)] diff --git a/core/src/consensus/stake/action_data.rs b/state/src/item/stake.rs similarity index 99% rename from core/src/consensus/stake/action_data.rs rename to state/src/item/stake.rs index 67b9ce35e6..c27459ad35 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/state/src/item/stake.rs @@ -14,8 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use crate::{ActionData, StakeKeyBuilder, StateResult, TopLevelState, TopState, TopStateView}; use ckey::{public_to_address, Address, Ed25519Public as Public}; -use cstate::{ActionData, StakeKeyBuilder, StateResult, TopLevelState, TopState, TopStateView}; use ctypes::errors::RuntimeError; use ctypes::{CompactValidatorEntry, CompactValidatorSet}; use primitives::{Bytes, H256}; @@ -27,7 +27,7 @@ use std::collections::{btree_map, HashMap, HashSet}; use std::ops::Deref; use std::vec; -pub fn get_account_key(address: &Address) -> H256 { +pub fn get_stake_account_key(address: &Address) -> H256 { StakeKeyBuilder::new(2).append(&"Account").append(address).into_key() } @@ -54,7 +54,7 @@ pub struct StakeAccount<'a> { impl<'a> StakeAccount<'a> { pub fn load_from_state(state: &TopLevelState, address: &'a Address) -> StateResult> { - let account_key = get_account_key(address); + let account_key = get_stake_account_key(address); let action_data = state.action_data(&account_key)?; let balance = match action_data { @@ -69,7 +69,7 @@ impl<'a> StakeAccount<'a> { } pub fn save_to_state(&self, state: &mut TopLevelState) -> StateResult<()> { - let account_key = get_account_key(self.address); + let account_key = get_stake_account_key(self.address); if self.balance != 0 { let rlp = rlp::encode(&self.balance); state.update_action_data(&account_key, rlp)?; @@ -128,7 +128,6 @@ impl Stakeholders { Ok(result) } - #[cfg(test)] pub fn contains(&self, address: &Address) -> bool { self.0.contains(address) } @@ -504,10 +503,10 @@ impl Candidates { self.0.iter().find(|c| public_to_address(&c.pubkey) == *account) } - #[cfg(test)] pub fn len(&self) -> usize { self.0.len() } + pub fn is_empty(&self) -> bool { self.0.len() == 0 } @@ -782,7 +781,7 @@ where #[cfg(test)] mod tests { use super::*; - use cstate::tests::helpers; + use crate::tests::helpers; use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -871,7 +870,7 @@ mod tests { assert!(result.is_ok()); account.save_to_state(&mut state).unwrap(); - let data = state.action_data(&get_account_key(&address)).unwrap(); + let data = state.action_data(&get_stake_account_key(&address)).unwrap(); assert_eq!(data, None); } diff --git a/state/src/lib.rs b/state/src/lib.rs index 4cad4ebe0f..a5dc4234f8 100644 --- a/state/src/lib.rs +++ b/state/src/lib.rs @@ -22,6 +22,8 @@ extern crate codechain_key as ckey; extern crate codechain_types as ctypes; #[macro_use] extern crate log; +#[macro_use] +extern crate rlp_derive; mod cache; mod checkpoint; @@ -43,6 +45,10 @@ pub use crate::item::action_data::ActionData; pub use crate::item::dummy_shard_text::{ShardText, ShardTextAddress}; pub use crate::item::metadata::{Metadata, MetadataAddress}; 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, +}; pub use crate::stake::{query as query_stake_state, FindStakeHandler, StakeHandler, StakeKeyBuilder}; pub use crate::traits::{ShardState, ShardStateView, StateWithCache, TopState, TopStateView}; From 5d85938727cd31e2d5e9d8bf338eebff8589b1ab Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Fri, 27 Mar 2020 00:35:26 +0900 Subject: [PATCH 3/5] Move Deposit to the types crate And this commit renames another Deposit type which means the quantity of deposit to DepositQuantity. --- core/src/consensus/stake/mod.rs | 2 +- core/src/consensus/tendermint/mod.rs | 3 ++- core/src/consensus/tendermint/params.rs | 12 ++---------- state/src/item/stake.rs | 16 +++++++-------- types/src/deposit.rs | 26 +++++++++++++++++++++++++ types/src/lib.rs | 2 ++ 6 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 types/src/deposit.rs diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index 37e3583504..c1fc3bd2ba 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -28,9 +28,9 @@ use rlp::{Decodable, Rlp}; use std::collections::HashMap; use std::sync::{Arc, Weak}; -use super::tendermint::Deposit; use super::ValidatorSet; use crate::consensus::ConsensusMessage; +use ctypes::Deposit; #[derive(Default)] pub struct Stake { diff --git a/core/src/consensus/tendermint/mod.rs b/core/src/consensus/tendermint/mod.rs index ced8e597fb..500faa7a19 100644 --- a/core/src/consensus/tendermint/mod.rs +++ b/core/src/consensus/tendermint/mod.rs @@ -27,7 +27,7 @@ mod worker; use self::chain_notify::TendermintChainNotify; pub use self::message::{ConsensusMessage, VoteOn, VoteStep}; -pub use self::params::{Deposit, TendermintParams, TimeGapParams, TimeoutParams}; +pub use self::params::{TendermintParams, TimeGapParams, TimeoutParams}; pub use self::types::{Height, Step, View}; pub use super::{stake, ValidatorSet}; use crate::client::ConsensusClient; @@ -38,6 +38,7 @@ use crate::ChainNotify; use ckey::Address; use crossbeam_channel as crossbeam; use ctimer::TimerToken; +use ctypes::Deposit; use parking_lot::RwLock; use std::collections::HashMap; use std::sync::atomic::AtomicBool; diff --git a/core/src/consensus/tendermint/params.rs b/core/src/consensus/tendermint/params.rs index 31b9917153..e1468a88af 100644 --- a/core/src/consensus/tendermint/params.rs +++ b/core/src/consensus/tendermint/params.rs @@ -16,8 +16,8 @@ use super::types::View; use super::Step; -use ckey::{Address, Ed25519Public as Public}; -use primitives::Bytes; +use ckey::Address; +use ctypes::Deposit; use std::collections::HashMap; use std::time::Duration; @@ -31,14 +31,6 @@ pub struct TendermintParams { pub genesis_delegations: HashMap>, } -#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] -pub struct Deposit { - pub pubkey: Public, - pub deposit: u64, - pub nomination_ends_at: u64, - pub metadata: Bytes, -} - impl From for TendermintParams { fn from(p: cjson::scheme::TendermintParams) -> Self { let dt = TimeoutParams::default(); diff --git a/state/src/item/stake.rs b/state/src/item/stake.rs index c27459ad35..d211a3ccab 100644 --- a/state/src/item/stake.rs +++ b/state/src/item/stake.rs @@ -45,7 +45,7 @@ pub fn get_delegation_key(address: &Address) -> H256 { } pub type StakeQuantity = u64; -pub type Deposit = u64; +pub type DepositQuantity = u64; pub struct StakeAccount<'a> { pub address: &'a Address, @@ -226,12 +226,12 @@ impl<'a> Delegation<'a> { pub struct Validator { weight: StakeQuantity, delegation: StakeQuantity, - deposit: Deposit, + deposit: DepositQuantity, pubkey: Public, } impl Validator { - pub fn new_for_test(delegation: StakeQuantity, deposit: Deposit, pubkey: Public) -> Self { + pub fn new_for_test(delegation: StakeQuantity, deposit: DepositQuantity, pubkey: Public) -> Self { Self { weight: delegation, delegation, @@ -240,7 +240,7 @@ impl Validator { } } - fn new(delegation: StakeQuantity, deposit: Deposit, pubkey: Public) -> Self { + fn new(delegation: StakeQuantity, deposit: DepositQuantity, pubkey: Public) -> Self { Self { weight: delegation, delegation, @@ -455,7 +455,7 @@ pub struct Candidates(Vec); #[derive(Clone, Debug, Eq, PartialEq, RlpEncodable, RlpDecodable)] pub struct Candidate { pub pubkey: Public, - pub deposit: Deposit, + pub deposit: DepositQuantity, pub nomination_ends_at: u64, pub metadata: Bytes, } @@ -481,7 +481,7 @@ impl Candidates { // Sorted list of validators in ascending order of (delegation, deposit, priority). fn prepare_validators( state: &TopLevelState, - min_deposit: Deposit, + min_deposit: DepositQuantity, delegations: &HashMap, ) -> StateResult> { let Candidates(candidates) = Self::load_from_state(state)?; @@ -516,7 +516,7 @@ impl Candidates { self.0.iter().position(|c| public_to_address(&c.pubkey) == *account) } - pub fn add_deposit(&mut self, pubkey: &Public, quantity: Deposit, nomination_ends_at: u64, metadata: Bytes) { + pub fn add_deposit(&mut self, pubkey: &Public, quantity: DepositQuantity, nomination_ends_at: u64, metadata: Bytes) { if let Some(index) = self.0.iter().position(|c| c.pubkey == *pubkey) { let candidate = &mut self.0[index]; candidate.deposit += quantity; @@ -589,7 +589,7 @@ pub struct Jail(BTreeMap); #[derive(Clone, Debug, Eq, PartialEq, RlpEncodable, RlpDecodable)] pub struct Prisoner { pub address: Address, - pub deposit: Deposit, + pub deposit: DepositQuantity, pub custody_until: u64, pub released_at: u64, } diff --git a/types/src/deposit.rs b/types/src/deposit.rs new file mode 100644 index 0000000000..438a6c6f1b --- /dev/null +++ b/types/src/deposit.rs @@ -0,0 +1,26 @@ +// 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; +use primitives::Bytes; + +#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] +pub struct Deposit { + pub pubkey: Public, + pub deposit: u64, + pub nomination_ends_at: u64, + pub metadata: Bytes, +} diff --git a/types/src/lib.rs b/types/src/lib.rs index d45020e0e2..cb40f56c6f 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -21,6 +21,7 @@ extern crate rlp_derive; mod block_hash; mod common_params; +mod deposit; mod tracker; mod tx_hash; mod validator_set; @@ -35,6 +36,7 @@ pub type ShardId = u16; pub use block_hash::BlockHash; pub use common_params::CommonParams; +pub use deposit::Deposit; pub use header::Header; pub use tracker::Tracker; pub use tx_hash::TxHash; From 39846ba69a2732a4617537ce384fd0a6c6ff9e7c Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Fri, 27 Mar 2020 00:59:09 +0900 Subject: [PATCH 4/5] Move stake operations to the state crate --- core/src/consensus/solo/mod.rs | 4 +- core/src/consensus/stake/mod.rs | 1767 ++--------------------- core/src/consensus/tendermint/engine.rs | 8 +- state/src/item/stake.rs | 9 +- state/src/lib.rs | 6 +- state/src/stake/actions.rs | 1302 +++++++++++++++++ state/src/stake/mod.rs | 6 + 7 files changed, 1443 insertions(+), 1659 deletions(-) create mode 100644 state/src/stake/actions.rs diff --git a/core/src/consensus/solo/mod.rs b/core/src/consensus/solo/mod.rs index a9b122d1eb..fa580e1f60 100644 --- a/core/src/consensus/solo/mod.rs +++ b/core/src/consensus/solo/mod.rs @@ -26,7 +26,7 @@ use crate::codechain_machine::CodeChainMachine; use crate::consensus::{EngineError, EngineType}; use crate::error::Error; use ckey::Address; -use cstate::{StakeHandler, StateDB, StateResult, StateWithCache, TopLevelState}; +use cstate::{init_stake, StakeHandler, StateDB, StateResult, StateWithCache, TopLevelState}; use ctypes::{BlockHash, Header}; use parking_lot::RwLock; use primitives::H256; @@ -136,7 +136,7 @@ impl ConsensusEngine for Solo { fn initialize_genesis_state(&self, db: StateDB, root: H256) -> StateResult<(StateDB, H256)> { let mut top_level = TopLevelState::from_existing(db, root)?; - stake::init(&mut top_level, self.genesis_stakes.clone(), Default::default(), Default::default())?; + init_stake(&mut top_level, self.genesis_stakes.clone(), Default::default(), Default::default())?; Ok(top_level.commit_and_into_db()?) } } diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index c1fc3bd2ba..f99ec0e105 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -16,21 +16,19 @@ use crate::client::ConsensusClient; use ckey::{public_to_address, Address, Ed25519Public as Public}; -use cstate::{Banned, Candidates, Delegation, Jail, NextValidators, ReleaseResult, StakeAccount, Stakeholders}; -use cstate::{StakeHandler, StateResult, TopLevelState, TopState, TopStateView}; +use cstate::{ + ban, execute_stake_action, jail, release_jailed_prisoners, revert_delegations, update_candidates, StakeHandler, + StateResult, TopLevelState, TopState, TopStateView, +}; use ctypes::errors::{RuntimeError, SyntaxError}; -use ctypes::transaction::{Approval, StakeAction}; -use ctypes::util::unexpected::Mismatch; +use ctypes::transaction::StakeAction; use ctypes::CommonParams; use parking_lot::RwLock; -use primitives::Bytes; use rlp::{Decodable, Rlp}; -use std::collections::HashMap; use std::sync::{Arc, Weak}; use super::ValidatorSet; use crate::consensus::ConsensusMessage; -use ctypes::Deposit; #[derive(Default)] pub struct Stake { @@ -68,47 +66,7 @@ impl StakeHandler for Stake { execute_report_double_vote(message1, state, sender_address, &*client, &*validators)?; } - - match action { - StakeAction::TransferCCS { - address, - quantity, - } => transfer_ccs(state, sender_address, &address, quantity), - StakeAction::DelegateCCS { - address, - quantity, - } => delegate_ccs(state, sender_address, &address, quantity), - StakeAction::Revoke { - address, - quantity, - } => revoke(state, sender_address, &address, quantity), - StakeAction::Redelegate { - prev_delegatee, - next_delegatee, - quantity, - } => redelegate(state, sender_address, &prev_delegatee, &next_delegatee, quantity), - StakeAction::SelfNominate { - deposit, - metadata, - } => { - let (current_term, nomination_ends_at) = { - let metadata = state.metadata()?.expect("Metadata must exist"); - let current_term = metadata.current_term_id(); - let expiration = metadata.params().nomination_expiration(); - let nomination_ends_at = current_term + expiration; - (current_term, nomination_ends_at) - }; - self_nominate(state, sender_address, sender_public, deposit, current_term, nomination_ends_at, metadata) - } - StakeAction::ChangeParams { - metadata_seq, - params, - approvals, - } => change_params(state, metadata_seq, *params, &approvals), - StakeAction::ReportDoubleVote { - .. - } => Ok(()), - } + execute_stake_action(action, state, sender_address, sender_public) } fn verify(&self, bytes: &[u8], current_params: &CommonParams) -> Result<(), SyntaxError> { @@ -122,1235 +80,126 @@ impl StakeHandler for Stake { { let client: Arc = self.client.read().as_ref().and_then(Weak::upgrade).expect("Client should be initialized"); - let validators: Arc = - self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet should be initialized"); - - let message1: ConsensusMessage = - rlp::decode(&message1).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - let message2: ConsensusMessage = - rlp::decode(&message2).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - - verify_report_double_vote(message1, message2, &*client, &*validators)?; - } - Ok(()) - } -} - -fn execute_report_double_vote( - message1: ConsensusMessage, - state: &mut TopLevelState, - sender_address: &Address, - client: &dyn ConsensusClient, - validators: &dyn ValidatorSet, -) -> StateResult<()> { - let parent_hash = client.block_header(&(message1.height() - 1).into()).expect("Parent header verified").hash(); - let malicious_user_public = validators.get(&parent_hash, message1.signer_index()); - - ban(state, sender_address, public_to_address(&malicious_user_public)) -} - -pub fn verify_report_double_vote( - message1: ConsensusMessage, - message2: ConsensusMessage, - client: &dyn ConsensusClient, - validators: &dyn ValidatorSet, -) -> Result<(), SyntaxError> { - if message1.round() != message2.round() { - return Err(SyntaxError::InvalidCustomAction(String::from("The messages are from two different voting rounds"))) - } - - let signer_idx1 = message1.signer_index(); - let signer_idx2 = message2.signer_index(); - - if signer_idx1 != signer_idx2 { - return Err(SyntaxError::InvalidCustomAction(format!( - "Two messages have different signer indexes: {}, {}", - signer_idx1, signer_idx2 - ))) - } - - assert_eq!( - message1.height(), - message2.height(), - "Heights of both messages must be same because message1.round() == message2.round()" - ); - - let signed_block_height = message1.height(); - if signed_block_height == 0 { - return Err(SyntaxError::InvalidCustomAction(String::from( - "Double vote on the genesis block does not make sense", - ))) - } - let parent_hash = client - .block_header(&(signed_block_height - 1).into()) - .ok_or_else(|| { - SyntaxError::InvalidCustomAction(format!("Cannot get header from the height {}", signed_block_height)) - })? - .hash(); - let signer_idx1 = message1.signer_index(); - let signer = validators.get(&parent_hash, signer_idx1); - if !message1.verify(&signer) || !message2.verify(&signer) { - return Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))) - } - Ok(()) -} - -fn transfer_ccs(state: &mut TopLevelState, sender: &Address, receiver: &Address, quantity: u64) -> StateResult<()> { - let mut stakeholders = Stakeholders::load_from_state(state)?; - let mut sender_account = StakeAccount::load_from_state(state, sender)?; - let mut receiver_account = StakeAccount::load_from_state(state, receiver)?; - let sender_delegations = Delegation::load_from_state(state, sender)?; - - sender_account.subtract_balance(quantity)?; - receiver_account.add_balance(quantity)?; - - stakeholders.update_by_decreased_balance(&sender_account, &sender_delegations); - stakeholders.update_by_increased_balance(&receiver_account); - - stakeholders.save_to_state(state)?; - sender_account.save_to_state(state)?; - receiver_account.save_to_state(state)?; - - ctrace!(ENGINE, "Transferred CCS sender: {}, receiver: {}, quantity: {}", sender, receiver, quantity); - Ok(()) -} - -fn delegate_ccs(state: &mut TopLevelState, delegator: &Address, delegatee: &Address, quantity: u64) -> StateResult<()> { - let candidates = Candidates::load_from_state(state)?; - if candidates.get_candidate(delegatee).is_none() { - return Err(RuntimeError::FailedToHandleCustomAction("Can delegate to who is a candidate".into()).into()) - } - - let banned = Banned::load_from_state(state)?; - let jailed = Jail::load_from_state(state)?; - assert!(!banned.is_banned(&delegatee), "A candidate must not be banned"); - assert_eq!(None, jailed.get_prisoner(delegatee), "A candidate must not be jailed"); - - let mut delegator_account = StakeAccount::load_from_state(state, delegator)?; - let mut delegation = Delegation::load_from_state(state, &delegator)?; - - delegator_account.subtract_balance(quantity)?; - delegation.add_quantity(*delegatee, quantity)?; - // delegation does not touch stakeholders - - delegation.save_to_state(state)?; - delegator_account.save_to_state(state)?; - - ctrace!(ENGINE, "Delegated CCS. delegator: {}, delegatee: {}, quantity: {}", delegator, delegatee, quantity); - Ok(()) -} - -fn revoke(state: &mut TopLevelState, delegator: &Address, delegatee: &Address, quantity: u64) -> StateResult<()> { - let mut delegator_account = StakeAccount::load_from_state(state, delegator)?; - let mut delegation = Delegation::load_from_state(state, &delegator)?; - - delegator_account.add_balance(quantity)?; - delegation.subtract_quantity(*delegatee, quantity)?; - // delegation does not touch stakeholders - - delegation.save_to_state(state)?; - delegator_account.save_to_state(state)?; - - ctrace!(ENGINE, "Revoked CCS. delegator: {}, delegatee: {}, quantity: {}", delegator, delegatee, quantity); - Ok(()) -} - -fn redelegate( - state: &mut TopLevelState, - delegator: &Address, - prev_delegatee: &Address, - next_delegatee: &Address, - quantity: u64, -) -> StateResult<()> { - let candidates = Candidates::load_from_state(state)?; - if candidates.get_candidate(next_delegatee).is_none() { - return Err(RuntimeError::FailedToHandleCustomAction("Can delegate to who is a candidate".into()).into()) - } - - let banned = Banned::load_from_state(state)?; - let jailed = Jail::load_from_state(state)?; - assert!(!banned.is_banned(&next_delegatee), "A candidate must not be banned"); - assert_eq!(None, jailed.get_prisoner(next_delegatee), "A candidate must not be jailed"); - - let delegator_account = StakeAccount::load_from_state(state, delegator)?; - let mut delegation = Delegation::load_from_state(state, &delegator)?; - - delegation.subtract_quantity(*prev_delegatee, quantity)?; - delegation.add_quantity(*next_delegatee, quantity)?; - - delegation.save_to_state(state)?; - delegator_account.save_to_state(state)?; - - ctrace!( - ENGINE, - "Redelegated CCS. delegator: {}, prev_delegatee: {}, next_delegatee: {}, quantity: {}", - delegator, - prev_delegatee, - next_delegatee, - quantity - ); - Ok(()) -} - -fn self_nominate( - state: &mut TopLevelState, - nominee: &Address, - nominee_public: &Public, - deposit: u64, - current_term: u64, - nomination_ends_at: u64, - metadata: Bytes, -) -> StateResult<()> { - let blacklist = Banned::load_from_state(state)?; - if blacklist.is_banned(&nominee) { - return Err(RuntimeError::FailedToHandleCustomAction("Account is blacklisted".to_string()).into()) - } - - let mut jail = Jail::load_from_state(&state)?; - let total_deposit = match jail.try_release(nominee, current_term) { - ReleaseResult::InCustody => { - return Err(RuntimeError::FailedToHandleCustomAction("Account is still in custody".to_string()).into()) - } - ReleaseResult::NotExists => deposit, - ReleaseResult::Released(prisoner) => { - assert_eq!(&prisoner.address, nominee); - prisoner.deposit + deposit - } - }; - - let mut candidates = Candidates::load_from_state(&state)?; - state.sub_balance(nominee, deposit)?; - candidates.add_deposit(nominee_public, total_deposit, nomination_ends_at, metadata); - - jail.save_to_state(state)?; - candidates.save_to_state(state)?; - - ctrace!( - ENGINE, - "Self-nominated. nominee: {}, deposit: {}, current_term: {}, ends_at: {}", - nominee, - deposit, - current_term, - nomination_ends_at - ); - Ok(()) -} - -pub fn get_stakes(state: &TopLevelState) -> StateResult> { - let stakeholders = Stakeholders::load_from_state(state)?; - let mut result = HashMap::new(); - for stakeholder in stakeholders.iter() { - let account = StakeAccount::load_from_state(state, stakeholder)?; - let delegation = Delegation::load_from_state(state, stakeholder)?; - result.insert(*stakeholder, account.balance + delegation.sum()); - } - Ok(result) -} - -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) -} - -fn change_params( - state: &mut TopLevelState, - metadata_seq: u64, - params: CommonParams, - approvals: &[Approval], -) -> StateResult<()> { - // Update state first because the signature validation is more expensive. - state.update_params(metadata_seq, params)?; - - let stakes = get_stakes(state)?; - // Approvals are verified - let signed_stakes = approvals.iter().try_fold(0, |sum, approval| { - let public = approval.signer_public(); - let address = public_to_address(public); - stakes.get(&address).map(|stake| sum + stake).ok_or_else(|| RuntimeError::SignatureOfInvalidAccount(address)) - })?; - let total_stakes: u64 = stakes.values().sum(); - if total_stakes / 2 >= signed_stakes { - return Err(RuntimeError::InsufficientStakes(Mismatch { - expected: total_stakes, - found: signed_stakes, - }) - .into()) - } - - ctrace!(ENGINE, "ChangeParams. params: {:?}", params); - 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(()) -} - -fn update_candidates( - state: &mut TopLevelState, - current_term: u64, - nomination_expiration: u64, - inactive_validators: &[Address], -) -> 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); - - let expired = candidates.drain_expired_candidates(current_term); - for candidate in &expired { - let address = public_to_address(&candidate.pubkey); - state.add_balance(&address, candidate.deposit)?; - 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()) -} - -fn release_jailed_prisoners(state: &mut TopLevelState, current_term: u64) -> StateResult> { - let mut jailed = Jail::load_from_state(&state)?; - let released = jailed.drain_released_prisoners(current_term); - for prisoner in &released { - 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()) -} - -pub fn jail(state: &mut TopLevelState, addresses: &[Address], custody_until: u64, kick_at: u64) -> StateResult<()> { - if addresses.is_empty() { - return Ok(()) - } - let mut candidates = Candidates::load_from_state(state)?; - let mut jail = Jail::load_from_state(state)?; - - for address in addresses { - let candidate = candidates.remove(address).expect("There should be a candidate to jail"); - ctrace!(ENGINE, "on_term_close::jail. candidate: {}, deposit: {}", address, candidate.deposit); - jail.add(candidate, custody_until, kick_at); - } - - jail.save_to_state(state)?; - candidates.save_to_state(state)?; - Ok(()) -} - -pub fn ban(state: &mut TopLevelState, informant: &Address, criminal: Address) -> StateResult<()> { - let mut banned = Banned::load_from_state(state)?; - if banned.is_banned(&criminal) { - return Err(RuntimeError::FailedToHandleCustomAction("Account is already banned".to_string()).into()) - } - - let mut candidates = Candidates::load_from_state(state)?; - let mut jailed = Jail::load_from_state(state)?; - let mut validators = NextValidators::load_from_state(state)?; - - let deposit = match (candidates.remove(&criminal), jailed.remove(&criminal)) { - (Some(_), Some(_)) => unreachable!("A candidate that are jailed cannot exist"), - (Some(candidate), _) => candidate.deposit, - (_, Some(jailed)) => jailed.deposit, - _ => 0, - }; - // confiscate criminal's deposit and give the same deposit amount to the informant. - state.add_balance(informant, deposit)?; - - jailed.remove(&criminal); - banned.add(criminal); - validators.remove(&criminal); - - jailed.save_to_state(state)?; - banned.save_to_state(state)?; - candidates.save_to_state(state)?; - validators.save_to_state(state)?; - - // Revert delegations - revert_delegations(state, &[criminal])?; - - Ok(()) -} - -fn revert_delegations(state: &mut TopLevelState, reverted_delegatees: &[Address]) -> StateResult<()> { - // Stakeholders list isn't changed while reverting. - - let stakeholders = Stakeholders::load_from_state(state)?; - for stakeholder in stakeholders.iter() { - let mut delegator = StakeAccount::load_from_state(state, stakeholder)?; - let mut delegation = Delegation::load_from_state(state, stakeholder)?; - - for delegatee in reverted_delegatees { - let quantity = delegation.get_quantity(delegatee); - if quantity > 0 { - delegation.subtract_quantity(*delegatee, quantity)?; - delegator.add_balance(quantity)?; - ctrace!( - ENGINE, - "revert_delegation delegator: {}, delegatee: {}, quantity: {}", - stakeholder, - delegatee, - quantity - ); - } - } - delegation.save_to_state(state)?; - delegator.save_to_state(state)?; - } - Ok(()) -} - -pub(super) fn init( - state: &mut TopLevelState, - genesis_stakes: HashMap, - genesis_candidates: HashMap, - genesis_delegations: HashMap>, -) -> StateResult<()> { - let mut genesis_stakes = genesis_stakes; - for (delegator, delegation) in &genesis_delegations { - let stake = genesis_stakes.entry(*delegator).or_default(); - let total_delegation = delegation.values().sum(); - if *stake < total_delegation { - cerror!(STATE, "{} has insufficient stakes to delegate", delegator); - return Err(RuntimeError::InsufficientStakes(Mismatch { - expected: total_delegation, - found: *stake, - }) - .into()) - } - for delegatee in delegation.keys() { - if !genesis_candidates.contains_key(delegatee) { - return Err(RuntimeError::FailedToHandleCustomAction("Can delegate to who is a candidate".into()).into()) - } - } - *stake -= total_delegation; - } - - let mut stakeholders = Stakeholders::load_from_state(state)?; - for (address, amount) in &genesis_stakes { - let account = StakeAccount { - address, - balance: *amount, - }; - account.save_to_state(state)?; - stakeholders.update_by_increased_balance(&account); - } - stakeholders.save_to_state(state)?; - - for (address, deposit) in &genesis_candidates { - let balance = state.balance(address).unwrap_or_default(); - if balance < deposit.deposit { - cerror!(STATE, "{} has insufficient balance to become the candidate", address); - return Err(RuntimeError::InsufficientBalance { - address: *address, - balance, - cost: deposit.deposit, - } - .into()) - } - state.sub_balance(address, deposit.deposit).unwrap(); - } - - let mut candidates = Candidates::default(); - { - let mut values: Vec<_> = genesis_candidates.values().collect(); - values.sort_unstable(); // The insertion order of candidates is important. - - for candidate in values { - candidates.add_deposit( - &candidate.pubkey, - candidate.deposit, - candidate.nomination_ends_at, - candidate.metadata.clone(), - ); - } - } - candidates.save_to_state(state)?; - - for (delegator, delegations) in &genesis_delegations { - let mut delegation = Delegation::load_from_state(state, &delegator)?; - for (delegatee, amount) in delegations { - delegation.add_quantity(*delegatee, *amount)?; - } - delegation.save_to_state(state)?; - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use cstate::tests::helpers; - use cstate::{get_delegation_key, get_stake_account_key, Candidate, Prisoner, TopStateView}; - use rlp::Encodable; - - 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 - } - - #[test] - fn genesis_stakes() { - let address1 = Address::random(); - let address2 = Address::random(); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(address1, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - let account1 = StakeAccount::load_from_state(&state, &address1).unwrap(); - assert_eq!(account1.balance, 100); - - let account2 = StakeAccount::load_from_state(&state, &address2).unwrap(); - assert_eq!(account2.balance, 0); - - let stakeholders = Stakeholders::load_from_state(&state).unwrap(); - assert_eq!(stakeholders.iter().len(), 1); - assert!(stakeholders.contains(&address1)); - assert!(!stakeholders.contains(&address2)); - } - - #[test] - fn balance_transfer_partial() { - let address1 = Address::random(); - let address2 = Address::random(); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(address1, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - let result = transfer_ccs(&mut state, &address1, &address2, 10); - assert_eq!(result, Ok(())); - - let account1 = StakeAccount::load_from_state(&state, &address1).unwrap(); - assert_eq!(account1.balance, 90); - - let account2 = StakeAccount::load_from_state(&state, &address2).unwrap(); - assert_eq!(account2.balance, 10); - - let stakeholders = Stakeholders::load_from_state(&state).unwrap(); - assert_eq!(stakeholders.iter().len(), 2); - assert!(stakeholders.contains(&address1)); - assert!(stakeholders.contains(&address2)); - } - - #[test] - fn balance_transfer_all() { - let address1 = Address::random(); - let address2 = Address::random(); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(address1, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - let result = transfer_ccs(&mut state, &address1, &address2, 100); - assert_eq!(result, Ok(())); - - let account1 = StakeAccount::load_from_state(&state, &address1).unwrap(); - assert_eq!(account1.balance, 0); - assert_eq!(state.action_data(&get_stake_account_key(&address1)).unwrap(), None, "Should clear state"); - - let account2 = StakeAccount::load_from_state(&state, &address2).unwrap(); - assert_eq!(account2.balance, 100); - - let stakeholders = Stakeholders::load_from_state(&state).unwrap(); - assert_eq!(stakeholders.iter().len(), 1); - assert!(!stakeholders.contains(&address1), "Not be a stakeholder anymore"); - assert!(stakeholders.contains(&address2)); - } - - #[test] - fn delegate() { - let delegatee_pubkey = Public::random(); - let delegator_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 40, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert_eq!(result, Ok(())); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 60); - - let delegatee_account = StakeAccount::load_from_state(&state, &delegatee).unwrap(); - assert_eq!(delegatee_account.balance, 100, "Shouldn't be touched"); - - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegation.iter().count(), 1); - assert_eq!(delegation.get_quantity(&delegatee), 40); - - let delegation_delegatee = Delegation::load_from_state(&state, &delegatee).unwrap(); - assert_eq!(delegation_delegatee.iter().count(), 0, "Shouldn't be touched"); - - let stakeholders = Stakeholders::load_from_state(&state).unwrap(); - assert_eq!(stakeholders.iter().len(), 2); - assert!(stakeholders.contains(&delegator)); - assert!(stakeholders.contains(&delegatee)); - } - - #[test] - fn delegate_all() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 100, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert_eq!(result, Ok(())); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 0); - assert_eq!(state.action_data(&get_stake_account_key(&delegator)).unwrap(), None, "Should clear state"); - - let delegatee_account = StakeAccount::load_from_state(&state, &delegatee).unwrap(); - assert_eq!(delegatee_account.balance, 100, "Shouldn't be touched"); - - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegation.iter().count(), 1); - assert_eq!(delegation.get_quantity(&delegatee), 100); - - let delegation_delegatee = Delegation::load_from_state(&state, &delegatee).unwrap(); - assert_eq!(delegation_delegatee.iter().count(), 0, "Shouldn't be touched"); - - let stakeholders = Stakeholders::load_from_state(&state).unwrap(); - assert_eq!(stakeholders.iter().len(), 2); - assert!(stakeholders.contains(&delegator), "Should still be a stakeholder after delegated all"); - assert!(stakeholders.contains(&delegatee)); - } - - #[test] - fn delegate_only_to_candidate() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 40, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); - } - - #[test] - fn delegate_too_much() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 200, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); - } - - #[test] - fn can_transfer_within_non_delegated_tokens() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 50, - }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - - let action = StakeAction::TransferCCS { - address: delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - } - - #[test] - fn cannot_transfer_over_non_delegated_tokens() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 50, - }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - - let action = StakeAction::TransferCCS { - address: delegatee, - quantity: 100, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); - } - - #[test] - fn can_revoke_delegated_tokens() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - - let action = StakeAction::Revoke { - address: delegatee, - quantity: 20, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert_eq!(Ok(()), result); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 100 - 50 + 20); - assert_eq!(delegation.iter().count(), 1); - assert_eq!(delegation.get_quantity(&delegatee), 50 - 20); - } - - #[test] - fn cannot_revoke_more_than_delegated_tokens() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - - let action = StakeAction::Revoke { - address: delegatee, - quantity: 70, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 100 - 50); - assert_eq!(delegation.iter().count(), 1); - assert_eq!(delegation.get_quantity(&delegatee), 50); - } - - #[test] - fn revoke_all_should_clear_state() { - let delegatee_pubkey = Public::random(); - let delegatee = public_to_address(&delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegatee, 100); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - - let action = StakeAction::Revoke { - address: delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert_eq!(Ok(()), result); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 100); - assert_eq!(state.action_data(&get_delegation_key(&delegator)).unwrap(), None); - } - - #[test] - fn can_redelegate_tokens() { - let prev_delegatee_pubkey = Public::random(); - let prev_delegatee = public_to_address(&prev_delegatee_pubkey); - let next_delegatee_pubkey = Public::random(); - let next_delegatee = public_to_address(&next_delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - self_nominate(&mut state, &next_delegatee, &next_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: prev_delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - - let action = StakeAction::Redelegate { - prev_delegatee, - next_delegatee, - quantity: 20, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert_eq!(Ok(()), result); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 100 - 50); - assert_eq!(delegation.iter().count(), 2); - assert_eq!(delegation.get_quantity(&prev_delegatee), 50 - 20); - assert_eq!(delegation.get_quantity(&next_delegatee), 20); - } - - #[test] - fn cannot_redelegate_more_than_delegated_tokens() { - let prev_delegatee_pubkey = Public::random(); - let prev_delegatee = public_to_address(&prev_delegatee_pubkey); - let next_delegatee_pubkey = Public::random(); - let next_delegatee = public_to_address(&next_delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - self_nominate(&mut state, &next_delegatee, &next_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: prev_delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - - let action = StakeAction::Redelegate { - prev_delegatee, - next_delegatee, - quantity: 70, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 100 - 50); - assert_eq!(delegation.iter().count(), 1); - assert_eq!(delegation.get_quantity(&prev_delegatee), 50); - assert_eq!(delegation.get_quantity(&next_delegatee), 0); - } - - #[test] - fn redelegate_all_should_clear_state() { - let prev_delegatee_pubkey = Public::random(); - let prev_delegatee = public_to_address(&prev_delegatee_pubkey); - let next_delegatee_pubkey = Public::random(); - let next_delegatee = public_to_address(&next_delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - self_nominate(&mut state, &next_delegatee, &next_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: prev_delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - - let action = StakeAction::Redelegate { - prev_delegatee, - next_delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert_eq!(Ok(()), result); - - let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegator_account.balance, 50); - assert_eq!(delegation.iter().count(), 1); - assert_eq!(delegation.get_quantity(&prev_delegatee), 0); - assert_eq!(delegation.get_quantity(&next_delegatee), 50); - } - - #[test] - fn redelegate_only_to_candidate() { - let prev_delegatee_pubkey = Public::random(); - let prev_delegatee = public_to_address(&prev_delegatee_pubkey); - let next_delegatee_pubkey = Public::random(); - let next_delegatee = public_to_address(&next_delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: prev_delegatee, - quantity: 40, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_ok()); - - let action = StakeAction::Redelegate { - prev_delegatee, - next_delegatee, - quantity: 50, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); - } - - #[test] - fn cannot_redelegate_to_banned_account() { - let informant_pubkey = Public::random(); - let criminal_pubkey = Public::random(); - let delegator_pubkey = Public::random(); - let informant = public_to_address(&informant_pubkey); - let criminal = public_to_address(&criminal_pubkey); - let delegator = public_to_address(&delegator_pubkey); - let prev_delegatee_pubkey = Public::random(); - let prev_delegatee = public_to_address(&prev_delegatee_pubkey); - - let mut state = helpers::get_temp_state(); - state.add_balance(&criminal, 1000).unwrap(); - - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - self_nominate(&mut state, &criminal, &criminal_pubkey, 100, 0, 10, b"".to_vec()).unwrap(); - - let action = StakeAction::DelegateCCS { - address: criminal, - quantity: 40, - }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - let action = StakeAction::DelegateCCS { - address: prev_delegatee, - quantity: 40, - }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.len(), 2); - - assert_eq!(Ok(()), ban(&mut state, &informant, criminal)); - - let banned = Banned::load_from_state(&state).unwrap(); - assert!(banned.is_banned(&criminal)); - - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.len(), 1); - - let action = StakeAction::Redelegate { - prev_delegatee, - next_delegatee: criminal, - quantity: 40, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); - } - - #[test] - fn cannot_redelegate_to_jailed_account() { - let jail_pubkey = Public::random(); - let jail_address = public_to_address(&jail_pubkey); - let prev_delegatee_pubkey = Public::random(); - let prev_delegatee = public_to_address(&prev_delegatee_pubkey); - let delegator_pubkey = Public::random(); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - state.add_balance(&jail_address, 1000).unwrap(); - - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - - let deposit = 200; - self_nominate(&mut state, &jail_address, &jail_pubkey, deposit, 0, 5, b"".to_vec()).unwrap(); + let validators: Arc = + self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet should be initialized"); - let action = StakeAction::DelegateCCS { - address: prev_delegatee, - quantity: 40, - }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); + let message1: ConsensusMessage = + rlp::decode(&message1).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; + let message2: ConsensusMessage = + rlp::decode(&message2).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.len(), 2); + verify_report_double_vote(message1, message2, &*client, &*validators)?; + } + Ok(()) + } +} - let custody_until = 10; - let released_at = 20; - let result = jail(&mut state, &[jail_address], custody_until, released_at); - assert!(result.is_ok()); +fn execute_report_double_vote( + message1: ConsensusMessage, + state: &mut TopLevelState, + sender_address: &Address, + client: &dyn ConsensusClient, + validators: &dyn ValidatorSet, +) -> StateResult<()> { + let parent_hash = client.block_header(&(message1.height() - 1).into()).expect("Parent header verified").hash(); + let malicious_user_public = validators.get(&parent_hash, message1.signer_index()); - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.len(), 1); + ban(state, sender_address, public_to_address(&malicious_user_public)) +} - let action = StakeAction::Redelegate { - prev_delegatee, - next_delegatee: jail_address, - quantity: 40, - }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); - assert!(result.is_err()); +pub fn verify_report_double_vote( + message1: ConsensusMessage, + message2: ConsensusMessage, + client: &dyn ConsensusClient, + validators: &dyn ValidatorSet, +) -> Result<(), SyntaxError> { + if message1.round() != message2.round() { + return Err(SyntaxError::InvalidCustomAction(String::from("The messages are from two different voting rounds"))) } - #[test] - fn self_nominate_deposit_test() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - - let mut state = helpers::get_temp_state(); - state.add_balance(&address, 1000).unwrap(); + let signer_idx1 = message1.signer_index(); + let signer_idx2 = message2.signer_index(); - super::init(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + if signer_idx1 != signer_idx2 { + return Err(SyntaxError::InvalidCustomAction(format!( + "Two messages have different signer indexes: {}, {}", + signer_idx1, signer_idx2 + ))) + } - // TODO: change with stake::execute() - let result = self_nominate(&mut state, &address, &address_pubkey, 0, 0, 5, b"metadata1".to_vec()); - assert_eq!(result, Ok(())); + assert_eq!( + message1.height(), + message2.height(), + "Heights of both messages must be same because message1.round() == message2.round()" + ); - assert_eq!(state.balance(&address).unwrap(), 1000); - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!( - candidates.get_candidate(&address), - Some(&Candidate { - pubkey: address_pubkey, - deposit: 0, - nomination_ends_at: 5, - metadata: b"metadata1".to_vec(), - }), - "nomination_ends_at should be updated even if candidate deposits 0" - ); + let signed_block_height = message1.height(); + if signed_block_height == 0 { + return Err(SyntaxError::InvalidCustomAction(String::from( + "Double vote on the genesis block does not make sense", + ))) + } + let parent_hash = client + .block_header(&(signed_block_height - 1).into()) + .ok_or_else(|| { + SyntaxError::InvalidCustomAction(format!("Cannot get header from the height {}", signed_block_height)) + })? + .hash(); + let signer_idx1 = message1.signer_index(); + let signer = validators.get(&parent_hash, signer_idx1); + if !message1.verify(&signer) || !message2.verify(&signer) { + return Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))) + } + Ok(()) +} - let result = self_nominate(&mut state, &address, &address_pubkey, 200, 0, 10, b"metadata2".to_vec()); - assert_eq!(result, 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); - assert_eq!(state.balance(&address).unwrap(), 800); - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!( - candidates.get_candidate(&address), - Some(&Candidate { - pubkey: address_pubkey, - deposit: 200, - nomination_ends_at: 10, - metadata: b"metadata2".to_vec(), - }) - ); + 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 result = self_nominate(&mut state, &address, &address_pubkey, 0, 0, 15, b"metadata3".to_vec()); - assert_eq!(result, Ok(())); + let expired = update_candidates(state, current_term, nomination_expiration, inactive_validators)?; + let released = release_jailed_prisoners(state, current_term)?; - assert_eq!(state.balance(&address).unwrap(), 800); - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!( - candidates.get_candidate(&address), - Some(&Candidate { - pubkey: address_pubkey, - deposit: 200, - nomination_ends_at: 15, - metadata: b"metadata3".to_vec(), - }), - "nomination_ends_at should be updated even if candidate deposits 0" - ); - } + let reverted: Vec<_> = expired.into_iter().chain(released).collect(); + revert_delegations(state, &reverted)?; - #[test] - fn self_nominate_fail_with_insufficient_balance() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); + jail(state, inactive_validators, custody_until, kick_at)?; - let mut state = helpers::get_temp_state(); - state.add_balance(&address, 1000).unwrap(); + state.increase_term_id(last_term_finished_block_num)?; + Ok(()) +} - super::init(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); +#[cfg(test)] +mod tests { + use super::*; + use cstate::tests::helpers; + use cstate::{init_stake, self_nominate, Candidate, Candidates, Delegation, Jail, StakeAccount, TopStateView}; + use rlp::Encodable; + use std::collections::HashMap; - // TODO: change with stake::execute() - let result = self_nominate(&mut state, &address, &address_pubkey, 2000, 0, 5, b"".to_vec()); - assert!(result.is_err(), "Cannot self-nominate without a sufficient balance"); + 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) { @@ -1370,7 +219,7 @@ mod tests { increase_term_id_until(&mut state, 29); state.add_balance(&address, 1000).unwrap(); - super::init(&mut state, Default::default(), Default::default(), Default::default()).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(); @@ -1415,7 +264,7 @@ mod tests { genesis_stakes.insert(delegator, 100); genesis_stakes }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + 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(); @@ -1443,43 +292,6 @@ mod tests { assert_eq!(delegation.get_quantity(&address), 0, "Should revert before expiration"); } - #[test] - fn jail_candidate() { - let address_pubkey = Public::random(); - let address = public_to_address(&address_pubkey); - - let mut state = helpers::get_temp_state(); - state.add_balance(&address, 1000).unwrap(); - - super::init(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); - - // TODO: change with stake::execute() - let deposit = 200; - self_nominate(&mut state, &address, &address_pubkey, deposit, 0, 5, b"".to_vec()).unwrap(); - - let custody_until = 10; - let released_at = 20; - let result = jail(&mut state, &[address], custody_until, released_at); - assert!(result.is_ok()); - - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.get_candidate(&address), None, "The candidate is removed"); - - let jail = Jail::load_from_state(&state).unwrap(); - assert_eq!( - jail.get_prisoner(&address), - Some(&Prisoner { - address, - deposit, - custody_until, - released_at, - }), - "The candidate become a prisoner" - ); - - assert_eq!(state.balance(&address).unwrap(), 1000 - deposit, "Deposited ccs is temporarily unavailable"); - } - #[test] fn cannot_self_nominate_while_custody() { let address_pubkey = Public::random(); @@ -1488,7 +300,7 @@ mod tests { let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); - super::init(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); // TODO: change with stake::execute() let deposit = 200; @@ -1526,7 +338,7 @@ mod tests { let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); - super::init(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); // TODO: change with stake::execute() let deposit = 200; @@ -1579,7 +391,7 @@ mod tests { let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); - super::init(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); // TODO: change with stake::execute() let deposit = 200; @@ -1625,7 +437,7 @@ mod tests { genesis_stakes.insert(delegator, 100); genesis_stakes }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); // TODO: change with stake::execute() let deposit = 200; @@ -1669,7 +481,7 @@ mod tests { genesis_stakes.insert(delegator, 100); genesis_stakes }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); // TODO: change with stake::execute() let deposit = 200; @@ -1712,7 +524,7 @@ mod tests { genesis_stakes.insert(delegator, 100); genesis_stakes }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); // TODO: change with stake::execute() let nominate_expire = 5; @@ -1751,354 +563,7 @@ mod tests { assert_eq!(account.balance, 100 - 40, "Delegation should be preserved"); } - #[test] - fn test_ban() { - let informant_pubkey = Public::random(); - let criminal_pubkey = Public::random(); - let delegator_pubkey = Public::random(); - let informant = public_to_address(&informant_pubkey); - let criminal = public_to_address(&criminal_pubkey); - let delegator = public_to_address(&delegator_pubkey); - - let mut state = helpers::get_temp_state(); - state.add_balance(&criminal, 1000).unwrap(); - - let genesis_stakes = { - let mut genesis_stakes = HashMap::new(); - genesis_stakes.insert(delegator, 100); - genesis_stakes - }; - super::init(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); - - let deposit = 100; - self_nominate(&mut state, &criminal, &criminal_pubkey, deposit, 0, 10, b"".to_vec()).unwrap(); - let action = StakeAction::DelegateCCS { - address: criminal, - quantity: 40, - }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - - assert_eq!(Ok(()), ban(&mut state, &informant, criminal)); - - let banned = Banned::load_from_state(&state).unwrap(); - assert!(banned.is_banned(&criminal)); - - let candidates = Candidates::load_from_state(&state).unwrap(); - assert_eq!(candidates.len(), 0); - - assert_eq!(state.balance(&criminal).unwrap(), 900, "Should lose deposit"); - - let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); - assert_eq!(delegation.get_quantity(&criminal), 0, "Delegation should be reverted"); - - let account_delegator = StakeAccount::load_from_state(&state, &delegator).unwrap(); - assert_eq!(account_delegator.balance, 100, "Delegation should be reverted"); - } - - #[test] - fn ban_should_remove_prisoner_from_jail() { - let informant_pubkey = Public::random(); - let criminal_pubkey = Public::random(); - let informant = public_to_address(&informant_pubkey); - let criminal = public_to_address(&criminal_pubkey); - - let mut state = helpers::get_temp_state(); - super::init(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); - assert_eq!(Ok(()), state.add_balance(&criminal, 100)); - - let deposit = 10; - self_nominate(&mut state, &criminal, &criminal_pubkey, deposit, 0, 10, b"".to_vec()).unwrap(); - let custody_until = 10; - let released_at = 20; - jail(&mut state, &[criminal], custody_until, released_at).unwrap(); - - assert_eq!(Ok(()), ban(&mut state, &informant, criminal)); - - let jail = Jail::load_from_state(&state).unwrap(); - assert_eq!(jail.get_prisoner(&criminal), None, "Should be removed from the jail"); - } - fn pseudo_term_to_block_num_calculator(term_id: u64) -> u64 { term_id * 10 + 1 } } - -#[cfg(test)] -mod tests_double_vote { - use super::*; - use crate::client::{ConsensusClient, TestBlockChainClient}; - use crate::consensus::{DynamicValidator, Step, VoteOn, VoteStep}; - use ckey::sign; - use ctypes::BlockHash; - use primitives::H256; - use rlp::Encodable; - - struct ConsensusMessageInfo { - pub height: u64, - pub view: u64, - pub step: Step, - pub block_hash: Option, - pub signer_index: usize, - } - - fn create_consensus_message( - info: ConsensusMessageInfo, - client: &TestBlockChainClient, - vote_step_twister: &F, - block_hash_twister: &G, - ) -> ConsensusMessage - where - F: Fn(VoteStep) -> VoteStep, - G: Fn(Option) -> Option, { - let ConsensusMessageInfo { - height, - view, - step, - block_hash, - signer_index, - } = info; - let vote_step = VoteStep::new(height, view, step); - let on = VoteOn { - step: vote_step, - block_hash, - }; - let twisted = VoteOn { - step: vote_step_twister(vote_step), - block_hash: block_hash_twister(block_hash), - }; - let reversed_idx = client.get_validators().len() - 1 - signer_index; - let pubkey = *client.get_validators().get(reversed_idx).unwrap().pubkey(); - let validator_keys = client.validator_keys.read(); - let privkey = validator_keys.get(&pubkey).unwrap(); - let signature = sign(&twisted.hash(), privkey); - - ConsensusMessage { - signature, - signer_index, - on, - } - } - - fn double_vote_verification_result( - message_info1: ConsensusMessageInfo, - message_info2: ConsensusMessageInfo, - vote_step_twister: &F, - block_hash_twister: &G, - ) -> Result<(), SyntaxError> - where - F: Fn(VoteStep) -> VoteStep, - G: Fn(Option) -> Option, { - let mut test_client = TestBlockChainClient::default(); - test_client.add_blocks(10, 1); - test_client.set_random_validators(10); - let validator_set = DynamicValidator::default(); - - let consensus_message1 = - create_consensus_message(message_info1, &test_client, vote_step_twister, block_hash_twister); - let consensus_message2 = - create_consensus_message(message_info2, &test_client, vote_step_twister, block_hash_twister); - let action = StakeAction::ReportDoubleVote { - message1: consensus_message1.rlp_bytes(), - message2: consensus_message2.rlp_bytes(), - }; - let arced_client: Arc = Arc::new(test_client); - validator_set.register_client(Arc::downgrade(&arced_client)); - action.verify(&CommonParams::default_for_test())?; - verify_report_double_vote(consensus_message1, consensus_message2, &*arced_client, &validator_set) - } - - #[test] - fn double_vote_verify_desirable_report() { - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 0, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &|v| v, - &|v| v, - ); - assert!(result.is_ok()); - } - - #[test] - fn double_vote_verify_same_message() { - let block_hash = Some(H256::random().into()); - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 3, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - ConsensusMessageInfo { - height: 3, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - &|v| v, - &|v| v, - ); - let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Messages are duplicated"))); - assert_eq!(result, expected_err); - } - - #[test] - fn double_vote_verify_different_height() { - let block_hash = Some(H256::random().into()); - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 3, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - ConsensusMessageInfo { - height: 2, - view: 1, - step: Step::Precommit, - block_hash, - signer_index: 2, - }, - &|v| v, - &|v| v, - ); - let expected_err = - Err(SyntaxError::InvalidCustomAction(String::from("The messages are from two different voting rounds"))); - assert_eq!(result, expected_err); - } - - #[test] - fn double_vote_verify_different_signer() { - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 1, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &|v| v, - &|v| v, - ); - match result { - Err(SyntaxError::InvalidCustomAction(ref s)) - if s.contains("Two messages have different signer indexes") => {} - _ => panic!(), - } - } - - #[test] - fn double_vote_verify_different_message_and_signer() { - let hash1 = Some(H256::random().into()); - let mut hash2 = Some(H256::random().into()); - while hash1 == hash2 { - hash2 = Some(H256::random().into()); - } - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: hash1, - signer_index: 1, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: hash2, - signer_index: 0, - }, - &|v| v, - &|v| v, - ); - match result { - Err(SyntaxError::InvalidCustomAction(ref s)) - if s.contains("Two messages have different signer indexes") => {} - _ => panic!(), - } - } - - #[test] - fn double_vote_verify_strange_sig1() { - let vote_step_twister = |original: VoteStep| VoteStep { - height: original.height + 1, - view: original.height + 1, - step: original.step, - }; - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 0, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &vote_step_twister, - &|v| v, - ); - let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))); - assert_eq!(result, expected_err); - } - - #[test] - fn double_vote_verify_strange_sig2() { - let block_hash_twister = |original: Option| { - original.map(|hash| { - let mut twisted = H256::random(); - while twisted == *hash { - twisted = H256::random(); - } - BlockHash::from(twisted) - }) - }; - let result = double_vote_verification_result( - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: None, - signer_index: 0, - }, - ConsensusMessageInfo { - height: 2, - view: 0, - step: Step::Precommit, - block_hash: Some(H256::random().into()), - signer_index: 0, - }, - &|v| v, - &block_hash_twister, - ); - let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Ed25519 signature verification fails"))); - assert_eq!(result, expected_err); - } -} diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 5a6127eeb5..fb79c7fdeb 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -33,8 +33,8 @@ use ckey::{public_to_address, Address}; use cnetwork::NetworkService; use crossbeam_channel as crossbeam; use cstate::{ - CurrentValidators, NextValidators, StakeHandler, StateDB, StateResult, StateWithCache, TopLevelState, TopState, - TopStateView, + init_stake, update_validator_weights, CurrentValidators, NextValidators, StakeHandler, StateDB, StateResult, + StateWithCache, TopLevelState, TopState, TopStateView, }; use ctypes::{BlockHash, Header}; use primitives::H256; @@ -138,7 +138,7 @@ impl ConsensusEngine for Tendermint { let metadata = block.state().metadata()?.expect("Metadata must exist"); let author = *block.header().author(); - stake::update_validator_weights(block.state_mut(), &author)?; + update_validator_weights(block.state_mut(), &author)?; let term = metadata.current_term_id(); let term_seconds = match term { @@ -268,7 +268,7 @@ impl ConsensusEngine for Tendermint { fn initialize_genesis_state(&self, db: StateDB, root: H256) -> StateResult<(StateDB, H256)> { let mut state = TopLevelState::from_existing(db, root)?; - stake::init( + init_stake( &mut state, self.genesis_stakes.clone(), self.genesis_candidates.clone(), diff --git a/state/src/item/stake.rs b/state/src/item/stake.rs index d211a3ccab..b7b6626d30 100644 --- a/state/src/item/stake.rs +++ b/state/src/item/stake.rs @@ -516,7 +516,13 @@ impl Candidates { self.0.iter().position(|c| public_to_address(&c.pubkey) == *account) } - pub fn add_deposit(&mut self, pubkey: &Public, quantity: DepositQuantity, nomination_ends_at: u64, metadata: Bytes) { + pub fn add_deposit( + &mut self, + pubkey: &Public, + quantity: DepositQuantity, + nomination_ends_at: u64, + metadata: Bytes, + ) { if let Some(index) = self.0.iter().position(|c| c.pubkey == *pubkey) { let candidate = &mut self.0[index]; candidate.deposit += quantity; @@ -778,6 +784,7 @@ where rlp.drain() } +#[allow(clippy::implicit_hasher)] // XXX: Fix this clippy warning if it becomes a real problem. #[cfg(test)] mod tests { use super::*; diff --git a/state/src/lib.rs b/state/src/lib.rs index a5dc4234f8..b63fbf31f7 100644 --- a/state/src/lib.rs +++ b/state/src/lib.rs @@ -49,7 +49,11 @@ pub use crate::item::stake::{ get_delegation_key, get_stake_account_key, Banned, Candidate, Candidates, CurrentValidators, Delegation, Jail, NextValidators, Prisoner, ReleaseResult, StakeAccount, Stakeholders, Validator, }; -pub use crate::stake::{query as query_stake_state, FindStakeHandler, StakeHandler, StakeKeyBuilder}; +pub use crate::stake::{ + ban, execute_stake_action, init_stake, jail, query as query_stake_state, release_jailed_prisoners, + revert_delegations, self_nominate, update_candidates, update_validator_weights, FindStakeHandler, StakeHandler, + StakeKeyBuilder, +}; pub use crate::traits::{ShardState, ShardStateView, StateWithCache, TopState, TopStateView}; use crate::cache::CacheableItem; diff --git a/state/src/stake/actions.rs b/state/src/stake/actions.rs new file mode 100644 index 0000000000..f519669c93 --- /dev/null +++ b/state/src/stake/actions.rs @@ -0,0 +1,1302 @@ +// 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 crate::{ + Banned, Candidates, Delegation, Jail, NextValidators, ReleaseResult, StakeAccount, Stakeholders, StateResult, + TopLevelState, TopState, TopStateView, +}; +use ckey::{public_to_address, Address, Ed25519Public as Public}; +use ctypes::errors::RuntimeError; +use ctypes::transaction::{Approval, StakeAction}; +use ctypes::util::unexpected::Mismatch; +use ctypes::{CommonParams, Deposit}; +use primitives::Bytes; +use std::collections::HashMap; + +#[allow(clippy::implicit_hasher)] // XXX: Fix this clippy warning if it becomes a real problem. +pub fn init_stake( + state: &mut TopLevelState, + genesis_stakes: HashMap, + genesis_candidates: HashMap, + genesis_delegations: HashMap>, +) -> StateResult<()> { + let mut genesis_stakes = genesis_stakes; + for (delegator, delegation) in &genesis_delegations { + let stake = genesis_stakes.entry(*delegator).or_default(); + let total_delegation = delegation.values().sum(); + if *stake < total_delegation { + cerror!(STATE, "{} has insufficient stakes to delegate", delegator); + return Err(RuntimeError::InsufficientStakes(Mismatch { + expected: total_delegation, + found: *stake, + }) + .into()) + } + for delegatee in delegation.keys() { + if !genesis_candidates.contains_key(delegatee) { + return Err(RuntimeError::FailedToHandleCustomAction("Can delegate to who is a candidate".into()).into()) + } + } + *stake -= total_delegation; + } + + let mut stakeholders = Stakeholders::load_from_state(state)?; + for (address, amount) in &genesis_stakes { + let account = StakeAccount { + address, + balance: *amount, + }; + account.save_to_state(state)?; + stakeholders.update_by_increased_balance(&account); + } + stakeholders.save_to_state(state)?; + + for (address, deposit) in &genesis_candidates { + let balance = state.balance(address).unwrap_or_default(); + if balance < deposit.deposit { + cerror!(STATE, "{} has insufficient balance to become the candidate", address); + return Err(RuntimeError::InsufficientBalance { + address: *address, + balance, + cost: deposit.deposit, + } + .into()) + } + state.sub_balance(address, deposit.deposit).unwrap(); + } + + let mut candidates = Candidates::default(); + { + let mut values: Vec<_> = genesis_candidates.values().collect(); + values.sort_unstable(); // The insertion order of candidates is important. + + for candidate in values { + candidates.add_deposit( + &candidate.pubkey, + candidate.deposit, + candidate.nomination_ends_at, + candidate.metadata.clone(), + ); + } + } + candidates.save_to_state(state)?; + + for (delegator, delegations) in &genesis_delegations { + let mut delegation = Delegation::load_from_state(state, &delegator)?; + for (delegatee, amount) in delegations { + delegation.add_quantity(*delegatee, *amount)?; + } + delegation.save_to_state(state)?; + } + + Ok(()) +} + +pub fn execute_stake_action( + action: StakeAction, + state: &mut TopLevelState, + sender_address: &Address, + sender_public: &Public, +) -> StateResult<()> { + match action { + StakeAction::TransferCCS { + address, + quantity, + } => transfer_ccs(state, sender_address, &address, quantity), + StakeAction::DelegateCCS { + address, + quantity, + } => delegate_ccs(state, sender_address, &address, quantity), + StakeAction::Revoke { + address, + quantity, + } => revoke(state, sender_address, &address, quantity), + StakeAction::Redelegate { + prev_delegatee, + next_delegatee, + quantity, + } => redelegate(state, sender_address, &prev_delegatee, &next_delegatee, quantity), + StakeAction::SelfNominate { + deposit, + metadata, + } => { + let (current_term, nomination_ends_at) = { + let metadata = state.metadata()?.expect("Metadata must exist"); + let current_term = metadata.current_term_id(); + let expiration = metadata.params().nomination_expiration(); + let nomination_ends_at = current_term + expiration; + (current_term, nomination_ends_at) + }; + self_nominate(state, sender_address, sender_public, deposit, current_term, nomination_ends_at, metadata) + } + StakeAction::ChangeParams { + metadata_seq, + params, + approvals, + } => change_params(state, metadata_seq, *params, &approvals), + StakeAction::ReportDoubleVote { + .. + } => Ok(()), + } +} + +fn transfer_ccs(state: &mut TopLevelState, sender: &Address, receiver: &Address, quantity: u64) -> StateResult<()> { + let mut stakeholders = Stakeholders::load_from_state(state)?; + let mut sender_account = StakeAccount::load_from_state(state, sender)?; + let mut receiver_account = StakeAccount::load_from_state(state, receiver)?; + let sender_delegations = Delegation::load_from_state(state, sender)?; + + sender_account.subtract_balance(quantity)?; + receiver_account.add_balance(quantity)?; + + stakeholders.update_by_decreased_balance(&sender_account, &sender_delegations); + stakeholders.update_by_increased_balance(&receiver_account); + + stakeholders.save_to_state(state)?; + sender_account.save_to_state(state)?; + receiver_account.save_to_state(state)?; + + ctrace!(ENGINE, "Transferred CCS sender: {}, receiver: {}, quantity: {}", sender, receiver, quantity); + Ok(()) +} + +fn delegate_ccs(state: &mut TopLevelState, delegator: &Address, delegatee: &Address, quantity: u64) -> StateResult<()> { + let candidates = Candidates::load_from_state(state)?; + if candidates.get_candidate(delegatee).is_none() { + return Err(RuntimeError::FailedToHandleCustomAction("Can delegate to who is a candidate".into()).into()) + } + + let banned = Banned::load_from_state(state)?; + let jailed = Jail::load_from_state(state)?; + assert!(!banned.is_banned(&delegatee), "A candidate must not be banned"); + assert_eq!(None, jailed.get_prisoner(delegatee), "A candidate must not be jailed"); + + let mut delegator_account = StakeAccount::load_from_state(state, delegator)?; + let mut delegation = Delegation::load_from_state(state, &delegator)?; + + delegator_account.subtract_balance(quantity)?; + delegation.add_quantity(*delegatee, quantity)?; + // delegation does not touch stakeholders + + delegation.save_to_state(state)?; + delegator_account.save_to_state(state)?; + + ctrace!(ENGINE, "Delegated CCS. delegator: {}, delegatee: {}, quantity: {}", delegator, delegatee, quantity); + Ok(()) +} + +fn revoke(state: &mut TopLevelState, delegator: &Address, delegatee: &Address, quantity: u64) -> StateResult<()> { + let mut delegator_account = StakeAccount::load_from_state(state, delegator)?; + let mut delegation = Delegation::load_from_state(state, &delegator)?; + + delegator_account.add_balance(quantity)?; + delegation.subtract_quantity(*delegatee, quantity)?; + // delegation does not touch stakeholders + + delegation.save_to_state(state)?; + delegator_account.save_to_state(state)?; + + ctrace!(ENGINE, "Revoked CCS. delegator: {}, delegatee: {}, quantity: {}", delegator, delegatee, quantity); + Ok(()) +} + +fn redelegate( + state: &mut TopLevelState, + delegator: &Address, + prev_delegatee: &Address, + next_delegatee: &Address, + quantity: u64, +) -> StateResult<()> { + let candidates = Candidates::load_from_state(state)?; + if candidates.get_candidate(next_delegatee).is_none() { + return Err(RuntimeError::FailedToHandleCustomAction("Can delegate to who is a candidate".into()).into()) + } + + let banned = Banned::load_from_state(state)?; + let jailed = Jail::load_from_state(state)?; + assert!(!banned.is_banned(&next_delegatee), "A candidate must not be banned"); + assert_eq!(None, jailed.get_prisoner(next_delegatee), "A candidate must not be jailed"); + + let delegator_account = StakeAccount::load_from_state(state, delegator)?; + let mut delegation = Delegation::load_from_state(state, &delegator)?; + + delegation.subtract_quantity(*prev_delegatee, quantity)?; + delegation.add_quantity(*next_delegatee, quantity)?; + + delegation.save_to_state(state)?; + delegator_account.save_to_state(state)?; + + ctrace!( + ENGINE, + "Redelegated CCS. delegator: {}, prev_delegatee: {}, next_delegatee: {}, quantity: {}", + delegator, + prev_delegatee, + next_delegatee, + quantity + ); + Ok(()) +} + +pub fn self_nominate( + state: &mut TopLevelState, + nominee: &Address, + nominee_public: &Public, + deposit: u64, + current_term: u64, + nomination_ends_at: u64, + metadata: Bytes, +) -> StateResult<()> { + let blacklist = Banned::load_from_state(state)?; + if blacklist.is_banned(&nominee) { + return Err(RuntimeError::FailedToHandleCustomAction("Account is blacklisted".to_string()).into()) + } + + let mut jail = Jail::load_from_state(&state)?; + let total_deposit = match jail.try_release(nominee, current_term) { + ReleaseResult::InCustody => { + return Err(RuntimeError::FailedToHandleCustomAction("Account is still in custody".to_string()).into()) + } + ReleaseResult::NotExists => deposit, + ReleaseResult::Released(prisoner) => { + assert_eq!(&prisoner.address, nominee); + prisoner.deposit + deposit + } + }; + + let mut candidates = Candidates::load_from_state(&state)?; + state.sub_balance(nominee, deposit)?; + candidates.add_deposit(nominee_public, total_deposit, nomination_ends_at, metadata); + + jail.save_to_state(state)?; + candidates.save_to_state(state)?; + + ctrace!( + ENGINE, + "Self-nominated. nominee: {}, deposit: {}, current_term: {}, ends_at: {}", + nominee, + deposit, + current_term, + nomination_ends_at + ); + Ok(()) +} + +fn change_params( + state: &mut TopLevelState, + metadata_seq: u64, + params: CommonParams, + approvals: &[Approval], +) -> StateResult<()> { + // Update state first because the signature validation is more expensive. + state.update_params(metadata_seq, params)?; + + let stakes = get_stakes(state)?; + // Approvals are verified + let signed_stakes = approvals.iter().try_fold(0, |sum, approval| { + let public = approval.signer_public(); + let address = public_to_address(public); + stakes.get(&address).map(|stake| sum + stake).ok_or_else(|| RuntimeError::SignatureOfInvalidAccount(address)) + })?; + let total_stakes: u64 = stakes.values().sum(); + if total_stakes / 2 >= signed_stakes { + return Err(RuntimeError::InsufficientStakes(Mismatch { + expected: total_stakes, + found: signed_stakes, + }) + .into()) + } + + ctrace!(ENGINE, "ChangeParams. params: {:?}", params); + Ok(()) +} + +fn get_stakes(state: &TopLevelState) -> StateResult> { + let stakeholders = Stakeholders::load_from_state(state)?; + let mut result = HashMap::new(); + for stakeholder in stakeholders.iter() { + let account = StakeAccount::load_from_state(state, stakeholder)?; + let delegation = Delegation::load_from_state(state, stakeholder)?; + result.insert(*stakeholder, account.balance + delegation.sum()); + } + Ok(result) +} + +pub fn release_jailed_prisoners(state: &mut TopLevelState, current_term: u64) -> StateResult> { + let mut jailed = Jail::load_from_state(&state)?; + let released = jailed.drain_released_prisoners(current_term); + for prisoner in &released { + 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()) +} + +pub fn jail(state: &mut TopLevelState, addresses: &[Address], custody_until: u64, kick_at: u64) -> StateResult<()> { + if addresses.is_empty() { + return Ok(()) + } + let mut candidates = Candidates::load_from_state(state)?; + let mut jail = Jail::load_from_state(state)?; + + for address in addresses { + let candidate = candidates.remove(address).expect("There should be a candidate to jail"); + ctrace!(ENGINE, "on_term_close::jail. candidate: {}, deposit: {}", address, candidate.deposit); + jail.add(candidate, custody_until, kick_at); + } + + jail.save_to_state(state)?; + candidates.save_to_state(state)?; + Ok(()) +} + +pub fn ban(state: &mut TopLevelState, informant: &Address, criminal: Address) -> StateResult<()> { + let mut banned = Banned::load_from_state(state)?; + if banned.is_banned(&criminal) { + return Err(RuntimeError::FailedToHandleCustomAction("Account is already banned".to_string()).into()) + } + + let mut candidates = Candidates::load_from_state(state)?; + let mut jailed = Jail::load_from_state(state)?; + let mut validators = NextValidators::load_from_state(state)?; + + let deposit = match (candidates.remove(&criminal), jailed.remove(&criminal)) { + (Some(_), Some(_)) => unreachable!("A candidate that are jailed cannot exist"), + (Some(candidate), _) => candidate.deposit, + (_, Some(jailed)) => jailed.deposit, + _ => 0, + }; + // confiscate criminal's deposit and give the same deposit amount to the informant. + state.add_balance(informant, deposit)?; + + jailed.remove(&criminal); + banned.add(criminal); + validators.remove(&criminal); + + jailed.save_to_state(state)?; + banned.save_to_state(state)?; + candidates.save_to_state(state)?; + validators.save_to_state(state)?; + + // Revert delegations + revert_delegations(state, &[criminal])?; + + Ok(()) +} + +pub fn revert_delegations(state: &mut TopLevelState, reverted_delegatees: &[Address]) -> StateResult<()> { + // Stakeholders list isn't changed while reverting. + + let stakeholders = Stakeholders::load_from_state(state)?; + for stakeholder in stakeholders.iter() { + let mut delegator = StakeAccount::load_from_state(state, stakeholder)?; + let mut delegation = Delegation::load_from_state(state, stakeholder)?; + + for delegatee in reverted_delegatees { + let quantity = delegation.get_quantity(delegatee); + if quantity > 0 { + delegation.subtract_quantity(*delegatee, quantity)?; + delegator.add_balance(quantity)?; + ctrace!( + ENGINE, + "revert_delegation delegator: {}, delegatee: {}, quantity: {}", + stakeholder, + delegatee, + quantity + ); + } + } + delegation.save_to_state(state)?; + delegator.save_to_state(state)?; + } + 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, + inactive_validators: &[Address], +) -> 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); + + let expired = candidates.drain_expired_candidates(current_term); + for candidate in &expired { + let address = public_to_address(&candidate.pubkey); + state.add_balance(&address, candidate.deposit)?; + 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()) +} + +#[cfg(test)] +mod tests { + use super::*; + 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 ctypes::transaction::StakeAction; + use std::collections::HashMap; + + #[test] + fn genesis_stakes() { + let address1 = Address::random(); + let address2 = Address::random(); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(address1, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + let account1 = StakeAccount::load_from_state(&state, &address1).unwrap(); + assert_eq!(account1.balance, 100); + + let account2 = StakeAccount::load_from_state(&state, &address2).unwrap(); + assert_eq!(account2.balance, 0); + + let stakeholders = Stakeholders::load_from_state(&state).unwrap(); + assert_eq!(stakeholders.iter().len(), 1); + assert!(stakeholders.contains(&address1)); + assert!(!stakeholders.contains(&address2)); + } + + #[test] + fn balance_transfer_partial() { + let address1 = Address::random(); + let address2 = Address::random(); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(address1, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + let result = transfer_ccs(&mut state, &address1, &address2, 10); + assert_eq!(result, Ok(())); + + let account1 = StakeAccount::load_from_state(&state, &address1).unwrap(); + assert_eq!(account1.balance, 90); + + let account2 = StakeAccount::load_from_state(&state, &address2).unwrap(); + assert_eq!(account2.balance, 10); + + let stakeholders = Stakeholders::load_from_state(&state).unwrap(); + assert_eq!(stakeholders.iter().len(), 2); + assert!(stakeholders.contains(&address1)); + assert!(stakeholders.contains(&address2)); + } + + #[test] + fn balance_transfer_all() { + let address1 = Address::random(); + let address2 = Address::random(); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(address1, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + let result = transfer_ccs(&mut state, &address1, &address2, 100); + assert_eq!(result, Ok(())); + + let account1 = StakeAccount::load_from_state(&state, &address1).unwrap(); + assert_eq!(account1.balance, 0); + assert_eq!(state.action_data(&get_stake_account_key(&address1)).unwrap(), None, "Should clear state"); + + let account2 = StakeAccount::load_from_state(&state, &address2).unwrap(); + assert_eq!(account2.balance, 100); + + let stakeholders = Stakeholders::load_from_state(&state).unwrap(); + assert_eq!(stakeholders.iter().len(), 1); + assert!(!stakeholders.contains(&address1), "Not be a stakeholder anymore"); + assert!(stakeholders.contains(&address2)); + } + + #[test] + fn delegate() { + let delegatee_pubkey = Public::random(); + let delegator_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 40, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert_eq!(result, Ok(())); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 60); + + let delegatee_account = StakeAccount::load_from_state(&state, &delegatee).unwrap(); + assert_eq!(delegatee_account.balance, 100, "Shouldn't be touched"); + + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegation.iter().count(), 1); + assert_eq!(delegation.get_quantity(&delegatee), 40); + + let delegation_delegatee = Delegation::load_from_state(&state, &delegatee).unwrap(); + assert_eq!(delegation_delegatee.iter().count(), 0, "Shouldn't be touched"); + + let stakeholders = Stakeholders::load_from_state(&state).unwrap(); + assert_eq!(stakeholders.iter().len(), 2); + assert!(stakeholders.contains(&delegator)); + assert!(stakeholders.contains(&delegatee)); + } + + #[test] + fn delegate_all() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 100, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert_eq!(result, Ok(())); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 0); + assert_eq!(state.action_data(&get_stake_account_key(&delegator)).unwrap(), None, "Should clear state"); + + let delegatee_account = StakeAccount::load_from_state(&state, &delegatee).unwrap(); + assert_eq!(delegatee_account.balance, 100, "Shouldn't be touched"); + + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegation.iter().count(), 1); + assert_eq!(delegation.get_quantity(&delegatee), 100); + + let delegation_delegatee = Delegation::load_from_state(&state, &delegatee).unwrap(); + assert_eq!(delegation_delegatee.iter().count(), 0, "Shouldn't be touched"); + + let stakeholders = Stakeholders::load_from_state(&state).unwrap(); + assert_eq!(stakeholders.iter().len(), 2); + assert!(stakeholders.contains(&delegator), "Should still be a stakeholder after delegated all"); + assert!(stakeholders.contains(&delegatee)); + } + + #[test] + fn delegate_only_to_candidate() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 40, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + } + + #[test] + fn delegate_too_much() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 200, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + } + + #[test] + fn can_transfer_within_non_delegated_tokens() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 50, + }; + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); + + let action = StakeAction::TransferCCS { + address: delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + } + + #[test] + fn cannot_transfer_over_non_delegated_tokens() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 50, + }; + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); + + let action = StakeAction::TransferCCS { + address: delegatee, + quantity: 100, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + } + + #[test] + fn can_revoke_delegated_tokens() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + + let action = StakeAction::Revoke { + address: delegatee, + quantity: 20, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert_eq!(Ok(()), result); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 100 - 50 + 20); + assert_eq!(delegation.iter().count(), 1); + assert_eq!(delegation.get_quantity(&delegatee), 50 - 20); + } + + #[test] + fn cannot_revoke_more_than_delegated_tokens() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + + let action = StakeAction::Revoke { + address: delegatee, + quantity: 70, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 100 - 50); + assert_eq!(delegation.iter().count(), 1); + assert_eq!(delegation.get_quantity(&delegatee), 50); + } + + #[test] + fn revoke_all_should_clear_state() { + let delegatee_pubkey = Public::random(); + let delegatee = public_to_address(&delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + let genesis_stakes = { + let mut genesis_stakes = HashMap::new(); + genesis_stakes.insert(delegatee, 100); + genesis_stakes.insert(delegator, 100); + genesis_stakes + }; + init_stake(&mut state, genesis_stakes, Default::default(), Default::default()).unwrap(); + self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + + let action = StakeAction::Revoke { + address: delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert_eq!(Ok(()), result); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 100); + assert_eq!(state.action_data(&get_delegation_key(&delegator)).unwrap(), None); + } + + #[test] + fn can_redelegate_tokens() { + let prev_delegatee_pubkey = Public::random(); + let prev_delegatee = public_to_address(&prev_delegatee_pubkey); + let next_delegatee_pubkey = Public::random(); + let next_delegatee = public_to_address(&next_delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + 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(); + self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + self_nominate(&mut state, &next_delegatee, &next_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: prev_delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + + let action = StakeAction::Redelegate { + prev_delegatee, + next_delegatee, + quantity: 20, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert_eq!(Ok(()), result); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 100 - 50); + assert_eq!(delegation.iter().count(), 2); + assert_eq!(delegation.get_quantity(&prev_delegatee), 50 - 20); + assert_eq!(delegation.get_quantity(&next_delegatee), 20); + } + + #[test] + fn cannot_redelegate_more_than_delegated_tokens() { + let prev_delegatee_pubkey = Public::random(); + let prev_delegatee = public_to_address(&prev_delegatee_pubkey); + let next_delegatee_pubkey = Public::random(); + let next_delegatee = public_to_address(&next_delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + 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(); + self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + self_nominate(&mut state, &next_delegatee, &next_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: prev_delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + + let action = StakeAction::Redelegate { + prev_delegatee, + next_delegatee, + quantity: 70, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 100 - 50); + assert_eq!(delegation.iter().count(), 1); + assert_eq!(delegation.get_quantity(&prev_delegatee), 50); + assert_eq!(delegation.get_quantity(&next_delegatee), 0); + } + + #[test] + fn redelegate_all_should_clear_state() { + let prev_delegatee_pubkey = Public::random(); + let prev_delegatee = public_to_address(&prev_delegatee_pubkey); + let next_delegatee_pubkey = Public::random(); + let next_delegatee = public_to_address(&next_delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + 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(); + self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + self_nominate(&mut state, &next_delegatee, &next_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: prev_delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + + let action = StakeAction::Redelegate { + prev_delegatee, + next_delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert_eq!(Ok(()), result); + + let delegator_account = StakeAccount::load_from_state(&state, &delegator).unwrap(); + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegator_account.balance, 50); + assert_eq!(delegation.iter().count(), 1); + assert_eq!(delegation.get_quantity(&prev_delegatee), 0); + assert_eq!(delegation.get_quantity(&next_delegatee), 50); + } + + #[test] + fn redelegate_only_to_candidate() { + let prev_delegatee_pubkey = Public::random(); + let prev_delegatee = public_to_address(&prev_delegatee_pubkey); + let next_delegatee_pubkey = Public::random(); + let next_delegatee = public_to_address(&next_delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + 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(); + + self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: prev_delegatee, + quantity: 40, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_ok()); + + let action = StakeAction::Redelegate { + prev_delegatee, + next_delegatee, + quantity: 50, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + } + + #[test] + fn cannot_redelegate_to_banned_account() { + let informant_pubkey = Public::random(); + let criminal_pubkey = Public::random(); + let delegator_pubkey = Public::random(); + let informant = public_to_address(&informant_pubkey); + let criminal = public_to_address(&criminal_pubkey); + let delegator = public_to_address(&delegator_pubkey); + let prev_delegatee_pubkey = Public::random(); + let prev_delegatee = public_to_address(&prev_delegatee_pubkey); + + let mut state = helpers::get_temp_state(); + state.add_balance(&criminal, 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(); + self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + self_nominate(&mut state, &criminal, &criminal_pubkey, 100, 0, 10, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: criminal, + quantity: 40, + }; + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); + let action = StakeAction::DelegateCCS { + address: prev_delegatee, + quantity: 40, + }; + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.len(), 2); + + assert_eq!(Ok(()), ban(&mut state, &informant, criminal)); + + let banned = Banned::load_from_state(&state).unwrap(); + assert!(banned.is_banned(&criminal)); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.len(), 1); + + let action = StakeAction::Redelegate { + prev_delegatee, + next_delegatee: criminal, + quantity: 40, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + } + + #[test] + fn cannot_redelegate_to_jailed_account() { + let jail_pubkey = Public::random(); + let jail_address = public_to_address(&jail_pubkey); + let prev_delegatee_pubkey = Public::random(); + let prev_delegatee = public_to_address(&prev_delegatee_pubkey); + let delegator_pubkey = Public::random(); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + state.add_balance(&jail_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(); + self_nominate(&mut state, &prev_delegatee, &prev_delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); + + let deposit = 200; + self_nominate(&mut state, &jail_address, &jail_pubkey, deposit, 0, 5, b"".to_vec()).unwrap(); + + let action = StakeAction::DelegateCCS { + address: prev_delegatee, + quantity: 40, + }; + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.len(), 2); + + let custody_until = 10; + let released_at = 20; + let result = jail(&mut state, &[jail_address], custody_until, released_at); + assert!(result.is_ok()); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.len(), 1); + + let action = StakeAction::Redelegate { + prev_delegatee, + next_delegatee: jail_address, + quantity: 40, + }; + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); + assert!(result.is_err()); + } + + #[test] + fn self_nominate_deposit_test() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + + let mut state = helpers::get_temp_state(); + state.add_balance(&address, 1000).unwrap(); + + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let result = self_nominate(&mut state, &address, &address_pubkey, 0, 0, 5, b"metadata1".to_vec()); + assert_eq!(result, Ok(())); + + assert_eq!(state.balance(&address).unwrap(), 1000); + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!( + candidates.get_candidate(&address), + Some(&Candidate { + pubkey: address_pubkey, + deposit: 0, + nomination_ends_at: 5, + metadata: b"metadata1".to_vec(), + }), + "nomination_ends_at should be updated even if candidate deposits 0" + ); + + let result = self_nominate(&mut state, &address, &address_pubkey, 200, 0, 10, b"metadata2".to_vec()); + assert_eq!(result, Ok(())); + + assert_eq!(state.balance(&address).unwrap(), 800); + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!( + candidates.get_candidate(&address), + Some(&Candidate { + pubkey: address_pubkey, + deposit: 200, + nomination_ends_at: 10, + metadata: b"metadata2".to_vec(), + }) + ); + + let result = self_nominate(&mut state, &address, &address_pubkey, 0, 0, 15, b"metadata3".to_vec()); + assert_eq!(result, Ok(())); + + assert_eq!(state.balance(&address).unwrap(), 800); + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!( + candidates.get_candidate(&address), + Some(&Candidate { + pubkey: address_pubkey, + deposit: 200, + nomination_ends_at: 15, + metadata: b"metadata3".to_vec(), + }), + "nomination_ends_at should be updated even if candidate deposits 0" + ); + } + + #[test] + fn self_nominate_fail_with_insufficient_balance() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + + let mut state = helpers::get_temp_state(); + state.add_balance(&address, 1000).unwrap(); + + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + + // TODO: change with stake::execute() + let result = self_nominate(&mut state, &address, &address_pubkey, 2000, 0, 5, b"".to_vec()); + assert!(result.is_err(), "Cannot self-nominate without a sufficient balance"); + } + + #[test] + fn jail_candidate() { + let address_pubkey = Public::random(); + let address = public_to_address(&address_pubkey); + + let mut state = helpers::get_temp_state(); + 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; + self_nominate(&mut state, &address, &address_pubkey, deposit, 0, 5, b"".to_vec()).unwrap(); + + let custody_until = 10; + let released_at = 20; + let result = jail(&mut state, &[address], custody_until, released_at); + assert!(result.is_ok()); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.get_candidate(&address), None, "The candidate is removed"); + + let jail = Jail::load_from_state(&state).unwrap(); + assert_eq!( + jail.get_prisoner(&address), + Some(&Prisoner { + address, + deposit, + custody_until, + released_at, + }), + "The candidate become a prisoner" + ); + + assert_eq!(state.balance(&address).unwrap(), 1000 - deposit, "Deposited ccs is temporarily unavailable"); + } + + #[test] + fn test_ban() { + let informant_pubkey = Public::random(); + let criminal_pubkey = Public::random(); + let delegator_pubkey = Public::random(); + let informant = public_to_address(&informant_pubkey); + let criminal = public_to_address(&criminal_pubkey); + let delegator = public_to_address(&delegator_pubkey); + + let mut state = helpers::get_temp_state(); + state.add_balance(&criminal, 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(); + + let deposit = 100; + self_nominate(&mut state, &criminal, &criminal_pubkey, deposit, 0, 10, b"".to_vec()).unwrap(); + let action = StakeAction::DelegateCCS { + address: criminal, + quantity: 40, + }; + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); + + assert_eq!(Ok(()), ban(&mut state, &informant, criminal)); + + let banned = Banned::load_from_state(&state).unwrap(); + assert!(banned.is_banned(&criminal)); + + let candidates = Candidates::load_from_state(&state).unwrap(); + assert_eq!(candidates.len(), 0); + + assert_eq!(state.balance(&criminal).unwrap(), 900, "Should lose deposit"); + + let delegation = Delegation::load_from_state(&state, &delegator).unwrap(); + assert_eq!(delegation.get_quantity(&criminal), 0, "Delegation should be reverted"); + + let account_delegator = StakeAccount::load_from_state(&state, &delegator).unwrap(); + assert_eq!(account_delegator.balance, 100, "Delegation should be reverted"); + } + + #[test] + fn ban_should_remove_prisoner_from_jail() { + let informant_pubkey = Public::random(); + let criminal_pubkey = Public::random(); + let informant = public_to_address(&informant_pubkey); + let criminal = public_to_address(&criminal_pubkey); + + let mut state = helpers::get_temp_state(); + init_stake(&mut state, Default::default(), Default::default(), Default::default()).unwrap(); + assert_eq!(Ok(()), state.add_balance(&criminal, 100)); + + let deposit = 10; + self_nominate(&mut state, &criminal, &criminal_pubkey, deposit, 0, 10, b"".to_vec()).unwrap(); + let custody_until = 10; + let released_at = 20; + jail(&mut state, &[criminal], custody_until, released_at).unwrap(); + + assert_eq!(Ok(()), ban(&mut state, &informant, criminal)); + + let jail = Jail::load_from_state(&state).unwrap(); + assert_eq!(jail.get_prisoner(&criminal), None, "Should be removed from the jail"); + } +} diff --git a/state/src/stake/mod.rs b/state/src/stake/mod.rs index ba25f209e0..e082b01a96 100644 --- a/state/src/stake/mod.rs +++ b/state/src/stake/mod.rs @@ -14,6 +14,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +mod actions; + +pub use self::actions::{ + ban, execute_stake_action, init_stake, jail, release_jailed_prisoners, revert_delegations, self_nominate, + update_candidates, update_validator_weights, +}; use super::TopStateView; use crate::{StateResult, TopLevelState}; use ccrypto::blake256; From 25ff3e0f71f8739a938b616726b5ab5345adc8c6 Mon Sep 17 00:00:00 2001 From: Seulgi Kim Date: Fri, 27 Mar 2020 02:03:01 +0900 Subject: [PATCH 5/5] Replace StakeHandler to the handeler dedicated to ReportDoubleVote --- core/src/block.rs | 8 +-- core/src/client/client.rs | 6 +- core/src/client/mod.rs | 4 +- core/src/client/test_client.rs | 4 +- core/src/consensus/mod.rs | 18 +++-- core/src/consensus/solo/mod.rs | 4 +- core/src/consensus/stake/mod.rs | 96 ++++++++++--------------- core/src/consensus/tendermint/engine.rs | 4 +- core/src/miner/miner.rs | 7 +- core/src/miner/mod.rs | 4 +- rpc/src/v1/impls/chain.rs | 4 +- rpc/src/v1/impls/engine.rs | 8 +-- state/src/impls/top_level.rs | 24 ++++--- state/src/lib.rs | 4 +- state/src/stake/mod.rs | 19 ++--- state/src/tests.rs | 4 +- 16 files changed, 104 insertions(+), 114 deletions(-) diff --git a/core/src/block.rs b/core/src/block.rs index c9c4cac90c..2d06fa9c7b 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -21,7 +21,7 @@ use crate::error::{BlockError, Error}; use crate::transaction::{UnverifiedTransaction, VerifiedTransaction}; use ccrypto::BLAKE_NULL_RLP; use ckey::Address; -use cstate::{FindStakeHandler, NextValidators, StateDB, StateError, StateWithCache, TopLevelState}; +use cstate::{FindDoubleVoteHandler, NextValidators, StateDB, StateError, StateWithCache, TopLevelState}; use ctypes::errors::HistoryError; use ctypes::header::{Header, Seal}; use ctypes::util::unexpected::Mismatch; @@ -146,7 +146,7 @@ impl<'x> OpenBlock<'x> { } /// Push a transaction into the block. - pub fn push_transaction( + pub fn push_transaction( &mut self, tx: VerifiedTransaction, h: Option, @@ -187,7 +187,7 @@ impl<'x> OpenBlock<'x> { } /// Push transactions onto the block. - pub fn push_transactions( + pub fn push_transactions( &mut self, transactions: &[VerifiedTransaction], client: &C, @@ -351,7 +351,7 @@ impl<'x> IsBlock for ClosedBlock { } /// Enact the block given by block header, transactions and uncles -pub fn enact( +pub fn enact( header: &Header, transactions: &[VerifiedTransaction], engine: &dyn CodeChainEngine, diff --git a/core/src/client/client.rs b/core/src/client/client.rs index e1539b0b54..2e3e9c6bb1 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -37,7 +37,7 @@ use crate::MemPoolMinFees; use cdb::{new_journaldb, Algorithm, AsHashDB}; use cio::IoChannel; use ckey::{Address, NetworkId, PlatformAddress}; -use cstate::{FindStakeHandler, StakeHandler, StateDB, StateResult, TopLevelState, TopStateView}; +use cstate::{DoubleVoteHandler, FindDoubleVoteHandler, StateDB, StateResult, TopLevelState, TopStateView}; use ctimer::{TimeoutHandler, TimerApi, TimerScheduleError, TimerToken}; use ctypes::header::Header; use ctypes::transaction::ShardTransaction; @@ -705,8 +705,8 @@ impl MiningBlockChainClient for Client { } } -impl FindStakeHandler for Client { - fn stake_handler(&self) -> Option<&dyn StakeHandler> { +impl FindDoubleVoteHandler for Client { + fn double_vote_handler(&self) -> Option<&dyn DoubleVoteHandler> { self.engine.stake_handler() } } diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 1560b18cce..6da6d4cd02 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -38,7 +38,7 @@ use crate::transaction::{LocalizedTransaction, PendingVerifiedTransactions, Veri use crate::types::{BlockId, BlockStatus, TransactionId, VerificationQueueInfo as BlockQueueInfo}; use cdb::DatabaseError; use ckey::{Address, NetworkId, PlatformAddress}; -use cstate::{FindStakeHandler, StateResult, TopLevelState, TopStateView}; +use cstate::{FindDoubleVoteHandler, StateResult, TopLevelState, TopStateView}; use ctypes::header::Header; use ctypes::transaction::ShardTransaction; use ctypes::{BlockHash, BlockNumber, CommonParams, ShardId, TxHash}; @@ -247,7 +247,7 @@ pub trait BlockProducer { } /// Extended client interface used for mining -pub trait MiningBlockChainClient: BlockChainClient + BlockProducer + FindStakeHandler { +pub trait MiningBlockChainClient: BlockChainClient + BlockProducer + FindDoubleVoteHandler { /// Returns malicious users who sent failing transactions. fn get_malicious_users(&self) -> Vec
; diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index e5ec60874f..2cd54dd9f3 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -50,7 +50,7 @@ use ckey::{ Generator, KeyPairTrait, NetworkId, PlatformAddress, Random, }; use cstate::tests::helpers::empty_top_state_with_metadata; -use cstate::{FindStakeHandler, NextValidators, StateDB, TopLevelState, Validator}; +use cstate::{FindDoubleVoteHandler, NextValidators, StateDB, TopLevelState, Validator}; use ctimer::{TimeoutHandler, TimerToken}; use ctypes::header::Header; use ctypes::transaction::{Action, Transaction}; @@ -577,7 +577,7 @@ impl TimeoutHandler for TestBlockChainClient { fn on_timeout(&self, _token: TimerToken) {} } -impl FindStakeHandler for TestBlockChainClient {} +impl FindDoubleVoteHandler for TestBlockChainClient {} impl super::EngineClient for TestBlockChainClient { fn update_sealing(&self, parent_block: BlockId, allow_empty_block: bool) { diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 405601387c..351b18e6a1 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -41,9 +41,9 @@ use crate::views::HeaderView; use crate::Client; use ckey::{Address, Signature}; use cnetwork::NetworkService; -use cstate::{StakeHandler, StateDB, StateResult}; +use cstate::{DoubleVoteHandler, StateDB, StateResult}; use ctypes::errors::SyntaxError; -use ctypes::transaction::Action; +use ctypes::transaction::{Action, StakeAction}; use ctypes::util::unexpected::{Mismatch, OutOfBounds}; use ctypes::{BlockHash, CommonParams, Header}; use primitives::{Bytes, H256}; @@ -233,7 +233,7 @@ pub trait ConsensusEngine: Sync + Send { true } - fn stake_handler(&self) -> Option<&dyn StakeHandler> { + fn stake_handler(&self) -> Option<&dyn DoubleVoteHandler> { None } @@ -327,9 +327,19 @@ pub trait CodeChainEngine: ConsensusEngine { .. } = &tx.transaction().action { + let action = rlp::decode(bytes).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; + let handler = self.stake_handler().ok_or_else(|| SyntaxError::InvalidCustomAction("no valid handler".to_string()))?; - handler.verify(bytes, common_params)?; + if let StakeAction::ReportDoubleVote { + message1, + message2, + } = &action + { + handler.verify(message1, message2)?; + } + + action.verify(common_params)?; } self.machine().verify_transaction_with_params(tx, common_params) } diff --git a/core/src/consensus/solo/mod.rs b/core/src/consensus/solo/mod.rs index fa580e1f60..0c99a0be15 100644 --- a/core/src/consensus/solo/mod.rs +++ b/core/src/consensus/solo/mod.rs @@ -26,7 +26,7 @@ use crate::codechain_machine::CodeChainMachine; use crate::consensus::{EngineError, EngineType}; use crate::error::Error; use ckey::Address; -use cstate::{init_stake, StakeHandler, StateDB, StateResult, StateWithCache, TopLevelState}; +use cstate::{init_stake, DoubleVoteHandler, StateDB, StateResult, StateWithCache, TopLevelState}; use ctypes::{BlockHash, Header}; use parking_lot::RwLock; use primitives::H256; @@ -126,7 +126,7 @@ impl ConsensusEngine for Solo { } } - fn stake_handler(&self) -> Option<&dyn StakeHandler> { + fn stake_handler(&self) -> Option<&dyn DoubleVoteHandler> { Some(&self.stake) } diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index f99ec0e105..4d939349de 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -15,16 +15,13 @@ // along with this program. If not, see . use crate::client::ConsensusClient; -use ckey::{public_to_address, Address, Ed25519Public as Public}; +use ckey::{public_to_address, Address}; use cstate::{ - ban, execute_stake_action, jail, release_jailed_prisoners, revert_delegations, update_candidates, StakeHandler, - StateResult, TopLevelState, TopState, TopStateView, + ban, jail, release_jailed_prisoners, revert_delegations, update_candidates, DoubleVoteHandler, StateResult, + TopLevelState, TopState, TopStateView, }; use ctypes::errors::{RuntimeError, SyntaxError}; -use ctypes::transaction::StakeAction; -use ctypes::CommonParams; use parking_lot::RwLock; -use rlp::{Decodable, Rlp}; use std::sync::{Arc, Weak}; use super::ValidatorSet; @@ -43,55 +40,31 @@ impl Stake { } } -impl StakeHandler for Stake { - fn execute( - &self, - bytes: &[u8], - state: &mut TopLevelState, - sender_address: &Address, - sender_public: &Public, - ) -> StateResult<()> { - let action = StakeAction::decode(&Rlp::new(bytes)).expect("Verification passed"); - - if let StakeAction::ReportDoubleVote { - message1, - .. - } = &action - { - let message1: ConsensusMessage = - rlp::decode(message1).map_err(|err| RuntimeError::FailedToHandleCustomAction(err.to_string()))?; - let validators = - self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet must be initialized"); - let client = self.client.read().as_ref().and_then(Weak::upgrade).expect("Client must be initialized"); - - execute_report_double_vote(message1, state, sender_address, &*client, &*validators)?; - } - execute_stake_action(action, state, sender_address, sender_public) - } +impl DoubleVoteHandler for Stake { + fn execute(&self, message1: &[u8], state: &mut TopLevelState, sender_address: &Address) -> StateResult<()> { + let message1: ConsensusMessage = + rlp::decode(message1).map_err(|err| RuntimeError::FailedToHandleCustomAction(err.to_string()))?; + let validators = + self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet must be initialized"); + let client = self.client.read().as_ref().and_then(Weak::upgrade).expect("Client must be initialized"); - fn verify(&self, bytes: &[u8], current_params: &CommonParams) -> Result<(), SyntaxError> { - let action = - StakeAction::decode(&Rlp::new(bytes)).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - action.verify(current_params)?; - if let StakeAction::ReportDoubleVote { - message1, - message2, - } = action - { - let client: Arc = - self.client.read().as_ref().and_then(Weak::upgrade).expect("Client should be initialized"); - let validators: Arc = - self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet should be initialized"); - - let message1: ConsensusMessage = - rlp::decode(&message1).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - let message2: ConsensusMessage = - rlp::decode(&message2).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - - verify_report_double_vote(message1, message2, &*client, &*validators)?; - } + execute_report_double_vote(message1, state, sender_address, &*client, &*validators)?; Ok(()) } + + fn verify(&self, message1: &[u8], message2: &[u8]) -> Result<(), SyntaxError> { + let client: Arc = + self.client.read().as_ref().and_then(Weak::upgrade).expect("Client should be initialized"); + let validators: Arc = + self.validators.read().as_ref().and_then(Weak::upgrade).expect("ValidatorSet should be initialized"); + + let message1: ConsensusMessage = + rlp::decode(message1).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; + let message2: ConsensusMessage = + rlp::decode(message2).map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; + + verify_report_double_vote(message1, message2, &*client, &*validators) + } } fn execute_report_double_vote( @@ -188,9 +161,14 @@ pub fn on_term_close( #[cfg(test)] mod tests { use super::*; + use ckey::Ed25519Public as Public; use cstate::tests::helpers; - use cstate::{init_stake, self_nominate, Candidate, Candidates, Delegation, Jail, StakeAccount, TopStateView}; - use rlp::Encodable; + use cstate::{ + execute_stake_action, init_stake, self_nominate, Candidate, Candidates, Delegation, Jail, StakeAccount, + TopStateView, + }; + use ctypes::transaction::StakeAction; + use ctypes::CommonParams; use std::collections::HashMap; fn metadata_for_election() -> TopLevelState { @@ -273,7 +251,7 @@ mod tests { address, quantity: 40, }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); let result = on_term_close(&mut state, pseudo_term_to_block_num_calculator(29), &[]); assert_eq!(result, Ok(())); @@ -452,7 +430,7 @@ mod tests { address, quantity: 1, }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); assert_ne!(Ok(()), result); on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); @@ -462,7 +440,7 @@ mod tests { address, quantity: 1, }; - let result = Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); + let result = execute_stake_action(action, &mut state, &delegator, &delegator_pubkey); assert!(result.is_err()); } @@ -494,7 +472,7 @@ mod tests { address, quantity: 40, }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); jail(&mut state, &[address], custody_until, released_at).unwrap(); @@ -536,7 +514,7 @@ mod tests { address, quantity: 40, }; - Stake::default().execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); + execute_stake_action(action, &mut state, &delegator, &delegator_pubkey).unwrap(); jail(&mut state, &[address], custody_until, released_at).unwrap(); diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index fb79c7fdeb..40e9782954 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -33,7 +33,7 @@ use ckey::{public_to_address, Address}; use cnetwork::NetworkService; use crossbeam_channel as crossbeam; use cstate::{ - init_stake, update_validator_weights, CurrentValidators, NextValidators, StakeHandler, StateDB, StateResult, + init_stake, update_validator_weights, CurrentValidators, DoubleVoteHandler, NextValidators, StateDB, StateResult, StateWithCache, TopLevelState, TopState, TopStateView, }; use ctypes::{BlockHash, Header}; @@ -245,7 +245,7 @@ impl ConsensusEngine for Tendermint { parent_hash_of_new_header == prev_best_hash || grandparent_hash_of_new_header == prev_best_hash } - fn stake_handler(&self) -> Option<&dyn StakeHandler> { + fn stake_handler(&self) -> Option<&dyn DoubleVoteHandler> { Some(&*self.stake) } diff --git a/core/src/miner/miner.rs b/core/src/miner/miner.rs index bc7def8fc9..29f86cf761 100644 --- a/core/src/miner/miner.rs +++ b/core/src/miner/miner.rs @@ -30,7 +30,7 @@ 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::{FindStakeHandler, TopLevelState}; +use cstate::{FindDoubleVoteHandler, TopLevelState}; use ctypes::errors::HistoryError; use ctypes::transaction::{Action, IncompleteTransaction}; use ctypes::{BlockHash, TxHash}; @@ -292,7 +292,7 @@ impl Miner { /// Prepares new block for sealing including top transactions from queue and seal it. fn prepare_and_seal_block< - C: AccountData + BlockChainTrait + BlockProducer + EngineInfo + FindStakeHandler + TermInfo, + C: AccountData + BlockChainTrait + BlockProducer + EngineInfo + FindDoubleVoteHandler + TermInfo, >( &self, parent_block_id: BlockId, @@ -510,7 +510,8 @@ impl MinerService for Miner { fn update_sealing(&self, chain: &C, parent_block: BlockId, allow_empty_block: bool) where - C: AccountData + BlockChainTrait + BlockProducer + EngineInfo + ImportBlock + FindStakeHandler + TermInfo, { + C: AccountData + BlockChainTrait + BlockProducer + EngineInfo + ImportBlock + FindDoubleVoteHandler + TermInfo, + { ctrace!(MINER, "update_sealing: preparing a block"); let block = match self.prepare_and_seal_block(parent_block, chain) { diff --git a/core/src/miner/mod.rs b/core/src/miner/mod.rs index 458e697c33..331c62fb5f 100644 --- a/core/src/miner/mod.rs +++ b/core/src/miner/mod.rs @@ -21,7 +21,7 @@ mod mem_pool_types; mod miner; use ckey::{public_to_address, Address, Ed25519Public as Public, Password, PlatformAddress}; -use cstate::{FindStakeHandler, TopStateView}; +use cstate::{FindDoubleVoteHandler, TopStateView}; use ctypes::transaction::IncompleteTransaction; use ctypes::{BlockHash, TxHash}; use primitives::Bytes; @@ -76,7 +76,7 @@ pub trait MinerService: Send + Sync { /// New chain head event. Restart mining operation. fn update_sealing(&self, chain: &C, parent_block: BlockId, allow_empty_block: bool) where - C: AccountData + BlockChainTrait + BlockProducer + ImportBlock + EngineInfo + FindStakeHandler + TermInfo; + C: AccountData + BlockChainTrait + BlockProducer + ImportBlock + EngineInfo + FindDoubleVoteHandler + TermInfo; /// Imports transactions to mem pool. fn import_external_transactions( diff --git a/rpc/src/v1/impls/chain.rs b/rpc/src/v1/impls/chain.rs index b4fc7f44b2..10778ea5dd 100644 --- a/rpc/src/v1/impls/chain.rs +++ b/rpc/src/v1/impls/chain.rs @@ -21,7 +21,7 @@ use ccore::{AccountData, BlockId, EngineInfo, ExecuteClient, MiningBlockChainCli use cjson::scheme::Params; use cjson::uint::Uint; use ckey::{public_to_address, NetworkId, PlatformAddress}; -use cstate::FindStakeHandler; +use cstate::FindDoubleVoteHandler; use ctypes::transaction::Action; use ctypes::{BlockHash, BlockNumber, ShardId, TxHash}; use jsonrpc_core::Result; @@ -53,7 +53,7 @@ where + AccountData + ExecuteClient + EngineInfo - + FindStakeHandler + + FindDoubleVoteHandler + TermInfo + 'static, { diff --git a/rpc/src/v1/impls/engine.rs b/rpc/src/v1/impls/engine.rs index 626fd2b462..39a72bc19a 100644 --- a/rpc/src/v1/impls/engine.rs +++ b/rpc/src/v1/impls/engine.rs @@ -19,13 +19,13 @@ use super::super::traits::Engine; use ccore::{BlockId, EngineInfo, MinerService, StateInfo}; use cjson::bytes::{Bytes, WithoutPrefix}; use ckey::PlatformAddress; -use cstate::{query_stake_state, FindStakeHandler}; +use cstate::{query_stake_state, FindDoubleVoteHandler}; use jsonrpc_core::Result; use std::sync::Arc; pub struct EngineClient where - C: EngineInfo + StateInfo + FindStakeHandler, + C: EngineInfo + StateInfo + FindDoubleVoteHandler, M: MinerService, { client: Arc, miner: Arc, @@ -33,7 +33,7 @@ where impl EngineClient where - C: EngineInfo + StateInfo + FindStakeHandler, + C: EngineInfo + StateInfo + FindDoubleVoteHandler, M: MinerService, { pub fn new(client: Arc, miner: Arc) -> Self { @@ -46,7 +46,7 @@ where impl Engine for EngineClient where - C: EngineInfo + StateInfo + FindStakeHandler + 'static, + C: EngineInfo + StateInfo + FindDoubleVoteHandler + 'static, M: MinerService + 'static, { fn get_coinbase(&self) -> Result> { diff --git a/state/src/impls/top_level.rs b/state/src/impls/top_level.rs index a2f5344e56..daa095e1ad 100644 --- a/state/src/impls/top_level.rs +++ b/state/src/impls/top_level.rs @@ -39,14 +39,14 @@ use crate::cache::{ShardCache, TopCache}; use crate::checkpoint::{CheckpointId, StateWithCheckpoint}; use crate::traits::{ShardState, ShardStateView, StateWithCache, TopState, TopStateView}; use crate::{ - Account, ActionData, FindStakeHandler, Metadata, MetadataAddress, Shard, ShardAddress, ShardLevelState, StateDB, - StateResult, + execute_stake_action, Account, ActionData, FindDoubleVoteHandler, Metadata, MetadataAddress, Shard, ShardAddress, + ShardLevelState, StateDB, StateResult, }; use ccrypto::BLAKE_NULL_RLP; use cdb::{AsHashDB, DatabaseError}; use ckey::{public_to_address, Address, Ed25519Public as Public, NetworkId}; use ctypes::errors::RuntimeError; -use ctypes::transaction::{Action, ShardTransaction, Transaction}; +use ctypes::transaction::{Action, ShardTransaction, StakeAction, Transaction}; use ctypes::util::unexpected::Mismatch; #[cfg(test)] use ctypes::Tracker; @@ -230,7 +230,7 @@ impl TopLevelState { /// Execute a given tranasction, charging tranasction fee. /// This will change the state accordingly. - pub fn apply( + pub fn apply( &mut self, tx: &Transaction, signed_hash: &TxHash, @@ -261,7 +261,7 @@ impl TopLevelState { result } - fn apply_internal( + fn apply_internal( &mut self, tx: &Transaction, signed_hash: &TxHash, @@ -313,7 +313,7 @@ impl TopLevelState { } #[allow(clippy::too_many_arguments)] - fn apply_action( + fn apply_action( &mut self, action: &Action, network_id: NetworkId, @@ -367,8 +367,16 @@ impl TopLevelState { bytes, .. } => { - let handler = client.stake_handler().expect("Unknown custom transaction applied!"); - handler.execute(bytes, self, sender_address, sender_public)?; + let action = rlp::decode(&bytes).expect("Verification passed"); + let handler = client.double_vote_handler().expect("Unknown custom transaction applied!"); + if let StakeAction::ReportDoubleVote { + message1, + .. + } = &action + { + handler.execute(message1, self, sender_address)?; + } + execute_stake_action(action, self, sender_address, sender_public)?; return Ok(()) } }; diff --git a/state/src/lib.rs b/state/src/lib.rs index b63fbf31f7..cd3507d4f9 100644 --- a/state/src/lib.rs +++ b/state/src/lib.rs @@ -51,8 +51,8 @@ pub use crate::item::stake::{ }; pub use crate::stake::{ ban, execute_stake_action, init_stake, jail, query as query_stake_state, release_jailed_prisoners, - revert_delegations, self_nominate, update_candidates, update_validator_weights, FindStakeHandler, StakeHandler, - StakeKeyBuilder, + revert_delegations, self_nominate, update_candidates, update_validator_weights, DoubleVoteHandler, + FindDoubleVoteHandler, StakeKeyBuilder, }; pub use crate::traits::{ShardState, ShardStateView, StateWithCache, TopState, TopStateView}; diff --git a/state/src/stake/mod.rs b/state/src/stake/mod.rs index e082b01a96..821f12006f 100644 --- a/state/src/stake/mod.rs +++ b/state/src/stake/mod.rs @@ -23,22 +23,15 @@ pub use self::actions::{ use super::TopStateView; use crate::{StateResult, TopLevelState}; use ccrypto::blake256; -use ckey::{Address, Ed25519Public as Public}; +use ckey::Address; use ctypes::errors::SyntaxError; -use ctypes::CommonParams; use primitives::H256; use rlp::{Encodable, RlpStream}; use std::convert::From; -pub trait StakeHandler: Send + Sync { - fn execute( - &self, - bytes: &[u8], - state: &mut TopLevelState, - fee_payer: &Address, - sender_pubkey: &Public, - ) -> StateResult<()>; - fn verify(&self, bytes: &[u8], common_params: &CommonParams) -> Result<(), SyntaxError>; +pub trait DoubleVoteHandler: Send + Sync { + fn execute(&self, message1: &[u8], state: &mut TopLevelState, fee_payer: &Address) -> StateResult<()>; + fn verify(&self, message1: &[u8], message2: &[u8]) -> Result<(), SyntaxError>; } pub fn query(key_fragment: &[u8], state: &TopLevelState) -> StateResult>> { @@ -47,8 +40,8 @@ pub fn query(key_fragment: &[u8], state: &TopLevelState) -> StateResult Option<&dyn StakeHandler> { +pub trait FindDoubleVoteHandler { + fn double_vote_handler(&self) -> Option<&dyn DoubleVoteHandler> { None } } diff --git a/state/src/tests.rs b/state/src/tests.rs index 6aaf0fe555..cf6f2c2ed5 100644 --- a/state/src/tests.rs +++ b/state/src/tests.rs @@ -16,7 +16,7 @@ pub mod helpers { use crate::impls::TopLevelState; - use crate::{FindStakeHandler, Metadata, MetadataAddress, StateDB}; + use crate::{FindDoubleVoteHandler, Metadata, MetadataAddress, StateDB}; use cdb::AsHashDB; use ctypes::CommonParams; use kvdb::KeyValueDB; @@ -26,7 +26,7 @@ pub mod helpers { use std::sync::Arc; pub struct TestClient {} - impl FindStakeHandler for TestClient {} + impl FindDoubleVoteHandler for TestClient {} pub fn get_memory_db() -> Arc { Arc::new(kvdb_memorydb::create(1))