diff --git a/core/src/client/test_client.rs b/core/src/client/test_client.rs index 475a8ba677..f2f38e73ad 100644 --- a/core/src/client/test_client.rs +++ b/core/src/client/test_client.rs @@ -36,9 +36,10 @@ use std::ops::Range; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrder}; use std::sync::Arc; -use ckey::{public_to_address, Address, Generator, NetworkId, PlatformAddress, Public, Random}; +use ckey::{public_to_address, Address, Generator, KeyPair, NetworkId, PlatformAddress, Private, Public, Random}; use cmerkle::skewed_merkle_root; use cnetwork::NodeId; +use cstate::tests::helpers::empty_top_state; use cstate::{FindActionHandler, StateDB, TopLevelState}; use ctimer::{TimeoutHandler, TimerToken}; use ctypes::transaction::{Action, Transaction}; @@ -58,6 +59,7 @@ use crate::client::{ AccountData, BlockChainClient, BlockChainTrait, BlockProducer, BlockStatus, EngineInfo, ImportBlock, MiningBlockChainClient, StateInfo, StateOrBlock, TermInfo, }; +use crate::consensus::stake::{Validator, Validators}; use crate::consensus::EngineError; use crate::db::{COL_STATE, NUM_COLUMNS}; use crate::encoded; @@ -102,6 +104,10 @@ pub struct TestBlockChainClient { pub history: RwLock>, /// Term ID pub term_id: Option, + /// Fixed validator keys + pub validator_keys: RwLock>, + /// Fixed validators + pub validators: Validators, } impl Default for TestBlockChainClient { @@ -153,7 +159,9 @@ impl TestBlockChainClient { scheme, latest_block_timestamp: RwLock::new(10_000_000), history: RwLock::new(None), - term_id: None, + term_id: Some(1), + validator_keys: RwLock::new(HashMap::new()), + validators: Validators::from_vector_to_test(vec![]), }; // insert genesis hash. @@ -308,6 +316,26 @@ impl TestBlockChainClient { pub fn set_history(&self, h: Option) { *self.history.write() = h; } + + /// Set validators which can be brought from state. + pub fn set_random_validators(&mut self, count: usize) { + let mut pubkeys: Vec = vec![]; + for _ in 0..count { + let random_priv_key = Private::from(H256::random()); + let key_pair = KeyPair::from_private(random_priv_key).unwrap(); + self.validator_keys.write().insert(*key_pair.public(), *key_pair.private()); + pubkeys.push(*key_pair.public()); + } + let fixed_validators: Validators = Validators::from_vector_to_test( + pubkeys.into_iter().map(|pubkey| Validator::new_for_test(0, 0, pubkey)).collect(), + ); + + self.validators = fixed_validators; + } + + pub fn get_validators(&self) -> &Validators { + &self.validators + } } pub fn get_temp_state_db() -> StateDB { @@ -627,6 +655,10 @@ impl TermInfo for TestBlockChainClient { impl StateInfo for TestBlockChainClient { fn state_at(&self, _id: BlockId) -> Option { - None + let statedb = StateDB::new_with_memorydb(); + let mut top_state = empty_top_state(statedb); + let _ = self.validators.save_to_state(&mut top_state); + + Some(top_state) } } diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 1d5caf5fe8..3d24e92bdf 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -21,7 +21,7 @@ mod null_engine; mod signer; mod simple_poa; mod solo; -mod stake; +pub mod stake; mod tendermint; mod validator_set; mod vote_collector; @@ -31,9 +31,13 @@ pub use self::cuckoo::Cuckoo; pub use self::null_engine::NullEngine; pub use self::simple_poa::SimplePoA; pub use self::solo::Solo; -pub use self::tendermint::{Tendermint, TendermintParams, TimeGapParams}; +pub use self::tendermint::{ + message_info_rlp, ConsensusMessage, Height, Step, Tendermint, TendermintParams, TimeGapParams, View, VoteOn, + VoteStep, +}; pub use self::validator_set::validator_list::RoundRobinValidator; -pub use self::validator_set::ValidatorSet; +pub use self::validator_set::{DynamicValidator, ValidatorSet}; +pub use self::vote_collector::Message; use std::fmt; use std::sync::{Arc, Weak}; @@ -48,7 +52,6 @@ use ctypes::{CommonParams, Header}; use primitives::{Bytes, H256, U256}; use self::bit_set::BitSet; -use self::tendermint::types::View; use crate::account_provider::AccountProvider; use crate::block::{ExecutedBlock, SealedBlock}; use crate::client::ConsensusClient; diff --git a/core/src/consensus/solo/mod.rs b/core/src/consensus/solo/mod.rs index b8c9bfd155..a6711fcc2b 100644 --- a/core/src/consensus/solo/mod.rs +++ b/core/src/consensus/solo/mod.rs @@ -18,16 +18,18 @@ mod params; use std::sync::Arc; -use ckey::Address; +use ckey::{Address, Error as KeyError, Public, SchnorrSignature}; use cstate::{ActionHandler, HitHandler}; use ctypes::{CommonParams, Header}; +use primitives::H256; +use rlp::{Decodable, DecoderError, Encodable, RlpStream, UntrustedRlp}; use self::params::SoloParams; use super::stake; use super::{ConsensusEngine, Seal}; use crate::block::{ExecutedBlock, IsBlock}; use crate::codechain_machine::CodeChainMachine; -use crate::consensus::{EngineError, EngineType}; +use crate::consensus::{EngineError, EngineType, Message}; use crate::error::Error; /// A consensus engine which does not provide any consensus mechanism. @@ -37,6 +39,53 @@ pub struct Solo { action_handlers: Vec>, } +#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)] +pub struct SoloMessage {} + +impl Encodable for SoloMessage { + fn rlp_append(&self, s: &mut RlpStream) { + s.append_empty_data(); + } +} + +impl Decodable for SoloMessage { + fn decode(_rlp: &UntrustedRlp) -> Result { + Ok(SoloMessage {}) + } +} + +impl Message for SoloMessage { + type Round = bool; + + fn signature(&self) -> SchnorrSignature { + SchnorrSignature::random() + } + + fn signer_index(&self) -> usize { + Default::default() + } + + fn block_hash(&self) -> Option { + None + } + + fn round(&self) -> &bool { + &false + } + + fn height(&self) -> u64 { + 0 + } + + fn is_broadcastable(&self) -> bool { + false + } + + fn verify(&self, _signer_public: &Public) -> Result { + Ok(true) + } +} + impl Solo { /// Returns new instance of Solo over the given state machine. pub fn new(params: SoloParams, machine: CodeChainMachine) -> Self { @@ -44,7 +93,7 @@ impl Solo { if params.enable_hit_handler { action_handlers.push(Arc::new(HitHandler::new())); } - action_handlers.push(Arc::new(stake::Stake::new(params.genesis_stakes.clone()))); + action_handlers.push(Arc::new(stake::Stake::::new(params.genesis_stakes.clone()))); Solo { params, diff --git a/core/src/consensus/stake/action_data.rs b/core/src/consensus/stake/action_data.rs index 7f1267978d..312e2cbc31 100644 --- a/core/src/consensus/stake/action_data.rs +++ b/core/src/consensus/stake/action_data.rs @@ -238,6 +238,15 @@ pub struct Validator { } impl Validator { + pub fn new_for_test(delegation: StakeQuantity, deposit: Deposit, pubkey: Public) -> Self { + Self { + weight: delegation, + delegation, + deposit, + pubkey, + } + } + fn new(delegation: StakeQuantity, deposit: Deposit, pubkey: Public) -> Self { Self { weight: delegation, @@ -263,6 +272,10 @@ impl Validator { #[derive(Debug)] pub struct Validators(Vec); impl Validators { + pub fn from_vector_to_test(vec: Vec) -> Self { + Validators(vec) + } + pub fn load_from_state(state: &TopLevelState) -> StateResult { let key = &*VALIDATORS_KEY; let validators = state.action_data(&key)?.map(|data| decode_list(&data)).unwrap_or_default(); diff --git a/core/src/consensus/stake/actions.rs b/core/src/consensus/stake/actions.rs index 4a27109cc4..3cc4affd71 100644 --- a/core/src/consensus/stake/actions.rs +++ b/core/src/consensus/stake/actions.rs @@ -14,8 +14,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use std::sync::Arc; + use ccrypto::Blake; use ckey::{recover, Address, Signature}; +use client::ConsensusClient; +use consensus::vote_collector::Message; +use consensus::ValidatorSet; use ctypes::errors::SyntaxError; use ctypes::CommonParams; use primitives::{Bytes, H256}; @@ -25,10 +30,11 @@ const ACTION_TAG_TRANSFER_CCS: u8 = 1; const ACTION_TAG_DELEGATE_CCS: u8 = 2; const ACTION_TAG_REVOKE: u8 = 3; const ACTION_TAG_SELF_NOMINATE: u8 = 4; +const ACTION_TAG_REPORT_DOUBLE_VOTE: u8 = 5; const ACTION_TAG_CHANGE_PARAMS: u8 = 0xFF; #[derive(Debug, PartialEq)] -pub enum Action { +pub enum Action { TransferCCS { address: Address, quantity: u64, @@ -50,10 +56,19 @@ pub enum Action { params: Box, signatures: Vec, }, + ReportDoubleVote { + message1: M, + message2: M, + }, } -impl Action { - pub fn verify(&self, current_params: &CommonParams) -> Result<(), SyntaxError> { +impl Action { + pub fn verify( + &self, + current_params: &CommonParams, + client: Option>, + validators: Option>, + ) -> Result<(), SyntaxError> { match self { Action::TransferCCS { .. @@ -89,7 +104,7 @@ impl Action { ))) } params.verify().map_err(SyntaxError::InvalidCustomAction)?; - let action = Action::ChangeParams { + let action = Action::::ChangeParams { metadata_seq: *metadata_seq, params: params.clone(), signatures: vec![], @@ -102,12 +117,64 @@ impl Action { })?; } } + Action::ReportDoubleVote { + message1, + message2, + } => { + if message1 == message2 { + return Err(SyntaxError::InvalidCustomAction(String::from("Messages are duplicated"))) + } + 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) != Ok(true) || message2.verify(&signer) != Ok(true) { + return Err(SyntaxError::InvalidCustomAction(String::from("Schnorr signature verification fails"))) + } + } } Ok(()) } } -impl Encodable for Action { +impl Encodable for Action { fn rlp_append(&self, s: &mut RlpStream) { match self { Action::TransferCCS { @@ -147,11 +214,17 @@ impl Encodable for Action { s.append(signature); } } + Action::ReportDoubleVote { + message1, + message2, + } => { + s.begin_list(3).append(&ACTION_TAG_REPORT_DOUBLE_VOTE).append(message1).append(message2); + } }; } } -impl Decodable for Action { +impl Decodable for Action { fn decode(rlp: &UntrustedRlp) -> Result { let tag = rlp.val_at(0)?; match tag { @@ -224,6 +297,21 @@ impl Decodable for Action { signatures, }) } + ACTION_TAG_REPORT_DOUBLE_VOTE => { + let item_count = rlp.item_count()?; + if item_count != 3 { + return Err(DecoderError::RlpIncorrectListLen { + expected: 3, + got: item_count, + }) + } + let message1 = rlp.val_at(1)?; + let message2 = rlp.val_at(2)?; + Ok(Action::ReportDoubleVote { + message1, + message2, + }) + } _ => Err(DecoderError::Custom("Unexpected Tendermint Stake Action Type")), } } @@ -231,13 +319,17 @@ impl Decodable for Action { #[cfg(test)] mod tests { - use rlp::rlp_encode_and_decode_test; - use super::*; + use ccrypto::blake256; + use ckey::sign_schnorr; + use client::TestBlockChainClient; + use consensus::solo::SoloMessage; + use consensus::{message_info_rlp, ConsensusMessage, DynamicValidator, Step, VoteOn, VoteStep}; + use rlp::rlp_encode_and_decode_test; #[test] fn decode_fail_if_change_params_have_no_signatures() { - let action = Action::ChangeParams { + let action = Action::::ChangeParams { metadata_seq: 3, params: CommonParams::default_for_test().into(), signatures: vec![], @@ -247,16 +339,282 @@ mod tests { expected: 4, got: 3, }), - UntrustedRlp::new(&rlp::encode(&action)).as_val::() + UntrustedRlp::new(&rlp::encode(&action)).as_val::>() ); } #[test] fn rlp_of_change_params() { - rlp_encode_and_decode_test!(Action::ChangeParams { + rlp_encode_and_decode_test!(Action::::ChangeParams { metadata_seq: 3, params: CommonParams::default_for_test().into(), signatures: vec![Signature::random(), Signature::random()], }); } + + 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 message_for_signature = + blake256(message_info_rlp(vote_step_twister(vote_step), 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 privkey = *client.validator_keys.read().get(&pubkey).unwrap(); + let signature = sign_schnorr(&privkey, &message_for_signature).unwrap(); + + 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::new(test_client.get_validators().iter().map(|val| *val.pubkey()).collect()); + + 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 = Action::::ReportDoubleVote { + message1: consensus_message1, + message2: consensus_message2, + }; + 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()), + signer_index: 0, + }, + &|v| v, + &|v| v, + ); + assert!(result.is_ok()); + } + + #[test] + fn double_vote_verify_same_message() { + let block_hash = Some(H256::random()); + 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()); + 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()), + 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()); + let mut hash2 = Some(H256::random()); + while hash1 == hash2 { + hash2 = Some(H256::random()); + } + 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()), + signer_index: 0, + }, + &vote_step_twister, + &|v| v, + ); + let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Schnorr 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(); + } + 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()), + signer_index: 0, + }, + &|v| v, + &block_hash_twister, + ); + let expected_err = Err(SyntaxError::InvalidCustomAction(String::from("Schnorr signature verification fails"))); + assert_eq!(result, expected_err); + } } diff --git a/core/src/consensus/stake/mod.rs b/core/src/consensus/stake/mod.rs index fc4924a9f3..4c29576047 100644 --- a/core/src/consensus/stake/mod.rs +++ b/core/src/consensus/stake/mod.rs @@ -20,38 +20,52 @@ mod distribute; use std::collections::btree_map::BTreeMap; use std::collections::HashMap; +use std::marker::PhantomData; +use std::sync::{Arc, Weak}; +use crate::client::ConsensusClient; use ccrypto::Blake; use ckey::{public_to_address, recover, Address, Public, Signature}; +use consensus::vote_collector::Message; use cstate::{ActionHandler, StateResult, TopLevelState, TopState, TopStateView}; use ctypes::errors::{RuntimeError, SyntaxError}; use ctypes::util::unexpected::Mismatch; use ctypes::{CommonParams, Header}; +use parking_lot::RwLock; use primitives::{Bytes, H256}; use rlp::{Decodable, UntrustedRlp}; pub use self::action_data::{Banned, Validator, Validators}; -use self::action_data::{ - Candidates, Delegation, Deposit, IntermediateRewards, Jail, ReleaseResult, StakeAccount, Stakeholders, -}; +use self::action_data::{Candidates, Delegation, IntermediateRewards, Jail, ReleaseResult, StakeAccount, Stakeholders}; use self::actions::Action; pub use self::distribute::fee_distribute; +use super::ValidatorSet; const CUSTOM_ACTION_HANDLER_ID: u64 = 2; -pub struct Stake { +pub struct Stake { genesis_stakes: HashMap, + client: RwLock>>, + validators: RwLock>>, + phantom: PhantomData, } -impl Stake { - pub fn new(genesis_stakes: HashMap) -> Stake { +impl Stake { + pub fn new(genesis_stakes: HashMap) -> Stake { Stake { genesis_stakes, + phantom: PhantomData, + client: Default::default(), + validators: Default::default(), } } + pub fn register_resources(&self, client: Weak, validators: Weak) { + *self.client.write() = Some(Weak::clone(&client)); + *self.validators.write() = Some(Weak::clone(&validators)); + } } -impl ActionHandler for Stake { +impl ActionHandler for Stake { fn name(&self) -> &'static str { "stake handler" } @@ -81,7 +95,7 @@ impl ActionHandler for Stake { fee_payer: &Address, sender_public: &Public, ) -> StateResult<()> { - let action = Action::decode(&UntrustedRlp::new(bytes)).expect("Verification passed"); + let action = Action::::decode(&UntrustedRlp::new(bytes)).expect("Verification passed"); match action { Action::TransferCCS { address, @@ -116,14 +130,29 @@ impl ActionHandler for Stake { metadata_seq, params, signatures, - } => change_params(state, metadata_seq, *params, &signatures), + } => change_params::(state, metadata_seq, *params, &signatures), + Action::ReportDoubleVote { + message1, + .. + } => { + 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_public, public_to_address(&malicious_user_public)) + } } } fn verify(&self, bytes: &[u8], current_params: &CommonParams) -> Result<(), SyntaxError> { - let action = Action::decode(&UntrustedRlp::new(bytes)) + let action = Action::::decode(&UntrustedRlp::new(bytes)) .map_err(|err| SyntaxError::InvalidCustomAction(err.to_string()))?; - action.verify(current_params) + 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) } fn on_close_block( @@ -286,7 +315,7 @@ pub fn update_validator_weights(state: &mut TopLevelState, block_author: &Addres validators.save_to_state(state) } -fn change_params( +fn change_params( state: &mut TopLevelState, metadata_seq: u64, params: CommonParams, @@ -295,7 +324,7 @@ fn change_params( // Update state first because the signature validation is more expensive. state.update_params(metadata_seq, params)?; - let action = Action::ChangeParams { + let action = Action::::ChangeParams { metadata_seq, params: params.into(), signatures: vec![], @@ -413,13 +442,13 @@ pub fn jail(state: &mut TopLevelState, addresses: &[Address], custody_until: u64 Ok(()) } -#[allow(dead_code)] -pub fn ban(state: &mut TopLevelState, criminal: Address) -> StateResult { - // TODO: remove pending rewards. - // TODO: give criminal's deposits to the informant - // TODO: give criminal's rewards to diligent validators - let mut candidates = Candidates::load_from_state(state)?; +pub fn ban(state: &mut TopLevelState, informant: &Public, 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 = Validators::load_from_state(state)?; @@ -429,6 +458,10 @@ pub fn ban(state: &mut TopLevelState, criminal: Address) -> StateResult (_, Some(jailed)) => jailed.deposit, _ => 0, }; + // confiscate criminal's deposit and give the same deposit amount to the informant. + state.add_balance(&public_to_address(informant), deposit)?; + + jailed.remove(&criminal); banned.add(criminal); validators.remove(&criminal); @@ -440,7 +473,7 @@ pub fn ban(state: &mut TopLevelState, criminal: Address) -> StateResult // Revert delegations revert_delegations(state, &[criminal])?; - Ok(deposit) + Ok(()) } fn revert_delegations(state: &mut TopLevelState, reverted_delegatees: &[Address]) -> StateResult<()> { @@ -476,6 +509,7 @@ mod tests { use super::action_data::get_account_key; use super::*; + use consensus::solo::SoloMessage; use consensus::stake::action_data::{get_delegation_key, Candidate, Prisoner}; use cstate::tests::helpers; use cstate::TopStateView; @@ -499,7 +533,7 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(address1, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); @@ -524,7 +558,7 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(address1, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); @@ -552,7 +586,7 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(address1, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); @@ -584,12 +618,12 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 40, }; @@ -627,12 +661,12 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 100, }; @@ -671,11 +705,11 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 40, }; @@ -695,12 +729,12 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 200, }; @@ -720,18 +754,18 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 50, }; stake.execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - let action = Action::TransferCCS { + let action = Action::::TransferCCS { address: delegatee, quantity: 50, }; @@ -751,18 +785,18 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 50, }; stake.execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - let action = Action::TransferCCS { + let action = Action::::TransferCCS { address: delegatee, quantity: 100, }; @@ -782,19 +816,19 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 50, }; let result = stake.execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); assert!(result.is_ok()); - let action = Action::Revoke { + let action = Action::::Revoke { address: delegatee, quantity: 20, }; @@ -820,19 +854,19 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 50, }; let result = stake.execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); assert!(result.is_ok()); - let action = Action::Revoke { + let action = Action::::Revoke { address: delegatee, quantity: 70, }; @@ -858,19 +892,19 @@ mod tests { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegatee, 100); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); self_nominate(&mut state, &delegatee, &delegatee_pubkey, 0, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: delegatee, quantity: 50, }; let result = stake.execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey); assert!(result.is_ok()); - let action = Action::Revoke { + let action = Action::::Revoke { address: delegatee, quantity: 50, }; @@ -890,7 +924,7 @@ mod tests { let mut state = helpers::get_temp_state(); state.add_balance(&address, 1000).unwrap(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); // TODO: change with stake.execute() @@ -950,7 +984,7 @@ mod tests { let mut state = helpers::get_temp_state(); state.add_balance(&address, 1000).unwrap(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); // TODO: change with stake.execute() @@ -975,7 +1009,7 @@ mod tests { increase_term_id_until(&mut state, 29); state.add_balance(&address, 1000).unwrap(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); // TODO: change with stake.execute() @@ -1019,14 +1053,14 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); // TODO: change with stake.execute() self_nominate(&mut state, &address, &address_pubkey, 0, 0, 30, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address, quantity: 40, }; @@ -1057,7 +1091,7 @@ mod tests { let mut state = helpers::get_temp_state(); state.add_balance(&address, 1000).unwrap(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); // TODO: change with stake.execute() @@ -1095,7 +1129,7 @@ mod tests { let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); // TODO: change with stake.execute() @@ -1134,7 +1168,7 @@ mod tests { let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); // TODO: change with stake.execute() @@ -1188,7 +1222,7 @@ mod tests { let mut state = metadata_for_election(); state.add_balance(&address, 1000).unwrap(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); // TODO: change with stake.execute() @@ -1233,7 +1267,7 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); @@ -1246,7 +1280,7 @@ mod tests { jail(&mut state, &[address], custody_until, released_at).unwrap(); for current_term in 0..=released_at { - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address, quantity: 1, }; @@ -1256,7 +1290,7 @@ mod tests { on_term_close(&mut state, pseudo_term_to_block_num_calculator(current_term), &[]).unwrap(); } - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address, quantity: 1, }; @@ -1277,7 +1311,7 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); @@ -1288,7 +1322,7 @@ mod tests { let released_at = 20; self_nominate(&mut state, &address, &address_pubkey, deposit, 0, nominate_expire, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address, quantity: 40, }; @@ -1320,7 +1354,7 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); @@ -1330,7 +1364,7 @@ mod tests { let released_at = 20; self_nominate(&mut state, &address, &address_pubkey, 0, 0, nominate_expire, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address, quantity: 40, }; @@ -1363,6 +1397,7 @@ mod tests { #[test] fn test_ban() { + let informant_pubkey = Public::random(); let criminal_pubkey = Public::random(); let delegator_pubkey = Public::random(); let criminal = public_to_address(&criminal_pubkey); @@ -1374,19 +1409,19 @@ mod tests { let stake = { let mut genesis_stakes = HashMap::new(); genesis_stakes.insert(delegator, 100); - Stake::new(genesis_stakes) + Stake::::new(genesis_stakes) }; stake.init(&mut state).unwrap(); let deposit = 100; self_nominate(&mut state, &criminal, &criminal_pubkey, deposit, 0, 10, b"".to_vec()).unwrap(); - let action = Action::DelegateCCS { + let action = Action::::DelegateCCS { address: criminal, quantity: 40, }; stake.execute(&action.rlp_bytes(), &mut state, &delegator, &delegator_pubkey).unwrap(); - assert_eq!(Ok(deposit), ban(&mut state, criminal)); + assert_eq!(Ok(()), ban(&mut state, &informant_pubkey, criminal)); let banned = Banned::load_from_state(&state).unwrap(); assert!(banned.is_banned(&criminal)); @@ -1405,11 +1440,12 @@ mod tests { #[test] fn ban_should_remove_prisoner_from_jail() { + let informant_pubkey = Public::random(); let criminal_pubkey = Public::random(); let criminal = public_to_address(&criminal_pubkey); let mut state = helpers::get_temp_state(); - let stake = Stake::new(HashMap::new()); + let stake = Stake::::new(HashMap::new()); stake.init(&mut state).unwrap(); assert_eq!(Ok(()), state.add_balance(&criminal, 100)); @@ -1419,7 +1455,7 @@ mod tests { let released_at = 20; jail(&mut state, &[criminal], custody_until, released_at).unwrap(); - assert_eq!(Ok(deposit), ban(&mut state, criminal)); + assert_eq!(Ok(()), ban(&mut state, &informant_pubkey, 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/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 3e90d612b7..3f13e6d693 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -250,6 +250,7 @@ impl ConsensusEngine for Tendermint { fn register_client(&self, client: Weak) { *self.client.write() = Some(Weak::clone(&client)); + self.stake.register_resources(client, Arc::downgrade(&self.validators)); } fn is_proposal(&self, header: &Header) -> bool { diff --git a/core/src/consensus/tendermint/message.rs b/core/src/consensus/tendermint/message.rs index fb012a1a55..a0dd9b65ad 100644 --- a/core/src/consensus/tendermint/message.rs +++ b/core/src/consensus/tendermint/message.rs @@ -17,7 +17,7 @@ use std::cmp; use ccrypto::blake256; -use ckey::{verify_schnorr, Public, SchnorrSignature}; +use ckey::{verify_schnorr, Error as KeyError, Public, SchnorrSignature}; use ctypes::Header; use primitives::{Bytes, H256}; use rlp::{Decodable, DecoderError, Encodable, RlpStream, UntrustedRlp}; @@ -27,7 +27,6 @@ use super::super::validator_set::DynamicValidator; use super::super::vote_collector::Message; use super::super::BitSet; use super::{BlockHash, Height, Step, View}; -use crate::error::Error; /// Complete step of the consensus process. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, RlpDecodable, RlpEncodable)] @@ -295,11 +294,6 @@ impl ConsensusMessage { }, }) } - - pub fn verify(&self, signer_public: &Public) -> Result { - let vote_info = message_info_rlp(self.on.step, self.on.block_hash); - Ok(verify_schnorr(signer_public, &self.signature, &blake256(vote_info))?) - } } impl Message for ConsensusMessage { @@ -321,9 +315,18 @@ impl Message for ConsensusMessage { &self.on.step } + fn height(&self) -> u64 { + self.on.step.height + } + fn is_broadcastable(&self) -> bool { self.on.step.step.is_pre() } + + fn verify(&self, signer_public: &Public) -> Result { + let vote_info = message_info_rlp(self.on.step, self.on.block_hash); + verify_schnorr(signer_public, &self.signature, &blake256(vote_info)) + } } pub fn message_info_rlp(step: VoteStep, block_hash: Option) -> Bytes { diff --git a/core/src/consensus/tendermint/mod.rs b/core/src/consensus/tendermint/mod.rs index 13eb9cfe1b..8c324429cd 100644 --- a/core/src/consensus/tendermint/mod.rs +++ b/core/src/consensus/tendermint/mod.rs @@ -34,8 +34,9 @@ use parking_lot::RwLock; use primitives::H256; use self::chain_notify::TendermintChainNotify; +pub use self::message::{message_info_rlp, ConsensusMessage, VoteOn, VoteStep}; pub use self::params::{TendermintParams, TimeGapParams, TimeoutParams}; -use self::types::{Height, Step, View}; +pub use self::types::{Height, Step, View}; use super::{stake, ValidatorSet}; use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; @@ -69,6 +70,8 @@ pub struct Tendermint { machine: Arc, /// Action handlers for this consensus method action_handlers: Vec>, + /// stake object to register client data later + stake: Arc>, /// Chain notify chain_notify: Arc, has_signer: AtomicBool, @@ -87,13 +90,13 @@ impl Tendermint { /// Create a new instance of Tendermint engine pub fn new(our_params: TendermintParams, machine: CodeChainMachine) -> Arc { let validators = Arc::clone(&our_params.validators); - let stake = stake::Stake::new(our_params.genesis_stakes); + let stake = Arc::new(stake::Stake::::new(our_params.genesis_stakes)); let timeouts = our_params.timeouts; let machine = Arc::new(machine); let (join, external_params_initializer, extension_initializer, inner, quit_tendermint) = worker::spawn(our_params.validators); - let action_handlers: Vec> = vec![Arc::new(stake)]; + let action_handlers: Vec> = vec![stake.clone()]; let chain_notify = Arc::new(TendermintChainNotify::new(inner.clone())); Arc::new(Tendermint { @@ -108,6 +111,7 @@ impl Tendermint { block_reward: our_params.block_reward, machine, action_handlers, + stake, chain_notify, has_signer: false.into(), }) diff --git a/core/src/consensus/tendermint/worker.rs b/core/src/consensus/tendermint/worker.rs index b3c688f9c8..7e4815fe15 100644 --- a/core/src/consensus/tendermint/worker.rs +++ b/core/src/consensus/tendermint/worker.rs @@ -44,7 +44,7 @@ use crate::block::*; use crate::client::ConsensusClient; use crate::consensus::signer::EngineSigner; use crate::consensus::validator_set::{DynamicValidator, ValidatorSet}; -use crate::consensus::vote_collector::VoteCollector; +use crate::consensus::vote_collector::{Message, VoteCollector}; use crate::consensus::{EngineError, Seal}; use crate::encoded; use crate::error::{BlockError, Error}; diff --git a/core/src/consensus/vote_collector.rs b/core/src/consensus/vote_collector.rs index 8025d54436..110567c9b5 100644 --- a/core/src/consensus/vote_collector.rs +++ b/core/src/consensus/vote_collector.rs @@ -19,16 +19,18 @@ use std::fmt::Debug; use std::hash::Hash; use std::iter::Iterator; -use ckey::SchnorrSignature; +use ckey::{Error as KeyError, Public, SchnorrSignature}; use parking_lot::RwLock; use primitives::H256; -use rlp::{Encodable, RlpStream}; +use rlp::{Decodable, Encodable, RlpStream}; use super::BitSet; -pub trait Message: Clone + PartialEq + Eq + Hash + Encodable + Debug { +pub trait Message: Clone + PartialEq + Eq + Hash + Encodable + Decodable + Debug + Sync + Send { type Round: Clone + Copy + PartialEq + Eq + Hash + Default + Debug + Ord; + fn height(&self) -> u64; + fn signature(&self) -> SchnorrSignature; fn signer_index(&self) -> usize; @@ -38,6 +40,8 @@ pub trait Message: Clone + PartialEq + Eq + Hash + Encodable + Debug { fn round(&self) -> &Self::Round; fn is_broadcastable(&self) -> bool; + + fn verify(&self, signer_public: &Public) -> Result; } /// Storing all Proposals, Prevotes and Precommits. diff --git a/core/src/lib.rs b/core/src/lib.rs index 4a635673de..4bc39ca47e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -88,7 +88,7 @@ pub use crate::client::{ EngineClient, EngineInfo, ExecuteClient, ImportBlock, MiningBlockChainClient, Shard, StateInfo, TermInfo, TestBlockChainClient, TextClient, }; -pub use crate::consensus::{EngineType, TimeGapParams}; +pub use crate::consensus::{EngineType, Message, TimeGapParams}; pub use crate::db::{COL_STATE, NUM_COLUMNS}; pub use crate::error::{BlockImportError, Error, ImportError}; pub use crate::miner::{Miner, MinerOptions, MinerService, Stratum, StratumConfig, StratumError}; diff --git a/types/src/errors/runtime_error.rs b/types/src/errors/runtime_error.rs index d32b065bd5..5ba8a1dd17 100644 --- a/types/src/errors/runtime_error.rs +++ b/types/src/errors/runtime_error.rs @@ -117,6 +117,10 @@ pub enum Error { }, SignatureOfInvalidAccount(Address), InsufficientStakes(Mismatch), + InvalidValidatorIndex { + idx: usize, + parent_height: u64, + }, } const ERROR_ID_ASSET_NOT_FOUND: u8 = 1; @@ -151,6 +155,7 @@ const ERROR_ID_NON_ACTIVE_ACCOUNT: u8 = 30; const ERROR_ID_FAILED_TO_HANDLE_CUSTOM_ACTION: u8 = 31; const ERROR_ID_SIGNATURE_OF_INVALID_ACCOUNT: u8 = 32; const ERROR_ID_INSUFFICIENT_STAKES: u8 = 33; +const ERROR_ID_INVALID_VALIDATOR_INDEX: u8 = 34; struct RlpHelper; impl TaggedRlp for RlpHelper { @@ -190,6 +195,7 @@ impl TaggedRlp for RlpHelper { ERROR_ID_NON_ACTIVE_ACCOUNT => 3, ERROR_ID_SIGNATURE_OF_INVALID_ACCOUNT => 2, ERROR_ID_INSUFFICIENT_STAKES => 3, + ERROR_ID_INVALID_VALIDATOR_INDEX => 3, _ => return Err(DecoderError::Custom("Invalid RuntimeError")), }) } @@ -317,6 +323,10 @@ impl Encodable for Error { expected, found, }) => RlpHelper::new_tagged_list(s, ERROR_ID_INSUFFICIENT_STAKES).append(expected).append(found), + Error::InvalidValidatorIndex { + idx, + parent_height, + } => RlpHelper::new_tagged_list(s, ERROR_ID_INVALID_VALIDATOR_INDEX).append(idx).append(parent_height), }; } } @@ -405,6 +415,10 @@ impl Decodable for Error { expected: rlp.val_at(1)?, found: rlp.val_at(2)?, }), + ERROR_ID_INVALID_VALIDATOR_INDEX => Error::InvalidValidatorIndex { + idx: rlp.val_at(1)?, + parent_height: rlp.val_at(2)?, + }, _ => return Err(DecoderError::Custom("Invalid RuntimeError")), }; RlpHelper::check_size(rlp, tag)?; @@ -503,6 +517,9 @@ impl Display for Error { write!(f, "Signature of invalid account({}) received", address), Error::InsufficientStakes(mismatch) => write!(f, "Insufficient stakes: {}", mismatch), + Error::InvalidValidatorIndex { + idx, parent_height, + } => write!(f, "The validator index {} is invalid at the parent hash {}", idx, parent_height), } } }