// Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity Ethereum. // Parity Ethereum is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Ethereum 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 General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Ethereum. If not, see . //! Consensus tests for `PoA Clique Engine`, see http://eips.ethereum.org/EIPS/eip-225 for more information use std::sync::Arc; use std::collections::HashMap; use common_types::errors::{EthcoreError as Error, EngineError}; use ethcore::{ block::*, test_helpers::get_temp_state_db, }; use engine::Engine; use ethereum_types::{Address, H256}; use parity_crypto::publickey::{Secret, KeyPair}; use state_db::StateDB; use super::*; /// Possible signers pub const SIGNER_TAGS: [char; 6] = ['A', 'B', 'C', 'D', 'E', 'F']; /// Clique block types pub enum CliqueBlockType { /// Epoch transition block must contain list of signers Checkpoint, /// Block with no votes Empty, /// Vote Vote(VoteType), } /// Clique tester pub struct CliqueTester { /// Mocked Clique pub clique: Clique, /// Mocked genesis state pub genesis: Header, /// StateDB pub db: StateDB, /// List of signers pub signers: HashMap, } impl CliqueTester { /// Create a `Clique` tester with settings pub fn with(epoch: u64, period: u64, initial_signers: Vec) -> Self { assert_eq!(initial_signers.iter().all(|s| SIGNER_TAGS.contains(s)), true, "Not all the initial signers is in SIGNER_TAGS, possible keys are 'A' ..= 'F'"); let clique = Clique::with_test(epoch, period); let mut genesis = Header::default(); let mut signers = HashMap::new(); let call = |_a, _b| { unimplemented!("Clique doesn't use Engine::Call"); }; let mut extra_data = vec![0; VANITY_LENGTH]; for &signer in SIGNER_TAGS.iter() { let secret = Secret::from(H256::from_low_u64_be(signer as u64)); let keypair = KeyPair::from_secret(secret).unwrap(); if initial_signers.contains(&signer) { extra_data.extend(keypair.address().as_bytes()); } signers.insert(signer, keypair); } // append dummy signature extra_data.extend(std::iter::repeat(0).take(SIGNATURE_LENGTH)); genesis.set_extra_data(extra_data); genesis.set_gas_limit(U256::from(0xa00000)); genesis.set_difficulty(U256::from(1)); genesis.set_seal(util::null_seal()); clique.genesis_epoch_data(&genesis, &call).expect("Create genesis failed"); Self {clique, genesis, db: get_temp_state_db(), signers} } /// Get difficulty for a given block pub fn get_difficulty(&self, block_num: BlockNumber, header: &Header, signer: &Address) -> U256 { let state = self.clique.state(header).unwrap(); if state.is_inturn(block_num, signer) { DIFF_INTURN } else { DIFF_NOTURN } } /// Get the state of a given block // Note, this will read the cache and `will` not work with more than 128 blocks pub fn get_state_at_block(&self, hash: &H256) -> CliqueBlockState { self.clique.block_state_by_hash.write() .get_mut(hash) .expect("CliqueBlockState not found tested failed") .clone() } /// Get signers after a certain state // This is generally used to fetch the state after a test has been executed and checked against // the initial list of signers provided in the test pub fn clique_signers(&self, hash: &H256) -> impl Iterator { self.get_state_at_block(hash).signers().clone().into_iter() } /// Fetches all addresses at current `block` and converts them back to `tags (char)` and sorts them /// Addresses are supposed sorted based on address but these tests are using `tags` just for simplicity /// and the order is not important! pub fn into_tags>(&self, addr: T) -> Vec { let mut tags: Vec = addr.filter_map(|addr| { for (t, kp) in self.signers.iter() { if addr == kp.address() { return Some(*t) } } None }) .collect(); tags.sort(); tags } /// Create a new `Clique` block and import pub fn new_block_and_import( &self, block_type: CliqueBlockType, last_header: &Header, beneficary: Option
, signer: char, ) -> Result { let mut extra_data = vec![0; VANITY_LENGTH]; let mut seal = util::null_seal(); let last_hash = last_header.hash(); match block_type { CliqueBlockType::Checkpoint => { let signers = self.clique.state(&last_header).unwrap().signers().clone(); for signer in signers { extra_data.extend(signer.as_bytes()); } } CliqueBlockType::Vote(v) => seal = v.as_rlp(), CliqueBlockType::Empty => (), }; let db = self.db.boxed_clone(); let mut block = OpenBlock::new( &self.clique, Default::default(), false, db, &last_header.clone(), Arc::new(vec![last_hash]), beneficary.unwrap_or_default(), (3141562.into(), 31415620.into()), extra_data, false, ).unwrap(); { let difficulty = self.get_difficulty(block.header.number(), last_header, &self.signers[&signer].address()); let b = block.block_mut(); b.header.set_timestamp(last_header.timestamp() + self.clique.period); b.header.set_difficulty(difficulty); b.header.set_seal(seal); let sign = parity_crypto::publickey::sign(self.signers[&signer].secret(), &b.header.hash()).unwrap(); let mut extra_data = b.header.extra_data().clone(); extra_data.extend_from_slice(&*sign); b.header.set_extra_data(extra_data); } let current_header = &block.header; self.clique.verify_block_basic(current_header)?; self.clique.verify_block_family(current_header, &last_header)?; Ok(current_header.clone()) } } #[test] fn one_signer_with_no_votes() { let tester = CliqueTester::with(10, 1, vec!['A']); let empty_block = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&empty_block.hash())); assert_eq!(&tags, &['A']); } #[test] fn one_signer_two_votes() { let tester = CliqueTester::with(10, 1, vec!['A']); // Add a vote for `B` signed by `A` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis, Some(tester.signers[&'B'].address()), 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&vote.hash())); assert_eq!(&tags, &['A', 'B']); // Add a empty block signed by `B` let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap(); // Add vote for `C` signed by A but should not be accepted let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty, Some(tester.signers[&'C'].address()), 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&vote.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn two_signers_six_votes_deny_last() { let tester = CliqueTester::with(10, 1, vec!['A', 'B']); let mut prev_header = tester.genesis.clone(); // Add two votes for `C` signed by `A` and `B` for &signer in SIGNER_TAGS.iter().take(2) { let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header, Some(tester.signers[&'C'].address()), signer).unwrap(); prev_header = vote.clone(); } // Add two votes for `D` signed by `A` and `B` for &signer in SIGNER_TAGS.iter().take(2) { let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header, Some(tester.signers[&'D'].address()), signer).unwrap(); prev_header = vote.clone(); } // Add a empty block signed by `C` let empty = tester.new_block_and_import(CliqueBlockType::Empty, &prev_header, None, 'C').unwrap(); prev_header = empty.clone(); // Add two votes for `E` signed by `A` and `B` for &signer in SIGNER_TAGS.iter().take(2) { let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header, Some(tester.signers[&'E'].address()), signer).unwrap(); prev_header = vote.clone(); } let tags = tester.into_tags(tester.clique_signers(&prev_header.hash())); assert_eq!(&tags, &['A', 'B', 'C', 'D']); } #[test] fn one_signer_dropping_itself() { let tester = CliqueTester::with(10, 1, vec!['A']); let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'A'].address()), 'A').unwrap(); let signers = tester.clique_signers(&vote.hash()); assert!(signers.count() == 0); } #[test] fn two_signers_one_remove_vote_no_consensus() { let tester = CliqueTester::with(10, 1, vec!['A', 'B']); let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'B'].address()), 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&vote.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn two_signers_consensus_remove_b() { let tester = CliqueTester::with(10, 1, vec!['A', 'B']); let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'B'].address()), 'A').unwrap(); let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote, Some(tester.signers[&'B'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&second_vote.hash())); assert_eq!(&tags, &['A']); } #[test] fn three_signers_consensus_remove_c() { let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C']); let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote, Some(tester.signers[&'C'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&second_vote.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn four_signers_half_no_consensus() { let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C', 'D']); let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote, Some(tester.signers[&'C'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&second_vote.hash())); assert_eq!(&tags, &['A', 'B', 'C', 'D']); } #[test] fn four_signers_three_consensus_rm() { let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C', 'D']); let mut prev_header = tester.genesis.clone(); // Three votes to remove `D` signed by ['A', 'B', 'C'] for signer in SIGNER_TAGS.iter().take(3) { let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header, Some(tester.signers[&'D'].address()), *signer).unwrap(); prev_header = vote.clone(); } let tags = tester.into_tags(tester.clique_signers(&prev_header.hash())); assert_eq!(&tags, &['A', 'B', 'C']); } #[test] fn vote_add_only_counted_once_per_signer() { let tester = CliqueTester::with(10, 1, vec!['A', 'B']); // Add a vote for `C` signed by `A` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Empty block signed by B` let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap(); // Add a vote for `C` signed by `A` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Empty block signed by `B` let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap(); // Add a vote for `C` signed by `A` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty, Some(tester.signers[&'C'].address()), 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&vote.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn vote_add_concurrently_is_permitted() { let tester = CliqueTester::with(10, 1, vec!['A', 'B']); // Add a vote for `C` signed by `A` let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Empty block signed by `B` let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap(); // Add a vote for `D` signed by `A` let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b, Some(tester.signers[&'D'].address()), 'A').unwrap(); // Empty block signed by `B` let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap(); // Empty block signed by `A` let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap(); // Add a vote for `D` signed by `B` let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b, Some(tester.signers[&'D'].address()), 'B').unwrap(); // Empty block signed by `A` let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap(); // Add a vote for `C` signed by `B` let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b, Some(tester.signers[&'C'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&b.hash())); assert_eq!(&tags, &['A', 'B', 'C', 'D']); } #[test] fn vote_rm_only_counted_once_per_signer() { let tester = CliqueTester::with(10, 1, vec!['A', 'B']); let mut prev_header = tester.genesis.clone(); for _ in 0..2 { // Vote to remove `B` signed by `A` let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header, Some(tester.signers[&'B'].address()), 'A').unwrap(); // Empty block signed by `B` let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap(); prev_header = b.clone(); } // Add a vote for `B` signed by `A` let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header, Some(tester.signers[&'B'].address()), 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&b.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn vote_rm_concurrently_is_permitted() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']); // Add a vote for `C` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Empty block signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Add a vote for `D` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'A').unwrap(); // Empty block signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Empty block signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap(); // Add a vote for `D` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'B').unwrap(); // Add a vote for `D` signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'C').unwrap(); // Empty block signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap(); // Add a vote for `C` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn vote_to_rm_are_immediate_and_ensure_votes_are_rm() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C']); // Vote to remove `B` signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'B'].address()), 'C').unwrap(); // Vote to remove `C` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Vote to remove `C` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); // Vote to remove `B` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'B'].address()), 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn vote_to_rm_are_immediate_and_votes_should_be_dropped_from_kicked_signer() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C']); // Vote to add `D` signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis, Some(tester.signers[&'D'].address()), 'C').unwrap(); // Vote to remove `C` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Vote to remove `C` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); // Vote to add `D` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block, Some(tester.signers[&'D'].address()), 'A').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn cascading_not_allowed() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']); // Vote against `C` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Empty block signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Vote against `D` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'A').unwrap(); // Vote against `C` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Empty block signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap(); // Vote against `D` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'B').unwrap(); // Vote against `D` signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'C').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B', 'C']); } #[test] fn consensus_out_of_bounds_consensus_execute_on_touch() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']); // Vote against `C` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Empty block signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Vote against `D` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'A').unwrap(); // Vote against `C` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Empty block signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap(); // Vote against `D` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'B').unwrap(); // Vote against `D` signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'C').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B', 'C'], "D should have been removed after 3/4 remove votes"); // Empty block signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap(); // Vote for `C` signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block, Some(tester.signers[&'C'].address()), 'C').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B']); } #[test] fn consensus_out_of_bounds_first_touch() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']); // Vote against `C` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); // Empty block signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Vote against `D` signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'A').unwrap(); // Vote against `C` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); // Empty block signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap(); // Empty block signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap(); // Vote against `D` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'B').unwrap(); // Vote against `D` signed by `C` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block, Some(tester.signers[&'D'].address()), 'C').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B', 'C']); // Empty block signed by `A` let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap(); // Vote for `C` signed by `B` let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B', 'C']); } #[test] fn pending_votes_doesnt_survive_authorization_changes() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D', 'E']); let mut prev_header = tester.genesis.clone(); // Vote for `F` from [`A`, `B`, `C`] for sign in SIGNER_TAGS.iter().take(3) { let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header, Some(tester.signers[&'F'].address()), *sign).unwrap(); prev_header = block.clone(); } let tags = tester.into_tags(tester.clique_signers(&prev_header.hash())); assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E', 'F'], "F should have been added"); // Vote against `F` from [`D`, `E`, `B`, `C`] for sign in SIGNER_TAGS.iter().skip(3).chain(SIGNER_TAGS.iter().skip(1).take(2)) { let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header, Some(tester.signers[&'F'].address()), *sign).unwrap(); prev_header = block.clone(); } let tags = tester.into_tags(tester.clique_signers(&prev_header.hash())); assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E'], "F should have been removed"); // Vote for `F` from [`D`, `E`] for sign in SIGNER_TAGS.iter().skip(3).take(2) { let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header, Some(tester.signers[&'F'].address()), *sign).unwrap(); prev_header = block.clone(); } // Vote against `A` from [`B`, `C`, `D`] for sign in SIGNER_TAGS.iter().skip(1).take(3) { let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header, Some(tester.signers[&'A'].address()), *sign).unwrap(); prev_header = block.clone(); } let tags = tester.into_tags(tester.clique_signers(&prev_header.hash())); assert_eq!(&tags, &['B', 'C', 'D', 'E'], "A should have been removed"); let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header, Some(tester.signers[&'F'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['B', 'C', 'D', 'E', 'F'], "F should have been added again"); } #[test] fn epoch_transition_reset_all_votes() { let tester = CliqueTester::with(3, 1, vec!['A', 'B']); let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis, Some(tester.signers[&'C'].address()), 'A').unwrap(); let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap(); let block = tester.new_block_and_import(CliqueBlockType::Checkpoint, &block, None, 'A').unwrap(); let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block, Some(tester.signers[&'C'].address()), 'B').unwrap(); let tags = tester.into_tags(tester.clique_signers(&block.hash())); assert_eq!(&tags, &['A', 'B'], "Votes should have been reset after checkpoint"); } #[test] fn unauthorized_signer_should_not_be_able_to_sign_block() { let tester = CliqueTester::with(3, 1, vec!['A']); let err = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'B').unwrap_err(); match err { Error::Engine(EngineError::NotAuthorized(_)) => (), _ => assert!(true == false, "Wrong error kind"), } } #[test] fn signer_should_not_be_able_to_sign_two_consequtive_blocks() { let tester = CliqueTester::with(3, 1, vec!['A', 'B']); let b = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap(); let err = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap_err(); match err { Error::Engine(EngineError::CliqueTooRecentlySigned(_)) => (), _ => assert!(true == false, "Wrong error kind"), } } #[test] fn recent_signers_should_not_reset_on_checkpoint() { let tester = CliqueTester::with(3, 1, vec!['A', 'B', 'C']); let block = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap(); let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap(); let block = tester.new_block_and_import(CliqueBlockType::Checkpoint, &block, None, 'A').unwrap(); let err = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap_err(); match err { Error::Engine(EngineError::CliqueTooRecentlySigned(_)) => (), _ => assert!(true == false, "Wrong error kind"), } } // Not part of http://eips.ethereum.org/EIPS/eip-225 #[test] fn bonus_consensus_should_keep_track_of_votes_before_latest_per_signer() { let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']); // Add a vote for `E` signed by `A` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis, Some(tester.signers[&'E'].address()), 'A').unwrap(); // Empty block signed by `B` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap(); // Empty block signed by `C` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap(); // Empty block signed by `D` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap(); // Add a vote for `F` signed by `A` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote, Some(tester.signers[&'F'].address()), 'A').unwrap(); // Empty block signed by `C` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap(); // Empty block signed by `D` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap(); // Add a vote for `E` signed by `B` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote, Some(tester.signers[&'E'].address()), 'B').unwrap(); // Empty block signed by `A` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'A').unwrap(); // Empty block signed by `C` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap(); // Empty block signed by `D` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap(); // Add a vote for `F` signed by `B` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote, Some(tester.signers[&'F'].address()), 'B').unwrap(); // Empty block signed by A` let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'A').unwrap(); // Add a vote for `E` signed by `C` let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote, Some(tester.signers[&'E'].address()), 'C').unwrap(); let tags = tester.into_tags(tester.clique_signers(&vote.hash())); assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E']); }