Reward submitters only when submitted block is finalized (#34)

* reward submitters on finalization

* fix grumble

* make submitter part of ImportContext
This commit is contained in:
Svyatoslav Nikolsky
2020-03-19 15:21:02 +03:00
committed by Bastian Köcher
parent 8232bdfe30
commit d904a282c8
5 changed files with 252 additions and 85 deletions
+19 -12
View File
@@ -49,9 +49,10 @@ pub fn finalize_blocks<S: Storage>(
best_finalized_hash: &H256,
header_validators: (&H256, &[Address]),
hash: &H256,
submitter: Option<&S::Submitter>,
header: &Header,
two_thirds_majority_transition: u64,
) -> Result<Vec<(u64, H256)>, Error> {
) -> Result<Vec<(u64, H256, Option<S::Submitter>)>, 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<S: Storage>(
&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<S: Storage>(
validators: &BTreeSet<&Address>,
hash: &H256,
header: &Header,
submitter: Option<&S::Submitter>,
two_thirds_majority_transition: u64,
) -> Result<(BTreeMap<Address, u64>, VecDeque<(H256, u64, BTreeSet<Address>)>), Error> {
) -> Result<(BTreeMap<Address, u64>, VecDeque<(H256, u64, Option<S::Submitter>, BTreeSet<Address>)>), 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<S: Storage>(
// 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<S: Storage>(
// 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);
}
+100 -23
View File
@@ -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<S: Storage>(
aura_config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration,
prune_depth: u64,
submitter: Option<S::Submitter>,
headers: Vec<(Header, Option<Vec<Receipt>>)>,
finalized_headers: &mut BTreeMap<S::Submitter, u64>,
) -> 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<S: Storage>(
///
/// Transactions receipts must be provided if `header_import_requires_receipts()`
/// has returned true.
///
/// Returns imported block hash.
pub fn import_header<S: Storage>(
storage: &mut S,
aura_config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration,
prune_depth: u64,
submitter: Option<S::Submitter>,
header: Header,
receipts: Option<Vec<Receipt>>,
) -> Result<H256, Error> {
) -> Result<(H256, Vec<(u64, H256, Option<S::Submitter>)>), 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<S: Storage>(
&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<S: Storage>(
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(
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);
}
}
+115 -36
View File
@@ -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> {
/// Submitter of this header. May be `None` if header has been submitted
/// using unsigned transaction.
pub submitter: Option<Submitter>,
/// 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<Submitter> {
/// Header import context,
pub context: ImportContext,
pub context: ImportContext<Submitter>,
/// 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> {
submitter: Option<Submitter>,
parent_header: Header,
parent_total_difficulty: U256,
next_validators_set_id: u64,
next_validators_set: (H256, Vec<Address>),
}
impl ImportContext {
impl<Submitter> ImportContext<Submitter> {
/// Create import context using passing parameters;
pub fn new(
submitter: Option<Submitter>,
parent_header: Header,
parent_total_difficulty: U256,
next_validators_set_id: u64,
next_validators_set: (H256, Vec<Address>),
) -> 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<Vec<Address>>,
scheduled_change: Option<Vec<Address>>,
) -> HeaderToImport {
) -> HeaderToImport<Submitter> {
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<Header>;
///
/// Returns header and its submitter (if known).
fn header(&self, hash: &H256) -> Option<(Header, Option<Self::Submitter>)>;
/// Get header import context by parent header hash.
fn import_context(&self, parent_hash: &H256) -> Option<ImportContext>;
fn import_context(
&self,
submitter: Option<Self::Submitter>,
parent_hash: &H256,
) -> Option<ImportContext<Self::Submitter>>;
/// Get new validators that are scheduled by given header.
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>>;
/// Insert imported header.
fn insert_header(&mut self, header: HeaderToImport);
fn insert_header(&mut self, header: HeaderToImport<Self::Submitter>);
/// 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<AccountId> {
/// 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<AccountId> OnHeadersSubmitted<AccountId> 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<Vec<Receipt>>)>) {
let submitter = frame_system::ensure_signed(origin)?;
let mut finalized_headers = BTreeMap::new();
let import_result = import::import_headers(
&mut BridgeStorage,
&mut BridgeStorage::<T>::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<StoredHeader>;
Headers: map hasher(blake2_256) H256 => Option<StoredHeader<T::AccountId>>;
/// Map of imported header hashes by number.
HeadersByNumber: map hasher(blake2_256) u64 => Option<Vec<H256>>;
/// 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::<T>::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<T: Trait> Module<T> {
/// 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::<T>::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::<T>::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::<T>::new().header(&hash).is_some()
}
}
/// Runtime bridge storage.
struct BridgeStorage;
#[derive(Default)]
struct BridgeStorage<T>(sp_std::marker::PhantomData<T>);
impl<T> BridgeStorage<T> {
pub fn new() -> Self {
BridgeStorage(sp_std::marker::PhantomData::<T>::default())
}
}
impl<T: Trait> Storage for BridgeStorage<T> {
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<Header> {
Headers::get(hash).map(|header| header.header)
fn header(&self, hash: &H256) -> Option<(Header, Option<Self::Submitter>)> {
Headers::<T>::get(hash).map(|header| (header.header, header.submitter))
}
fn import_context(&self, parent_hash: &H256) -> Option<ImportContext> {
Headers::get(parent_hash).map(|parent_header| {
fn import_context(
&self,
submitter: Option<Self::Submitter>,
parent_hash: &H256,
) -> Option<ImportContext<Self::Submitter>> {
Headers::<T>::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<Self::Submitter>) {
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::<T>::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::<T>::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<Item = (H256, Header)> + 'a {
pub(crate) fn ancestry<'a, S: Storage>(
storage: &'a S,
header: &Header,
) -> impl Iterator<Item = (H256, Header, Option<S::Submitter>)> + '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<H256, StoredHeader>,
headers: HashMap<H256, StoredHeader<AccountId>>,
headers_by_number: HashMap<u64, Vec<H256>>,
next_validators_set_id: u64,
validators_sets: HashMap<u64, (H256, Vec<Address>)>,
@@ -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<AccountId>> {
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<Header> {
self.headers.get(hash).map(|header| header.header.clone())
fn header(&self, hash: &H256) -> Option<(Header, Option<Self::Submitter>)> {
self.headers.get(hash).map(|header| (header.header.clone(), header.submitter.clone()))
}
fn import_context(&self, parent_hash: &H256) -> Option<ImportContext> {
fn import_context(
&self,
submitter: Option<Self::Submitter>,
parent_hash: &H256,
) -> Option<ImportContext<Self::Submitter>> {
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<Self::Submitter>) {
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,
+2 -2
View File
@@ -198,9 +198,9 @@ impl<'a> Validators<'a> {
pub fn finalize_validators_change<S: Storage>(
&self,
storage: &mut S,
finalized_blocks: &[(u64, H256)],
finalized_blocks: &[(u64, H256, Option<S::Submitter>)],
) -> Option<Vec<Address>> {
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);
}
+10 -6
View File
@@ -40,14 +40,15 @@ use sp_io::crypto::secp256k1_ecdsa_recover;
pub fn verify_aura_header<S: Storage>(
storage: &S,
params: &AuraConfiguration,
submitter: Option<S::Submitter>,
header: &Header,
) -> Result<ImportContext, Error> {
) -> Result<ImportContext<S::Submitter>, 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<ImportContext, Error> {
fn verify_with_config(
config: &AuraConfiguration,
header: &Header,
) -> Result<ImportContext<AccountId>, 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<ImportContext, Error> {
fn default_verify(header: &Header) -> Result<ImportContext<AccountId>, Error> {
verify_with_config(&kovan_aura_config(), header)
}