From 21adcfeaa76a786f74bb4e0014c810f34471482e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 May 2020 13:43:26 +0300 Subject: [PATCH 01/14] removeInMemoryStorage + extract Kovan stuff to runtime --- bin/node/node/src/chain_spec.rs | 81 +---- bin/node/runtime/src/kovan.rs | 191 +++++++++++ bin/node/runtime/src/lib.rs | 9 + modules/ethereum/src/finality.rs | 186 +++++----- modules/ethereum/src/import.rs | 339 ++++++++++--------- modules/ethereum/src/lib.rs | 485 ++------------------------- modules/ethereum/src/mock.rs | 168 ++++++++++ modules/ethereum/src/validators.rs | 3 +- modules/ethereum/src/verification.rs | 195 +++++++---- primitives/ethereum-poa/src/lib.rs | 1 - 10 files changed, 781 insertions(+), 877 deletions(-) create mode 100644 bin/node/runtime/src/kovan.rs create mode 100644 modules/ethereum/src/mock.rs diff --git a/bin/node/node/src/chain_spec.rs b/bin/node/node/src/chain_spec.rs index 11a31c7ae1c..7127ee8d771 100644 --- a/bin/node/node/src/chain_spec.rs +++ b/bin/node/node/src/chain_spec.rs @@ -169,85 +169,8 @@ fn testnet_genesis( fn load_kovan_config() -> Option { Some(BridgeEthPoAConfig { - initial_header: sp_bridge_eth_poa::Header { - parent_hash: Default::default(), - timestamp: 0, - number: 0, - author: Default::default(), - transactions_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - .parse() - .unwrap(), - uncles_hash: "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - .parse() - .unwrap(), - extra_data: vec![], - state_root: "2480155b48a1cea17d67dbfdfaafe821c1d19cdd478c5358e8ec56dec24502b2" - .parse() - .unwrap(), - receipts_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - .parse() - .unwrap(), - log_bloom: Default::default(), - gas_used: Default::default(), - gas_limit: 6000000.into(), - difficulty: 131072.into(), - seal: vec![ - vec![128].into(), - vec![ - 184, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, - ] - .into(), - ], - }, + initial_header: bridge_node_runtime::kovan::kovan_genesis_header(), initial_difficulty: 0.into(), - initial_validators: vec![ - [ - 0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58, 0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, 0x5B, - 0xAd, 0xc0, 0xED, - ] - .into(), - [ - 0x00, 0x42, 0x7f, 0xea, 0xe2, 0x41, 0x9c, 0x15, 0xb8, 0x9d, 0x1c, 0x21, 0xaf, 0x10, 0xd1, 0xb6, 0x65, - 0x0a, 0x4d, 0x3d, - ] - .into(), - [ - 0x4E, 0xd9, 0xB0, 0x8e, 0x63, 0x54, 0xC7, 0x0f, 0xE6, 0xF8, 0xCB, 0x04, 0x11, 0xb0, 0xd3, 0x24, 0x6b, - 0x42, 0x4d, 0x6c, - ] - .into(), - [ - 0x00, 0x20, 0xee, 0x4B, 0xe0, 0xe2, 0x02, 0x7d, 0x76, 0x60, 0x3c, 0xB7, 0x51, 0xeE, 0x06, 0x95, 0x19, - 0xbA, 0x81, 0xA1, - ] - .into(), - [ - 0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5, 0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, 0x3a, - 0xfd, 0x03, 0x2d, - ] - .into(), - [ - 0x00, 0x77, 0x33, 0xa1, 0xFE, 0x69, 0xCF, 0x3f, 0x2C, 0xF9, 0x89, 0xF8, 0x1C, 0x7b, 0x4c, 0xAc, 0x16, - 0x93, 0x38, 0x7A, - ] - .into(), - [ - 0x00, 0xE6, 0xd2, 0xb9, 0x31, 0xF5, 0x5a, 0x3f, 0x17, 0x01, 0xc7, 0x38, 0x9d, 0x59, 0x2a, 0x77, 0x78, - 0x89, 0x78, 0x79, - ] - .into(), - [ - 0x00, 0xe4, 0xa1, 0x06, 0x50, 0xe5, 0xa6, 0xD6, 0x00, 0x1C, 0x38, 0xff, 0x8E, 0x64, 0xF9, 0x70, 0x16, - 0xa1, 0x64, 0x5c, - ] - .into(), - [ - 0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c, 0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, 0x12, - 0x31, 0x94, 0xde, - ] - .into(), - ], + initial_validators: bridge_node_runtime::kovan::kovan_genesis_validators(), }) } diff --git a/bin/node/runtime/src/kovan.rs b/bin/node/runtime/src/kovan.rs new file mode 100644 index 00000000000..560673035c8 --- /dev/null +++ b/bin/node/runtime/src/kovan.rs @@ -0,0 +1,191 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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 Bridges Common 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 Bridges Common. If not, see . + +use pallet_bridge_eth_poa::{AuraConfiguration, ValidatorsConfiguration, ValidatorsSource}; +use sp_bridge_eth_poa::{Address, Header, U256}; +use sp_std::prelude::*; + +/// Aura engine configuration for Kovan chain. +pub fn kovan_aura_configuration() -> AuraConfiguration { + AuraConfiguration { + empty_steps_transition: u64::max_value(), + strict_empty_steps_transition: 0, + validate_step_transition: 0x16e360, + validate_score_transition: 0x41a3c4, + two_thirds_majority_transition: u64::max_value(), + min_gas_limit: 0x1388.into(), + max_gas_limit: U256::max_value(), + maximum_extra_data_size: 0x20, + } +} + +/// Validators configuration for Kovan chain. +pub fn kovan_validators_configuration() -> ValidatorsConfiguration { + ValidatorsConfiguration::Multi(vec![ + (0, ValidatorsSource::List(kovan_genesis_validators())), + ( + 10960440, + ValidatorsSource::List(vec![ + [ + 0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58, 0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, + 0x5B, 0xAd, 0xc0, 0xED, + ] + .into(), + [ + 0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5, 0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, + 0x3a, 0xfd, 0x03, 0x2d, + ] + .into(), + [ + 0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c, 0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, + 0x12, 0x31, 0x94, 0xde, + ] + .into(), + ]), + ), + ( + 10960500, + ValidatorsSource::Contract( + [ + 0xaE, 0x71, 0x80, 0x7C, 0x1B, 0x0a, 0x09, 0x3c, 0xB1, 0x54, 0x7b, 0x68, 0x2D, 0xC7, 0x83, 0x16, + 0xD9, 0x45, 0xc9, 0xB8, + ] + .into(), + vec![ + [ + 0xd0, 0x5f, 0x74, 0x78, 0xc6, 0xaa, 0x10, 0x78, 0x12, 0x58, 0xc5, 0xcc, 0x8b, 0x4f, 0x38, 0x5f, + 0xc8, 0xfa, 0x98, 0x9c, + ] + .into(), + [ + 0x03, 0x80, 0x1e, 0xfb, 0x0e, 0xfe, 0x2a, 0x25, 0xed, 0xe5, 0xdd, 0x3a, 0x00, 0x3a, 0xe8, 0x80, + 0xc0, 0x29, 0x2e, 0x4d, + ] + .into(), + [ + 0xa4, 0xdf, 0x25, 0x5e, 0xcf, 0x08, 0xbb, 0xf2, 0xc2, 0x80, 0x55, 0xc6, 0x52, 0x25, 0xc9, 0xa9, + 0x84, 0x7a, 0xbd, 0x94, + ] + .into(), + [ + 0x59, 0x6e, 0x82, 0x21, 0xa3, 0x0b, 0xfe, 0x6e, 0x7e, 0xff, 0x67, 0xfe, 0xe6, 0x64, 0xa0, 0x1c, + 0x73, 0xba, 0x3c, 0x56, + ] + .into(), + [ + 0xfa, 0xad, 0xfa, 0xce, 0x3f, 0xbd, 0x81, 0xce, 0x37, 0xb0, 0xe1, 0x9c, 0x0b, 0x65, 0xff, 0x42, + 0x34, 0x14, 0x81, 0x32, + ] + .into(), + ], + ), + ), + ]) +} + +/// Genesis validators set of Kovan chain. +pub fn kovan_genesis_validators() -> Vec
{ + vec![ + [ + 0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58, 0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, 0x5B, 0xAd, + 0xc0, 0xED, + ] + .into(), + [ + 0x00, 0x42, 0x7f, 0xea, 0xe2, 0x41, 0x9c, 0x15, 0xb8, 0x9d, 0x1c, 0x21, 0xaf, 0x10, 0xd1, 0xb6, 0x65, 0x0a, + 0x4d, 0x3d, + ] + .into(), + [ + 0x4E, 0xd9, 0xB0, 0x8e, 0x63, 0x54, 0xC7, 0x0f, 0xE6, 0xF8, 0xCB, 0x04, 0x11, 0xb0, 0xd3, 0x24, 0x6b, 0x42, + 0x4d, 0x6c, + ] + .into(), + [ + 0x00, 0x20, 0xee, 0x4B, 0xe0, 0xe2, 0x02, 0x7d, 0x76, 0x60, 0x3c, 0xB7, 0x51, 0xeE, 0x06, 0x95, 0x19, 0xbA, + 0x81, 0xA1, + ] + .into(), + [ + 0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5, 0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, 0x3a, 0xfd, + 0x03, 0x2d, + ] + .into(), + [ + 0x00, 0x77, 0x33, 0xa1, 0xFE, 0x69, 0xCF, 0x3f, 0x2C, 0xF9, 0x89, 0xF8, 0x1C, 0x7b, 0x4c, 0xAc, 0x16, 0x93, + 0x38, 0x7A, + ] + .into(), + [ + 0x00, 0xE6, 0xd2, 0xb9, 0x31, 0xF5, 0x5a, 0x3f, 0x17, 0x01, 0xc7, 0x38, 0x9d, 0x59, 0x2a, 0x77, 0x78, 0x89, + 0x78, 0x79, + ] + .into(), + [ + 0x00, 0xe4, 0xa1, 0x06, 0x50, 0xe5, 0xa6, 0xD6, 0x00, 0x1C, 0x38, 0xff, 0x8E, 0x64, 0xF9, 0x70, 0x16, 0xa1, + 0x64, 0x5c, + ] + .into(), + [ + 0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c, 0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, 0x12, 0x31, + 0x94, 0xde, + ] + .into(), + ] +} + +/// Genesis header of the Kovan chain. +pub fn kovan_genesis_header() -> Header { + Header { + parent_hash: Default::default(), + timestamp: 0, + number: 0, + author: Default::default(), + transactions_root: [ + 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, 0x5b, 0x48, + 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, + ] + .into(), + uncles_hash: [ + 0x1d, 0xcc, 0x4d, 0xe8, 0xde, 0xc7, 0x5d, 0x7a, 0xab, 0x85, 0xb5, 0x67, 0xb6, 0xcc, 0xd4, 0x1a, 0xd3, 0x12, + 0x45, 0x1b, 0x94, 0x8a, 0x74, 0x13, 0xf0, 0xa1, 0x42, 0xfd, 0x40, 0xd4, 0x93, 0x47, + ] + .into(), + extra_data: vec![], + state_root: [ + 0x24, 0x80, 0x15, 0x5b, 0x48, 0xa1, 0xce, 0xa1, 0x7d, 0x67, 0xdb, 0xfd, 0xfa, 0xaf, 0xe8, 0x21, 0xc1, 0xd1, + 0x9c, 0xdd, 0x47, 0x8c, 0x53, 0x58, 0xe8, 0xec, 0x56, 0xde, 0xc2, 0x45, 0x02, 0xb2, + ] + .into(), + receipts_root: [ + 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, 0x5b, 0x48, + 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, + ] + .into(), + log_bloom: Default::default(), + gas_used: Default::default(), + gas_limit: 6000000.into(), + difficulty: 131072.into(), + seal: vec![ + vec![128].into(), + vec![ + 184, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + .into(), + ], + } +} diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 45db36476e3..ff4d796cf93 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -24,6 +24,8 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +pub mod kovan; + use codec::{Decode, Encode}; use pallet_grandpa::fg_primitives; use pallet_grandpa::AuthorityList as GrandpaAuthorityList; @@ -204,7 +206,14 @@ impl pallet_aura::Trait for Runtime { type AuthorityId = AuraId; } +parameter_types! { + pub const KovanAuraConfiguration: pallet_bridge_eth_poa::AuraConfiguration = kovan::kovan_aura_configuration(); + pub const KovanValidatorsConfiguration: pallet_bridge_eth_poa::ValidatorsConfiguration = kovan::kovan_validators_configuration(); +} + impl pallet_bridge_eth_poa::Trait for Runtime { + type AuraConfiguration = KovanAuraConfiguration; + type ValidatorsConfiguration = KovanValidatorsConfiguration; type OnHeadersSubmitted = (); } diff --git a/modules/ethereum/src/finality.rs b/modules/ethereum/src/finality.rs index 1ee9f356028..676108a30dc 100644 --- a/modules/ethereum/src/finality.rs +++ b/modules/ethereum/src/finality.rs @@ -214,106 +214,110 @@ pub(crate) fn ancestry<'a, S: Storage>( #[cfg(test)] mod tests { use super::*; - use crate::tests::{genesis, validator, validators_addresses, InMemoryStorage}; - use crate::HeaderToImport; + use crate::mock::{custom_test_ext, genesis, validator, validators_addresses, TestRuntime}; + use crate::{BridgeStorage, HeaderToImport}; #[test] fn verifies_header_author() { - assert_eq!( - finalize_blocks( - &InMemoryStorage::new(genesis(), validators_addresses(5)), - &Default::default(), - (&Default::default(), &[]), - &Default::default(), - None, - &Header::default(), - 0, - ), - Err(Error::NotValidator), - ); + custom_test_ext(genesis(), validators_addresses(5)).execute_with(|| { + assert_eq!( + finalize_blocks( + &BridgeStorage::::new(), + &Default::default(), + (&Default::default(), &[]), + &Default::default(), + None, + &Header::default(), + 0, + ), + Err(Error::NotValidator), + ); + }); } #[test] fn prepares_votes() { - // let's say we have 5 validators (we need 'votes' from 3 validators to achieve - // finality) - let mut storage = InMemoryStorage::new(genesis(), validators_addresses(5)); + custom_test_ext(genesis(), validators_addresses(5)).execute_with(|| { + // let's say we have 5 validators (we need 'votes' from 3 validators to achieve + // finality) + let mut storage = BridgeStorage::::new(); - // when header#1 is inserted, nothing is finalized (1 vote) - let header1 = Header { - author: validator(0).address().as_fixed_bytes().into(), - parent_hash: genesis().hash(), - number: 1, - ..Default::default() - }; - let hash1 = header1.hash(); - let mut header_to_import = HeaderToImport { - context: storage.import_context(None, &genesis().hash()).unwrap(), - is_best: true, - hash: hash1, - header: header1, - total_difficulty: 0.into(), - enacted_change: None, - scheduled_change: None, - }; - assert_eq!( - finalize_blocks( - &storage, - &Default::default(), - (&Default::default(), &validators_addresses(5)), - &hash1, - None, - &header_to_import.header, - u64::max_value(), - ), - Ok(Vec::new()), - ); - storage.insert_header(header_to_import.clone()); + // when header#1 is inserted, nothing is finalized (1 vote) + let header1 = Header { + author: validator(0).address().as_fixed_bytes().into(), + parent_hash: genesis().hash(), + number: 1, + ..Default::default() + }; + let hash1 = header1.hash(); + let mut header_to_import = HeaderToImport { + context: storage.import_context(None, &genesis().hash()).unwrap(), + is_best: true, + hash: hash1, + header: header1, + total_difficulty: 0.into(), + enacted_change: None, + scheduled_change: None, + }; + assert_eq!( + finalize_blocks( + &storage, + &Default::default(), + (&Default::default(), &validators_addresses(5)), + &hash1, + None, + &header_to_import.header, + u64::max_value(), + ), + Ok(Vec::new()), + ); + storage.insert_header(header_to_import.clone()); - // when header#2 is inserted, nothing is finalized (2 votes) - header_to_import.header = Header { - author: validator(1).address().as_fixed_bytes().into(), - parent_hash: hash1, - number: 2, - ..Default::default() - }; - header_to_import.hash = header_to_import.header.hash(); - let hash2 = header_to_import.header.hash(); - assert_eq!( - finalize_blocks( - &storage, - &Default::default(), - (&Default::default(), &validators_addresses(5)), - &hash2, - None, - &header_to_import.header, - u64::max_value(), - ), - Ok(Vec::new()), - ); - storage.insert_header(header_to_import.clone()); + // when header#2 is inserted, nothing is finalized (2 votes) + header_to_import.header = Header { + author: validator(1).address().as_fixed_bytes().into(), + parent_hash: hash1, + number: 2, + ..Default::default() + }; + header_to_import.hash = header_to_import.header.hash(); + let hash2 = header_to_import.header.hash(); + assert_eq!( + finalize_blocks( + &storage, + &Default::default(), + (&Default::default(), &validators_addresses(5)), + &hash2, + None, + &header_to_import.header, + u64::max_value(), + ), + Ok(Vec::new()), + ); + storage.insert_header(header_to_import.clone()); - // when header#3 is inserted, header#1 is finalized (3 votes) - header_to_import.header = Header { - author: validator(2).address().as_fixed_bytes().into(), - parent_hash: hash2, - number: 3, - ..Default::default() - }; - header_to_import.hash = header_to_import.header.hash(); - let hash3 = header_to_import.header.hash(); - assert_eq!( - finalize_blocks( - &storage, - &Default::default(), - (&Default::default(), &validators_addresses(5)), - &hash3, - None, - &header_to_import.header, - u64::max_value(), - ), - Ok(vec![(1, hash1, None)]), - ); - storage.insert_header(header_to_import); + // when header#3 is inserted, header#1 is finalized (3 votes) + header_to_import.header = Header { + author: validator(2).address().as_fixed_bytes().into(), + parent_hash: hash2, + number: 3, + ..Default::default() + }; + header_to_import.hash = header_to_import.header.hash(); + let hash3 = header_to_import.header.hash(); + assert_eq!( + finalize_blocks( + &storage, + &Default::default(), + (&Default::default(), &validators_addresses(5)), + &hash3, + None, + &header_to_import.header, + u64::max_value(), + ), + Ok(vec![(1, hash1, None)]), + ); + storage.insert_header(header_to_import); + }); } } diff --git a/modules/ethereum/src/import.rs b/modules/ethereum/src/import.rs index 20a5d10e457..8fc6644ead6 100644 --- a/modules/ethereum/src/import.rs +++ b/modules/ethereum/src/import.rs @@ -166,214 +166,221 @@ pub fn header_import_requires_receipts( #[cfg(test)] mod tests { use super::*; - use crate::tests::{ - block_i, custom_block_i, genesis, signed_header, validator, validators_addresses, InMemoryStorage, + use crate::mock::{ + block_i, custom_block_i, custom_test_ext, genesis, signed_header, test_aura_config, test_validators_config, + validator, validators, validators_addresses, TestRuntime, }; use crate::validators::ValidatorsSource; - use crate::{kovan_aura_config, kovan_validators_config}; + use crate::{BridgeStorage, Headers, OldestUnprunedBlock}; + use frame_support::{StorageMap, StorageValue}; #[test] fn rejects_finalized_block_competitors() { - let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3)); - storage.finalize_headers(Some((100, Default::default())), None); - assert_eq!( - import_header( - &mut storage, - &kovan_aura_config(), - &kovan_validators_config(), - PRUNE_DEPTH, - None, - Default::default(), - None, - ), - Err(Error::AncientHeader), - ); + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let mut storage = BridgeStorage::::new(); + storage.finalize_headers(Some((100, Default::default())), None); + assert_eq!( + import_header( + &mut storage, + &test_aura_config(), + &test_validators_config(), + PRUNE_DEPTH, + None, + Default::default(), + None, + ), + Err(Error::AncientHeader), + ); + }); } #[test] fn rejects_known_header() { - let validators = (0..3).map(|i| validator(i as u8)).collect::>(); - let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3)); - let block = block_i(&storage, 1, &validators); - assert_eq!( - import_header( - &mut storage, - &kovan_aura_config(), - &kovan_validators_config(), - PRUNE_DEPTH, - None, - block.clone(), - None, - ) - .map(|_| ()), - Ok(()), - ); - assert_eq!( - import_header( - &mut storage, - &kovan_aura_config(), - &kovan_validators_config(), - PRUNE_DEPTH, - None, - block, - None, - ) - .map(|_| ()), - Err(Error::KnownHeader), - ); + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let validators = validators(3); + let mut storage = BridgeStorage::::new(); + let block = block_i(1, &validators); + assert_eq!( + import_header( + &mut storage, + &test_aura_config(), + &test_validators_config(), + PRUNE_DEPTH, + None, + block.clone(), + None, + ) + .map(|_| ()), + Ok(()), + ); + assert_eq!( + import_header( + &mut storage, + &test_aura_config(), + &test_validators_config(), + PRUNE_DEPTH, + None, + block, + None, + ) + .map(|_| ()), + Err(Error::KnownHeader), + ); + }); } #[test] fn import_header_works() { - let validators_config = ValidatorsConfiguration::Multi(vec![ - (0, ValidatorsSource::List(validators_addresses(3))), - (1, ValidatorsSource::List(validators_addresses(2))), - ]); - let validators = (0..3).map(|i| validator(i as u8)).collect::>(); - let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3)); - let header = block_i(&storage, 1, &validators); - let hash = header.hash(); - assert_eq!( - import_header( - &mut storage, - &kovan_aura_config(), - &validators_config, - PRUNE_DEPTH, - None, - header, - None - ) - .map(|_| ()), - Ok(()), - ); + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let validators_config = ValidatorsConfiguration::Multi(vec![ + (0, ValidatorsSource::List(validators_addresses(3))), + (1, ValidatorsSource::List(validators_addresses(2))), + ]); + let validators = validators(3); + let mut storage = BridgeStorage::::new(); + let header = block_i(1, &validators); + let hash = header.hash(); + assert_eq!( + import_header( + &mut storage, + &test_aura_config(), + &validators_config, + PRUNE_DEPTH, + None, + header, + None + ) + .map(|_| ()), + Ok(()), + ); - // check that new validators will be used for next header - let imported_header = storage.stored_header(&hash).unwrap(); - assert_eq!( - imported_header.next_validators_set_id, - 1, // new set is enacted from config - ); + // check that new validators will be used for next header + let imported_header = Headers::::get(&hash).unwrap(); + assert_eq!( + imported_header.next_validators_set_id, + 1, // new set is enacted from config + ); + }); } #[test] fn headers_are_pruned() { - let validators_config = - ValidatorsConfiguration::Single(ValidatorsSource::Contract([3; 20].into(), validators_addresses(3))); - let validators = vec![validator(0), validator(1), validator(2)]; - let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3)); + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let validators_config = + ValidatorsConfiguration::Single(ValidatorsSource::Contract([3; 20].into(), validators_addresses(3))); + let validators = vec![validator(0), validator(1), validator(2)]; + let mut storage = BridgeStorage::::new(); - // header [0..11] are finalizing blocks [0; 9] - // => since we want to keep 10 finalized blocks, we aren't pruning anything - let mut latest_block_hash = Default::default(); - for i in 1..11 { - let header = block_i(&storage, i, &validators); + // header [0..11] are finalizing blocks [0; 9] + // => since we want to keep 10 finalized blocks, we aren't pruning anything + let mut latest_block_hash = Default::default(); + for i in 1..11 { + let header = block_i(i, &validators); + let (rolling_last_block_hash, finalized_blocks) = import_header( + &mut storage, + &test_aura_config(), + &validators_config, + 10, + Some(100), + header, + None, + ) + .unwrap(); + match i { + 2..=10 => assert_eq!( + finalized_blocks, + vec![(i - 1, block_i(i - 1, &validators).hash(), Some(100))], + "At {}", + i, + ), + _ => assert_eq!(finalized_blocks, vec![], "At {}", i), + } + latest_block_hash = rolling_last_block_hash; + } + assert!(storage.header(&genesis().hash()).is_some()); + + // header 11 finalizes headers [10] AND schedules change + // => we prune header#0 + let header11 = custom_block_i(11, &validators, |header| { + header.log_bloom = (&[0xff; 256]).into(); + header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951" + .parse() + .unwrap(); + }); let (rolling_last_block_hash, finalized_blocks) = import_header( &mut storage, - &kovan_aura_config(), + &test_aura_config(), &validators_config, 10, - Some(100), - header, - None, + Some(101), + header11.clone(), + Some(vec![crate::validators::tests::validators_change_recept( + latest_block_hash, + )]), ) .unwrap(); - match i { - 2..=10 => assert_eq!( - finalized_blocks, - vec![(i - 1, block_i(&storage, i - 1, &validators).hash(), Some(100))], - "At {}", - i, - ), - _ => assert_eq!(finalized_blocks, vec![], "At {}", i), - } + assert_eq!(finalized_blocks, vec![(10, block_i(10, &validators).hash(), Some(100))],); + assert!(storage.header(&genesis().hash()).is_none()); latest_block_hash = rolling_last_block_hash; - } - assert!(storage.header(&genesis().hash()).is_some()); - // header 11 finalizes headers [10] AND schedules change - // => we prune header#0 - let header11 = custom_block_i(&storage, 11, &validators, |header| { - header.log_bloom = (&[0xff; 256]).into(); - header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951" - .parse() + // and now let's say validators 1 && 2 went offline + // => in the range 12-25 no blocks are finalized, but we still continue to prune old headers + // until header#11 is met. we can't prune #11, because it schedules change + let mut step = 56; + let mut expected_blocks = vec![(11, header11.hash(), Some(101))]; + for i in 12..25 { + let header = Header { + number: i as _, + parent_hash: latest_block_hash, + gas_limit: 0x2000.into(), + author: validator(2).address().to_fixed_bytes().into(), + seal: vec![vec![step].into(), vec![].into()], + difficulty: i.into(), + ..Default::default() + }; + let header = signed_header(&validators, header, step as _); + expected_blocks.push((i, header.hash(), Some(102))); + let (rolling_last_block_hash, finalized_blocks) = import_header( + &mut storage, + &test_aura_config(), + &validators_config, + 10, + Some(102), + header, + None, + ) .unwrap(); - }); - let (rolling_last_block_hash, finalized_blocks) = import_header( - &mut storage, - &kovan_aura_config(), - &validators_config, - 10, - Some(101), - header11.clone(), - Some(vec![crate::validators::tests::validators_change_recept( - latest_block_hash, - )]), - ) - .unwrap(); - assert_eq!( - finalized_blocks, - vec![(10, block_i(&storage, 10, &validators).hash(), Some(100))], - ); - assert!(storage.header(&genesis().hash()).is_none()); - latest_block_hash = rolling_last_block_hash; + assert_eq!(finalized_blocks, vec![],); + latest_block_hash = rolling_last_block_hash; + step += 3; + } + assert_eq!(OldestUnprunedBlock::get(), 11); - // and now let's say validators 1 && 2 went offline - // => in the range 12-25 no blocks are finalized, but we still continue to prune old headers - // until header#11 is met. we can't prune #11, because it schedules change - let mut step = 56; - let mut expected_blocks = vec![(11, header11.hash(), Some(101))]; - for i in 12..25 { + // now let's insert block signed by validator 1 + // => blocks 11..24 are finalized and blocks 11..14 are pruned + step -= 2; let header = Header { - number: i as _, + number: 25, parent_hash: latest_block_hash, gas_limit: 0x2000.into(), - author: validator(2).address().to_fixed_bytes().into(), + author: validator(0).address().to_fixed_bytes().into(), seal: vec![vec![step].into(), vec![].into()], - difficulty: i.into(), + difficulty: 25.into(), ..Default::default() }; let header = signed_header(&validators, header, step as _); - expected_blocks.push((i, header.hash(), Some(102))); - let (rolling_last_block_hash, finalized_blocks) = import_header( + let (_, finalized_blocks) = import_header( &mut storage, - &kovan_aura_config(), + &test_aura_config(), &validators_config, 10, - Some(102), + Some(103), header, None, ) .unwrap(); - assert_eq!(finalized_blocks, vec![],); - latest_block_hash = rolling_last_block_hash; - step += 3; - } - assert_eq!(storage.oldest_unpruned_block(), 11); - - // now let's insert block signed by validator 1 - // => blocks 11..24 are finalized and blocks 11..14 are pruned - step -= 2; - let header = Header { - number: 25, - parent_hash: latest_block_hash, - gas_limit: 0x2000.into(), - author: validator(0).address().to_fixed_bytes().into(), - seal: vec![vec![step].into(), vec![].into()], - difficulty: 25.into(), - ..Default::default() - }; - let header = signed_header(&validators, header, step as _); - let (_, finalized_blocks) = import_header( - &mut storage, - &kovan_aura_config(), - &validators_config, - 10, - Some(103), - header, - None, - ) - .unwrap(); - assert_eq!(finalized_blocks, expected_blocks); - assert_eq!(storage.oldest_unpruned_block(), 15); + assert_eq!(finalized_blocks, expected_blocks); + assert_eq!(OldestUnprunedBlock::get(), 15); + }); } } diff --git a/modules/ethereum/src/lib.rs b/modules/ethereum/src/lib.rs index 9f0b0cbd031..5285eeea44d 100644 --- a/modules/ethereum/src/lib.rs +++ b/modules/ethereum/src/lib.rs @@ -17,7 +17,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; -use frame_support::{decl_module, decl_storage}; +use frame_support::{decl_module, decl_storage, traits::Get}; use primitives::{Address, Header, Receipt, H256, U256}; use sp_runtime::{ transaction_validity::{ @@ -27,9 +27,8 @@ use sp_runtime::{ RuntimeDebug, }; use sp_std::{cmp::Ord, collections::btree_map::BTreeMap, prelude::*}; -use validators::{ValidatorsConfiguration, ValidatorsSource}; -pub use import::{header_import_requires_receipts, import_header}; +pub use validators::{ValidatorsConfiguration, ValidatorsSource}; mod error; mod finality; @@ -37,6 +36,9 @@ mod import; mod validators; mod verification; +#[cfg(test)] +mod mock; + /// Authority round engine configuration parameters. #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)] pub struct AuraConfiguration { @@ -286,6 +288,10 @@ impl OnHeadersSubmitted for () { /// The module configuration trait pub trait Trait: frame_system::Trait { + /// Aura configuration. + type AuraConfiguration: Get; + /// Validators configuration. + type ValidatorsConfiguration: Get; /// Handler for headers submission result. type OnHeadersSubmitted: OnHeadersSubmitted; } @@ -297,10 +303,10 @@ decl_module! { pub fn import_unsigned_header(origin, header: Header, receipts: Option>) { frame_system::ensure_none(origin)?; - import_header( + import::import_header( &mut BridgeStorage::::new(), - &kovan_aura_config(), - &kovan_validators_config(), + &T::AuraConfiguration::get(), + &T::ValidatorsConfiguration::get(), crate::import::PRUNE_DEPTH, None, header, @@ -320,8 +326,8 @@ decl_module! { let mut finalized_headers = BTreeMap::new(); let import_result = import::import_headers( &mut BridgeStorage::::new(), - &kovan_aura_config(), - &kovan_validators_config(), + &T::AuraConfiguration::get(), + &T::ValidatorsConfiguration::get(), crate::import::PRUNE_DEPTH, Some(submitter.clone()), headers_with_receipts, @@ -358,7 +364,9 @@ decl_storage! { BestBlock: (u64, H256, U256); /// Best finalized block. FinalizedBlock: (u64, H256); - /// Oldest unpruned block(s) number. + /// The range (inclusive from both ends) of blocks that we want to prune. + /// + /// The first number number of the oldest blo OldestUnprunedBlock: u64; /// Map of imported headers by hash. Headers: map hasher(identity) H256 => Option>; @@ -424,7 +432,7 @@ impl Module { /// Returns true if the import of given block requires transactions receipts. pub fn is_import_requires_receipts(header: Header) -> bool { - import::header_import_requires_receipts(&BridgeStorage::::new(), &kovan_validators_config(), &header) + import::header_import_requires_receipts(&BridgeStorage::::new(), &T::ValidatorsConfiguration::get(), &header) } /// Returns true if header is known to the runtime. @@ -441,8 +449,8 @@ impl frame_support::unsigned::ValidateUnsigned for Module { Self::Call::import_unsigned_header(ref header, ref receipts) => { let accept_result = verification::accept_aura_header_into_pool( &BridgeStorage::::new(), - &kovan_aura_config(), - &kovan_validators_config(), + &T::AuraConfiguration::get(), + &T::ValidatorsConfiguration::get(), &pool_configuration(), header, receipts.as_ref(), @@ -623,462 +631,9 @@ impl Storage for BridgeStorage { } } -/// Aura engine configuration for Kovan chain. -pub fn kovan_aura_config() -> AuraConfiguration { - AuraConfiguration { - empty_steps_transition: u64::max_value(), - strict_empty_steps_transition: 0, - validate_step_transition: 0x16e360, - validate_score_transition: 0x41a3c4, - two_thirds_majority_transition: u64::max_value(), - min_gas_limit: 0x1388.into(), - max_gas_limit: U256::max_value(), - maximum_extra_data_size: 0x20, - } -} - -/// Validators configuration for Kovan chain. -pub fn kovan_validators_config() -> ValidatorsConfiguration { - ValidatorsConfiguration::Multi(vec![ - ( - 0, - ValidatorsSource::List(vec![ - [ - 0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58, 0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, - 0x5B, 0xAd, 0xc0, 0xED, - ] - .into(), - [ - 0x00, 0x42, 0x7f, 0xea, 0xe2, 0x41, 0x9c, 0x15, 0xb8, 0x9d, 0x1c, 0x21, 0xaf, 0x10, 0xd1, 0xb6, - 0x65, 0x0a, 0x4d, 0x3d, - ] - .into(), - [ - 0x4E, 0xd9, 0xB0, 0x8e, 0x63, 0x54, 0xC7, 0x0f, 0xE6, 0xF8, 0xCB, 0x04, 0x11, 0xb0, 0xd3, 0x24, - 0x6b, 0x42, 0x4d, 0x6c, - ] - .into(), - [ - 0x00, 0x20, 0xee, 0x4B, 0xe0, 0xe2, 0x02, 0x7d, 0x76, 0x60, 0x3c, 0xB7, 0x51, 0xeE, 0x06, 0x95, - 0x19, 0xbA, 0x81, 0xA1, - ] - .into(), - [ - 0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5, 0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, - 0x3a, 0xfd, 0x03, 0x2d, - ] - .into(), - [ - 0x00, 0x77, 0x33, 0xa1, 0xFE, 0x69, 0xCF, 0x3f, 0x2C, 0xF9, 0x89, 0xF8, 0x1C, 0x7b, 0x4c, 0xAc, - 0x16, 0x93, 0x38, 0x7A, - ] - .into(), - [ - 0x00, 0xE6, 0xd2, 0xb9, 0x31, 0xF5, 0x5a, 0x3f, 0x17, 0x01, 0xc7, 0x38, 0x9d, 0x59, 0x2a, 0x77, - 0x78, 0x89, 0x78, 0x79, - ] - .into(), - [ - 0x00, 0xe4, 0xa1, 0x06, 0x50, 0xe5, 0xa6, 0xD6, 0x00, 0x1C, 0x38, 0xff, 0x8E, 0x64, 0xF9, 0x70, - 0x16, 0xa1, 0x64, 0x5c, - ] - .into(), - [ - 0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c, 0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, - 0x12, 0x31, 0x94, 0xde, - ] - .into(), - ]), - ), - ( - 10960440, - ValidatorsSource::List(vec![ - [ - 0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58, 0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, - 0x5B, 0xAd, 0xc0, 0xED, - ] - .into(), - [ - 0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5, 0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, - 0x3a, 0xfd, 0x03, 0x2d, - ] - .into(), - [ - 0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c, 0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, - 0x12, 0x31, 0x94, 0xde, - ] - .into(), - ]), - ), - ( - 10960500, - ValidatorsSource::Contract( - [ - 0xaE, 0x71, 0x80, 0x7C, 0x1B, 0x0a, 0x09, 0x3c, 0xB1, 0x54, 0x7b, 0x68, 0x2D, 0xC7, 0x83, 0x16, - 0xD9, 0x45, 0xc9, 0xB8, - ] - .into(), - vec![ - [ - 0xd0, 0x5f, 0x74, 0x78, 0xc6, 0xaa, 0x10, 0x78, 0x12, 0x58, 0xc5, 0xcc, 0x8b, 0x4f, 0x38, 0x5f, - 0xc8, 0xfa, 0x98, 0x9c, - ] - .into(), - [ - 0x03, 0x80, 0x1e, 0xfb, 0x0e, 0xfe, 0x2a, 0x25, 0xed, 0xe5, 0xdd, 0x3a, 0x00, 0x3a, 0xe8, 0x80, - 0xc0, 0x29, 0x2e, 0x4d, - ] - .into(), - [ - 0xa4, 0xdf, 0x25, 0x5e, 0xcf, 0x08, 0xbb, 0xf2, 0xc2, 0x80, 0x55, 0xc6, 0x52, 0x25, 0xc9, 0xa9, - 0x84, 0x7a, 0xbd, 0x94, - ] - .into(), - [ - 0x59, 0x6e, 0x82, 0x21, 0xa3, 0x0b, 0xfe, 0x6e, 0x7e, 0xff, 0x67, 0xfe, 0xe6, 0x64, 0xa0, 0x1c, - 0x73, 0xba, 0x3c, 0x56, - ] - .into(), - [ - 0xfa, 0xad, 0xfa, 0xce, 0x3f, 0xbd, 0x81, 0xce, 0x37, 0xb0, 0xe1, 0x9c, 0x0b, 0x65, 0xff, 0x42, - 0x34, 0x14, 0x81, 0x32, - ] - .into(), - ], - ), - ), - ]) -} - /// Transaction pool configuration. fn pool_configuration() -> PoolConfiguration { PoolConfiguration { max_future_number_difference: 10, } } - -#[cfg(test)] -pub(crate) mod tests { - use super::*; - use parity_crypto::publickey::{sign, KeyPair, Secret}; - use primitives::{rlp_encode, H520}; - use std::collections::{hash_map::Entry, HashMap}; - - pub type AccountId = u64; - - pub fn genesis() -> Header { - Header { - seal: vec![vec![42].into(), vec![].into()], - ..Default::default() - } - } - - pub fn block_i(storage: &InMemoryStorage, number: u64, validators: &[KeyPair]) -> Header { - custom_block_i(storage, number, validators, |_| {}) - } - - pub fn custom_block_i( - storage: &InMemoryStorage, - number: u64, - validators: &[KeyPair], - customize: impl FnOnce(&mut Header), - ) -> Header { - let validator_index: u8 = (number % (validators.len() as u64)) as _; - let mut header = Header { - number, - parent_hash: storage.headers_by_number[&(number - 1)][0].clone(), - gas_limit: 0x2000.into(), - author: validator(validator_index).address().to_fixed_bytes().into(), - seal: vec![vec![number as u8 + 42].into(), vec![].into()], - difficulty: number.into(), - ..Default::default() - }; - customize(&mut header); - signed_header(validators, header, number + 42) - } - - pub fn signed_header(validators: &[KeyPair], mut header: Header, step: u64) -> Header { - let message = header.seal_hash(false).unwrap(); - let validator_index = (step % validators.len() as u64) as usize; - let signature = sign(validators[validator_index].secret(), &message.as_fixed_bytes().into()).unwrap(); - let signature: [u8; 65] = signature.into(); - let signature = H520::from(signature); - header.seal[1] = rlp_encode(&signature); - header - } - - pub fn validator(index: u8) -> KeyPair { - KeyPair::from_secret(Secret::from([index + 1; 32])).unwrap() - } - - pub fn validators_addresses(count: u8) -> Vec
{ - (0..count as usize) - .map(|i| validator(i as u8).address().as_fixed_bytes().into()) - .collect() - } - - pub struct InMemoryStorage { - best_block: (u64, H256, U256), - finalized_block: (u64, H256), - oldest_unpruned_block: u64, - headers: HashMap>, - headers_by_number: HashMap>, - next_validators_set_id: u64, - validators_sets: HashMap, - validators_sets_rc: HashMap, - scheduled_changes: HashMap, - } - - impl InMemoryStorage { - pub fn new(initial_header: Header, initial_validators: Vec
) -> Self { - let hash = initial_header.hash(); - InMemoryStorage { - best_block: (initial_header.number, hash, 0.into()), - finalized_block: (initial_header.number, hash), - oldest_unpruned_block: initial_header.number, - headers_by_number: vec![(initial_header.number, vec![hash])].into_iter().collect(), - headers: vec![( - hash, - StoredHeader { - submitter: None, - header: initial_header, - total_difficulty: 0.into(), - next_validators_set_id: 0, - last_signal_block: None, - }, - )] - .into_iter() - .collect(), - next_validators_set_id: 1, - validators_sets: vec![( - 0, - ValidatorsSet { - validators: initial_validators, - signal_block: None, - enact_block: hash, - }, - )] - .into_iter() - .collect(), - validators_sets_rc: vec![(0, 1)].into_iter().collect(), - scheduled_changes: HashMap::new(), - } - } - - pub(crate) fn insert(&mut self, header: Header) { - let hash = header.hash(); - self.headers_by_number.entry(header.number).or_default().push(hash); - self.headers.insert( - hash, - StoredHeader { - submitter: None, - header, - total_difficulty: 0.into(), - next_validators_set_id: 0, - last_signal_block: None, - }, - ); - } - - pub(crate) fn change_validators_set_at( - &mut self, - number: u64, - finalized_set: Vec
, - signalled_set: Option>, - ) { - let set_id = self.next_validators_set_id; - self.next_validators_set_id += 1; - self.validators_sets.insert( - set_id, - ValidatorsSet { - validators: finalized_set, - signal_block: None, - enact_block: self.headers_by_number[&0][0], - }, - ); - - let mut header = self.headers.get_mut(&self.headers_by_number[&number][0]).unwrap(); - header.next_validators_set_id = set_id; - if let Some(signalled_set) = signalled_set { - header.last_signal_block = Some(self.headers_by_number[&(number - 1)][0]); - self.scheduled_changes.insert( - self.headers_by_number[&(number - 1)][0], - ScheduledChange { - validators: signalled_set, - prev_signal_block: None, - }, - ); - } - } - - pub(crate) fn set_best_block(&mut self, best_block: (u64, H256)) { - self.best_block.0 = best_block.0; - self.best_block.1 = best_block.1; - } - - pub(crate) fn set_finalized_block(&mut self, finalized_block: (u64, H256)) { - self.finalized_block = finalized_block; - } - - pub(crate) fn oldest_unpruned_block(&self) -> u64 { - self.oldest_unpruned_block - } - - pub(crate) fn stored_header(&self, hash: &H256) -> Option<&StoredHeader> { - self.headers.get(hash) - } - } - - impl Storage for InMemoryStorage { - type Submitter = AccountId; - - fn best_block(&self) -> (u64, H256, U256) { - self.best_block.clone() - } - - fn finalized_block(&self) -> (u64, H256) { - self.finalized_block.clone() - } - - fn header(&self, hash: &H256) -> Option<(Header, Option)> { - self.headers - .get(hash) - .map(|header| (header.header.clone(), header.submitter.clone())) - } - - fn import_context( - &self, - submitter: Option, - parent_hash: &H256, - ) -> Option> { - self.headers.get(parent_hash).map(|parent_header| { - let validators_set = self - .validators_sets - .get(&parent_header.next_validators_set_id) - .unwrap() - .clone(); - let parent_scheduled_change = self.scheduled_changes.get(parent_hash).cloned(); - ImportContext { - submitter, - parent_hash: *parent_hash, - parent_header: parent_header.header.clone(), - parent_total_difficulty: parent_header.total_difficulty, - parent_scheduled_change, - validators_set_id: parent_header.next_validators_set_id, - validators_set, - last_signal_block: parent_header.last_signal_block, - } - }) - } - - fn scheduled_change(&self, hash: &H256) -> Option { - self.scheduled_changes.get(hash).cloned() - } - - fn insert_header(&mut self, header: HeaderToImport) { - if header.is_best { - self.best_block = (header.header.number, header.hash, header.total_difficulty); - } - if let Some(scheduled_change) = header.scheduled_change { - self.scheduled_changes.insert( - header.hash, - ScheduledChange { - validators: scheduled_change, - prev_signal_block: header.context.last_signal_block, - }, - ); - } - let next_validators_set_id = match header.enacted_change { - Some(enacted_change) => { - let next_validators_set_id = self.next_validators_set_id; - self.next_validators_set_id += 1; - self.validators_sets.insert( - next_validators_set_id, - ValidatorsSet { - validators: enacted_change.validators, - enact_block: header.hash, - signal_block: enacted_change.signal_block, - }, - ); - self.validators_sets_rc.insert(next_validators_set_id, 1); - next_validators_set_id - } - None => { - *self - .validators_sets_rc - .entry(header.context.validators_set_id) - .or_default() += 1; - header.context.validators_set_id - } - }; - - let last_signal_block = header.context.last_signal_block().cloned(); - self.headers_by_number - .entry(header.header.number) - .or_default() - .push(header.hash); - self.headers.insert( - header.hash, - StoredHeader { - submitter: header.context.submitter, - header: header.header, - total_difficulty: header.total_difficulty, - next_validators_set_id, - last_signal_block, - }, - ); - } - - fn finalize_headers(&mut self, finalized: Option<(u64, H256)>, prune_end: Option) { - let finalized_number = finalized - .as_ref() - .map(|f| f.0) - .unwrap_or_else(|| self.finalized_block.0); - if let Some(finalized) = finalized { - self.finalized_block = finalized; - } - - if let Some(prune_end) = prune_end { - let prune_begin = self.oldest_unpruned_block; - - for number in prune_begin..prune_end { - let blocks_at_number = self.headers_by_number.remove(&number); - - // ensure that unfinalized headers we want to prune do not have scheduled changes - if number > finalized_number { - if let Some(ref blocks_at_number) = blocks_at_number { - if blocks_at_number - .iter() - .any(|block| self.scheduled_changes.contains_key(block)) - { - self.headers_by_number.insert(number, blocks_at_number.clone()); - self.oldest_unpruned_block = number; - return; - } - } - } - - // physically remove headers and (probably) obsolete validators sets - for hash in blocks_at_number.into_iter().flat_map(|x| x) { - let header = self.headers.remove(&hash); - self.scheduled_changes.remove(&hash); - if let Some(header) = header { - match self.validators_sets_rc.entry(header.next_validators_set_id) { - Entry::Occupied(mut entry) => { - if *entry.get() == 1 { - entry.remove(); - } else { - *entry.get_mut() -= 1; - } - } - Entry::Vacant(_) => unreachable!("there's entry for each header"), - }; - } - } - } - - self.oldest_unpruned_block = prune_end; - } - } - } -} diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs new file mode 100644 index 00000000000..a7ce10c039a --- /dev/null +++ b/modules/ethereum/src/mock.rs @@ -0,0 +1,168 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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 Bridges Common 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 Bridges Common. If not, see . + +use crate::validators::{ValidatorsConfiguration, ValidatorsSource}; +use crate::{AuraConfiguration, GenesisConfig, HeadersByNumber, Trait}; +use frame_support::StorageMap; +use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; +use parity_crypto::publickey::{sign, KeyPair, Secret}; +use primitives::{rlp_encode, H520}; +use primitives::{Address, Header, H256, U256}; +use sp_runtime::{ + testing::Header as SubstrateHeader, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub type AccountId = u64; + +#[derive(Clone, Eq, PartialEq)] +pub struct TestRuntime; + +impl_outer_origin! { + pub enum Origin for TestRuntime where system = frame_system {} +} + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl frame_system::Trait for TestRuntime { + type Origin = Origin; + type Index = u64; + type Call = (); + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = SubstrateHeader; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + type ModuleToIndex = (); + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); +} + +parameter_types! { + pub const TestAuraConfiguration: AuraConfiguration = test_aura_config(); + pub const TestValidatorsConfiguration: ValidatorsConfiguration = test_validators_config(); +} + +impl Trait for TestRuntime { + type AuraConfiguration = TestAuraConfiguration; + type ValidatorsConfiguration = TestValidatorsConfiguration; + type OnHeadersSubmitted = (); +} + +/// Aura configuration that is used in tests by default. +pub fn test_aura_config() -> AuraConfiguration { + AuraConfiguration { + empty_steps_transition: u64::max_value(), + strict_empty_steps_transition: 0, + validate_step_transition: 0x16e360, + validate_score_transition: 0x41a3c4, + two_thirds_majority_transition: u64::max_value(), + min_gas_limit: 0x1388.into(), + max_gas_limit: U256::max_value(), + maximum_extra_data_size: 0x20, + } +} + +/// Validators configuration that is used in tests by default. +pub fn test_validators_config() -> ValidatorsConfiguration { + ValidatorsConfiguration::Single(ValidatorsSource::List(validators_addresses(3))) +} + +/// Genesis header that is used in tests by default. +pub fn genesis() -> Header { + Header { + seal: vec![vec![42].into(), vec![].into()], + ..Default::default() + } +} + +/// Build default i-th block, using data from runtime storage. +pub fn block_i(number: u64, validators: &[KeyPair]) -> Header { + custom_block_i(number, validators, |_| {}) +} + +/// Build custom i-th block, using data from runtime storage. +pub fn custom_block_i(number: u64, validators: &[KeyPair], customize: impl FnOnce(&mut Header)) -> Header { + let validator_index: u8 = (number % (validators.len() as u64)) as _; + let mut header = Header { + number, + parent_hash: HeadersByNumber::get(number - 1).unwrap()[0].clone(), + gas_limit: 0x2000.into(), + author: validator(validator_index).address().to_fixed_bytes().into(), + seal: vec![vec![number as u8 + 42].into(), vec![].into()], + difficulty: number.into(), + ..Default::default() + }; + customize(&mut header); + signed_header(validators, header, number + 42) +} + +/// Build signed header from given header. +pub fn signed_header(validators: &[KeyPair], mut header: Header, step: u64) -> Header { + let message = header.seal_hash(false).unwrap(); + let validator_index = (step % validators.len() as u64) as usize; + let signature = sign(validators[validator_index].secret(), &message.as_fixed_bytes().into()).unwrap(); + let signature: [u8; 65] = signature.into(); + let signature = H520::from(signature); + header.seal[1] = rlp_encode(&signature); + header +} + +/// Return key pair of given test validator. +pub fn validator(index: u8) -> KeyPair { + KeyPair::from_secret(Secret::from([index + 1; 32])).unwrap() +} + +/// Return key pairs of all test validators. +pub fn validators(count: u8) -> Vec { + (0..count as usize).map(|index| validator(index as u8)).collect() +} + +/// Return addresses of all test validators. +pub fn validators_addresses(count: u8) -> Vec
{ + (0..count as usize) + .map(|i| validator(i as u8).address().as_fixed_bytes().into()) + .collect() +} + +/// Prepare externalities to start with custom initial header. +pub fn custom_test_ext(initial_header: Header, initial_validators: Vec
) -> sp_io::TestExternalities { + let t = GenesisConfig { + initial_header, + initial_difficulty: 0.into(), + initial_validators, + } + .build_storage::() + .unwrap(); + sp_io::TestExternalities::new(t) +} diff --git a/modules/ethereum/src/validators.rs b/modules/ethereum/src/validators.rs index a93c21f8de4..7a1fbb52f03 100644 --- a/modules/ethereum/src/validators.rs +++ b/modules/ethereum/src/validators.rs @@ -254,7 +254,6 @@ pub fn step_validator(header_validators: &[Address], header_step: u64) -> Addres #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::kovan_validators_config; use primitives::TransactionOutcome; pub(crate) fn validators_change_recept(parent_hash: H256) -> Receipt { @@ -314,7 +313,7 @@ pub(crate) mod tests { #[test] fn maybe_signals_validators_change_works() { // when contract is active, but bloom has no required bits set - let config = kovan_validators_config(); + let config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new())); let validators = Validators::new(&config); let mut header = Header::default(); header.number = u64::max_value(); diff --git a/modules/ethereum/src/verification.rs b/modules/ethereum/src/verification.rs index 23f8980b3a0..16e232ef1d7 100644 --- a/modules/ethereum/src/verification.rs +++ b/modules/ethereum/src/verification.rs @@ -339,11 +339,16 @@ fn find_next_validators_signal(storage: &S, context: &ImportContext< #[cfg(test)] mod tests { use super::*; - use crate::tests::{ - block_i, custom_block_i, genesis, signed_header, validator, validators_addresses, AccountId, InMemoryStorage, + use crate::mock::{ + block_i, custom_block_i, custom_test_ext, genesis, signed_header, test_aura_config, validator, + validators_addresses, AccountId, TestRuntime, }; use crate::validators::{tests::validators_change_recept, ValidatorsSource}; - use crate::{kovan_aura_config, pool_configuration}; + use crate::{ + pool_configuration, BridgeStorage, FinalizedBlock, Headers, HeadersByNumber, NextValidatorsSetId, + ScheduledChanges, ValidatorsSet, ValidatorsSets, + }; + use frame_support::{StorageMap, StorageValue}; use parity_crypto::publickey::{sign, KeyPair}; use primitives::{rlp_encode, TransactionOutcome, H520}; @@ -362,41 +367,88 @@ mod tests { } fn verify_with_config(config: &AuraConfiguration, header: &Header) -> Result, Error> { - let storage = InMemoryStorage::new(genesis(), validators_addresses(3)); - verify_aura_header(&storage, &config, None, header) + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let storage = BridgeStorage::::new(); + verify_aura_header(&storage, &config, None, header) + }) } fn default_verify(header: &Header) -> Result, Error> { - verify_with_config(&kovan_aura_config(), header) + verify_with_config(&test_aura_config(), header) } fn default_accept_into_pool( - mut make_header: impl FnMut(&mut InMemoryStorage, &[KeyPair]) -> (Header, Option>), + mut make_header: impl FnMut(&[KeyPair]) -> (Header, Option>), ) -> Result<(Vec>, Vec>), Error> { - let validators = vec![validator(0), validator(1), validator(2)]; - let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3)); - let block1 = block_i(&storage, 1, &validators); - storage.insert(block1); - let block2 = block_i(&storage, 2, &validators); - let block2_hash = block2.hash(); - storage.insert(block2); - let block3 = block_i(&storage, 3, &validators); - let block3_hash = block3.hash(); - storage.insert(block3); - storage.set_finalized_block((2, block2_hash)); - storage.set_best_block((3, block3_hash)); - - let validators_config = - ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new())); - let (header, receipts) = make_header(&mut storage, &validators); - accept_aura_header_into_pool( - &storage, - &kovan_aura_config(), - &validators_config, - &pool_configuration(), - &header, - receipts.as_ref(), - ) + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let validators = vec![validator(0), validator(1), validator(2)]; + let mut storage = BridgeStorage::::new(); + let block1 = block_i(1, &validators); + storage.insert_header( + storage + .import_context(None, &block1.parent_hash) + .unwrap() + .into_import_header(true, block1.hash(), block1, 0.into(), None, None), + ); + let block2 = block_i(2, &validators); + let block2_hash = block2.hash(); + storage.insert_header( + storage + .import_context(None, &block2.parent_hash) + .unwrap() + .into_import_header(true, block2.hash(), block2, 0.into(), None, None), + ); + let block3 = block_i(3, &validators); + storage.insert_header( + storage + .import_context(None, &block3.parent_hash) + .unwrap() + .into_import_header(true, block3.hash(), block3, 0.into(), None, None), + ); + + FinalizedBlock::put((2, block2_hash)); + + let validators_config = + ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new())); + let (header, receipts) = make_header(&validators); + accept_aura_header_into_pool( + &storage, + &test_aura_config(), + &validators_config, + &pool_configuration(), + &header, + receipts.as_ref(), + ) + }) + } + + fn change_validators_set_at(number: u64, finalized_set: Vec
, signalled_set: Option>) { + let set_id = NextValidatorsSetId::get(); + NextValidatorsSetId::put(set_id + 1); + ValidatorsSets::insert( + set_id, + ValidatorsSet { + validators: finalized_set, + signal_block: None, + enact_block: HeadersByNumber::get(&0).unwrap()[0].clone(), + }, + ); + + let header_hash = HeadersByNumber::get(&number).unwrap()[0].clone(); + let mut header = Headers::::get(&header_hash).unwrap(); + header.next_validators_set_id = set_id; + if let Some(signalled_set) = signalled_set { + header.last_signal_block = Some(header.header.parent_hash); + ScheduledChanges::insert( + header.header.parent_hash, + ScheduledChange { + validators: signalled_set, + prev_signal_block: None, + }, + ); + } + + Headers::::insert(header_hash, header); } #[test] @@ -409,7 +461,7 @@ mod tests { header.seal = vec![vec![].into()]; assert_eq!(default_verify(&header), Err(Error::InvalidSealArity)); - // when there's 3 seals (we expect 2 on Kovan) + // when there's 3 seals (we expect 2 by default) header.seal = vec![vec![].into(), vec![].into(), vec![].into()]; assert_eq!(default_verify(&header), Err(Error::InvalidSealArity)); @@ -452,7 +504,7 @@ mod tests { #[test] fn verifies_gas_limit() { - let mut config = kovan_aura_config(); + let mut config = test_aura_config(); config.min_gas_limit = 100.into(); config.max_gas_limit = 200.into(); @@ -478,7 +530,7 @@ mod tests { // when extra data is too large let mut header = Header { seal: vec![vec![].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, extra_data: std::iter::repeat(42).take(1000).collect::>().into(), number: 1, ..Default::default() @@ -495,7 +547,7 @@ mod tests { // when timestamp overflows i32 let mut header = Header { seal: vec![vec![].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, timestamp: i32::max_value() as u64 + 1, ..Default::default() }; @@ -511,7 +563,7 @@ mod tests { // when there's no parent in the storage let mut header = Header { seal: vec![vec![].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, ..Default::default() }; assert_eq!(default_verify(&header), Err(Error::MissingParentBlock)); @@ -526,7 +578,7 @@ mod tests { // when step is missing from seals let mut header = Header { seal: vec![vec![].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: genesis().hash(), ..Default::default() }; @@ -541,7 +593,7 @@ mod tests { assert_ne!(default_verify(&header), Err(Error::DoubleVote)); // now check with validate_step check enabled - let mut config = kovan_aura_config(); + let mut config = test_aura_config(); config.validate_step_transition = 0; // when step is lesser that for the parent block @@ -556,7 +608,7 @@ mod tests { #[test] fn verifies_empty_step() { let validators = vec![validator(0), validator(1), validator(2)]; - let mut config = kovan_aura_config(); + let mut config = test_aura_config(); config.empty_steps_transition = 0; // when empty step duplicates parent step @@ -566,7 +618,7 @@ mod tests { vec![142].into(), SealedEmptyStep::rlp_of(&[sealed_empty_step(&validators, &genesis().hash(), 42)]), ], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: genesis().hash(), ..Default::default() }; @@ -596,13 +648,13 @@ mod tests { #[test] fn verifies_chain_score() { - let mut config = kovan_aura_config(); + let mut config = test_aura_config(); config.validate_score_transition = 0; // when chain score is invalid let mut header = Header { seal: vec![vec![43].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: genesis().hash(), ..Default::default() }; @@ -621,7 +673,7 @@ mod tests { Header { author: validators[1].address().as_fixed_bytes().into(), seal: vec![vec![43].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: genesis().hash(), ..Default::default() }, @@ -646,7 +698,7 @@ mod tests { fn pool_verifies_known_blocks() { // when header is known assert_eq!( - default_accept_into_pool(|storage, validators| (block_i(storage, 3, validators), None)), + default_accept_into_pool(|validators| (block_i(3, validators), None)), Err(Error::KnownHeader), ); } @@ -655,8 +707,8 @@ mod tests { fn pool_verifies_ancient_blocks() { // when header number is less than finalized assert_eq!( - default_accept_into_pool(|storage, validators| ( - custom_block_i(storage, 2, validators, |header| header.gas_limit += 1.into()), + default_accept_into_pool(|validators| ( + custom_block_i(2, validators, |header| header.gas_limit += 1.into()), None, ),), Err(Error::AncientHeader), @@ -666,11 +718,11 @@ mod tests { #[test] fn pool_rejects_headers_without_required_receipts() { assert_eq!( - default_accept_into_pool(|_, _| ( + default_accept_into_pool(|_| ( Header { number: 20_000_000, seal: vec![vec![].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, log_bloom: (&[0xff; 256]).into(), ..Default::default() }, @@ -683,8 +735,8 @@ mod tests { #[test] fn pool_rejects_headers_with_redundant_receipts() { assert_eq!( - default_accept_into_pool(|storage, validators| ( - block_i(storage, 4, validators), + default_accept_into_pool(|validators| ( + block_i(4, validators), Some(vec![Receipt { gas_used: 1.into(), log_bloom: (&[0xff; 256]).into(), @@ -700,10 +752,7 @@ mod tests { fn pool_verifies_future_block_number() { // when header is too far from the future assert_eq!( - default_accept_into_pool(|storage, validators| ( - custom_block_i(storage, 4, validators, |header| header.number = 100), - None, - ),), + default_accept_into_pool(|validators| (custom_block_i(4, validators, |header| header.number = 100), None,),), Err(Error::UnsignedTooFarInTheFuture), ); } @@ -713,9 +762,9 @@ mod tests { // if parent is known, then we'll execute contextual_checks, which // checks for DoubleVote assert_eq!( - default_accept_into_pool(|storage, validators| ( - custom_block_i(storage, 4, validators, |header| header.seal[0] = - block_i(storage, 3, validators).seal[0].clone()), + default_accept_into_pool(|validators| ( + custom_block_i(4, validators, |header| header.seal[0] = + block_i(3, validators).seal[0].clone()), None, ),), Err(Error::DoubleVote), @@ -728,13 +777,13 @@ mod tests { // (even if header will be considered invalid/duplicate later, we can use this signature // as a proof of malicious action by this validator) assert_eq!( - default_accept_into_pool(|_, validators| ( + default_accept_into_pool(|validators| ( signed_header( validators, Header { author: validators[1].address().as_fixed_bytes().into(), seal: vec![vec![8].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: [42; 32].into(), number: 8, ..Default::default() @@ -751,8 +800,8 @@ mod tests { fn pool_verifies_header_with_known_parent() { let mut hash = None; assert_eq!( - default_accept_into_pool(|storage, validators| { - let header = block_i(&storage, 4, &validators); + default_accept_into_pool(|validators| { + let header = block_i(4, &validators); hash = Some(header.hash()); (header, None) }), @@ -772,13 +821,13 @@ mod tests { fn pool_verifies_header_with_unknown_parent() { let mut hash = None; assert_eq!( - default_accept_into_pool(|_, validators| { + default_accept_into_pool(|validators| { let header = signed_header( validators, Header { author: validators[2].address().as_fixed_bytes().into(), seal: vec![vec![47].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: [42; 32].into(), number: 5, ..Default::default() @@ -803,9 +852,9 @@ mod tests { #[test] fn pool_uses_next_validators_set_when_finalized_fails() { assert_eq!( - default_accept_into_pool(|storage, actual_validators| { + default_accept_into_pool(|actual_validators| { // change finalized set at parent header - storage.change_validators_set_at(3, validators_addresses(1), None); + change_validators_set_at(3, validators_addresses(1), None); // header is signed using wrong set let header = signed_header( @@ -813,7 +862,7 @@ mod tests { Header { author: actual_validators[2].address().as_fixed_bytes().into(), seal: vec![vec![47].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: [42; 32].into(), number: 5, ..Default::default() @@ -828,9 +877,9 @@ mod tests { let mut hash = None; assert_eq!( - default_accept_into_pool(|storage, actual_validators| { + default_accept_into_pool(|actual_validators| { // change finalized set at parent header + signal valid set at parent block - storage.change_validators_set_at(3, validators_addresses(10), Some(validators_addresses(3))); + change_validators_set_at(3, validators_addresses(10), Some(validators_addresses(3))); // header is signed using wrong set let header = signed_header( @@ -838,7 +887,7 @@ mod tests { Header { author: actual_validators[2].address().as_fixed_bytes().into(), seal: vec![vec![47].into(), vec![].into()], - gas_limit: kovan_aura_config().min_gas_limit, + gas_limit: test_aura_config().min_gas_limit, parent_hash: [42; 32].into(), number: 5, ..Default::default() @@ -864,8 +913,8 @@ mod tests { #[test] fn pool_rejects_headers_with_invalid_receipts() { assert_eq!( - default_accept_into_pool(|storage, validators| { - let header = custom_block_i(&storage, 4, &validators, |header| { + default_accept_into_pool(|validators| { + let header = custom_block_i(4, &validators, |header| { header.log_bloom = (&[0xff; 256]).into(); }); (header, Some(vec![validators_change_recept(Default::default())])) @@ -878,8 +927,8 @@ mod tests { fn pool_accepts_headers_with_valid_receipts() { let mut hash = None; assert_eq!( - default_accept_into_pool(|storage, validators| { - let header = custom_block_i(&storage, 4, &validators, |header| { + default_accept_into_pool(|validators| { + let header = custom_block_i(4, &validators, |header| { header.log_bloom = (&[0xff; 256]).into(); header.receipts_root = "81ce88dc524403b796222046bf3daf543978329b87ffd50228f1d3987031dc45" .parse() diff --git a/primitives/ethereum-poa/src/lib.rs b/primitives/ethereum-poa/src/lib.rs index ca2afa605db..662079c301f 100644 --- a/primitives/ethereum-poa/src/lib.rs +++ b/primitives/ethereum-poa/src/lib.rs @@ -322,7 +322,6 @@ impl PartialEq for Bloom { } } -#[cfg(feature = "std")] impl Default for Bloom { fn default() -> Self { Bloom([0; 256]) From ed0c928c12668a1882f689e57c736a458190c3e4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 May 2020 15:12:49 +0300 Subject: [PATCH 02/14] removed comment from the future --- modules/ethereum/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/ethereum/src/lib.rs b/modules/ethereum/src/lib.rs index 5285eeea44d..376d800d34d 100644 --- a/modules/ethereum/src/lib.rs +++ b/modules/ethereum/src/lib.rs @@ -364,9 +364,7 @@ decl_storage! { BestBlock: (u64, H256, U256); /// Best finalized block. FinalizedBlock: (u64, H256); - /// The range (inclusive from both ends) of blocks that we want to prune. - /// - /// The first number number of the oldest blo + /// Oldest unpruned block(s) number. OldestUnprunedBlock: u64; /// Map of imported headers by hash. Headers: map hasher(identity) H256 => Option>; From a039387d73555452bfe800aa719daf49a5bf7a8e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 May 2020 13:33:23 +0300 Subject: [PATCH 03/14] remove redundant conversions --- modules/ethereum/src/import.rs | 4 ++-- modules/ethereum/src/mock.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ethereum/src/import.rs b/modules/ethereum/src/import.rs index 8fc6644ead6..17c7237fab6 100644 --- a/modules/ethereum/src/import.rs +++ b/modules/ethereum/src/import.rs @@ -333,7 +333,7 @@ mod tests { number: i as _, parent_hash: latest_block_hash, gas_limit: 0x2000.into(), - author: validator(2).address().to_fixed_bytes().into(), + author: validator(2).address(), seal: vec![vec![step].into(), vec![].into()], difficulty: i.into(), ..Default::default() @@ -363,7 +363,7 @@ mod tests { number: 25, parent_hash: latest_block_hash, gas_limit: 0x2000.into(), - author: validator(0).address().to_fixed_bytes().into(), + author: validator(0).address(), seal: vec![vec![step].into(), vec![].into()], difficulty: 25.into(), ..Default::default() diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index a7ce10c039a..d953c48b833 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -118,7 +118,7 @@ pub fn custom_block_i(number: u64, validators: &[KeyPair], customize: impl FnOnc number, parent_hash: HeadersByNumber::get(number - 1).unwrap()[0].clone(), gas_limit: 0x2000.into(), - author: validator(validator_index).address().to_fixed_bytes().into(), + author: validator(validator_index).address(), seal: vec![vec![number as u8 + 42].into(), vec![].into()], difficulty: number.into(), ..Default::default() From c654ad5949e9f1f1942f31db8a8713983d1d3537 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 May 2020 13:37:35 +0300 Subject: [PATCH 04/14] remove redundant `u8 as usize` --- modules/ethereum/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index d953c48b833..b95e32092a4 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -145,7 +145,7 @@ pub fn validator(index: u8) -> KeyPair { /// Return key pairs of all test validators. pub fn validators(count: u8) -> Vec { - (0..count as usize).map(|index| validator(index as u8)).collect() + (0..count).map(validator).collect() } /// Return addresses of all test validators. From 1aff027176cec3d70bf4d9a7e740300660323095 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 May 2020 13:40:49 +0300 Subject: [PATCH 05/14] remove redundant `u8 as usize` --- modules/ethereum/src/mock.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index b95e32092a4..d11116e7a01 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -150,8 +150,8 @@ pub fn validators(count: u8) -> Vec { /// Return addresses of all test validators. pub fn validators_addresses(count: u8) -> Vec
{ - (0..count as usize) - .map(|i| validator(i as u8).address().as_fixed_bytes().into()) + (0..count) + .map(|i| validator(i).address()) .collect() } From eee0c08f283f3489c30de4dd9d3c76f62a4221ad Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 May 2020 13:43:29 +0300 Subject: [PATCH 06/14] Update modules/ethereum/src/mock.rs Co-authored-by: Hernando Castano --- modules/ethereum/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index d11116e7a01..6ce2c44f4b3 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -29,7 +29,7 @@ use sp_runtime::{ pub type AccountId = u64; -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq, Debug)] pub struct TestRuntime; impl_outer_origin! { From 55c7a025f3a10d670425e47851f8a6e254423936 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 May 2020 14:08:42 +0300 Subject: [PATCH 07/14] use hex-literal in kovan config --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 3 + bin/node/runtime/src/kovan.rs | 133 ++++++---------------------------- 3 files changed, 27 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dab79461965..fb884542050 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,6 +539,7 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", + "hex-literal", "pallet-aura", "pallet-balances", "pallet-bridge-eth-poa", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 65b0484007b..3a4134107e6 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -6,6 +6,9 @@ edition = "2018" homepage = "https://substrate.dev" repository = "https://github.com/paritytech/parity-bridges-common/" +[dependencies] +hex-literal = "0.2" + [dependencies.codec] package = "parity-scale-codec" version = "1.0.0" diff --git a/bin/node/runtime/src/kovan.rs b/bin/node/runtime/src/kovan.rs index 560673035c8..2592d8e057b 100644 --- a/bin/node/runtime/src/kovan.rs +++ b/bin/node/runtime/src/kovan.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +use hex_literal::hex; use pallet_bridge_eth_poa::{AuraConfiguration, ValidatorsConfiguration, ValidatorsSource}; use sp_bridge_eth_poa::{Address, Header, U256}; use sp_std::prelude::*; @@ -39,57 +40,21 @@ pub fn kovan_validators_configuration() -> ValidatorsConfiguration { ( 10960440, ValidatorsSource::List(vec![ - [ - 0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58, 0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, - 0x5B, 0xAd, 0xc0, 0xED, - ] - .into(), - [ - 0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5, 0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, - 0x3a, 0xfd, 0x03, 0x2d, - ] - .into(), - [ - 0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c, 0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, - 0x12, 0x31, 0x94, 0xde, - ] - .into(), + hex!("00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED").into(), + hex!("0010f94b296a852aaac52ea6c5ac72e03afd032d").into(), + hex!("00a0a24b9f0e5ec7aa4c7389b8302fd0123194de").into(), ]), ), ( 10960500, ValidatorsSource::Contract( - [ - 0xaE, 0x71, 0x80, 0x7C, 0x1B, 0x0a, 0x09, 0x3c, 0xB1, 0x54, 0x7b, 0x68, 0x2D, 0xC7, 0x83, 0x16, - 0xD9, 0x45, 0xc9, 0xB8, - ] - .into(), + hex!("aE71807C1B0a093cB1547b682DC78316D945c9B8").into(), vec![ - [ - 0xd0, 0x5f, 0x74, 0x78, 0xc6, 0xaa, 0x10, 0x78, 0x12, 0x58, 0xc5, 0xcc, 0x8b, 0x4f, 0x38, 0x5f, - 0xc8, 0xfa, 0x98, 0x9c, - ] - .into(), - [ - 0x03, 0x80, 0x1e, 0xfb, 0x0e, 0xfe, 0x2a, 0x25, 0xed, 0xe5, 0xdd, 0x3a, 0x00, 0x3a, 0xe8, 0x80, - 0xc0, 0x29, 0x2e, 0x4d, - ] - .into(), - [ - 0xa4, 0xdf, 0x25, 0x5e, 0xcf, 0x08, 0xbb, 0xf2, 0xc2, 0x80, 0x55, 0xc6, 0x52, 0x25, 0xc9, 0xa9, - 0x84, 0x7a, 0xbd, 0x94, - ] - .into(), - [ - 0x59, 0x6e, 0x82, 0x21, 0xa3, 0x0b, 0xfe, 0x6e, 0x7e, 0xff, 0x67, 0xfe, 0xe6, 0x64, 0xa0, 0x1c, - 0x73, 0xba, 0x3c, 0x56, - ] - .into(), - [ - 0xfa, 0xad, 0xfa, 0xce, 0x3f, 0xbd, 0x81, 0xce, 0x37, 0xb0, 0xe1, 0x9c, 0x0b, 0x65, 0xff, 0x42, - 0x34, 0x14, 0x81, 0x32, - ] - .into(), + hex!("d05f7478c6aa10781258c5cc8b4f385fc8fa989c").into(), + hex!("03801efb0efe2a25ede5dd3a003ae880c0292e4d").into(), + hex!("a4df255ecf08bbf2c28055c65225c9a9847abd94").into(), + hex!("596e8221a30bfe6e7eff67fee664a01c73ba3c56").into(), + hex!("faadface3fbd81ce37b0e19c0b65ff4234148132").into(), ], ), ), @@ -99,51 +64,15 @@ pub fn kovan_validators_configuration() -> ValidatorsConfiguration { /// Genesis validators set of Kovan chain. pub fn kovan_genesis_validators() -> Vec
{ vec![ - [ - 0x00, 0xD6, 0xCc, 0x1B, 0xA9, 0xcf, 0x89, 0xBD, 0x2e, 0x58, 0x00, 0x97, 0x41, 0xf4, 0xF7, 0x32, 0x5B, 0xAd, - 0xc0, 0xED, - ] - .into(), - [ - 0x00, 0x42, 0x7f, 0xea, 0xe2, 0x41, 0x9c, 0x15, 0xb8, 0x9d, 0x1c, 0x21, 0xaf, 0x10, 0xd1, 0xb6, 0x65, 0x0a, - 0x4d, 0x3d, - ] - .into(), - [ - 0x4E, 0xd9, 0xB0, 0x8e, 0x63, 0x54, 0xC7, 0x0f, 0xE6, 0xF8, 0xCB, 0x04, 0x11, 0xb0, 0xd3, 0x24, 0x6b, 0x42, - 0x4d, 0x6c, - ] - .into(), - [ - 0x00, 0x20, 0xee, 0x4B, 0xe0, 0xe2, 0x02, 0x7d, 0x76, 0x60, 0x3c, 0xB7, 0x51, 0xeE, 0x06, 0x95, 0x19, 0xbA, - 0x81, 0xA1, - ] - .into(), - [ - 0x00, 0x10, 0xf9, 0x4b, 0x29, 0x6a, 0x85, 0x2a, 0xaa, 0xc5, 0x2e, 0xa6, 0xc5, 0xac, 0x72, 0xe0, 0x3a, 0xfd, - 0x03, 0x2d, - ] - .into(), - [ - 0x00, 0x77, 0x33, 0xa1, 0xFE, 0x69, 0xCF, 0x3f, 0x2C, 0xF9, 0x89, 0xF8, 0x1C, 0x7b, 0x4c, 0xAc, 0x16, 0x93, - 0x38, 0x7A, - ] - .into(), - [ - 0x00, 0xE6, 0xd2, 0xb9, 0x31, 0xF5, 0x5a, 0x3f, 0x17, 0x01, 0xc7, 0x38, 0x9d, 0x59, 0x2a, 0x77, 0x78, 0x89, - 0x78, 0x79, - ] - .into(), - [ - 0x00, 0xe4, 0xa1, 0x06, 0x50, 0xe5, 0xa6, 0xD6, 0x00, 0x1C, 0x38, 0xff, 0x8E, 0x64, 0xF9, 0x70, 0x16, 0xa1, - 0x64, 0x5c, - ] - .into(), - [ - 0x00, 0xa0, 0xa2, 0x4b, 0x9f, 0x0e, 0x5e, 0xc7, 0xaa, 0x4c, 0x73, 0x89, 0xb8, 0x30, 0x2f, 0xd0, 0x12, 0x31, - 0x94, 0xde, - ] - .into(), + hex!("00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED").into(), + hex!("00427feae2419c15b89d1c21af10d1b6650a4d3d").into(), + hex!("4Ed9B08e6354C70fE6F8CB0411b0d3246b424d6c").into(), + hex!("0020ee4Be0e2027d76603cB751eE069519bA81A1").into(), + hex!("0010f94b296a852aaac52ea6c5ac72e03afd032d").into(), + hex!("007733a1FE69CF3f2CF989F81C7b4cAc1693387A").into(), + hex!("00E6d2b931F55a3f1701c7389d592a7778897879").into(), + hex!("00e4a10650e5a6D6001C38ff8E64F97016a1645c").into(), + hex!("00a0a24b9f0e5ec7aa4c7389b8302fd0123194de").into(), ] } @@ -154,27 +83,11 @@ pub fn kovan_genesis_header() -> Header { timestamp: 0, number: 0, author: Default::default(), - transactions_root: [ - 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, 0x5b, 0x48, - 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, - ] - .into(), - uncles_hash: [ - 0x1d, 0xcc, 0x4d, 0xe8, 0xde, 0xc7, 0x5d, 0x7a, 0xab, 0x85, 0xb5, 0x67, 0xb6, 0xcc, 0xd4, 0x1a, 0xd3, 0x12, - 0x45, 0x1b, 0x94, 0x8a, 0x74, 0x13, 0xf0, 0xa1, 0x42, 0xfd, 0x40, 0xd4, 0x93, 0x47, - ] - .into(), + transactions_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + uncles_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").into(), extra_data: vec![], - state_root: [ - 0x24, 0x80, 0x15, 0x5b, 0x48, 0xa1, 0xce, 0xa1, 0x7d, 0x67, 0xdb, 0xfd, 0xfa, 0xaf, 0xe8, 0x21, 0xc1, 0xd1, - 0x9c, 0xdd, 0x47, 0x8c, 0x53, 0x58, 0xe8, 0xec, 0x56, 0xde, 0xc2, 0x45, 0x02, 0xb2, - ] - .into(), - receipts_root: [ - 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, 0x5b, 0x48, - 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, - ] - .into(), + state_root: hex!("2480155b48a1cea17d67dbfdfaafe821c1d19cdd478c5358e8ec56dec24502b2").into(), + receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), log_bloom: Default::default(), gas_used: Default::default(), gas_limit: 6000000.into(), From 898e93272266485d8a1b38ee9391caffe806d635 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 27 May 2020 14:10:28 +0300 Subject: [PATCH 08/14] cargo fmt --all --- modules/ethereum/src/mock.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index 6ce2c44f4b3..c0155ef49bd 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -150,9 +150,7 @@ pub fn validators(count: u8) -> Vec { /// Return addresses of all test validators. pub fn validators_addresses(count: u8) -> Vec
{ - (0..count) - .map(|i| validator(i).address()) - .collect() + (0..count).map(|i| validator(i).address()).collect() } /// Prepare externalities to start with custom initial header. From 54453209bbe278e263db42daaf427edf80058b1d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 1 Jun 2020 11:57:24 +0300 Subject: [PATCH 09/14] extracted insert_header --- modules/ethereum/src/mock.rs | 15 ++++++++++++++- modules/ethereum/src/verification.rs | 25 +++++-------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index c0155ef49bd..6624b149a90 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -15,7 +15,7 @@ // along with Parity Bridges Common. If not, see . use crate::validators::{ValidatorsConfiguration, ValidatorsSource}; -use crate::{AuraConfiguration, GenesisConfig, HeadersByNumber, Trait}; +use crate::{AuraConfiguration, GenesisConfig, HeaderToImport, HeadersByNumber, Storage, Trait}; use frame_support::StorageMap; use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; use parity_crypto::publickey::{sign, KeyPair, Secret}; @@ -164,3 +164,16 @@ pub fn custom_test_ext(initial_header: Header, initial_validators: Vec
) .unwrap(); sp_io::TestExternalities::new(t) } + +/// Insert header into storage. +pub fn insert_header(storage: &mut S, header: Header) { + storage.insert_header(HeaderToImport { + context: storage.import_context(None, &header.parent_hash).unwrap(), + is_best: true, + hash: header.hash(), + header, + total_difficulty: 0.into(), + enacted_change: None, + scheduled_change: None, + }); +} diff --git a/modules/ethereum/src/verification.rs b/modules/ethereum/src/verification.rs index 16e232ef1d7..de1cb162357 100644 --- a/modules/ethereum/src/verification.rs +++ b/modules/ethereum/src/verification.rs @@ -340,8 +340,8 @@ fn find_next_validators_signal(storage: &S, context: &ImportContext< mod tests { use super::*; use crate::mock::{ - block_i, custom_block_i, custom_test_ext, genesis, signed_header, test_aura_config, validator, - validators_addresses, AccountId, TestRuntime, + block_i, custom_block_i, custom_test_ext, genesis, insert_header, signed_header, + test_aura_config, validator, validators_addresses, AccountId, TestRuntime, }; use crate::validators::{tests::validators_change_recept, ValidatorsSource}; use crate::{ @@ -384,27 +384,12 @@ mod tests { let validators = vec![validator(0), validator(1), validator(2)]; let mut storage = BridgeStorage::::new(); let block1 = block_i(1, &validators); - storage.insert_header( - storage - .import_context(None, &block1.parent_hash) - .unwrap() - .into_import_header(true, block1.hash(), block1, 0.into(), None, None), - ); + insert_header(&mut storage, block1); let block2 = block_i(2, &validators); let block2_hash = block2.hash(); - storage.insert_header( - storage - .import_context(None, &block2.parent_hash) - .unwrap() - .into_import_header(true, block2.hash(), block2, 0.into(), None, None), - ); + insert_header(&mut storage, block2); let block3 = block_i(3, &validators); - storage.insert_header( - storage - .import_context(None, &block3.parent_hash) - .unwrap() - .into_import_header(true, block3.hash(), block3, 0.into(), None, None), - ); + insert_header(&mut storage, block3); FinalizedBlock::put((2, block2_hash)); From 743b7d2ab5522faa3ad44cb540dfdfa83760d9b9 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 1 Jun 2020 12:00:18 +0300 Subject: [PATCH 10/14] cargo fmt --all --- modules/ethereum/src/verification.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ethereum/src/verification.rs b/modules/ethereum/src/verification.rs index de1cb162357..567d14f3b54 100644 --- a/modules/ethereum/src/verification.rs +++ b/modules/ethereum/src/verification.rs @@ -340,8 +340,8 @@ fn find_next_validators_signal(storage: &S, context: &ImportContext< mod tests { use super::*; use crate::mock::{ - block_i, custom_block_i, custom_test_ext, genesis, insert_header, signed_header, - test_aura_config, validator, validators_addresses, AccountId, TestRuntime, + block_i, custom_block_i, custom_test_ext, genesis, insert_header, signed_header, test_aura_config, validator, + validators_addresses, AccountId, TestRuntime, }; use crate::validators::{tests::validators_change_recept, ValidatorsSource}; use crate::{ From fc63d3089106a9aad1db487a203de6f6cdef07f5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 1 Jun 2020 10:35:20 +0300 Subject: [PATCH 11/14] finality cache --- bin/node/runtime/src/lib.rs | 5 +- modules/ethereum/src/finality.rs | 446 ++++++++++++++++++++------- modules/ethereum/src/import.rs | 72 +++-- modules/ethereum/src/lib.rs | 255 ++++++++++++--- modules/ethereum/src/mock.rs | 10 +- modules/ethereum/src/validators.rs | 124 +++++++- modules/ethereum/src/verification.rs | 80 ++--- primitives/ethereum-poa/src/lib.rs | 23 +- 8 files changed, 769 insertions(+), 246 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ff4d796cf93..13c7f424647 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -207,12 +207,14 @@ impl pallet_aura::Trait for Runtime { } parameter_types! { + pub const FinalityVotesCachingInterval: Option = Some(16); pub const KovanAuraConfiguration: pallet_bridge_eth_poa::AuraConfiguration = kovan::kovan_aura_configuration(); pub const KovanValidatorsConfiguration: pallet_bridge_eth_poa::ValidatorsConfiguration = kovan::kovan_validators_configuration(); } impl pallet_bridge_eth_poa::Trait for Runtime { type AuraConfiguration = KovanAuraConfiguration; + type FinalityVotesCachingInterval = FinalityVotesCachingInterval; type ValidatorsConfiguration = KovanValidatorsConfiguration; type OnHeadersSubmitted = (); } @@ -434,7 +436,8 @@ impl_runtime_apis! { impl sp_bridge_eth_poa::EthereumHeadersApi for Runtime { fn best_block() -> (u64, sp_bridge_eth_poa::H256) { - BridgeEthPoA::best_block() + let best_block = BridgeEthPoA::best_block(); + (best_block.number, best_block.hash) } fn is_import_requires_receipts(header: sp_bridge_eth_poa::Header) -> bool { diff --git a/modules/ethereum/src/finality.rs b/modules/ethereum/src/finality.rs index 676108a30dc..9cf43379cae 100644 --- a/modules/ethereum/src/finality.rs +++ b/modules/ethereum/src/finality.rs @@ -16,55 +16,108 @@ use crate::error::Error; use crate::Storage; -use primitives::{public_to_address, Address, Header, SealedEmptyStep, H256}; +use codec::{Decode, Encode}; +use primitives::{public_to_address, Address, Header, HeaderId, SealedEmptyStep, H256}; use sp_io::crypto::secp256k1_ecdsa_recover; -use sp_std::prelude::*; -use sp_std::{ - collections::{ - btree_map::{BTreeMap, Entry}, - btree_set::BTreeSet, - vec_deque::VecDeque, - }, - iter::from_fn, +use sp_runtime::RuntimeDebug; +use sp_std::collections::{ + btree_map::{BTreeMap, Entry}, + btree_set::BTreeSet, + vec_deque::VecDeque, }; +use sp_std::prelude::*; + +/// Cached finality votes for given block. +#[derive(RuntimeDebug, Default)] +#[cfg_attr(test, derive(PartialEq))] +pub struct CachedFinalityVotes { + /// Header ancestors that were read while we have been searching for + /// cached votes entry. Newest header has index 0. + pub unaccounted_ancestry: VecDeque<(HeaderId, Option, Header)>, + /// Cached finality votes, if they have been found. The associated + /// header is not included into `unaccounted_ancestry`. + pub votes: Option>, +} + +/// Finality effects. +#[derive(RuntimeDebug)] +#[cfg_attr(test, derive(PartialEq))] +pub struct FinalityEffects { + /// Finalized headers. + pub finalized_headers: Vec<(HeaderId, Option)>, + /// Finality votes used in computation. + pub votes: FinalityVotes, +} + +/// Finality votes for given block. +#[derive(RuntimeDebug, Decode, Encode)] +#[cfg_attr(test, derive(Clone, Default, PartialEq))] +pub struct FinalityVotes { + /// Number of votes per each validator. + pub votes: BTreeMap, + /// Ancestry blocks with oldest ancestors at the beginning and newest at the + /// end of the queue. + pub ancestry: VecDeque>, +} + +/// Information about block ancestor that is used in computations. +#[derive(RuntimeDebug, Decode, Encode)] +#[cfg_attr(test, derive(Clone, Default, PartialEq))] +pub struct FinalityAncestor { + /// Bock id. + pub id: HeaderId, + /// Block submitter. + pub submitter: Option, + /// Validators that have signed this block and empty steps on top + /// of this block. + pub signers: BTreeSet
, +} /// Tries to finalize blocks when given block is imported. /// /// Returns numbers and hashes of finalized blocks in ascending order. pub fn finalize_blocks( storage: &S, - best_finalized_hash: &H256, - header_validators: (&H256, &[Address]), - hash: &H256, + best_finalized: HeaderId, + header_validators: (HeaderId, &[Address]), + id: HeaderId, submitter: Option<&S::Submitter>, header: &Header, two_thirds_majority_transition: u64, -) -> Result)>, Error> { +) -> Result, Error> { // compute count of voters for every unfinalized block in ancestry let validators = header_validators.1.iter().collect(); - let (mut votes, mut headers) = prepare_votes( - storage, - best_finalized_hash, - &header_validators.0, + let votes = prepare_votes( + storage.cached_finality_votes(&header.parent_hash, |hash| { + *hash == header_validators.0.hash || *hash == best_finalized.hash + }), + best_finalized.number, &validators, - hash, + id, header, - submitter, - two_thirds_majority_transition, + submitter.cloned(), )?; // now let's iterate in reverse order && find just finalized blocks - let mut newly_finalized = Vec::new(); - while let Some((oldest_hash, oldest_number, submitter, signers)) = headers.pop_front() { - if !is_finalized(&validators, &votes, oldest_number >= two_thirds_majority_transition) { + let mut finalized_headers = Vec::new(); + let mut current_votes = votes.votes.clone(); + for ancestor in &votes.ancestry { + if !is_finalized( + &validators, + ¤t_votes, + ancestor.id.number >= two_thirds_majority_transition, + ) { break; } - remove_signers_votes(&signers, &mut votes); - newly_finalized.push((oldest_number, oldest_hash, submitter)); + remove_signers_votes(&ancestor.signers, &mut current_votes); + finalized_headers.push((ancestor.id, ancestor.submitter.clone())); } - Ok(newly_finalized) + Ok(FinalityEffects { + finalized_headers, + votes, + }) } /// Returns true if there are enough votes to treat this header as finalized. @@ -78,67 +131,69 @@ fn is_finalized( } /// Prepare 'votes' of header and its ancestors' signers. -fn prepare_votes( - storage: &S, - best_finalized_hash: &H256, - validators_begin: &H256, +fn prepare_votes( + mut cached_votes: CachedFinalityVotes, + best_finalized_number: u64, validators: &BTreeSet<&Address>, - hash: &H256, + id: HeaderId, header: &Header, - submitter: Option<&S::Submitter>, - two_thirds_majority_transition: u64, -) -> Result< - ( - BTreeMap, - VecDeque<(H256, u64, Option, BTreeSet
)>, - ), - Error, -> { + submitter: Option, +) -> Result, Error> { // this fn can only work with single validators set if !validators.contains(&header.author) { return Err(Error::NotValidator); } - // prepare iterator of signers of all ancestors of the header - // we only take ancestors that are not yet pruned and those signed by - // the same set of validators - let mut parent_empty_step_signers = empty_steps_signers(header); - let ancestry = ancestry(storage, header) - .map(|(hash, header, submitter)| { - let mut signers = BTreeSet::new(); - sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers); - signers.insert(header.author); - - let empty_step_signers = empty_steps_signers(&header); - let res = (hash, header.number, submitter, signers); - parent_empty_step_signers = empty_step_signers; - res - }) - .take_while(|&(hash, _, _, _)| hash != *validators_begin && hash != *best_finalized_hash); - - // now let's iterate built iterator and compute number of validators - // 'voted' for each header - // we stop when finalized block is met (because we only interested in - // just finalized blocks) - let mut votes = BTreeMap::new(); - let mut headers = VecDeque::new(); - for (hash, number, submitter, signers) in ancestry { - add_signers_votes(validators, &signers, &mut votes)?; - if is_finalized(validators, &votes, number >= two_thirds_majority_transition) { - remove_signers_votes(&signers, &mut votes); + // now we have votes that were valid when some block B has been inserted + // things may have changed a bit, but we do not need to read anything else + // from the db, because we have ancestry + // so the only thing we need to do is: + // 1) remove votes from blocks that have been finalized after B has been inserted; + // 2) add votes from B descendants + let mut votes = cached_votes.votes.unwrap_or_else(|| FinalityVotes { + votes: BTreeMap::new(), + ancestry: VecDeque::new(), + }); + + // remove votes from finalized blocks + while let Some(old_ancestor) = votes.ancestry.pop_front() { + if old_ancestor.id.number > best_finalized_number { + votes.ancestry.push_front(old_ancestor); break; } - headers.push_front((hash, number, submitter, signers)); + remove_signers_votes(&old_ancestor.signers, &mut votes.votes); + } + + // add votes from new blocks + let mut parent_empty_step_signers = empty_steps_signers(header); + let mut unaccounted_ancestry = VecDeque::new(); + while let Some((ancestor_id, ancestor_submitter, ancestor)) = cached_votes.unaccounted_ancestry.pop_front() { + let mut signers = empty_steps_signers(&ancestor); + sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers); + signers.insert(ancestor.author); + + add_signers_votes(validators, &signers, &mut votes.votes)?; + + unaccounted_ancestry.push_front(FinalityAncestor { + id: ancestor_id, + submitter: ancestor_submitter, + signers, + }); } + votes.ancestry.extend(unaccounted_ancestry); - // update votes with last header vote + // add votes from block itself let mut header_signers = BTreeSet::new(); header_signers.insert(header.author); - *votes.entry(header.author).or_insert(0) += 1; - headers.push_back((*hash, header.number, submitter.cloned(), header_signers)); + *votes.votes.entry(header.author).or_insert(0) += 1; + votes.ancestry.push_back(FinalityAncestor { + id, + submitter, + signers: header_signers, + }); - Ok((votes, headers)) + Ok(votes) } /// Increase count of 'votes' for every passed signer. @@ -193,29 +248,12 @@ fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option .map(|public| public_to_address(&public)) } -/// Return iterator of given header ancestors. -pub(crate) fn ancestry<'a, S: Storage>( - storage: &'a S, - header: &Header, -) -> impl Iterator)> + 'a { - let mut parent_hash = header.parent_hash.clone(); - from_fn(move || { - let (header, submitter) = storage.header(&parent_hash)?; - if header.number == 0 { - return None; - } - - let hash = parent_hash.clone(); - parent_hash = header.parent_hash.clone(); - Some((hash, header, submitter)) - }) -} - #[cfg(test)] mod tests { use super::*; - use crate::mock::{custom_test_ext, genesis, validator, validators_addresses, TestRuntime}; - use crate::{BridgeStorage, HeaderToImport}; + use crate::mock::{custom_test_ext, genesis, insert_header, validator, validators_addresses, TestRuntime}; + use crate::{BridgeStorage, FinalityCache, HeaderToImport}; + use frame_support::StorageMap; #[test] fn verifies_header_author() { @@ -223,9 +261,9 @@ mod tests { assert_eq!( finalize_blocks( &BridgeStorage::::new(), - &Default::default(), - (&Default::default(), &[]), - &Default::default(), + Default::default(), + (Default::default(), &[]), + Default::default(), None, &Header::default(), 0, @@ -236,7 +274,7 @@ mod tests { } #[test] - fn prepares_votes() { + fn finalize_blocks_works() { custom_test_ext(genesis(), validators_addresses(5)).execute_with(|| { // let's say we have 5 validators (we need 'votes' from 3 validators to achieve // finality) @@ -245,30 +283,32 @@ mod tests { // when header#1 is inserted, nothing is finalized (1 vote) let header1 = Header { author: validator(0).address().as_fixed_bytes().into(), - parent_hash: genesis().hash(), + parent_hash: genesis().compute_hash(), number: 1, ..Default::default() }; - let hash1 = header1.hash(); + let id1 = header1.compute_id(); let mut header_to_import = HeaderToImport { - context: storage.import_context(None, &genesis().hash()).unwrap(), + context: storage.import_context(None, &genesis().compute_hash()).unwrap(), is_best: true, - hash: hash1, + id: id1, header: header1, total_difficulty: 0.into(), enacted_change: None, scheduled_change: None, + finality_votes: Default::default(), }; assert_eq!( finalize_blocks( &storage, - &Default::default(), - (&Default::default(), &validators_addresses(5)), - &hash1, + Default::default(), + (Default::default(), &validators_addresses(5)), + id1, None, &header_to_import.header, u64::max_value(), - ), + ) + .map(|eff| eff.finalized_headers), Ok(Vec::new()), ); storage.insert_header(header_to_import.clone()); @@ -276,22 +316,23 @@ mod tests { // when header#2 is inserted, nothing is finalized (2 votes) header_to_import.header = Header { author: validator(1).address().as_fixed_bytes().into(), - parent_hash: hash1, + parent_hash: id1.hash, number: 2, ..Default::default() }; - header_to_import.hash = header_to_import.header.hash(); - let hash2 = header_to_import.header.hash(); + let id2 = header_to_import.header.compute_id(); + header_to_import.id = id2; assert_eq!( finalize_blocks( &storage, - &Default::default(), - (&Default::default(), &validators_addresses(5)), - &hash2, + Default::default(), + (Default::default(), &validators_addresses(5)), + id2, None, &header_to_import.header, u64::max_value(), - ), + ) + .map(|eff| eff.finalized_headers), Ok(Vec::new()), ); storage.insert_header(header_to_import.clone()); @@ -299,25 +340,192 @@ mod tests { // when header#3 is inserted, header#1 is finalized (3 votes) header_to_import.header = Header { author: validator(2).address().as_fixed_bytes().into(), - parent_hash: hash2, + parent_hash: id2.hash, number: 3, ..Default::default() }; - header_to_import.hash = header_to_import.header.hash(); - let hash3 = header_to_import.header.hash(); + let id3 = header_to_import.header.compute_id(); + header_to_import.id = id3; assert_eq!( finalize_blocks( &storage, - &Default::default(), - (&Default::default(), &validators_addresses(5)), - &hash3, + Default::default(), + (Default::default(), &validators_addresses(5)), + id3, None, &header_to_import.header, u64::max_value(), - ), - Ok(vec![(1, hash1, None)]), + ) + .map(|eff| eff.finalized_headers), + Ok(vec![(id1, None)]), ); storage.insert_header(header_to_import); }); } + + #[test] + fn cached_votes_are_updated_with_ancestry() { + // we're inserting header#5 + // cached votes are from header#3 + // header#4 has finalized header#1 and header#2 + // => when inserting header#5, we need to: + // 1) remove votes from header#1 and header#2 + // 2) add votes from header#4 and header#5 + let validators = validators_addresses(5); + let headers = (1..6) + .map(|number| Header { + number: number, + author: validators[number as usize - 1], + ..Default::default() + }) + .collect::>(); + let ancestry = headers + .iter() + .map(|header| FinalityAncestor { + id: header.compute_id(), + signers: vec![header.author].into_iter().collect(), + ..Default::default() + }) + .collect::>(); + let header5 = headers[4].clone(); + assert_eq!( + prepare_votes::<()>( + CachedFinalityVotes { + unaccounted_ancestry: vec![(headers[3].compute_id(), None, headers[3].clone()),] + .into_iter() + .collect(), + votes: Some(FinalityVotes { + votes: vec![(validators[0], 1), (validators[1], 1), (validators[2], 1),] + .into_iter() + .collect(), + ancestry: ancestry[..3].iter().cloned().collect(), + }), + }, + 2, + &validators.iter().collect(), + header5.compute_id(), + &header5, + None, + ) + .unwrap(), + FinalityVotes { + votes: vec![(validators[2], 1), (validators[3], 1), (validators[4], 1),] + .into_iter() + .collect(), + ancestry: ancestry[2..].iter().cloned().collect(), + }, + ); + } + + #[test] + fn prepare_votes_respects_finality_cache() { + let validators_addresses = validators_addresses(5); + custom_test_ext(genesis(), validators_addresses.clone()).execute_with(move || { + // we need signatures of 3 validators to finalize block + let mut storage = BridgeStorage::::new(); + + // headers 1..3 are signed by validator#0 + // headers 4..6 are signed by validator#1 + // headers 7..9 are signed by validator#2 + let mut hashes = Vec::new(); + let mut headers = Vec::new(); + let mut ancestry = Vec::new(); + let mut parent_hash = genesis().compute_hash(); + for i in 1..10 { + let header = Header { + author: validator((i - 1) / 3).address().as_fixed_bytes().into(), + parent_hash, + number: i as _, + ..Default::default() + }; + let id = header.compute_id(); + insert_header(&mut storage, header.clone()); + hashes.push(id.hash); + ancestry.push(FinalityAncestor { + id: header.compute_id(), + submitter: None, + signers: vec![header.author].into_iter().collect(), + }); + headers.push(header); + parent_hash = id.hash; + } + + // when we're inserting header#7 and last finalized header is 0: + // check that votes at #7 are computed correctly without cache + let expected_votes_at_7 = FinalityVotes { + votes: vec![ + (validators_addresses[0].clone(), 3), + (validators_addresses[1].clone(), 3), + (validators_addresses[2].clone(), 1), + ] + .into_iter() + .collect(), + ancestry: ancestry[..7].iter().cloned().collect(), + }; + let id7 = headers[6].compute_id(); + assert_eq!( + prepare_votes( + storage.cached_finality_votes(&hashes.get(5).unwrap(), |_| false,), + 0, + &validators_addresses.iter().collect(), + id7, + headers.get(6).unwrap(), + None, + ) + .unwrap(), + expected_votes_at_7, + ); + + // cached votes at #5 + let expected_votes_at_5 = FinalityVotes { + votes: vec![ + (validators_addresses[0].clone(), 3), + (validators_addresses[1].clone(), 2), + ] + .into_iter() + .collect(), + ancestry: ancestry[..5].iter().cloned().collect(), + }; + FinalityCache::::insert(hashes[4], expected_votes_at_5); + + // when we're inserting header#7 and last finalized header is 0: + // check that votes at #7 are computed correctly with cache + assert_eq!( + prepare_votes( + storage.cached_finality_votes(&hashes.get(5).unwrap(), |_| false,), + 0, + &validators_addresses.iter().collect(), + id7, + headers.get(6).unwrap(), + None, + ) + .unwrap(), + expected_votes_at_7, + ); + + // when we're inserting header#7 and last finalized header is 3: + // check that votes at #7 are computed correctly with cache + let expected_votes_at_7 = FinalityVotes { + votes: vec![ + (validators_addresses[1].clone(), 3), + (validators_addresses[2].clone(), 1), + ] + .into_iter() + .collect(), + ancestry: ancestry[3..7].iter().cloned().collect(), + }; + assert_eq!( + prepare_votes( + storage.cached_finality_votes(&hashes.get(5).unwrap(), |hash| *hash == hashes[2],), + 3, + &validators_addresses.iter().collect(), + id7, + headers.get(6).unwrap(), + None, + ) + .unwrap(), + expected_votes_at_7, + ); + }); + } } diff --git a/modules/ethereum/src/import.rs b/modules/ethereum/src/import.rs index 17c7237fab6..23ffd402e00 100644 --- a/modules/ethereum/src/import.rs +++ b/modules/ethereum/src/import.rs @@ -19,7 +19,7 @@ use crate::finality::finalize_blocks; use crate::validators::{Validators, ValidatorsConfiguration}; use crate::verification::{is_importable_header, verify_aura_header}; use crate::{AuraConfiguration, ChangeToEnact, Storage}; -use primitives::{Header, Receipt, H256}; +use primitives::{Header, HeaderId, Receipt}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; /// Maximal number of headers behind best blocks that we are aiming to store. When there @@ -64,7 +64,7 @@ pub fn import_headers( match import_result { Ok((_, finalized)) => { - for (_, _, submitter) in finalized { + for (_, submitter) in finalized { if let Some(submitter) = submitter { *finalized_headers.entry(submitter).or_default() += 1; } @@ -84,7 +84,7 @@ pub fn import_headers( /// Transactions receipts must be provided if `header_import_requires_receipts()` /// has returned true. /// -/// Returns imported block hash. +/// Returns imported block id and list of all finalized headers. pub fn import_header( storage: &mut S, aura_config: &AuraConfiguration, @@ -93,9 +93,9 @@ pub fn import_header( submitter: Option, header: Header, receipts: Option>, -) -> Result<(H256, Vec<(u64, H256, Option)>), Error> { +) -> Result<(HeaderId, Vec<(HeaderId, Option)>), Error> { // first check that we are able to import this header at all - let (hash, prev_finalized_hash) = is_importable_header(storage, &header)?; + let (header_id, finalized_id) = is_importable_header(storage, &header)?; // verify header let import_context = verify_aura_header(storage, aura_config, submitter, &header)?; @@ -108,9 +108,9 @@ pub fn import_header( let validators_set = import_context.validators_set(); let finalized_blocks = finalize_blocks( storage, - &prev_finalized_hash, - (&validators_set.enact_block, &validators_set.validators), - &hash, + finalized_id, + (validators_set.enact_block, &validators_set.validators), + header_id, import_context.submitter(), &header, aura_config.two_thirds_majority_transition, @@ -120,35 +120,36 @@ pub fn import_header( signal_block: None, validators, }) - .or_else(|| validators.finalize_validators_change(storage, &finalized_blocks)); + .or_else(|| validators.finalize_validators_change(storage, &finalized_blocks.finalized_headers)); // NOTE: we can't return Err() from anywhere below this line // (because otherwise we'll have inconsistent storage if transaction will fail) // and finally insert the block - let (_, _, best_total_difficulty) = storage.best_block(); + let (_, best_total_difficulty) = storage.best_block(); let total_difficulty = import_context.total_difficulty() + header.difficulty; let is_best = total_difficulty > best_total_difficulty; let header_number = header.number; storage.insert_header(import_context.into_import_header( is_best, - hash, + header_id, header, total_difficulty, enacted_change, scheduled_change, + finalized_blocks.votes, )); // now mark finalized headers && prune old headers storage.finalize_headers( - finalized_blocks.last().map(|(number, hash, _)| (*number, *hash)), + finalized_blocks.finalized_headers.last().map(|(id, _)| *id), match is_best { true => header_number.checked_sub(prune_depth), false => None, }, ); - Ok((hash, finalized_blocks)) + Ok((header_id, finalized_blocks.finalized_headers)) } /// Returns true if transactions receipts are required to import given header. @@ -178,7 +179,13 @@ mod tests { fn rejects_finalized_block_competitors() { custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { let mut storage = BridgeStorage::::new(); - storage.finalize_headers(Some((100, Default::default())), None); + storage.finalize_headers( + Some(HeaderId { + number: 100, + ..Default::default() + }), + None, + ); assert_eq!( import_header( &mut storage, @@ -239,7 +246,7 @@ mod tests { let validators = validators(3); let mut storage = BridgeStorage::::new(); let header = block_i(1, &validators); - let hash = header.hash(); + let hash = header.compute_hash(); assert_eq!( import_header( &mut storage, @@ -273,10 +280,10 @@ mod tests { // header [0..11] are finalizing blocks [0; 9] // => since we want to keep 10 finalized blocks, we aren't pruning anything - let mut latest_block_hash = Default::default(); + let mut latest_block_id = Default::default(); for i in 1..11 { let header = block_i(i, &validators); - let (rolling_last_block_hash, finalized_blocks) = import_header( + let (rolling_last_block_id, finalized_blocks) = import_header( &mut storage, &test_aura_config(), &validators_config, @@ -289,15 +296,15 @@ mod tests { match i { 2..=10 => assert_eq!( finalized_blocks, - vec![(i - 1, block_i(i - 1, &validators).hash(), Some(100))], + vec![(block_i(i - 1, &validators).compute_id(), Some(100))], "At {}", i, ), _ => assert_eq!(finalized_blocks, vec![], "At {}", i), } - latest_block_hash = rolling_last_block_hash; + latest_block_id = rolling_last_block_id; } - assert!(storage.header(&genesis().hash()).is_some()); + assert!(storage.header(&genesis().compute_hash()).is_some()); // header 11 finalizes headers [10] AND schedules change // => we prune header#0 @@ -307,7 +314,7 @@ mod tests { .parse() .unwrap(); }); - let (rolling_last_block_hash, finalized_blocks) = import_header( + let (rolling_last_block_id, finalized_blocks) = import_header( &mut storage, &test_aura_config(), &validators_config, @@ -315,23 +322,26 @@ mod tests { Some(101), header11.clone(), Some(vec![crate::validators::tests::validators_change_recept( - latest_block_hash, + latest_block_id.hash, )]), ) .unwrap(); - assert_eq!(finalized_blocks, vec![(10, block_i(10, &validators).hash(), Some(100))],); - assert!(storage.header(&genesis().hash()).is_none()); - latest_block_hash = rolling_last_block_hash; + assert_eq!( + finalized_blocks, + vec![(block_i(10, &validators).compute_id(), Some(100))], + ); + assert!(storage.header(&genesis().compute_hash()).is_none()); + latest_block_id = rolling_last_block_id; // and now let's say validators 1 && 2 went offline // => in the range 12-25 no blocks are finalized, but we still continue to prune old headers // until header#11 is met. we can't prune #11, because it schedules change let mut step = 56; - let mut expected_blocks = vec![(11, header11.hash(), Some(101))]; + let mut expected_blocks = vec![(header11.compute_id(), Some(101))]; for i in 12..25 { let header = Header { number: i as _, - parent_hash: latest_block_hash, + parent_hash: latest_block_id.hash, gas_limit: 0x2000.into(), author: validator(2).address(), seal: vec![vec![step].into(), vec![].into()], @@ -339,8 +349,8 @@ mod tests { ..Default::default() }; let header = signed_header(&validators, header, step as _); - expected_blocks.push((i, header.hash(), Some(102))); - let (rolling_last_block_hash, finalized_blocks) = import_header( + expected_blocks.push((header.compute_id(), Some(102))); + let (rolling_last_block_id, finalized_blocks) = import_header( &mut storage, &test_aura_config(), &validators_config, @@ -351,7 +361,7 @@ mod tests { ) .unwrap(); assert_eq!(finalized_blocks, vec![],); - latest_block_hash = rolling_last_block_hash; + latest_block_id = rolling_last_block_id; step += 3; } assert_eq!(OldestUnprunedBlock::get(), 11); @@ -361,7 +371,7 @@ mod tests { step -= 2; let header = Header { number: 25, - parent_hash: latest_block_hash, + parent_hash: latest_block_id.hash, gas_limit: 0x2000.into(), author: validator(0).address(), seal: vec![vec![step].into(), vec![].into()], diff --git a/modules/ethereum/src/lib.rs b/modules/ethereum/src/lib.rs index 376d800d34d..912ccbaaf62 100644 --- a/modules/ethereum/src/lib.rs +++ b/modules/ethereum/src/lib.rs @@ -16,9 +16,10 @@ #![cfg_attr(not(feature = "std"), no_std)] +use crate::finality::{CachedFinalityVotes, FinalityVotes}; use codec::{Decode, Encode}; use frame_support::{decl_module, decl_storage, traits::Get}; -use primitives::{Address, Header, Receipt, H256, U256}; +use primitives::{Address, Header, HeaderId, Receipt, H256, U256}; use sp_runtime::{ transaction_validity::{ InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionSource, TransactionValidity, @@ -90,7 +91,7 @@ pub struct StoredHeader { /// Hash of the last block which has **SCHEDULED** validators set change. /// Note that signal doesn't mean that the set has been (or ever will be) enacted. /// Note that the header may already be pruned. - pub last_signal_block: Option, + pub last_signal_block: Option, } /// Validators set as it is stored in the runtime storage. @@ -100,9 +101,9 @@ pub struct ValidatorsSet { /// Validators of this set. pub validators: Vec
, /// Hash of the block where this set has been signalled. None if this is the first set. - pub signal_block: Option, + pub signal_block: Option, /// Hash of the block where this set has been enacted. - pub enact_block: H256, + pub enact_block: HeaderId, } /// Validators set change as it is stored in the runtime storage. @@ -112,7 +113,7 @@ pub struct ScheduledChange { /// Validators of this set. pub validators: Vec
, /// Hash of the block which has emitted previous validators change signal. - pub prev_signal_block: Option, + pub prev_signal_block: Option, } /// Header that we're importing. @@ -123,8 +124,8 @@ pub struct HeaderToImport { pub context: ImportContext, /// Should we consider this header as best? pub is_best: bool, - /// The hash of the header. - pub hash: H256, + /// The id of the header. + pub id: HeaderId, /// The header itself. pub header: Header, /// Total chain difficulty at the header. @@ -134,15 +135,17 @@ pub struct HeaderToImport { pub enacted_change: Option, /// Validators set scheduled change, if happened at the header. pub scheduled_change: Option>, + /// Finality votes at this header. + pub finality_votes: FinalityVotes, } /// Header that we're importing. #[derive(RuntimeDebug)] #[cfg_attr(test, derive(Clone, PartialEq))] pub struct ChangeToEnact { - /// The hash of the header where change has been scheduled. + /// The id of the header where change has been scheduled. /// None if it is a first set within current `ValidatorsSource`. - pub signal_block: Option, + pub signal_block: Option, /// Validators set that is enacted. pub validators: Vec
, } @@ -163,7 +166,7 @@ pub struct ImportContext { parent_scheduled_change: Option, validators_set_id: u64, validators_set: ValidatorsSet, - last_signal_block: Option, + last_signal_block: Option, } impl ImportContext { @@ -199,10 +202,13 @@ impl ImportContext { /// Returns reference to the latest block which has signalled change of validators set. /// This may point to parent if parent has signalled change. - pub fn last_signal_block(&self) -> Option<&H256> { + pub fn last_signal_block(&self) -> Option { match self.parent_scheduled_change { - Some(_) => Some(&self.parent_hash), - None => self.last_signal_block.as_ref(), + Some(_) => Some(HeaderId { + number: self.parent_header.number, + hash: self.parent_hash, + }), + None => self.last_signal_block, } } @@ -210,20 +216,22 @@ impl ImportContext { pub fn into_import_header( self, is_best: bool, - hash: H256, + id: HeaderId, header: Header, total_difficulty: U256, enacted_change: Option, scheduled_change: Option>, + finality_votes: FinalityVotes, ) -> HeaderToImport { HeaderToImport { context: self, is_best, - hash, + id, header, total_difficulty, enacted_change, scheduled_change, + finality_votes, } } } @@ -235,14 +243,22 @@ pub trait Storage { /// Header submitter identifier. type Submitter: Clone + Ord; - /// Get best known block. - fn best_block(&self) -> (u64, H256, U256); + /// Get best known block and total chain difficulty. + fn best_block(&self) -> (HeaderId, U256); /// Get last finalized block. - fn finalized_block(&self) -> (u64, H256); + fn finalized_block(&self) -> HeaderId; /// Get imported header by its hash. /// /// Returns header and its submitter (if known). fn header(&self, hash: &H256) -> Option<(Header, Option)>; + /// Returns latest cached finality votes (if any) for block ancestors, starting + /// from `parent_hash` block and stopping at genesis block, or block where `stop_at` + /// returns true. + fn cached_finality_votes( + &self, + parent_hash: &H256, + stop_at: impl Fn(&H256) -> bool, + ) -> CachedFinalityVotes; /// Get header import context by parent header hash. fn import_context( &self, @@ -259,7 +275,7 @@ pub trait Storage { /// It is the storage duty to ensure that unfinalized headers that have /// scheduled changes won't be pruned until they or their competitors /// are finalized. - fn finalize_headers(&mut self, finalized: Option<(u64, H256)>, prune_end: Option); + fn finalize_headers(&mut self, finalized: Option, prune_end: Option); } /// Decides whether the session should be ended. @@ -286,10 +302,17 @@ impl OnHeadersSubmitted for () { fn on_valid_headers_finalized(_submitter: AccountId, _finalized: u64) {} } -/// The module configuration trait +/// The module configuration trait. pub trait Trait: frame_system::Trait { /// Aura configuration. type AuraConfiguration: Get; + /// Interval (in blocks) for for finality votes caching. + /// If None, cache is disabled. + /// + /// Ideally, this should either be None (when we are sure that there won't + /// be any significant finalization delays), or something that is bit larger + /// than average finalization delay. + type FinalityVotesCachingInterval: Get>; /// Validators configuration. type ValidatorsConfiguration: Get; /// Handler for headers submission result. @@ -361,15 +384,17 @@ decl_module! { decl_storage! { trait Store for Module as Bridge { /// Best known block. - BestBlock: (u64, H256, U256); + BestBlock: (HeaderId, U256); /// Best finalized block. - FinalizedBlock: (u64, H256); - /// Oldest unpruned block(s) number. + FinalizedBlock: HeaderId; + /// Number of oldest block that we want to prune. OldestUnprunedBlock: u64; /// Map of imported headers by hash. Headers: map hasher(identity) H256 => Option>; /// Map of imported header hashes by number. HeadersByNumber: map hasher(blake2_128_concat) u64 => Option>; + /// Map of cached finality data by header hash. + FinalityCache: map hasher(identity) H256 => Option>; /// The ID of next validator set. NextValidatorsSetId: u64; /// Map of validators sets by their id. @@ -396,10 +421,14 @@ decl_storage! { "Initial validators set can't be empty", ); - let initial_hash = config.initial_header.hash(); - BestBlock::put((config.initial_header.number, initial_hash, config.initial_difficulty)); - FinalizedBlock::put((config.initial_header.number, initial_hash)); - OldestUnprunedBlock::put(config.initial_header.number); + let initial_hash = config.initial_header.compute_hash(); + let initial_id = HeaderId { + number: config.initial_header.number, + hash: initial_hash, + }; + BestBlock::put((initial_id, config.initial_difficulty)); + FinalizedBlock::put(initial_id); + OldestUnprunedBlock::put(initial_id.number); HeadersByNumber::insert(config.initial_header.number, vec![initial_hash]); Headers::::insert(initial_hash, StoredHeader { submitter: None, @@ -412,7 +441,7 @@ decl_storage! { ValidatorsSets::insert(0, ValidatorsSet { validators: config.initial_validators.clone(), signal_block: None, - enact_block: initial_hash, + enact_block: initial_id, }); ValidatorsSetsRc::insert(0, 1); }) @@ -423,9 +452,8 @@ impl Module { /// Returns number and hash of the best block known to the bridge module. /// The caller should only submit `import_header` transaction that makes /// (or leads to making) other header the best one. - pub fn best_block() -> (u64, H256) { - let (number, hash, _) = BridgeStorage::::new().best_block(); - (number, hash) + pub fn best_block() -> HeaderId { + BridgeStorage::::new().best_block().0 } /// Returns true if the import of given block requires transactions receipts. @@ -489,11 +517,11 @@ impl BridgeStorage { impl Storage for BridgeStorage { type Submitter = T::AccountId; - fn best_block(&self) -> (u64, H256, U256) { + fn best_block(&self) -> (HeaderId, U256) { BestBlock::get() } - fn finalized_block(&self) -> (u64, H256) { + fn finalized_block(&self) -> HeaderId { FinalizedBlock::get() } @@ -501,6 +529,41 @@ impl Storage for BridgeStorage { Headers::::get(hash).map(|header| (header.header, header.submitter)) } + fn cached_finality_votes( + &self, + parent_hash: &H256, + stop_at: impl Fn(&H256) -> bool, + ) -> CachedFinalityVotes { + let mut votes = CachedFinalityVotes::default(); + let mut current_hash = *parent_hash; + loop { + if stop_at(¤t_hash) { + return votes; + } + + let cached_votes = FinalityCache::::get(¤t_hash); + if let Some(cached_votes) = cached_votes { + votes.votes = Some(cached_votes); + return votes; + } + + let header = match Headers::::get(¤t_hash) { + Some(header) if header.header.number != 0 => header, + _ => return votes, + }; + let parent_hash = header.header.parent_hash; + let current_id = HeaderId { + number: header.header.number, + hash: current_hash, + }; + votes + .unaccounted_ancestry + .push_back((current_id, header.submitter, header.header)); + + current_hash = parent_hash; + } + } + fn import_context( &self, submitter: Option, @@ -529,11 +592,11 @@ impl Storage for BridgeStorage { fn insert_header(&mut self, header: HeaderToImport) { if header.is_best { - BestBlock::put((header.header.number, header.hash, header.total_difficulty)); + BestBlock::put((header.id, header.total_difficulty)); } if let Some(scheduled_change) = header.scheduled_change { ScheduledChanges::insert( - &header.hash, + &header.id.hash, ScheduledChange { validators: scheduled_change, prev_signal_block: header.context.last_signal_block, @@ -551,7 +614,7 @@ impl Storage for BridgeStorage { next_validators_set_id, ValidatorsSet { validators: enacted_change.validators, - enact_block: header.hash, + enact_block: header.id, signal_block: enacted_change.signal_block, }, ); @@ -567,10 +630,18 @@ impl Storage for BridgeStorage { } }; - let last_signal_block = header.context.last_signal_block().cloned(); - HeadersByNumber::append_or_insert(header.header.number, vec![header.hash]); + let finality_votes_caching_interval = T::FinalityVotesCachingInterval::get(); + if let Some(finality_votes_caching_interval) = finality_votes_caching_interval { + let cache_entry_required = header.id.number != 0 && header.id.number % finality_votes_caching_interval == 0; + if cache_entry_required { + FinalityCache::::insert(header.id.hash, header.finality_votes); + } + } + + let last_signal_block = header.context.last_signal_block(); + HeadersByNumber::append_or_insert(header.id.number, vec![header.id.hash]); Headers::::insert( - &header.hash, + &header.id.hash, StoredHeader { submitter: header.context.submitter, header: header.header, @@ -581,19 +652,18 @@ impl Storage for BridgeStorage { ); } - fn finalize_headers(&mut self, finalized: Option<(u64, H256)>, prune_end: Option) { + fn finalize_headers(&mut self, finalized: Option, prune_end: Option) { // remember just finalized block let finalized_number = finalized .as_ref() - .map(|f| f.0) - .unwrap_or_else(|| FinalizedBlock::get().0); + .map(|f| f.number) + .unwrap_or_else(|| FinalizedBlock::get().number); if let Some(finalized) = finalized { FinalizedBlock::put(finalized); } if let Some(prune_end) = prune_end { let prune_begin = OldestUnprunedBlock::get(); - for number in prune_begin..prune_end { let blocks_at_number = HeadersByNumber::take(number); @@ -615,6 +685,7 @@ impl Storage for BridgeStorage { for hash in blocks_at_number.into_iter().flat_map(|x| x) { let header = Headers::::take(&hash); ScheduledChanges::remove(hash); + FinalityCache::::remove(hash); if let Some(header) = header { ValidatorsSetsRc::mutate(header.next_validators_set_id, |rc| match *rc { Some(rc) if rc > 1 => Some(rc - 1), @@ -635,3 +706,101 @@ fn pool_configuration() -> PoolConfiguration { max_future_number_difference: 10, } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::finality::FinalityAncestor; + use crate::mock::{ + block_i, custom_test_ext, genesis, insert_header, validators, validators_addresses, TestRuntime, + }; + + #[test] + fn finality_votes_are_cached() { + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let mut storage = BridgeStorage::::new(); + let interval = ::FinalityVotesCachingInterval::get().unwrap(); + + // for all headers with number < interval, cache entry is not created + let validators = validators(3); + for i in 1..interval { + let header = block_i(i, &validators); + let id = header.compute_id(); + insert_header(&mut storage, header); + assert_eq!(FinalityCache::::get(&id.hash), None); + } + + // for header with number = interval, cache entry is created + let header_with_entry = block_i(interval, &validators); + let header_with_entry_hash = header_with_entry.compute_hash(); + insert_header(&mut storage, header_with_entry); + assert_eq!( + FinalityCache::::get(&header_with_entry_hash), + Some(Default::default()), + ); + + // when we later prune this header, cache entry is removed + storage.finalize_headers(None, Some(interval + 1)); + assert_eq!(FinalityCache::::get(&header_with_entry_hash), None); + }); + } + + #[test] + fn cached_finality_votes_finds_entry() { + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + // insert 5 headers + let validators = validators(3); + let mut storage = BridgeStorage::::new(); + let mut headers = Vec::new(); + for i in 1..5 { + let header = block_i(i, &validators); + headers.push(header.clone()); + insert_header(&mut storage, header); + } + + // when inserting header#6, entry isn't found + let hash5 = headers.last().unwrap().compute_hash(); + assert_eq!( + storage.cached_finality_votes(&hash5, |_| false), + CachedFinalityVotes { + unaccounted_ancestry: headers + .iter() + .map(|header| (header.compute_id(), None, header.clone(),)) + .rev() + .collect(), + votes: None, + }, + ); + + // let's now create entry at #3 + let hash3 = headers[2].compute_hash(); + let votes_at_3 = FinalityVotes { + votes: vec![([42; 20].into(), 21)].into_iter().collect(), + ancestry: vec![FinalityAncestor { + id: HeaderId { + number: 100, + hash: Default::default(), + }, + ..Default::default() + }] + .into_iter() + .collect(), + }; + FinalityCache::::insert(hash3, votes_at_3.clone()); + + // searching at #6 again => entry is found + assert_eq!( + storage.cached_finality_votes(&hash5, |_| false), + CachedFinalityVotes { + unaccounted_ancestry: headers + .iter() + .skip(3) + .map(|header| (header.compute_id(), None, header.clone(),)) + .rev() + .collect(), + votes: Some(votes_at_3), + }, + ); + }); + } +} diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index 6624b149a90..2c24f7f7c62 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . +use crate::finality::FinalityVotes; use crate::validators::{ValidatorsConfiguration, ValidatorsSource}; use crate::{AuraConfiguration, GenesisConfig, HeaderToImport, HeadersByNumber, Storage, Trait}; use frame_support::StorageMap; @@ -26,6 +27,7 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, Perbill, }; +use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque}; pub type AccountId = u64; @@ -69,12 +71,14 @@ impl frame_system::Trait for TestRuntime { } parameter_types! { + pub const TestFinalityVotesCachingInterval: Option = Some(16); pub const TestAuraConfiguration: AuraConfiguration = test_aura_config(); pub const TestValidatorsConfiguration: ValidatorsConfiguration = test_validators_config(); } impl Trait for TestRuntime { type AuraConfiguration = TestAuraConfiguration; + type FinalityVotesCachingInterval = TestFinalityVotesCachingInterval; type ValidatorsConfiguration = TestValidatorsConfiguration; type OnHeadersSubmitted = (); } @@ -170,10 +174,14 @@ pub fn insert_header(storage: &mut S, header: Header) { storage.insert_header(HeaderToImport { context: storage.import_context(None, &header.parent_hash).unwrap(), is_best: true, - hash: header.hash(), + id: header.compute_id(), header, total_difficulty: 0.into(), enacted_change: None, scheduled_change: None, + finality_votes: FinalityVotes { + votes: BTreeMap::new(), + ancestry: VecDeque::new(), + }, }); } diff --git a/modules/ethereum/src/validators.rs b/modules/ethereum/src/validators.rs index 7a1fbb52f03..7360829ec54 100644 --- a/modules/ethereum/src/validators.rs +++ b/modules/ethereum/src/validators.rs @@ -16,7 +16,7 @@ use crate::error::Error; use crate::{ChangeToEnact, Storage}; -use primitives::{Address, Header, LogEntry, Receipt, H256, U256}; +use primitives::{Address, Header, HeaderId, LogEntry, Receipt, U256}; use sp_std::prelude::*; /// The hash of InitiateChange event of the validators set contract. @@ -181,18 +181,45 @@ impl<'a> Validators<'a> { /// Finalize changes when blocks are finalized. pub fn finalize_validators_change( &self, - storage: &mut S, - finalized_blocks: &[(u64, H256, Option)], + storage: &S, + finalized_blocks: &[(HeaderId, Option)], ) -> Option { - for (_, finalized_hash, _) in finalized_blocks.iter().rev() { - if let Some(changes) = storage.scheduled_change(finalized_hash) { - return Some(ChangeToEnact { - signal_block: Some(*finalized_hash), - validators: changes.validators, - }); - } - } - None + // if we haven't finalized any blocks, no changes may be finalized + let newest_finalized_id = match finalized_blocks.last().map(|(id, _)| id) { + Some(last_finalized_id) => last_finalized_id, + None => return None, + }; + let oldest_finalized_id = finalized_blocks + .first() + .map(|(id, _)| id) + .expect("finalized_blocks is not empty; qed"); + + // try to directly go to the header that has scheduled last change + // + // if we're unable to create import context for some block, it means + // that the header has already been pruned => it and its ancestors had + // no scheduled changes + // + // if we're unable to find scheduled changes for some block, it means + // that these changes have been finalized already + storage + .import_context(None, &newest_finalized_id.hash) + .and_then(|context| context.last_signal_block()) + .and_then(|signal_block| { + if signal_block.number >= oldest_finalized_id.number { + Some(signal_block) + } else { + None + } + }) + .and_then(|signal_block| { + storage + .scheduled_change(&signal_block.hash) + .map(|change| ChangeToEnact { + signal_block: Some(signal_block), + validators: change.validators, + }) + }) } /// Returns source of validators that should author the header. @@ -254,7 +281,10 @@ pub fn step_validator(header_validators: &[Address], header_step: u64) -> Addres #[cfg(test)] pub(crate) mod tests { use super::*; - use primitives::TransactionOutcome; + use crate::mock::{custom_test_ext, genesis, validators_addresses, TestRuntime}; + use crate::{BridgeStorage, Headers, ScheduledChange, ScheduledChanges, StoredHeader}; + use frame_support::StorageMap; + use primitives::{TransactionOutcome, H256}; pub(crate) fn validators_change_recept(parent_hash: H256) -> Receipt { Receipt { @@ -393,4 +423,72 @@ pub(crate) mod tests { Err(Error::TransactionsReceiptsMismatch), ); } + + fn try_finalize_with_scheduled_change(scheduled_at: Option) -> Option { + custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { + let config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new())); + let validators = Validators::new(&config); + let storage = BridgeStorage::::new(); + + // when we're finailizing blocks 10...100 + let id10 = HeaderId { + number: 10, + hash: [10; 32].into(), + }; + let id100 = HeaderId { + number: 100, + hash: [100; 32].into(), + }; + let finalized_blocks = vec![(id10, None), (id100, None)]; + let header100 = StoredHeader:: { + submitter: None, + header: Header { + number: 100, + ..Default::default() + }, + total_difficulty: 0.into(), + next_validators_set_id: 0, + last_signal_block: scheduled_at, + }; + let scheduled_change = ScheduledChange { + validators: validators_addresses(1), + prev_signal_block: None, + }; + Headers::::insert(id100.hash, header100); + if let Some(scheduled_at) = scheduled_at { + ScheduledChanges::insert(scheduled_at.hash, scheduled_change); + } + + validators.finalize_validators_change(&storage, &finalized_blocks) + }) + } + + #[test] + fn finalize_validators_change_finalizes_scheduled_change() { + let id50 = HeaderId { + number: 50, + ..Default::default() + }; + assert_eq!( + try_finalize_with_scheduled_change(Some(id50)), + Some(ChangeToEnact { + signal_block: Some(id50), + validators: validators_addresses(1), + }), + ); + } + + #[test] + fn finalize_validators_change_does_not_finalize_when_changes_are_not_scheduled() { + assert_eq!(try_finalize_with_scheduled_change(None), None,); + } + + #[test] + fn finalize_validators_change_does_not_finalize_changes_when_they_are_outside_of_range() { + let id5 = HeaderId { + number: 5, + ..Default::default() + }; + assert_eq!(try_finalize_with_scheduled_change(Some(id5)), None,); + } } diff --git a/modules/ethereum/src/verification.rs b/modules/ethereum/src/verification.rs index 567d14f3b54..06c0f14f2bb 100644 --- a/modules/ethereum/src/verification.rs +++ b/modules/ethereum/src/verification.rs @@ -18,26 +18,26 @@ use crate::error::Error; use crate::validators::{step_validator, Validators, ValidatorsConfiguration}; use crate::{AuraConfiguration, ImportContext, PoolConfiguration, ScheduledChange, Storage}; use codec::Encode; -use primitives::{public_to_address, Address, Header, Receipt, SealedEmptyStep, H256, H520, U128, U256}; +use primitives::{public_to_address, Address, Header, HeaderId, Receipt, SealedEmptyStep, H256, H520, U128, U256}; use sp_io::crypto::secp256k1_ecdsa_recover; use sp_std::{vec, vec::Vec}; /// Pre-check to see if should try and import this header. /// Returns error if we should not try to import this block. -/// Returns hash of the header and number of the last finalized block otherwise. -pub fn is_importable_header(storage: &S, header: &Header) -> Result<(H256, H256), Error> { +/// Returns ID of passed header and best finalized header. +pub fn is_importable_header(storage: &S, header: &Header) -> Result<(HeaderId, HeaderId), Error> { // we never import any header that competes with finalized header - let (finalized_block_number, finalized_block_hash) = storage.finalized_block(); - if header.number <= finalized_block_number { + let finalized_id = storage.finalized_block(); + if header.number <= finalized_id.number { return Err(Error::AncientHeader); } // we never import any header with known hash - let hash = header.hash(); - if storage.header(&hash).is_some() { + let id = header.compute_id(); + if storage.header(&id.hash).is_some() { return Err(Error::KnownHeader); } - Ok((hash, finalized_block_hash)) + Ok((id, finalized_id)) } /// Try accept unsigned aura header into transaction pool. @@ -50,7 +50,7 @@ pub fn accept_aura_header_into_pool( receipts: Option<&Vec>, ) -> Result<(Vec>, Vec>), Error> { // check if we can verify further - let (hash, _) = is_importable_header(storage, header)?; + let (header_id, _) = is_importable_header(storage, header)?; // we can always do contextless checks contextless_checks(config, header)?; @@ -69,8 +69,8 @@ pub fn accept_aura_header_into_pool( // => if we see header with number > maximal ever seen header number + LIMIT, // => we consider this transaction invalid, but only at this moment (we do not want to ban it) // => let's mark it as Unknown transaction - let (best_number, best_hash, _) = storage.best_block(); - let difference = header.number.saturating_sub(best_number); + let (best_id, _) = storage.best_block(); + let difference = header.number.saturating_sub(best_id.number); if difference > pool_config.max_future_number_difference { return Err(Error::UnsignedTooFarInTheFuture); } @@ -87,7 +87,7 @@ pub fn accept_aura_header_into_pool( // previous headers here // => we can at least 'verify' that headers comprise a chain by providing and requiring // tag (header.number, header.hash) - let provides_header_number_and_hash_tag = (header.number, hash).encode(); + let provides_header_number_and_hash_tag = header_id.encode(); // depending on whether parent header is available, we either perform full or 'shortened' check let context = storage.import_context(None, &header.parent_hash); @@ -109,7 +109,7 @@ pub fn accept_aura_header_into_pool( // PoA chain AND that the header is produced either by previous, or next // scheduled validators set change let header_step = header.step().ok_or(Error::MissingStep)?; - let best_context = storage.import_context(None, &best_hash).expect( + let best_context = storage.import_context(None, &best_id.hash).expect( "import context is None only when header is missing from the storage;\ best header is always in the storage; qed", ); @@ -124,7 +124,11 @@ pub fn accept_aura_header_into_pool( // since our parent is missing from the storage, we **DO** require it // to be in the transaction pool // (- 1 can't underflow because there's always best block in the header) - let requires_header_number_and_hash_tag = (header.number - 1, header.parent_hash).encode(); + let requires_header_number_and_hash_tag = HeaderId { + number: header.number - 1, + hash: header.parent_hash, + } + .encode(); ( vec![requires_header_number_and_hash_tag], vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag], @@ -313,7 +317,7 @@ fn find_next_validators_signal(storage: &S, context: &ImportContext< // if parent schedules validators set change, then it may be our set // else we'll start with last known change - let mut current_set_signal_block = context.last_signal_block().cloned(); + let mut current_set_signal_block = context.last_signal_block(); let mut next_scheduled_set: Option = None; loop { @@ -325,7 +329,7 @@ fn find_next_validators_signal(storage: &S, context: &ImportContext< return next_scheduled_set.map(|scheduled_set| scheduled_set.validators) } None => return next_scheduled_set.map(|scheduled_set| scheduled_set.validators), - Some(current_set_signal_block) => storage.scheduled_change(¤t_set_signal_block).expect( + Some(current_set_signal_block) => storage.scheduled_change(¤t_set_signal_block.hash).expect( "header that is associated with this change is not pruned;\ scheduled changes are only removed when header is pruned; qed", ), @@ -386,12 +390,12 @@ mod tests { let block1 = block_i(1, &validators); insert_header(&mut storage, block1); let block2 = block_i(2, &validators); - let block2_hash = block2.hash(); + let block2_id = block2.compute_id(); insert_header(&mut storage, block2); let block3 = block_i(3, &validators); insert_header(&mut storage, block3); - FinalizedBlock::put((2, block2_hash)); + FinalizedBlock::put(block2_id); let validators_config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new())); @@ -415,7 +419,10 @@ mod tests { ValidatorsSet { validators: finalized_set, signal_block: None, - enact_block: HeadersByNumber::get(&0).unwrap()[0].clone(), + enact_block: HeaderId { + number: 0, + hash: HeadersByNumber::get(&0).unwrap()[0], + }, }, ); @@ -423,7 +430,10 @@ mod tests { let mut header = Headers::::get(&header_hash).unwrap(); header.next_validators_set_id = set_id; if let Some(signalled_set) = signalled_set { - header.last_signal_block = Some(header.header.parent_hash); + header.last_signal_block = Some(HeaderId { + number: header.header.number - 1, + hash: header.header.parent_hash, + }); ScheduledChanges::insert( header.header.parent_hash, ScheduledChange { @@ -554,7 +564,7 @@ mod tests { assert_eq!(default_verify(&header), Err(Error::MissingParentBlock)); // when parent is in the storage - header.parent_hash = genesis().hash(); + header.parent_hash = genesis().compute_hash(); assert_ne!(default_verify(&header), Err(Error::MissingParentBlock)); } @@ -564,7 +574,7 @@ mod tests { let mut header = Header { seal: vec![vec![].into(), vec![].into()], gas_limit: test_aura_config().min_gas_limit, - parent_hash: genesis().hash(), + parent_hash: genesis().compute_hash(), ..Default::default() }; assert_eq!(default_verify(&header), Err(Error::MissingStep)); @@ -601,16 +611,16 @@ mod tests { seal: vec![ vec![45].into(), vec![142].into(), - SealedEmptyStep::rlp_of(&[sealed_empty_step(&validators, &genesis().hash(), 42)]), + SealedEmptyStep::rlp_of(&[sealed_empty_step(&validators, &genesis().compute_hash(), 42)]), ], gas_limit: test_aura_config().min_gas_limit, - parent_hash: genesis().hash(), + parent_hash: genesis().compute_hash(), ..Default::default() }; assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof)); // when empty step signature check fails - let mut wrong_sealed_empty_step = sealed_empty_step(&validators, &genesis().hash(), 43); + let mut wrong_sealed_empty_step = sealed_empty_step(&validators, &genesis().compute_hash(), 43); wrong_sealed_empty_step.signature = Default::default(); header.seal[2] = SealedEmptyStep::rlp_of(&[wrong_sealed_empty_step]); assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof)); @@ -618,15 +628,15 @@ mod tests { // when we are accepting strict empty steps and they come not in order config.strict_empty_steps_transition = 0; header.seal[2] = SealedEmptyStep::rlp_of(&[ - sealed_empty_step(&validators, &genesis().hash(), 44), - sealed_empty_step(&validators, &genesis().hash(), 43), + sealed_empty_step(&validators, &genesis().compute_hash(), 44), + sealed_empty_step(&validators, &genesis().compute_hash(), 43), ]); assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof)); // when empty steps are OK header.seal[2] = SealedEmptyStep::rlp_of(&[ - sealed_empty_step(&validators, &genesis().hash(), 43), - sealed_empty_step(&validators, &genesis().hash(), 44), + sealed_empty_step(&validators, &genesis().compute_hash(), 43), + sealed_empty_step(&validators, &genesis().compute_hash(), 44), ]); assert_ne!(verify_with_config(&config, &header), Err(Error::InsufficientProof)); } @@ -640,7 +650,7 @@ mod tests { let mut header = Header { seal: vec![vec![43].into(), vec![].into()], gas_limit: test_aura_config().min_gas_limit, - parent_hash: genesis().hash(), + parent_hash: genesis().compute_hash(), ..Default::default() }; assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty)); @@ -659,7 +669,7 @@ mod tests { author: validators[1].address().as_fixed_bytes().into(), seal: vec![vec![43].into(), vec![].into()], gas_limit: test_aura_config().min_gas_limit, - parent_hash: genesis().hash(), + parent_hash: genesis().compute_hash(), ..Default::default() }, 43, @@ -787,7 +797,7 @@ mod tests { assert_eq!( default_accept_into_pool(|validators| { let header = block_i(4, &validators); - hash = Some(header.hash()); + hash = Some(header.compute_hash()); (header, None) }), Ok(( @@ -819,7 +829,7 @@ mod tests { }, 47, ); - hash = Some(header.hash()); + hash = Some(header.compute_hash()); (header, None) }), Ok(( @@ -879,7 +889,7 @@ mod tests { }, 47, ); - hash = Some(header.hash()); + hash = Some(header.compute_hash()); (header, None) }), @@ -919,7 +929,7 @@ mod tests { .parse() .unwrap(); }); - hash = Some(header.hash()); + hash = Some(header.compute_hash()); (header, Some(vec![validators_change_recept(Default::default())])) }), Ok(( diff --git a/primitives/ethereum-poa/src/lib.rs b/primitives/ethereum-poa/src/lib.rs index 662079c301f..6125e10cd60 100644 --- a/primitives/ethereum-poa/src/lib.rs +++ b/primitives/ethereum-poa/src/lib.rs @@ -46,6 +46,15 @@ impl_fixed_hash_serde!(H520, 65); /// An ethereum address. pub type Address = H160; +/// Complete header id. +#[derive(Encode, Decode, Default, RuntimeDebug, PartialEq, Clone, Copy)] +pub struct HeaderId { + /// Header number. + pub number: u64, + /// Header hash. + pub hash: H256, +} + /// An Aura header. #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Default, Serialize, Deserialize))] @@ -138,8 +147,16 @@ pub struct SealedEmptyStep { } impl Header { - /// Get the hash of this header (keccak of the RLP with seal). - pub fn hash(&self) -> H256 { + /// Compute id of this header. + pub fn compute_id(&self) -> HeaderId { + HeaderId { + number: self.number, + hash: self.compute_hash(), + } + } + + /// Compute hash of this header (keccak of the RLP with seal). + pub fn compute_hash(&self) -> H256 { keccak_256(&self.rlp(true)).into() } @@ -166,7 +183,7 @@ impl Header { pub fn seal_hash(&self, include_empty_steps: bool) -> Option { Some(match include_empty_steps { true => { - let mut message = self.hash().as_bytes().to_vec(); + let mut message = self.compute_hash().as_bytes().to_vec(); message.extend_from_slice(self.seal.get(2)?); keccak_256(&message).into() } From 9b1fe2b04fc505717268b3fe4bc0e11b11c579dc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 3 Jun 2020 11:16:56 +0300 Subject: [PATCH 12/14] cargo fmt --all --- modules/ethereum/src/finality.rs | 9 ++++++--- modules/ethereum/src/lib.rs | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modules/ethereum/src/finality.rs b/modules/ethereum/src/finality.rs index 06a122b18ff..0f3e0109cab 100644 --- a/modules/ethereum/src/finality.rs +++ b/modules/ethereum/src/finality.rs @@ -307,7 +307,8 @@ mod tests { None, &header_to_import.header, u64::max_value(), - ).map(|eff| eff.finalized_headers), + ) + .map(|eff| eff.finalized_headers), Ok(Vec::new()), ); storage.insert_header(header_to_import.clone()); @@ -330,7 +331,8 @@ mod tests { None, &header_to_import.header, u64::max_value(), - ).map(|eff| eff.finalized_headers), + ) + .map(|eff| eff.finalized_headers), Ok(Vec::new()), ); storage.insert_header(header_to_import.clone()); @@ -353,7 +355,8 @@ mod tests { None, &header_to_import.header, u64::max_value(), - ).map(|eff| eff.finalized_headers), + ) + .map(|eff| eff.finalized_headers), Ok(vec![(id1, None)]), ); storage.insert_header(header_to_import); diff --git a/modules/ethereum/src/lib.rs b/modules/ethereum/src/lib.rs index 91f0c1ee16b..8bafd752d04 100644 --- a/modules/ethereum/src/lib.rs +++ b/modules/ethereum/src/lib.rs @@ -784,7 +784,9 @@ fn pool_configuration() -> PoolConfiguration { mod tests { use super::*; use crate::finality::FinalityAncestor; - use crate::mock::{block_i, custom_block_i, custom_test_ext, genesis, insert_header, validators, validators_addresses, TestRuntime}; + use crate::mock::{ + block_i, custom_block_i, custom_test_ext, genesis, insert_header, validators, validators_addresses, TestRuntime, + }; fn with_headers_to_prune(f: impl Fn(BridgeStorage) -> T) -> T { custom_test_ext(genesis(), validators_addresses(3)).execute_with(|| { From 2deb11a227c7090d56eadb962a4daebaffcad0ea Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 5 Jun 2020 10:20:19 +0300 Subject: [PATCH 13/14] cargo fmt --all --- modules/ethereum/src/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/ethereum/src/lib.rs b/modules/ethereum/src/lib.rs index 1337da134f9..8b927bd54ef 100644 --- a/modules/ethereum/src/lib.rs +++ b/modules/ethereum/src/lib.rs @@ -838,10 +838,7 @@ fn pool_configuration() -> PoolConfiguration { } /// Return iterator of given header ancestors. -fn ancestry<'a, S: Storage>( - storage: &'a S, - mut parent_hash: H256, -) -> impl Iterator + 'a { +fn ancestry<'a, S: Storage>(storage: &'a S, mut parent_hash: H256) -> impl Iterator + 'a { sp_std::iter::from_fn(move || { let (header, _) = storage.header(&parent_hash)?; if header.number == 0 { @@ -1138,7 +1135,6 @@ pub(crate) mod tests { }); } - #[test] fn verify_transaction_finalized_works_for_best_finalized_header() { custom_test_ext(example_header(), validators_addresses(3)).execute_with(|| { @@ -1242,7 +1238,12 @@ pub(crate) mod tests { custom_test_ext(example_header(), validators_addresses(3)).execute_with(|| { let storage = BridgeStorage::::new(); assert_eq!( - verify_transaction_finalized(&storage, example_header().compute_hash(), 0, &vec![example_tx(), example_tx(),],), + verify_transaction_finalized( + &storage, + example_header().compute_hash(), + 0, + &vec![example_tx(), example_tx(),], + ), false, ); }); From b56ddd3515ffafcb0ffb3244654e99276e21c519 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 10 Jun 2020 10:15:02 +0300 Subject: [PATCH 14/14] impl Default for FinalityVotes --- modules/ethereum/src/finality.rs | 16 +++++++++++----- modules/ethereum/src/mock.rs | 6 +----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/ethereum/src/finality.rs b/modules/ethereum/src/finality.rs index 0f3e0109cab..0fb5f1f7774 100644 --- a/modules/ethereum/src/finality.rs +++ b/modules/ethereum/src/finality.rs @@ -51,7 +51,7 @@ pub struct FinalityEffects { /// Finality votes for given block. #[derive(RuntimeDebug, Decode, Encode)] -#[cfg_attr(test, derive(Clone, Default, PartialEq))] +#[cfg_attr(test, derive(Clone, PartialEq))] pub struct FinalityVotes { /// Number of votes per each validator. pub votes: BTreeMap, @@ -150,10 +150,7 @@ fn prepare_votes( // so the only thing we need to do is: // 1) remove votes from blocks that have been finalized after B has been inserted; // 2) add votes from B descendants - let mut votes = cached_votes.votes.unwrap_or_else(|| FinalityVotes { - votes: BTreeMap::new(), - ancestry: VecDeque::new(), - }); + let mut votes = cached_votes.votes.unwrap_or_default(); // remove votes from finalized blocks while let Some(old_ancestor) = votes.ancestry.pop_front() { @@ -248,6 +245,15 @@ fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option .map(|public| public_to_address(&public)) } +impl Default for FinalityVotes { + fn default() -> Self { + FinalityVotes { + votes: BTreeMap::new(), + ancestry: VecDeque::new(), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/modules/ethereum/src/mock.rs b/modules/ethereum/src/mock.rs index 7891143351a..a4f9e0fe4bb 100644 --- a/modules/ethereum/src/mock.rs +++ b/modules/ethereum/src/mock.rs @@ -27,7 +27,6 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, Perbill, }; -use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque}; pub type AccountId = u64; @@ -180,9 +179,6 @@ pub fn insert_header(storage: &mut S, header: Header) { total_difficulty: 0.into(), enacted_change: None, scheduled_change: None, - finality_votes: FinalityVotes { - votes: BTreeMap::new(), - ancestry: VecDeque::new(), - }, + finality_votes: FinalityVotes::default(), }); }