Sync ethereum headers using unsigned (substrate) transactions (#45)

* reward submitters on finalization

* PoA -> Substrate: unsigned_import_header API

* fix grumble

* make submitter part of ImportContext

* verify using next validators set + tests

* fix nostd compilation

* add sub-tx-mode argument

* support sub-tx-mode

* impl ValidateUnsigned for Runtime

* do not submit too much transactions to the pool

* cargo fmt

* fix bad merge

* revert license fix

* Update modules/ethereum/src/lib.rs

Co-Authored-By: Hernando Castano <HCastano@users.noreply.github.com>

* Update modules/ethereum/src/verification.rs

Co-Authored-By: Hernando Castano <HCastano@users.noreply.github.com>

* updated comment

* validate receipts before accepting unsigned tx to pool

* cargo fmt

* fix comment

* fix grumbles

* Update modules/ethereum/src/verification.rs

Co-Authored-By: Hernando Castano <HCastano@users.noreply.github.com>

* cargo fmt --all

* struct ChangeToEnact

* updated doc

* fix doc

* add docs to the code method

* simplify fn ancestry

* finish incomplete docs

* Update modules/ethereum/src/lib.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Update modules/ethereum/src/lib.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* return err from unsigned_import_header

* get header once

* Update relays/ethereum/src/ethereum_sync.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* fix

* UnsignedTooFarInTheFuture -> Custom(err.code())

* updated ImportContext::last_signal_block

* cargo fmt --all

* rename runtime calls

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
This commit is contained in:
Svyatoslav Nikolsky
2020-04-07 15:53:59 +03:00
committed by Bastian Köcher
parent b055027161
commit c6c46462ab
12 changed files with 1043 additions and 211 deletions
+286 -84
View File
@@ -19,8 +19,14 @@
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::{cmp::Ord, collections::btree_map::BTreeMap, iter::from_fn, prelude::*};
use sp_runtime::{
transaction_validity::{
InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionValidity, UnknownTransaction,
ValidTransaction,
},
RuntimeDebug,
};
use sp_std::{cmp::Ord, collections::btree_map::BTreeMap, prelude::*};
use validators::{ValidatorsConfiguration, ValidatorsSource};
pub use import::{header_import_requires_receipts, import_header};
@@ -52,6 +58,18 @@ pub struct AuraConfiguration {
pub maximum_extra_data_size: u64,
}
/// Transaction pool configuration.
///
/// This is used to limit number of unsigned headers transactions in
/// the pool. We never use it to verify signed transactions.
pub struct PoolConfiguration {
/// Maximal difference between number of header from unsigned transaction
/// and current best block. This must be selected with caution - the more
/// is the difference, the more (potentially invalid) transactions could be
/// accepted to the pool and mined later (filling blocks with spam).
pub max_future_number_difference: u64,
}
/// Block header as it is stored in the runtime storage.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug)]
pub struct StoredHeader<Submitter> {
@@ -67,6 +85,32 @@ pub struct StoredHeader<Submitter> {
/// this is the set that has produced the block itself.
/// The hash is the hash of block where validators set has been enacted.
pub next_validators_set_id: u64,
/// Hash of the last block which has **SCHEDULED** validators set change.
/// Note that signal doesn't mean that the set has been (or ever will be) enacted.
/// Note that the header may already be pruned.
pub last_signal_block: Option<H256>,
}
/// Validators set as it is stored in the runtime storage.
#[derive(Encode, Decode, PartialEq, RuntimeDebug)]
#[cfg_attr(test, derive(Clone))]
pub struct ValidatorsSet {
/// Validators of this set.
pub validators: Vec<Address>,
/// Hash of the block where this set has been signalled. None if this is the first set.
pub signal_block: Option<H256>,
/// Hash of the block where this set has been enacted.
pub enact_block: H256,
}
/// Validators set change as it is stored in the runtime storage.
#[derive(Encode, Decode, PartialEq, RuntimeDebug)]
#[cfg_attr(test, derive(Clone))]
pub struct ScheduledChange {
/// Validators of this set.
pub validators: Vec<Address>,
/// Hash of the block which has emitted previous validators change signal.
pub prev_signal_block: Option<H256>,
}
/// Header that we're importing.
@@ -83,41 +127,44 @@ pub struct HeaderToImport<Submitter> {
pub header: Header,
/// Total chain difficulty at the header.
pub total_difficulty: U256,
/// Validators set enacted change, if happened at the header.
pub enacted_change: Option<Vec<Address>>,
/// New validators set and the hash of block where it has been scheduled (if applicable).
/// Some if set is is enacted by this header.
pub enacted_change: Option<ChangeToEnact>,
/// Validators set scheduled change, if happened at the header.
pub scheduled_change: Option<Vec<Address>>,
}
/// Header that we're importing.
#[derive(RuntimeDebug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct ChangeToEnact {
/// The hash of the header where change has been scheduled.
/// None if it is a first set within current `ValidatorsSource`.
pub signal_block: Option<H256>,
/// Validators set that is enacted.
pub validators: Vec<Address>,
}
/// Header import context.
///
/// The import context contains information needed by the header verification
/// pipeline which is not directly part of the header being imported. This includes
/// information relating to its parent, and the current validator set (which
/// provide _context_ for the current header).
#[derive(RuntimeDebug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct ImportContext<Submitter> {
submitter: Option<Submitter>,
parent_hash: H256,
parent_header: Header,
parent_total_difficulty: U256,
next_validators_set_id: u64,
next_validators_set: (H256, Vec<Address>),
parent_scheduled_change: Option<ScheduledChange>,
validators_set_id: u64,
validators_set: ValidatorsSet,
last_signal_block: Option<H256>,
}
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,
next_validators_set,
}
}
/// Returns reference to header submitter (if known).
pub fn submitter(&self) -> Option<&Submitter> {
self.submitter.as_ref()
@@ -133,19 +180,28 @@ impl<Submitter> ImportContext<Submitter> {
&self.parent_total_difficulty
}
/// Returns the validator set change if the parent header has signaled a change.
pub fn parent_scheduled_change(&self) -> Option<&ScheduledChange> {
self.parent_scheduled_change.as_ref()
}
/// Returns id of the set of validators.
pub fn validators_set_id(&self) -> u64 {
self.next_validators_set_id
self.validators_set_id
}
/// Returns block whenre validators set has been enacted.
pub fn validators_start(&self) -> &H256 {
&self.next_validators_set.0
/// Returns reference to validators set for the block we're going to import.
pub fn validators_set(&self) -> &ValidatorsSet {
&self.validators_set
}
/// Returns reference to the set of validators of the block we're going to import.
pub fn validators(&self) -> &[Address] {
&self.next_validators_set.1
/// Returns reference to the latest block which has signalled change of validators set.
/// This may point to parent if parent has signalled change.
pub fn last_signal_block(&self) -> Option<&H256> {
match self.parent_scheduled_change {
Some(_) => Some(&self.parent_hash),
None => self.last_signal_block.as_ref(),
}
}
/// Converts import context into header we're going to import.
@@ -155,7 +211,7 @@ impl<Submitter> ImportContext<Submitter> {
hash: H256,
header: Header,
total_difficulty: U256,
enacted_change: Option<Vec<Address>>,
enacted_change: Option<ChangeToEnact>,
scheduled_change: Option<Vec<Address>>,
) -> HeaderToImport<Submitter> {
HeaderToImport {
@@ -191,8 +247,9 @@ pub trait Storage {
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>>;
/// Get new validators that are scheduled by given header and hash of the previous
/// block that has scheduled change.
fn scheduled_change(&self, hash: &H256) -> Option<ScheduledChange>;
/// Insert imported header.
fn insert_header(&mut self, header: HeaderToImport<Self::Submitter>);
/// Finalize given block and prune all headers with number < prune_end.
@@ -235,13 +292,28 @@ pub trait Trait: frame_system::Trait {
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
/// Import Aura chain headers. Ignores non-fatal errors (like when known
/// header is provided), rewards for successful headers import and penalizes
/// for fatal errors.
/// Import single Aura header. Requires transaction to be **UNSIGNED**.
pub fn import_unsigned_header(origin, header: Header, receipts: Option<Vec<Receipt>>) {
frame_system::ensure_none(origin)?;
import_header(
&mut BridgeStorage::<T>::new(),
&kovan_aura_config(),
&kovan_validators_config(),
crate::import::PRUNE_DEPTH,
None,
header,
receipts,
).map_err(|e| e.msg())?;
}
/// Import Aura chain headers in a single **SIGNED** transaction.
/// Ignores non-fatal errors (like when known header is provided), rewards
/// for successful headers import and penalizes for fatal errors.
///
/// This should be used with caution - passing too many headers could lead to
/// enormous block production/import time.
pub fn import_headers(origin, headers_with_receipts: Vec<(Header, Option<Vec<Receipt>>)>) {
pub fn import_signed_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(
@@ -293,13 +365,13 @@ decl_storage! {
/// The ID of next validator set.
NextValidatorsSetId: u64;
/// Map of validators sets by their id.
ValidatorsSets: map hasher(blake2_256) u64 => Option<(H256, Vec<Address>)>;
ValidatorsSets: map hasher(blake2_256) u64 => Option<ValidatorsSet>;
/// Validators sets reference count. Each header that is authored by this set increases
/// the reference count. When we prune this header, we decrease the reference count.
/// When it reaches zero, we are free to prune validator set as well.
ValidatorsSetsRc: map hasher(blake2_256) u64 => Option<u64>;
/// Map of validators set changes scheduled by given header.
ScheduledChanges: map hasher(blake2_256) H256 => Option<Vec<Address>>;
ScheduledChanges: map hasher(blake2_256) H256 => Option<ScheduledChange>;
}
add_extra_genesis {
config(initial_header): Header;
@@ -326,9 +398,14 @@ decl_storage! {
header: config.initial_header.clone(),
total_difficulty: config.initial_difficulty,
next_validators_set_id: 0,
last_signal_block: None,
});
NextValidatorsSetId::put(1);
ValidatorsSets::insert(0, (initial_hash, config.initial_validators.clone()));
ValidatorsSets::insert(0, ValidatorsSet {
validators: config.initial_validators.clone(),
signal_block: None,
enact_block: initial_hash,
});
ValidatorsSetsRc::insert(0, 1);
})
}
@@ -354,6 +431,43 @@ impl<T: Trait> Module<T> {
}
}
impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
fn validate_unsigned(call: &Self::Call) -> TransactionValidity {
match *call {
Self::Call::import_unsigned_header(ref header, ref receipts) => {
let accept_result = verification::accept_aura_header_into_pool(
&BridgeStorage::<T>::new(),
&kovan_aura_config(),
&kovan_validators_config(),
&pool_configuration(),
header,
receipts.as_ref(),
);
match accept_result {
Ok((requires, provides)) => Ok(ValidTransaction {
priority: TransactionPriority::max_value(),
requires,
provides,
longevity: TransactionLongevity::max_value(),
propagate: true,
}),
// UnsignedTooFarInTheFuture is the special error code used to limit
// number of transactions in the pool - we do not want to ban transaction
// in this case (see verification.rs for details)
Err(error::Error::UnsignedTooFarInTheFuture) => {
UnknownTransaction::Custom(error::Error::UnsignedTooFarInTheFuture.code()).into()
}
Err(error) => InvalidTransaction::Custom(error.code()).into(),
}
}
_ => InvalidTransaction::Call.into(),
}
}
}
/// Runtime bridge storage.
#[derive(Default)]
struct BridgeStorage<T>(sp_std::marker::PhantomData<T>);
@@ -385,20 +499,23 @@ impl<T: Trait> Storage for BridgeStorage<T> {
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");
let validators_set = ValidatorsSets::get(parent_header.next_validators_set_id)
.expect("validators set is only pruned when last ref is pruned; there is a ref; qed");
let parent_scheduled_change = ScheduledChanges::get(parent_hash);
ImportContext {
submitter,
parent_hash: *parent_hash,
parent_header: parent_header.header,
parent_total_difficulty: parent_header.total_difficulty,
next_validators_set_id: parent_header.next_validators_set_id,
next_validators_set: (next_validators_set_start, next_validators),
parent_scheduled_change,
validators_set_id: parent_header.next_validators_set_id,
validators_set,
last_signal_block: parent_header.last_signal_block,
}
})
}
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>> {
fn scheduled_change(&self, hash: &H256) -> Option<ScheduledChange> {
ScheduledChanges::get(hash)
}
@@ -407,7 +524,13 @@ impl<T: Trait> Storage for BridgeStorage<T> {
BestBlock::put((header.header.number, header.hash, header.total_difficulty));
}
if let Some(scheduled_change) = header.scheduled_change {
ScheduledChanges::insert(&header.hash, scheduled_change);
ScheduledChanges::insert(
&header.hash,
ScheduledChange {
validators: scheduled_change,
prev_signal_block: header.context.last_signal_block,
},
);
}
let next_validators_set_id = match header.enacted_change {
Some(enacted_change) => {
@@ -416,19 +539,27 @@ impl<T: Trait> Storage for BridgeStorage<T> {
*set_id += 1;
next_set_id
});
ValidatorsSets::insert(next_validators_set_id, (header.hash, enacted_change));
ValidatorsSets::insert(
next_validators_set_id,
ValidatorsSet {
validators: enacted_change.validators,
enact_block: header.hash,
signal_block: enacted_change.signal_block,
},
);
ValidatorsSetsRc::insert(next_validators_set_id, 1);
next_validators_set_id
}
None => {
ValidatorsSetsRc::mutate(header.context.next_validators_set_id, |rc| {
ValidatorsSetsRc::mutate(header.context.validators_set_id, |rc| {
*rc = Some(rc.map(|rc| rc + 1).unwrap_or(1));
*rc
});
header.context.next_validators_set_id
header.context.validators_set_id
}
};
let last_signal_block = header.context.last_signal_block().cloned();
HeadersByNumber::append_or_insert(header.header.number, vec![header.hash]);
Headers::<T>::insert(
&header.hash,
@@ -437,6 +568,7 @@ impl<T: Trait> Storage for BridgeStorage<T> {
header: header.header,
total_difficulty: header.total_difficulty,
next_validators_set_id,
last_signal_block,
},
);
}
@@ -616,27 +748,11 @@ 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, Option<S::Submitter>)> + 'a {
let mut parent_hash = header.parent_hash.clone();
from_fn(move || {
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, submitter))
}
None => None,
}
})
/// Transaction pool configuration.
fn pool_configuration() -> PoolConfiguration {
PoolConfiguration {
max_future_number_difference: 10,
}
}
#[cfg(test)]
@@ -706,9 +822,9 @@ pub(crate) mod tests {
headers: HashMap<H256, StoredHeader<AccountId>>,
headers_by_number: HashMap<u64, Vec<H256>>,
next_validators_set_id: u64,
validators_sets: HashMap<u64, (H256, Vec<Address>)>,
validators_sets: HashMap<u64, ValidatorsSet>,
validators_sets_rc: HashMap<u64, u64>,
scheduled_changes: HashMap<H256, Vec<Address>>,
scheduled_changes: HashMap<H256, ScheduledChange>,
}
impl InMemoryStorage {
@@ -726,17 +842,82 @@ pub(crate) mod tests {
header: initial_header,
total_difficulty: 0.into(),
next_validators_set_id: 0,
last_signal_block: None,
},
)]
.into_iter()
.collect(),
next_validators_set_id: 1,
validators_sets: vec![(0, (hash, initial_validators))].into_iter().collect(),
validators_sets: vec![(
0,
ValidatorsSet {
validators: initial_validators,
signal_block: None,
enact_block: hash,
},
)]
.into_iter()
.collect(),
validators_sets_rc: vec![(0, 1)].into_iter().collect(),
scheduled_changes: HashMap::new(),
}
}
pub(crate) fn insert(&mut self, header: Header) {
let hash = header.hash();
self.headers_by_number.entry(header.number).or_default().push(hash);
self.headers.insert(
hash,
StoredHeader {
submitter: None,
header,
total_difficulty: 0.into(),
next_validators_set_id: 0,
last_signal_block: None,
},
);
}
pub(crate) fn change_validators_set_at(
&mut self,
number: u64,
finalized_set: Vec<Address>,
signalled_set: Option<Vec<Address>>,
) {
let set_id = self.next_validators_set_id;
self.next_validators_set_id += 1;
self.validators_sets.insert(
set_id,
ValidatorsSet {
validators: finalized_set,
signal_block: None,
enact_block: self.headers_by_number[&0][0],
},
);
let mut header = self.headers.get_mut(&self.headers_by_number[&number][0]).unwrap();
header.next_validators_set_id = set_id;
if let Some(signalled_set) = signalled_set {
header.last_signal_block = Some(self.headers_by_number[&(number - 1)][0]);
self.scheduled_changes.insert(
self.headers_by_number[&(number - 1)][0],
ScheduledChange {
validators: signalled_set,
prev_signal_block: None,
},
);
}
}
pub(crate) fn set_best_block(&mut self, best_block: (u64, H256)) {
self.best_block.0 = best_block.0;
self.best_block.1 = best_block.1;
}
pub(crate) fn set_finalized_block(&mut self, finalized_block: (u64, H256)) {
self.finalized_block = finalized_block;
}
pub(crate) fn oldest_unpruned_block(&self) -> u64 {
self.oldest_unpruned_block
}
@@ -769,19 +950,26 @@ pub(crate) mod tests {
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();
let validators_set = self
.validators_sets
.get(&parent_header.next_validators_set_id)
.unwrap()
.clone();
let parent_scheduled_change = self.scheduled_changes.get(parent_hash).cloned();
ImportContext {
submitter,
parent_hash: *parent_hash,
parent_header: parent_header.header.clone(),
parent_total_difficulty: parent_header.total_difficulty,
next_validators_set_id: parent_header.next_validators_set_id,
next_validators_set: (*next_validators_set_start, next_validators.clone()),
parent_scheduled_change,
validators_set_id: parent_header.next_validators_set_id,
validators_set,
last_signal_block: parent_header.last_signal_block,
}
})
}
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>> {
fn scheduled_change(&self, hash: &H256) -> Option<ScheduledChange> {
self.scheduled_changes.get(hash).cloned()
}
@@ -790,26 +978,39 @@ pub(crate) mod tests {
self.best_block = (header.header.number, header.hash, header.total_difficulty);
}
if let Some(scheduled_change) = header.scheduled_change {
self.scheduled_changes.insert(header.hash, scheduled_change);
self.scheduled_changes.insert(
header.hash,
ScheduledChange {
validators: scheduled_change,
prev_signal_block: header.context.last_signal_block,
},
);
}
let next_validators_set_id = match header.enacted_change {
Some(enacted_change) => {
let next_validators_set_id = self.next_validators_set_id;
self.next_validators_set_id += 1;
self.validators_sets
.insert(next_validators_set_id, (header.hash, enacted_change));
self.validators_sets.insert(
next_validators_set_id,
ValidatorsSet {
validators: enacted_change.validators,
enact_block: header.hash,
signal_block: enacted_change.signal_block,
},
);
self.validators_sets_rc.insert(next_validators_set_id, 1);
next_validators_set_id
}
None => {
*self
.validators_sets_rc
.entry(header.context.next_validators_set_id)
.entry(header.context.validators_set_id)
.or_default() += 1;
header.context.next_validators_set_id
header.context.validators_set_id
}
};
let last_signal_block = header.context.last_signal_block().cloned();
self.headers_by_number
.entry(header.header.number)
.or_default()
@@ -821,6 +1022,7 @@ pub(crate) mod tests {
header: header.header,
total_difficulty: header.total_difficulty,
next_validators_set_id,
last_signal_block,
},
);
}