From d904a282c8b0f2314bb33e7768975604e60b119a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 19 Mar 2020 15:21:02 +0300 Subject: [PATCH] Reward submitters only when submitted block is finalized (#34) * reward submitters on finalization * fix grumble * make submitter part of ImportContext --- bridges/modules/ethereum/src/finality.rs | 31 ++-- bridges/modules/ethereum/src/import.rs | 135 +++++++++++++---- bridges/modules/ethereum/src/lib.rs | 151 ++++++++++++++----- bridges/modules/ethereum/src/validators.rs | 4 +- bridges/modules/ethereum/src/verification.rs | 16 +- 5 files changed, 252 insertions(+), 85 deletions(-) diff --git a/bridges/modules/ethereum/src/finality.rs b/bridges/modules/ethereum/src/finality.rs index cfa4c77a39..f41fd712e6 100644 --- a/bridges/modules/ethereum/src/finality.rs +++ b/bridges/modules/ethereum/src/finality.rs @@ -49,9 +49,10 @@ pub fn finalize_blocks( best_finalized_hash: &H256, header_validators: (&H256, &[Address]), hash: &H256, + 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( @@ -61,18 +62,19 @@ pub fn finalize_blocks( &validators, hash, header, + submitter, two_thirds_majority_transition, )?; // now let's iterate in reverse order && find just finalized blocks let mut newly_finalized = Vec::new(); - while let Some((oldest_hash, oldest_number, signers)) = headers.pop_front() { + while let Some((oldest_hash, oldest_number, submitter, signers)) = headers.pop_front() { if !is_finalized(&validators, &votes, oldest_number >= two_thirds_majority_transition) { break; } remove_signers_votes(&signers, &mut votes); - newly_finalized.push((oldest_number, oldest_hash)); + newly_finalized.push((oldest_number, oldest_hash, submitter)); } Ok(newly_finalized) @@ -96,8 +98,9 @@ fn prepare_votes( validators: &BTreeSet<&Address>, hash: &H256, header: &Header, + submitter: Option<&S::Submitter>, two_thirds_majority_transition: u64, -) -> Result<(BTreeMap, VecDeque<(H256, u64, BTreeSet
)>), Error> { +) -> Result<(BTreeMap, VecDeque<(H256, u64, Option, BTreeSet
)>), Error> { // this fn can only work with single validators set if !validators.contains(&header.author) { return Err(Error::NotValidator); @@ -108,17 +111,17 @@ fn prepare_votes( // the same set of validators let mut parent_empty_step_signers = empty_steps_signers(header); let ancestry = ancestry(storage, header) - .map(|(hash, 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, signers); + 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); + .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 @@ -126,21 +129,21 @@ fn prepare_votes( // just finalized blocks) let mut votes = BTreeMap::new(); let mut headers = VecDeque::new(); - for (hash, number, signers) in ancestry { + 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); break; } - headers.push_front((hash, number, signers)); + headers.push_front((hash, number, submitter, signers)); } // update votes with last header vote 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, header_signers)); + headers.push_back((*hash, header.number, submitter.cloned(), header_signers)); Ok((votes, headers)) } @@ -211,6 +214,7 @@ mod tests { &Default::default(), (&Default::default(), &[]), &Default::default(), + None, &Header::default(), 0, ), @@ -233,7 +237,7 @@ mod tests { }; let hash1 = header1.hash(); let mut header_to_import = HeaderToImport { - context: storage.import_context(&genesis().hash()).unwrap(), + context: storage.import_context(None, &genesis().hash()).unwrap(), is_best: true, hash: hash1, header: header1, @@ -247,6 +251,7 @@ mod tests { &Default::default(), (&Default::default(), &validators_addresses(5)), &hash1, + None, &header_to_import.header, u64::max_value(), ), @@ -269,6 +274,7 @@ mod tests { &Default::default(), (&Default::default(), &validators_addresses(5)), &hash2, + None, &header_to_import.header, u64::max_value(), ), @@ -291,10 +297,11 @@ mod tests { &Default::default(), (&Default::default(), &validators_addresses(5)), &hash3, + None, &header_to_import.header, u64::max_value(), ), - Ok(vec![(1, hash1)]), + 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 bf8d21ea02..14cb8f26a7 100644 --- a/bridges/modules/ethereum/src/import.rs +++ b/bridges/modules/ethereum/src/import.rs @@ -36,7 +36,10 @@ use crate::validators::{Validators, ValidatorsConfiguration}; use crate::verification::verify_aura_header; use crate::{AuraConfiguration, Storage}; use primitives::{Header, Receipt, H256}; -use sp_std::prelude::*; +use sp_std::{ + collections::btree_map::BTreeMap, + prelude::*, +}; /// Maximal number of headers behind best blocks that we are aiming to store. When there /// are too many unfinalized headers, it slows down finalization tracking significantly. @@ -61,15 +64,32 @@ pub fn import_headers( aura_config: &AuraConfiguration, validators_config: &ValidatorsConfiguration, prune_depth: u64, + submitter: Option, headers: Vec<(Header, Option>)>, + finalized_headers: &mut BTreeMap, ) -> Result<(u64, u64), Error> { let mut useful = 0; let mut useless = 0; for (header, receipts) in headers { - let import_result = import_header(storage, aura_config, validators_config, prune_depth, header, receipts); + let import_result = import_header( + storage, + aura_config, + validators_config, + prune_depth, + submitter.clone(), + header, + receipts, + ); match import_result { - Ok(_) => useful += 1, + Ok((_, finalized)) => { + for (_, _, submitter) in finalized { + if let Some(submitter) = submitter { + *finalized_headers.entry(submitter).or_default() += 1; + } + } + useful += 1; + }, Err(Error::AncientHeader) | Err(Error::KnownHeader) => useless += 1, Err(error) => return Err(error), } @@ -82,19 +102,22 @@ pub fn import_headers( /// /// Transactions receipts must be provided if `header_import_requires_receipts()` /// has returned true. +/// +/// Returns imported block hash. pub fn import_header( storage: &mut S, aura_config: &AuraConfiguration, validators_config: &ValidatorsConfiguration, prune_depth: u64, + submitter: Option, header: Header, receipts: Option>, -) -> Result { +) -> Result<(H256, Vec<(u64, H256, Option)>), Error> { // first check that we are able to import this header at all let (hash, prev_finalized_hash) = is_importable_header(storage, &header)?; // verify header - let import_context = verify_aura_header(storage, aura_config, &header)?; + let import_context = verify_aura_header(storage, aura_config, submitter, &header)?; // check if block schedules new validators let validators = Validators::new(validators_config); @@ -106,6 +129,7 @@ pub fn import_header( &prev_finalized_hash, (import_context.validators_start(), import_context.validators()), &hash, + import_context.submitter(), &header, aura_config.two_thirds_majority_transition, )?; @@ -119,25 +143,27 @@ pub fn import_header( 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, - total_difficulty, - enacted_change, - scheduled_change, - )); + storage.insert_header( + import_context.into_import_header( + is_best, + hash, + header, + total_difficulty, + enacted_change, + scheduled_change, + ), + ); // now mark finalized headers && prune old headers storage.finalize_headers( - finalized_blocks.last().cloned(), + finalized_blocks.last().map(|(number, hash, _)| (*number, *hash)), match is_best { true => header_number.checked_sub(prune_depth), false => None, }, ); - Ok(hash) + Ok((hash, finalized_blocks)) } /// Returns true if transactions receipts are required to import given header. @@ -189,6 +215,7 @@ mod tests { &kovan_aura_config(), &kovan_validators_config(), PRUNE_DEPTH, + None, Default::default(), None, ), @@ -207,6 +234,7 @@ mod tests { &kovan_aura_config(), &kovan_validators_config(), PRUNE_DEPTH, + None, block.clone(), None, ) @@ -219,6 +247,7 @@ mod tests { &kovan_aura_config(), &kovan_validators_config(), PRUNE_DEPTH, + None, block, None, ) @@ -243,6 +272,7 @@ mod tests { &kovan_aura_config(), &validators_config, PRUNE_DEPTH, + None, header, None ) @@ -267,43 +297,67 @@ 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 last_block_hash = Default::default(); + let mut latest_block_hash = Default::default(); for i in 1..11 { let header = block_i(&storage, i, &validators); - last_block_hash = - import_header(&mut storage, &kovan_aura_config(), &validators_config, 10, header, None).unwrap(); + let (rolling_last_block_hash, finalized_blocks) = + import_header( + &mut storage, + &kovan_aura_config(), + &validators_config, + 10, + Some(100), + header, + None, + ).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), + } + 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 header = custom_block_i(&storage, 11, &validators, |header| { + let header11 = custom_block_i(&storage, 11, &validators, |header| { header.log_bloom = (&[0xff; 256]).into(); header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951" .parse() .unwrap(); }); - last_block_hash = import_header( + let (rolling_last_block_hash, finalized_blocks) = import_header( &mut storage, &kovan_aura_config(), &validators_config, 10, - header, + Some(101), + header11.clone(), Some(vec![crate::validators::tests::validators_change_recept( - last_block_hash, + latest_block_hash, )]), - ) - .unwrap(); + ).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; // 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: last_block_hash, + parent_hash: latest_block_hash, gas_limit: 0x2000.into(), author: validator(2).address().to_fixed_bytes().into(), seal: vec![vec![step].into(), vec![].into()], @@ -311,8 +365,22 @@ mod tests { ..Default::default() }; let header = signed_header(&validators, header, step as _); - last_block_hash = - import_header(&mut storage, &kovan_aura_config(), &validators_config, 10, header, None).unwrap(); + expected_blocks.push((i, header.hash(), Some(102))); + let (rolling_last_block_hash, finalized_blocks) = + import_header( + &mut storage, + &kovan_aura_config(), + &validators_config, + 10, + Some(102), + header, + None, + ).unwrap(); + assert_eq!( + finalized_blocks, + vec![], + ); + latest_block_hash = rolling_last_block_hash; step += 3; } assert_eq!(storage.oldest_unpruned_block(), 11); @@ -322,7 +390,7 @@ mod tests { step -= 2; let header = Header { number: 25, - parent_hash: last_block_hash, + parent_hash: latest_block_hash, gas_limit: 0x2000.into(), author: validator(0).address().to_fixed_bytes().into(), seal: vec![vec![step].into(), vec![].into()], @@ -330,7 +398,16 @@ mod tests { ..Default::default() }; let header = signed_header(&validators, header, step as _); - import_header(&mut storage, &kovan_aura_config(), &validators_config, 10, header, None).unwrap(); + 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); } } diff --git a/bridges/modules/ethereum/src/lib.rs b/bridges/modules/ethereum/src/lib.rs index f69dbe0592..5d28bfd074 100644 --- a/bridges/modules/ethereum/src/lib.rs +++ b/bridges/modules/ethereum/src/lib.rs @@ -36,7 +36,12 @@ use codec::{Decode, Encode}; use frame_support::{decl_module, decl_storage}; use primitives::{Address, Header, Receipt, H256, U256}; use sp_runtime::RuntimeDebug; -use sp_std::{iter::from_fn, prelude::*}; +use sp_std::{ + prelude::*, + cmp::Ord, + collections::btree_map::BTreeMap, + iter::from_fn, +}; use validators::{ValidatorsConfiguration, ValidatorsSource}; pub use import::{header_import_requires_receipts, import_header}; @@ -70,7 +75,10 @@ pub struct AuraConfiguration { /// Block header as it is stored in the runtime storage. #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)] -pub struct StoredHeader { +pub struct StoredHeader { + /// Submitter of this header. May be `None` if header has been submitted + /// using unsigned transaction. + pub submitter: Option, /// The block header itself. pub header: Header, /// Total difficulty of the chain. @@ -85,9 +93,9 @@ pub struct StoredHeader { /// Header that we're importing. #[derive(RuntimeDebug)] #[cfg_attr(test, derive(Clone, PartialEq))] -pub struct HeaderToImport { +pub struct HeaderToImport { /// Header import context, - pub context: ImportContext, + pub context: ImportContext, /// Should we consider this header as best? pub is_best: bool, /// The hash of the header. @@ -105,22 +113,25 @@ pub struct HeaderToImport { /// Header import context. #[derive(RuntimeDebug)] #[cfg_attr(test, derive(Clone, PartialEq))] -pub struct ImportContext { +pub struct ImportContext { + submitter: Option, parent_header: Header, parent_total_difficulty: U256, next_validators_set_id: u64, next_validators_set: (H256, Vec
), } -impl ImportContext { +impl ImportContext { /// Create import context using passing parameters; pub fn new( + submitter: Option, parent_header: Header, parent_total_difficulty: U256, next_validators_set_id: u64, next_validators_set: (H256, Vec
), ) -> Self { ImportContext { + submitter, parent_header, parent_total_difficulty, next_validators_set_id, @@ -128,6 +139,11 @@ impl ImportContext { } } + /// Returns reference to header submitter (if known). + pub fn submitter(&self) -> Option<&Submitter> { + self.submitter.as_ref() + } + /// Returns reference to parent header. pub fn parent_header(&self) -> &Header { &self.parent_header @@ -162,7 +178,7 @@ impl ImportContext { total_difficulty: U256, enacted_change: Option>, scheduled_change: Option>, - ) -> HeaderToImport { + ) -> HeaderToImport { HeaderToImport { context: self, is_best, @@ -179,18 +195,27 @@ impl ImportContext { /// /// Storage modification must be discarded if block import has failed. pub trait Storage { + /// Header submitter identifier. + type Submitter: Clone + Ord; + /// Get best known block. fn best_block(&self) -> (u64, H256, U256); /// Get last finalized block. fn finalized_block(&self) -> (u64, H256); /// Get imported header by its hash. - fn header(&self, hash: &H256) -> Option
; + /// + /// Returns header and its submitter (if known). + fn header(&self, hash: &H256) -> Option<(Header, Option)>; /// Get header import context by parent header hash. - fn import_context(&self, parent_hash: &H256) -> Option; + fn import_context( + &self, + submitter: Option, + parent_hash: &H256, + ) -> Option>; /// Get new validators that are scheduled by given header. fn scheduled_change(&self, hash: &H256) -> Option>; /// Insert imported header. - fn insert_header(&mut self, header: HeaderToImport); + fn insert_header(&mut self, header: HeaderToImport); /// Finalize given block and prune all headers with number < prune_end. /// The headers in the pruning range could be either finalized, or not. /// It is the storage duty to ensure that unfinalized headers that have @@ -202,14 +227,25 @@ pub trait Storage { /// Decides whether the session should be ended. pub trait OnHeadersSubmitted { /// Called when valid headers have been submitted. + /// + /// The submitter **must not** be rewarded for submitting valid headers, because greedy authority + /// could produce and submit multiple valid headers (without relaying them to other peers) and + /// get rewarded. Instead, the provider could track submitters and stop rewarding if too many + /// headers have been submitted without finalization. fn on_valid_headers_submitted(submitter: AccountId, useful: u64, useless: u64); /// Called when invalid headers have been submitted. fn on_invalid_headers_submitted(submitter: AccountId); + /// Called when earlier submitted headers have been finalized. + /// + /// finalized is the number of headers that submitter has submitted and which + /// have been finalized. + fn on_valid_headers_finalized(submitter: AccountId, finalized: u64); } impl OnHeadersSubmitted for () { fn on_valid_headers_submitted(_submitter: AccountId, _useful: u64, _useless: u64) {} fn on_invalid_headers_submitted(_submitter: AccountId) {} + fn on_valid_headers_finalized(_submitter: AccountId, _finalized: u64) {} } /// The module configuration trait @@ -228,14 +264,27 @@ decl_module! { /// enormous block production/import time. pub fn import_headers(origin, headers_with_receipts: Vec<(Header, Option>)>) { let submitter = frame_system::ensure_signed(origin)?; + let mut finalized_headers = BTreeMap::new(); let import_result = import::import_headers( - &mut BridgeStorage, + &mut BridgeStorage::::new(), &kovan_aura_config(), &kovan_validators_config(), crate::import::PRUNE_DEPTH, + Some(submitter.clone()), headers_with_receipts, + &mut finalized_headers, ); + // if we have finalized some headers, we will reward their submitters even + // if current submitter has provided some invalid headers + for (f_submitter, f_count) in finalized_headers { + T::OnHeadersSubmitted::on_valid_headers_finalized( + f_submitter, + f_count, + ); + } + + // now track/penalize current submitter for providing new headers match import_result { Ok((useful, useless)) => T::OnHeadersSubmitted::on_valid_headers_submitted(submitter, useful, useless), @@ -259,7 +308,7 @@ decl_storage! { /// Oldest unpruned block(s) number. OldestUnprunedBlock: u64; /// Map of imported headers by hash. - Headers: map hasher(blake2_256) H256 => Option; + Headers: map hasher(blake2_256) H256 => Option>; /// Map of imported header hashes by number. HeadersByNumber: map hasher(blake2_256) u64 => Option>; /// The ID of next validator set. @@ -293,7 +342,8 @@ decl_storage! { FinalizedBlock::put((config.initial_header.number, initial_hash)); OldestUnprunedBlock::put(config.initial_header.number); HeadersByNumber::insert(config.initial_header.number, vec![initial_hash]); - Headers::insert(initial_hash, StoredHeader { + Headers::::insert(initial_hash, StoredHeader { + submitter: None, header: config.initial_header.clone(), total_difficulty: config.initial_difficulty, next_validators_set_id: 0, @@ -310,25 +360,34 @@ impl 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.best_block(); + let (number, hash, _) = BridgeStorage::::new().best_block(); (number, hash) } /// 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, &kovan_validators_config(), &header) + import::header_import_requires_receipts(&BridgeStorage::::new(), &kovan_validators_config(), &header) } /// Returns true if header is known to the runtime. pub fn is_known_block(hash: H256) -> bool { - BridgeStorage.header(&hash).is_some() + BridgeStorage::::new().header(&hash).is_some() } } /// Runtime bridge storage. -struct BridgeStorage; +#[derive(Default)] +struct BridgeStorage(sp_std::marker::PhantomData); + +impl BridgeStorage { + pub fn new() -> Self { + BridgeStorage(sp_std::marker::PhantomData::::default()) + } +} + +impl Storage for BridgeStorage { + type Submitter = T::AccountId; -impl Storage for BridgeStorage { fn best_block(&self) -> (u64, H256, U256) { BestBlock::get() } @@ -337,16 +396,21 @@ impl Storage for BridgeStorage { FinalizedBlock::get() } - fn header(&self, hash: &H256) -> Option
{ - Headers::get(hash).map(|header| header.header) + fn header(&self, hash: &H256) -> Option<(Header, Option)> { + Headers::::get(hash).map(|header| (header.header, header.submitter)) } - fn import_context(&self, parent_hash: &H256) -> Option { - Headers::get(parent_hash).map(|parent_header| { + fn import_context( + &self, + submitter: Option, + parent_hash: &H256, + ) -> Option> { + Headers::::get(parent_hash).map(|parent_header| { let (next_validators_set_start, next_validators) = ValidatorsSets::get(parent_header.next_validators_set_id) .expect("validators set is only pruned when last ref is pruned; there is a ref; qed"); ImportContext { + submitter, parent_header: parent_header.header, parent_total_difficulty: parent_header.total_difficulty, next_validators_set_id: parent_header.next_validators_set_id, @@ -359,7 +423,7 @@ impl Storage for BridgeStorage { ScheduledChanges::get(hash) } - fn insert_header(&mut self, header: HeaderToImport) { + fn insert_header(&mut self, header: HeaderToImport) { if header.is_best { BestBlock::put((header.header.number, header.hash, header.total_difficulty)); } @@ -387,9 +451,10 @@ impl Storage for BridgeStorage { }; HeadersByNumber::append_or_insert(header.header.number, vec![header.hash]); - Headers::insert( + Headers::::insert( &header.hash, StoredHeader { + submitter: header.context.submitter, header: header.header, total_difficulty: header.total_difficulty, next_validators_set_id, @@ -426,7 +491,7 @@ impl Storage for BridgeStorage { // physically remove headers and (probably) obsolete validators sets for hash in blocks_at_number.into_iter().flat_map(|x| x) { - let header = Headers::take(&hash); + let header = Headers::::take(&hash); ScheduledChanges::remove(hash); if let Some(header) = header { ValidatorsSetsRc::mutate(header.next_validators_set_id, |rc| match *rc { @@ -570,19 +635,22 @@ pub fn kovan_validators_config() -> ValidatorsConfiguration { } /// Return iterator of given header ancestors. -pub(crate) fn ancestry<'a, S: Storage>(storage: &'a S, header: &Header) -> impl Iterator + 'a { +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 = storage.header(&parent_hash); - match header { - Some(header) => { + let header_and_submitter = storage.header(&parent_hash); + match header_and_submitter { + Some((header, submitter)) => { if header.number == 0 { return None; } let hash = parent_hash.clone(); parent_hash = header.parent_hash.clone(); - Some((hash, header)) + Some((hash, header, submitter)) } None => None, } @@ -596,6 +664,8 @@ pub(crate) mod tests { 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()], @@ -651,7 +721,7 @@ pub(crate) mod tests { best_block: (u64, H256, U256), finalized_block: (u64, H256), oldest_unpruned_block: u64, - headers: HashMap, + headers: HashMap>, headers_by_number: HashMap>, next_validators_set_id: u64, validators_sets: HashMap)>, @@ -670,6 +740,7 @@ pub(crate) mod tests { headers: vec![( hash, StoredHeader { + submitter: None, header: initial_header, total_difficulty: 0.into(), next_validators_set_id: 0, @@ -688,12 +759,14 @@ pub(crate) mod tests { self.oldest_unpruned_block } - pub(crate) fn stored_header(&self, hash: &H256) -> Option<&StoredHeader> { + 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() } @@ -702,15 +775,20 @@ pub(crate) mod tests { self.finalized_block.clone() } - fn header(&self, hash: &H256) -> Option
{ - self.headers.get(hash).map(|header| header.header.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, parent_hash: &H256) -> Option { + fn import_context( + &self, + submitter: Option, + parent_hash: &H256, + ) -> Option> { self.headers.get(parent_hash).map(|parent_header| { let (next_validators_set_start, next_validators) = self.validators_sets.get(&parent_header.next_validators_set_id).unwrap(); ImportContext { + submitter, parent_header: parent_header.header.clone(), parent_total_difficulty: parent_header.total_difficulty, next_validators_set_id: parent_header.next_validators_set_id, @@ -723,7 +801,7 @@ pub(crate) mod tests { self.scheduled_changes.get(hash).cloned() } - fn insert_header(&mut self, header: HeaderToImport) { + fn insert_header(&mut self, header: HeaderToImport) { if header.is_best { self.best_block = (header.header.number, header.hash, header.total_difficulty); } @@ -755,6 +833,7 @@ pub(crate) mod tests { self.headers.insert( header.hash, StoredHeader { + submitter: header.context.submitter, header: header.header, total_difficulty: header.total_difficulty, next_validators_set_id, diff --git a/bridges/modules/ethereum/src/validators.rs b/bridges/modules/ethereum/src/validators.rs index 21f1f0b58b..3342f22450 100644 --- a/bridges/modules/ethereum/src/validators.rs +++ b/bridges/modules/ethereum/src/validators.rs @@ -198,9 +198,9 @@ impl<'a> Validators<'a> { pub fn finalize_validators_change( &self, storage: &mut S, - finalized_blocks: &[(u64, H256)], + finalized_blocks: &[(u64, H256, Option)], ) -> Option> { - for (_, finalized_hash) in finalized_blocks.iter().rev() { + for (_, finalized_hash, _) in finalized_blocks.iter().rev() { if let Some(changes) = storage.scheduled_change(finalized_hash) { return Some(changes); } diff --git a/bridges/modules/ethereum/src/verification.rs b/bridges/modules/ethereum/src/verification.rs index ea9b37fa52..c0edd5e56d 100644 --- a/bridges/modules/ethereum/src/verification.rs +++ b/bridges/modules/ethereum/src/verification.rs @@ -40,14 +40,15 @@ use sp_io::crypto::secp256k1_ecdsa_recover; pub fn verify_aura_header( storage: &S, params: &AuraConfiguration, + submitter: Option, header: &Header, -) -> Result { +) -> Result, Error> { // let's do the lightest check first contextless_checks(params, header)?; // the rest of checks requires parent let context = storage - .import_context(&header.parent_hash) + .import_context(submitter, &header.parent_hash) .ok_or(Error::MissingParentBlock)?; let validators = context.validators(); let header_step = header.step().ok_or(Error::MissingStep)?; @@ -179,7 +180,7 @@ fn verify_signature(expected_validator: &Address, signature: &H520, message: &H2 mod tests { use super::*; use crate::kovan_aura_config; - use crate::tests::{genesis, signed_header, validator, validators_addresses, InMemoryStorage}; + use crate::tests::{genesis, signed_header, validator, validators_addresses, AccountId, InMemoryStorage}; use parity_crypto::publickey::{sign, KeyPair}; use primitives::{rlp_encode, H520}; @@ -197,12 +198,15 @@ mod tests { empty_step } - fn verify_with_config(config: &AuraConfiguration, header: &Header) -> Result { + fn verify_with_config( + config: &AuraConfiguration, + header: &Header, + ) -> Result, Error> { let storage = InMemoryStorage::new(genesis(), validators_addresses(3)); - verify_aura_header(&storage, &config, header) + verify_aura_header(&storage, &config, None, header) } - fn default_verify(header: &Header) -> Result { + fn default_verify(header: &Header) -> Result, Error> { verify_with_config(&kovan_aura_config(), header) }