From e489160a1399004e41c34ad4d2e832a71f07dce2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 1 Jun 2020 17:51:12 +0300 Subject: [PATCH] Use runtime storage in PoA -> Substrate module tests (#103) * removeInMemoryStorage + extract Kovan stuff to runtime * removed comment from the future * remove redundant conversions * remove redundant `u8 as usize` * remove redundant `u8 as usize` * Update modules/ethereum/src/mock.rs Co-authored-by: Hernando Castano * use hex-literal in kovan config * cargo fmt --all * extracted insert_header * cargo fmt --all Co-authored-by: Hernando Castano --- bridges/bin/node/node/src/chain_spec.rs | 81 +--- bridges/bin/node/runtime/Cargo.toml | 3 + bridges/bin/node/runtime/src/kovan.rs | 104 ++++ bridges/bin/node/runtime/src/lib.rs | 9 + bridges/modules/ethereum/src/finality.rs | 186 +++---- bridges/modules/ethereum/src/import.rs | 339 ++++++------- bridges/modules/ethereum/src/lib.rs | 481 +------------------ bridges/modules/ethereum/src/mock.rs | 179 +++++++ bridges/modules/ethereum/src/validators.rs | 3 +- bridges/modules/ethereum/src/verification.rs | 180 ++++--- bridges/primitives/ethereum-poa/src/lib.rs | 1 - 11 files changed, 690 insertions(+), 876 deletions(-) create mode 100644 bridges/bin/node/runtime/src/kovan.rs create mode 100644 bridges/modules/ethereum/src/mock.rs diff --git a/bridges/bin/node/node/src/chain_spec.rs b/bridges/bin/node/node/src/chain_spec.rs index 11a31c7ae1cb5..7127ee8d77193 100644 --- a/bridges/bin/node/node/src/chain_spec.rs +++ b/bridges/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/bridges/bin/node/runtime/Cargo.toml b/bridges/bin/node/runtime/Cargo.toml index d0281b1d30f40..92351e0c44c27 100644 --- a/bridges/bin/node/runtime/Cargo.toml +++ b/bridges/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/bridges/bin/node/runtime/src/kovan.rs b/bridges/bin/node/runtime/src/kovan.rs new file mode 100644 index 0000000000000..2592d8e057b26 --- /dev/null +++ b/bridges/bin/node/runtime/src/kovan.rs @@ -0,0 +1,104 @@ +// 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 hex_literal::hex; +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![ + hex!("00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED").into(), + hex!("0010f94b296a852aaac52ea6c5ac72e03afd032d").into(), + hex!("00a0a24b9f0e5ec7aa4c7389b8302fd0123194de").into(), + ]), + ), + ( + 10960500, + ValidatorsSource::Contract( + hex!("aE71807C1B0a093cB1547b682DC78316D945c9B8").into(), + vec![ + hex!("d05f7478c6aa10781258c5cc8b4f385fc8fa989c").into(), + hex!("03801efb0efe2a25ede5dd3a003ae880c0292e4d").into(), + hex!("a4df255ecf08bbf2c28055c65225c9a9847abd94").into(), + hex!("596e8221a30bfe6e7eff67fee664a01c73ba3c56").into(), + hex!("faadface3fbd81ce37b0e19c0b65ff4234148132").into(), + ], + ), + ), + ]) +} + +/// Genesis validators set of Kovan chain. +pub fn kovan_genesis_validators() -> Vec
{ + vec![ + 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(), + ] +} + +/// 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: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + uncles_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").into(), + extra_data: vec![], + state_root: hex!("2480155b48a1cea17d67dbfdfaafe821c1d19cdd478c5358e8ec56dec24502b2").into(), + receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").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/bridges/bin/node/runtime/src/lib.rs b/bridges/bin/node/runtime/src/lib.rs index 45db36476e3df..ff4d796cf93a2 100644 --- a/bridges/bin/node/runtime/src/lib.rs +++ b/bridges/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/bridges/modules/ethereum/src/finality.rs b/bridges/modules/ethereum/src/finality.rs index 1ee9f356028e7..676108a30dc68 100644 --- a/bridges/modules/ethereum/src/finality.rs +++ b/bridges/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/bridges/modules/ethereum/src/import.rs b/bridges/modules/ethereum/src/import.rs index 20a5d10e457ec..17c7237fab612 100644 --- a/bridges/modules/ethereum/src/import.rs +++ b/bridges/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(), + 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(), 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/bridges/modules/ethereum/src/lib.rs b/bridges/modules/ethereum/src/lib.rs index 9f0b0cbd03171..376d800d34dba 100644 --- a/bridges/modules/ethereum/src/lib.rs +++ b/bridges/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, @@ -424,7 +430,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 +447,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 +629,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/bridges/modules/ethereum/src/mock.rs b/bridges/modules/ethereum/src/mock.rs new file mode 100644 index 0000000000000..6624b149a9045 --- /dev/null +++ b/bridges/modules/ethereum/src/mock.rs @@ -0,0 +1,179 @@ +// 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, 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}; +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, Debug)] +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(), + 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).map(validator).collect() +} + +/// Return addresses of all test validators. +pub fn validators_addresses(count: u8) -> Vec
{ + (0..count).map(|i| validator(i).address()).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) +} + +/// 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/bridges/modules/ethereum/src/validators.rs b/bridges/modules/ethereum/src/validators.rs index a93c21f8de4e7..7a1fbb52f036b 100644 --- a/bridges/modules/ethereum/src/validators.rs +++ b/bridges/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/bridges/modules/ethereum/src/verification.rs b/bridges/modules/ethereum/src/verification.rs index 23f8980b3a0bb..567d14f3b54a4 100644 --- a/bridges/modules/ethereum/src/verification.rs +++ b/bridges/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, insert_header, 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,73 @@ 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); + insert_header(&mut storage, block1); + let block2 = block_i(2, &validators); + let block2_hash = block2.hash(); + insert_header(&mut storage, block2); + let block3 = block_i(3, &validators); + insert_header(&mut storage, block3); + + 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 +446,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 +489,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 +515,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 +532,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 +548,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 +563,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 +578,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 +593,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 +603,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 +633,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 +658,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 +683,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 +692,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 +703,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 +720,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 +737,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 +747,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 +762,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 +785,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 +806,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 +837,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 +847,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 +862,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 +872,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 +898,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 +912,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/bridges/primitives/ethereum-poa/src/lib.rs b/bridges/primitives/ethereum-poa/src/lib.rs index ca2afa605db5b..662079c301f4a 100644 --- a/bridges/primitives/ethereum-poa/src/lib.rs +++ b/bridges/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])