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, best_finalized_hash: &H256,
header_validators: (&H256, &[Address]), header_validators: (&H256, &[Address]),
hash: &H256, hash: &H256,
submitter: Option<&S::Submitter>,
header: &Header, header: &Header,
two_thirds_majority_transition: u64, 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 // compute count of voters for every unfinalized block in ancestry
let validators = header_validators.1.iter().collect(); let validators = header_validators.1.iter().collect();
let (mut votes, mut headers) = prepare_votes( let (mut votes, mut headers) = prepare_votes(
@@ -61,18 +62,19 @@ pub fn finalize_blocks<S: Storage>(
&validators, &validators,
hash, hash,
header, header,
submitter,
two_thirds_majority_transition, two_thirds_majority_transition,
)?; )?;
// now let's iterate in reverse order && find just finalized blocks // now let's iterate in reverse order && find just finalized blocks
let mut newly_finalized = Vec::new(); 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) { if !is_finalized(&validators, &votes, oldest_number >= two_thirds_majority_transition) {
break; break;
} }
remove_signers_votes(&signers, &mut votes); remove_signers_votes(&signers, &mut votes);
newly_finalized.push((oldest_number, oldest_hash)); newly_finalized.push((oldest_number, oldest_hash, submitter));
} }
Ok(newly_finalized) Ok(newly_finalized)
@@ -96,8 +98,9 @@ fn prepare_votes<S: Storage>(
validators: &BTreeSet<&Address>, validators: &BTreeSet<&Address>,
hash: &H256, hash: &H256,
header: &Header, header: &Header,
submitter: Option<&S::Submitter>,
two_thirds_majority_transition: u64, 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 // this fn can only work with single validators set
if !validators.contains(&header.author) { if !validators.contains(&header.author) {
return Err(Error::NotValidator); return Err(Error::NotValidator);
@@ -108,17 +111,17 @@ fn prepare_votes<S: Storage>(
// the same set of validators // the same set of validators
let mut parent_empty_step_signers = empty_steps_signers(header); let mut parent_empty_step_signers = empty_steps_signers(header);
let ancestry = ancestry(storage, header) let ancestry = ancestry(storage, header)
.map(|(hash, header)| { .map(|(hash, header, submitter)| {
let mut signers = BTreeSet::new(); let mut signers = BTreeSet::new();
sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers); sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers);
signers.insert(header.author); signers.insert(header.author);
let empty_step_signers = empty_steps_signers(&header); 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; parent_empty_step_signers = empty_step_signers;
res 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 // now let's iterate built iterator and compute number of validators
// 'voted' for each header // 'voted' for each header
@@ -126,21 +129,21 @@ fn prepare_votes<S: Storage>(
// just finalized blocks) // just finalized blocks)
let mut votes = BTreeMap::new(); let mut votes = BTreeMap::new();
let mut headers = VecDeque::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)?; add_signers_votes(validators, &signers, &mut votes)?;
if is_finalized(validators, &votes, number >= two_thirds_majority_transition) { if is_finalized(validators, &votes, number >= two_thirds_majority_transition) {
remove_signers_votes(&signers, &mut votes); remove_signers_votes(&signers, &mut votes);
break; break;
} }
headers.push_front((hash, number, signers)); headers.push_front((hash, number, submitter, signers));
} }
// update votes with last header vote // update votes with last header vote
let mut header_signers = BTreeSet::new(); let mut header_signers = BTreeSet::new();
header_signers.insert(header.author); header_signers.insert(header.author);
*votes.entry(header.author).or_insert(0) += 1; *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)) Ok((votes, headers))
} }
@@ -211,6 +214,7 @@ mod tests {
&Default::default(), &Default::default(),
(&Default::default(), &[]), (&Default::default(), &[]),
&Default::default(), &Default::default(),
None,
&Header::default(), &Header::default(),
0, 0,
), ),
@@ -233,7 +237,7 @@ mod tests {
}; };
let hash1 = header1.hash(); let hash1 = header1.hash();
let mut header_to_import = HeaderToImport { let mut header_to_import = HeaderToImport {
context: storage.import_context(&genesis().hash()).unwrap(), context: storage.import_context(None, &genesis().hash()).unwrap(),
is_best: true, is_best: true,
hash: hash1, hash: hash1,
header: header1, header: header1,
@@ -247,6 +251,7 @@ mod tests {
&Default::default(), &Default::default(),
(&Default::default(), &validators_addresses(5)), (&Default::default(), &validators_addresses(5)),
&hash1, &hash1,
None,
&header_to_import.header, &header_to_import.header,
u64::max_value(), u64::max_value(),
), ),
@@ -269,6 +274,7 @@ mod tests {
&Default::default(), &Default::default(),
(&Default::default(), &validators_addresses(5)), (&Default::default(), &validators_addresses(5)),
&hash2, &hash2,
None,
&header_to_import.header, &header_to_import.header,
u64::max_value(), u64::max_value(),
), ),
@@ -291,10 +297,11 @@ mod tests {
&Default::default(), &Default::default(),
(&Default::default(), &validators_addresses(5)), (&Default::default(), &validators_addresses(5)),
&hash3, &hash3,
None,
&header_to_import.header, &header_to_import.header,
u64::max_value(), u64::max_value(),
), ),
Ok(vec![(1, hash1)]), Ok(vec![(1, hash1, None)]),
); );
storage.insert_header(header_to_import); storage.insert_header(header_to_import);
} }
+106 -29
View File
@@ -36,7 +36,10 @@ use crate::validators::{Validators, ValidatorsConfiguration};
use crate::verification::verify_aura_header; use crate::verification::verify_aura_header;
use crate::{AuraConfiguration, Storage}; use crate::{AuraConfiguration, Storage};
use primitives::{Header, Receipt, H256}; 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 /// 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. /// are too many unfinalized headers, it slows down finalization tracking significantly.
@@ -61,15 +64,32 @@ pub fn import_headers<S: Storage>(
aura_config: &AuraConfiguration, aura_config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration, validators_config: &ValidatorsConfiguration,
prune_depth: u64, prune_depth: u64,
submitter: Option<S::Submitter>,
headers: Vec<(Header, Option<Vec<Receipt>>)>, headers: Vec<(Header, Option<Vec<Receipt>>)>,
finalized_headers: &mut BTreeMap<S::Submitter, u64>,
) -> Result<(u64, u64), Error> { ) -> Result<(u64, u64), Error> {
let mut useful = 0; let mut useful = 0;
let mut useless = 0; let mut useless = 0;
for (header, receipts) in headers { 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 { 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::AncientHeader) | Err(Error::KnownHeader) => useless += 1,
Err(error) => return Err(error), 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()` /// Transactions receipts must be provided if `header_import_requires_receipts()`
/// has returned true. /// has returned true.
///
/// Returns imported block hash.
pub fn import_header<S: Storage>( pub fn import_header<S: Storage>(
storage: &mut S, storage: &mut S,
aura_config: &AuraConfiguration, aura_config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration, validators_config: &ValidatorsConfiguration,
prune_depth: u64, prune_depth: u64,
submitter: Option<S::Submitter>,
header: Header, header: Header,
receipts: Option<Vec<Receipt>>, 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 // first check that we are able to import this header at all
let (hash, prev_finalized_hash) = is_importable_header(storage, &header)?; let (hash, prev_finalized_hash) = is_importable_header(storage, &header)?;
// verify 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 // check if block schedules new validators
let validators = Validators::new(validators_config); let validators = Validators::new(validators_config);
@@ -106,6 +129,7 @@ pub fn import_header<S: Storage>(
&prev_finalized_hash, &prev_finalized_hash,
(import_context.validators_start(), import_context.validators()), (import_context.validators_start(), import_context.validators()),
&hash, &hash,
import_context.submitter(),
&header, &header,
aura_config.two_thirds_majority_transition, 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 total_difficulty = import_context.total_difficulty() + header.difficulty;
let is_best = total_difficulty > best_total_difficulty; let is_best = total_difficulty > best_total_difficulty;
let header_number = header.number; let header_number = header.number;
storage.insert_header(import_context.into_import_header( storage.insert_header(
is_best, import_context.into_import_header(
hash, is_best,
header, hash,
total_difficulty, header,
enacted_change, total_difficulty,
scheduled_change, enacted_change,
)); scheduled_change,
),
);
// now mark finalized headers && prune old headers // now mark finalized headers && prune old headers
storage.finalize_headers( storage.finalize_headers(
finalized_blocks.last().cloned(), finalized_blocks.last().map(|(number, hash, _)| (*number, *hash)),
match is_best { match is_best {
true => header_number.checked_sub(prune_depth), true => header_number.checked_sub(prune_depth),
false => None, false => None,
}, },
); );
Ok(hash) Ok((hash, finalized_blocks))
} }
/// Returns true if transactions receipts are required to import given header. /// Returns true if transactions receipts are required to import given header.
@@ -189,6 +215,7 @@ mod tests {
&kovan_aura_config(), &kovan_aura_config(),
&kovan_validators_config(), &kovan_validators_config(),
PRUNE_DEPTH, PRUNE_DEPTH,
None,
Default::default(), Default::default(),
None, None,
), ),
@@ -207,6 +234,7 @@ mod tests {
&kovan_aura_config(), &kovan_aura_config(),
&kovan_validators_config(), &kovan_validators_config(),
PRUNE_DEPTH, PRUNE_DEPTH,
None,
block.clone(), block.clone(),
None, None,
) )
@@ -219,6 +247,7 @@ mod tests {
&kovan_aura_config(), &kovan_aura_config(),
&kovan_validators_config(), &kovan_validators_config(),
PRUNE_DEPTH, PRUNE_DEPTH,
None,
block, block,
None, None,
) )
@@ -243,6 +272,7 @@ mod tests {
&kovan_aura_config(), &kovan_aura_config(),
&validators_config, &validators_config,
PRUNE_DEPTH, PRUNE_DEPTH,
None,
header, header,
None None
) )
@@ -267,43 +297,67 @@ mod tests {
// header [0..11] are finalizing blocks [0; 9] // header [0..11] are finalizing blocks [0; 9]
// => since we want to keep 10 finalized blocks, we aren't pruning anything // => 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 { for i in 1..11 {
let header = block_i(&storage, i, &validators); let header = block_i(&storage, i, &validators);
last_block_hash = let (rolling_last_block_hash, finalized_blocks) =
import_header(&mut storage, &kovan_aura_config(), &validators_config, 10, header, None).unwrap(); 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()); assert!(storage.header(&genesis().hash()).is_some());
// header 11 finalizes headers [10] AND schedules change // header 11 finalizes headers [10] AND schedules change
// => we prune header#0 // => 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.log_bloom = (&[0xff; 256]).into();
header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951" header.receipts_root = "2e60346495092587026484e868a5b3063749032b2ea3843844509a6320d7f951"
.parse() .parse()
.unwrap(); .unwrap();
}); });
last_block_hash = import_header( let (rolling_last_block_hash, finalized_blocks) = import_header(
&mut storage, &mut storage,
&kovan_aura_config(), &kovan_aura_config(),
&validators_config, &validators_config,
10, 10,
header, Some(101),
header11.clone(),
Some(vec![crate::validators::tests::validators_change_recept( 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()); assert!(storage.header(&genesis().hash()).is_none());
latest_block_hash = rolling_last_block_hash;
// and now let's say validators 1 && 2 went offline // 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 // => 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 // until header#11 is met. we can't prune #11, because it schedules change
let mut step = 56; let mut step = 56;
let mut expected_blocks = vec![(11, header11.hash(), Some(101))];
for i in 12..25 { for i in 12..25 {
let header = Header { let header = Header {
number: i as _, number: i as _,
parent_hash: last_block_hash, parent_hash: latest_block_hash,
gas_limit: 0x2000.into(), gas_limit: 0x2000.into(),
author: validator(2).address().to_fixed_bytes().into(), author: validator(2).address().to_fixed_bytes().into(),
seal: vec![vec![step].into(), vec![].into()], seal: vec![vec![step].into(), vec![].into()],
@@ -311,8 +365,22 @@ mod tests {
..Default::default() ..Default::default()
}; };
let header = signed_header(&validators, header, step as _); let header = signed_header(&validators, header, step as _);
last_block_hash = expected_blocks.push((i, header.hash(), Some(102)));
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(102),
header,
None,
).unwrap();
assert_eq!(
finalized_blocks,
vec![],
);
latest_block_hash = rolling_last_block_hash;
step += 3; step += 3;
} }
assert_eq!(storage.oldest_unpruned_block(), 11); assert_eq!(storage.oldest_unpruned_block(), 11);
@@ -322,7 +390,7 @@ mod tests {
step -= 2; step -= 2;
let header = Header { let header = Header {
number: 25, number: 25,
parent_hash: last_block_hash, parent_hash: latest_block_hash,
gas_limit: 0x2000.into(), gas_limit: 0x2000.into(),
author: validator(0).address().to_fixed_bytes().into(), author: validator(0).address().to_fixed_bytes().into(),
seal: vec![vec![step].into(), vec![].into()], seal: vec![vec![step].into(), vec![].into()],
@@ -330,7 +398,16 @@ mod tests {
..Default::default() ..Default::default()
}; };
let header = signed_header(&validators, header, step as _); 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); 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 frame_support::{decl_module, decl_storage};
use primitives::{Address, Header, Receipt, H256, U256}; use primitives::{Address, Header, Receipt, H256, U256};
use sp_runtime::RuntimeDebug; 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}; use validators::{ValidatorsConfiguration, ValidatorsSource};
pub use import::{header_import_requires_receipts, import_header}; 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. /// Block header as it is stored in the runtime storage.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)] #[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. /// The block header itself.
pub header: Header, pub header: Header,
/// Total difficulty of the chain. /// Total difficulty of the chain.
@@ -85,9 +93,9 @@ pub struct StoredHeader {
/// Header that we're importing. /// Header that we're importing.
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
#[cfg_attr(test, derive(Clone, PartialEq))] #[cfg_attr(test, derive(Clone, PartialEq))]
pub struct HeaderToImport { pub struct HeaderToImport<Submitter> {
/// Header import context, /// Header import context,
pub context: ImportContext, pub context: ImportContext<Submitter>,
/// Should we consider this header as best? /// Should we consider this header as best?
pub is_best: bool, pub is_best: bool,
/// The hash of the header. /// The hash of the header.
@@ -105,22 +113,25 @@ pub struct HeaderToImport {
/// Header import context. /// Header import context.
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
#[cfg_attr(test, derive(Clone, PartialEq))] #[cfg_attr(test, derive(Clone, PartialEq))]
pub struct ImportContext { pub struct ImportContext<Submitter> {
submitter: Option<Submitter>,
parent_header: Header, parent_header: Header,
parent_total_difficulty: U256, parent_total_difficulty: U256,
next_validators_set_id: u64, next_validators_set_id: u64,
next_validators_set: (H256, Vec<Address>), next_validators_set: (H256, Vec<Address>),
} }
impl ImportContext { impl<Submitter> ImportContext<Submitter> {
/// Create import context using passing parameters; /// Create import context using passing parameters;
pub fn new( pub fn new(
submitter: Option<Submitter>,
parent_header: Header, parent_header: Header,
parent_total_difficulty: U256, parent_total_difficulty: U256,
next_validators_set_id: u64, next_validators_set_id: u64,
next_validators_set: (H256, Vec<Address>), next_validators_set: (H256, Vec<Address>),
) -> Self { ) -> Self {
ImportContext { ImportContext {
submitter,
parent_header, parent_header,
parent_total_difficulty, parent_total_difficulty,
next_validators_set_id, 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. /// Returns reference to parent header.
pub fn parent_header(&self) -> &Header { pub fn parent_header(&self) -> &Header {
&self.parent_header &self.parent_header
@@ -162,7 +178,7 @@ impl ImportContext {
total_difficulty: U256, total_difficulty: U256,
enacted_change: Option<Vec<Address>>, enacted_change: Option<Vec<Address>>,
scheduled_change: Option<Vec<Address>>, scheduled_change: Option<Vec<Address>>,
) -> HeaderToImport { ) -> HeaderToImport<Submitter> {
HeaderToImport { HeaderToImport {
context: self, context: self,
is_best, is_best,
@@ -179,18 +195,27 @@ impl ImportContext {
/// ///
/// Storage modification must be discarded if block import has failed. /// Storage modification must be discarded if block import has failed.
pub trait Storage { pub trait Storage {
/// Header submitter identifier.
type Submitter: Clone + Ord;
/// Get best known block. /// Get best known block.
fn best_block(&self) -> (u64, H256, U256); fn best_block(&self) -> (u64, H256, U256);
/// Get last finalized block. /// Get last finalized block.
fn finalized_block(&self) -> (u64, H256); fn finalized_block(&self) -> (u64, H256);
/// Get imported header by its hash. /// 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. /// 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. /// Get new validators that are scheduled by given header.
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>>; fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>>;
/// Insert imported header. /// 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. /// Finalize given block and prune all headers with number < prune_end.
/// The headers in the pruning range could be either finalized, or not. /// The headers in the pruning range could be either finalized, or not.
/// It is the storage duty to ensure that unfinalized headers that have /// 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. /// Decides whether the session should be ended.
pub trait OnHeadersSubmitted<AccountId> { pub trait OnHeadersSubmitted<AccountId> {
/// Called when valid headers have been submitted. /// 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); fn on_valid_headers_submitted(submitter: AccountId, useful: u64, useless: u64);
/// Called when invalid headers have been submitted. /// Called when invalid headers have been submitted.
fn on_invalid_headers_submitted(submitter: AccountId); 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 () { impl<AccountId> OnHeadersSubmitted<AccountId> for () {
fn on_valid_headers_submitted(_submitter: AccountId, _useful: u64, _useless: u64) {} fn on_valid_headers_submitted(_submitter: AccountId, _useful: u64, _useless: u64) {}
fn on_invalid_headers_submitted(_submitter: AccountId) {} fn on_invalid_headers_submitted(_submitter: AccountId) {}
fn on_valid_headers_finalized(_submitter: AccountId, _finalized: u64) {}
} }
/// The module configuration trait /// The module configuration trait
@@ -228,14 +264,27 @@ decl_module! {
/// enormous block production/import time. /// enormous block production/import time.
pub fn import_headers(origin, headers_with_receipts: Vec<(Header, Option<Vec<Receipt>>)>) { pub fn import_headers(origin, headers_with_receipts: Vec<(Header, Option<Vec<Receipt>>)>) {
let submitter = frame_system::ensure_signed(origin)?; let submitter = frame_system::ensure_signed(origin)?;
let mut finalized_headers = BTreeMap::new();
let import_result = import::import_headers( let import_result = import::import_headers(
&mut BridgeStorage, &mut BridgeStorage::<T>::new(),
&kovan_aura_config(), &kovan_aura_config(),
&kovan_validators_config(), &kovan_validators_config(),
crate::import::PRUNE_DEPTH, crate::import::PRUNE_DEPTH,
Some(submitter.clone()),
headers_with_receipts, 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 { match import_result {
Ok((useful, useless)) => Ok((useful, useless)) =>
T::OnHeadersSubmitted::on_valid_headers_submitted(submitter, useful, useless), T::OnHeadersSubmitted::on_valid_headers_submitted(submitter, useful, useless),
@@ -259,7 +308,7 @@ decl_storage! {
/// Oldest unpruned block(s) number. /// Oldest unpruned block(s) number.
OldestUnprunedBlock: u64; OldestUnprunedBlock: u64;
/// Map of imported headers by hash. /// 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. /// Map of imported header hashes by number.
HeadersByNumber: map hasher(blake2_256) u64 => Option<Vec<H256>>; HeadersByNumber: map hasher(blake2_256) u64 => Option<Vec<H256>>;
/// The ID of next validator set. /// The ID of next validator set.
@@ -293,7 +342,8 @@ decl_storage! {
FinalizedBlock::put((config.initial_header.number, initial_hash)); FinalizedBlock::put((config.initial_header.number, initial_hash));
OldestUnprunedBlock::put(config.initial_header.number); OldestUnprunedBlock::put(config.initial_header.number);
HeadersByNumber::insert(config.initial_header.number, vec![initial_hash]); 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(), header: config.initial_header.clone(),
total_difficulty: config.initial_difficulty, total_difficulty: config.initial_difficulty,
next_validators_set_id: 0, next_validators_set_id: 0,
@@ -310,25 +360,34 @@ impl<T: Trait> Module<T> {
/// The caller should only submit `import_header` transaction that makes /// The caller should only submit `import_header` transaction that makes
/// (or leads to making) other header the best one. /// (or leads to making) other header the best one.
pub fn best_block() -> (u64, H256) { pub fn best_block() -> (u64, H256) {
let (number, hash, _) = BridgeStorage.best_block(); let (number, hash, _) = BridgeStorage::<T>::new().best_block();
(number, hash) (number, hash)
} }
/// Returns true if the import of given block requires transactions receipts. /// Returns true if the import of given block requires transactions receipts.
pub fn is_import_requires_receipts(header: Header) -> bool { 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. /// Returns true if header is known to the runtime.
pub fn is_known_block(hash: H256) -> bool { pub fn is_known_block(hash: H256) -> bool {
BridgeStorage.header(&hash).is_some() BridgeStorage::<T>::new().header(&hash).is_some()
} }
} }
/// Runtime bridge storage. /// 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) { fn best_block(&self) -> (u64, H256, U256) {
BestBlock::get() BestBlock::get()
} }
@@ -337,16 +396,21 @@ impl Storage for BridgeStorage {
FinalizedBlock::get() FinalizedBlock::get()
} }
fn header(&self, hash: &H256) -> Option<Header> { fn header(&self, hash: &H256) -> Option<(Header, Option<Self::Submitter>)> {
Headers::get(hash).map(|header| header.header) Headers::<T>::get(hash).map(|header| (header.header, header.submitter))
} }
fn import_context(&self, parent_hash: &H256) -> Option<ImportContext> { fn import_context(
Headers::get(parent_hash).map(|parent_header| { &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) = let (next_validators_set_start, next_validators) =
ValidatorsSets::get(parent_header.next_validators_set_id) ValidatorsSets::get(parent_header.next_validators_set_id)
.expect("validators set is only pruned when last ref is pruned; there is a ref; qed"); .expect("validators set is only pruned when last ref is pruned; there is a ref; qed");
ImportContext { ImportContext {
submitter,
parent_header: parent_header.header, parent_header: parent_header.header,
parent_total_difficulty: parent_header.total_difficulty, parent_total_difficulty: parent_header.total_difficulty,
next_validators_set_id: parent_header.next_validators_set_id, next_validators_set_id: parent_header.next_validators_set_id,
@@ -359,7 +423,7 @@ impl Storage for BridgeStorage {
ScheduledChanges::get(hash) ScheduledChanges::get(hash)
} }
fn insert_header(&mut self, header: HeaderToImport) { fn insert_header(&mut self, header: HeaderToImport<Self::Submitter>) {
if header.is_best { if header.is_best {
BestBlock::put((header.header.number, header.hash, header.total_difficulty)); 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]); HeadersByNumber::append_or_insert(header.header.number, vec![header.hash]);
Headers::insert( Headers::<T>::insert(
&header.hash, &header.hash,
StoredHeader { StoredHeader {
submitter: header.context.submitter,
header: header.header, header: header.header,
total_difficulty: header.total_difficulty, total_difficulty: header.total_difficulty,
next_validators_set_id, next_validators_set_id,
@@ -426,7 +491,7 @@ impl Storage for BridgeStorage {
// physically remove headers and (probably) obsolete validators sets // physically remove headers and (probably) obsolete validators sets
for hash in blocks_at_number.into_iter().flat_map(|x| x) { 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); ScheduledChanges::remove(hash);
if let Some(header) = header { if let Some(header) = header {
ValidatorsSetsRc::mutate(header.next_validators_set_id, |rc| match *rc { 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. /// 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(); let mut parent_hash = header.parent_hash.clone();
from_fn(move || { from_fn(move || {
let header = storage.header(&parent_hash); let header_and_submitter = storage.header(&parent_hash);
match header { match header_and_submitter {
Some(header) => { Some((header, submitter)) => {
if header.number == 0 { if header.number == 0 {
return None; return None;
} }
let hash = parent_hash.clone(); let hash = parent_hash.clone();
parent_hash = header.parent_hash.clone(); parent_hash = header.parent_hash.clone();
Some((hash, header)) Some((hash, header, submitter))
} }
None => None, None => None,
} }
@@ -596,6 +664,8 @@ pub(crate) mod tests {
use primitives::{rlp_encode, H520}; use primitives::{rlp_encode, H520};
use std::collections::{hash_map::Entry, HashMap}; use std::collections::{hash_map::Entry, HashMap};
pub type AccountId = u64;
pub fn genesis() -> Header { pub fn genesis() -> Header {
Header { Header {
seal: vec![vec![42].into(), vec![].into()], seal: vec![vec![42].into(), vec![].into()],
@@ -651,7 +721,7 @@ pub(crate) mod tests {
best_block: (u64, H256, U256), best_block: (u64, H256, U256),
finalized_block: (u64, H256), finalized_block: (u64, H256),
oldest_unpruned_block: u64, oldest_unpruned_block: u64,
headers: HashMap<H256, StoredHeader>, headers: HashMap<H256, StoredHeader<AccountId>>,
headers_by_number: HashMap<u64, Vec<H256>>, headers_by_number: HashMap<u64, Vec<H256>>,
next_validators_set_id: u64, next_validators_set_id: u64,
validators_sets: HashMap<u64, (H256, Vec<Address>)>, validators_sets: HashMap<u64, (H256, Vec<Address>)>,
@@ -670,6 +740,7 @@ pub(crate) mod tests {
headers: vec![( headers: vec![(
hash, hash,
StoredHeader { StoredHeader {
submitter: None,
header: initial_header, header: initial_header,
total_difficulty: 0.into(), total_difficulty: 0.into(),
next_validators_set_id: 0, next_validators_set_id: 0,
@@ -688,12 +759,14 @@ pub(crate) mod tests {
self.oldest_unpruned_block 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) self.headers.get(hash)
} }
} }
impl Storage for InMemoryStorage { impl Storage for InMemoryStorage {
type Submitter = AccountId;
fn best_block(&self) -> (u64, H256, U256) { fn best_block(&self) -> (u64, H256, U256) {
self.best_block.clone() self.best_block.clone()
} }
@@ -702,15 +775,20 @@ pub(crate) mod tests {
self.finalized_block.clone() self.finalized_block.clone()
} }
fn header(&self, hash: &H256) -> Option<Header> { fn header(&self, hash: &H256) -> Option<(Header, Option<Self::Submitter>)> {
self.headers.get(hash).map(|header| header.header.clone()) 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| { self.headers.get(parent_hash).map(|parent_header| {
let (next_validators_set_start, next_validators) = let (next_validators_set_start, next_validators) =
self.validators_sets.get(&parent_header.next_validators_set_id).unwrap(); self.validators_sets.get(&parent_header.next_validators_set_id).unwrap();
ImportContext { ImportContext {
submitter,
parent_header: parent_header.header.clone(), parent_header: parent_header.header.clone(),
parent_total_difficulty: parent_header.total_difficulty, parent_total_difficulty: parent_header.total_difficulty,
next_validators_set_id: parent_header.next_validators_set_id, next_validators_set_id: parent_header.next_validators_set_id,
@@ -723,7 +801,7 @@ pub(crate) mod tests {
self.scheduled_changes.get(hash).cloned() 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 { if header.is_best {
self.best_block = (header.header.number, header.hash, header.total_difficulty); self.best_block = (header.header.number, header.hash, header.total_difficulty);
} }
@@ -755,6 +833,7 @@ pub(crate) mod tests {
self.headers.insert( self.headers.insert(
header.hash, header.hash,
StoredHeader { StoredHeader {
submitter: header.context.submitter,
header: header.header, header: header.header,
total_difficulty: header.total_difficulty, total_difficulty: header.total_difficulty,
next_validators_set_id, next_validators_set_id,
+2 -2
View File
@@ -198,9 +198,9 @@ impl<'a> Validators<'a> {
pub fn finalize_validators_change<S: Storage>( pub fn finalize_validators_change<S: Storage>(
&self, &self,
storage: &mut S, storage: &mut S,
finalized_blocks: &[(u64, H256)], finalized_blocks: &[(u64, H256, Option<S::Submitter>)],
) -> Option<Vec<Address>> { ) -> 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) { if let Some(changes) = storage.scheduled_change(finalized_hash) {
return Some(changes); 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>( pub fn verify_aura_header<S: Storage>(
storage: &S, storage: &S,
params: &AuraConfiguration, params: &AuraConfiguration,
submitter: Option<S::Submitter>,
header: &Header, header: &Header,
) -> Result<ImportContext, Error> { ) -> Result<ImportContext<S::Submitter>, Error> {
// let's do the lightest check first // let's do the lightest check first
contextless_checks(params, header)?; contextless_checks(params, header)?;
// the rest of checks requires parent // the rest of checks requires parent
let context = storage let context = storage
.import_context(&header.parent_hash) .import_context(submitter, &header.parent_hash)
.ok_or(Error::MissingParentBlock)?; .ok_or(Error::MissingParentBlock)?;
let validators = context.validators(); let validators = context.validators();
let header_step = header.step().ok_or(Error::MissingStep)?; 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 { mod tests {
use super::*; use super::*;
use crate::kovan_aura_config; 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 parity_crypto::publickey::{sign, KeyPair};
use primitives::{rlp_encode, H520}; use primitives::{rlp_encode, H520};
@@ -197,12 +198,15 @@ mod tests {
empty_step 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)); 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) verify_with_config(&kovan_aura_config(), header)
} }