mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-20 20:55:41 +00:00
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:
committed by
Bastian Köcher
parent
b055027161
commit
c6c46462ab
@@ -247,7 +247,7 @@ construct_runtime!(
|
|||||||
Balances: pallet_balances::{Module, Call, Storage, Config<T>, Event<T>},
|
Balances: pallet_balances::{Module, Call, Storage, Config<T>, Event<T>},
|
||||||
TransactionPayment: pallet_transaction_payment::{Module, Storage},
|
TransactionPayment: pallet_transaction_payment::{Module, Storage},
|
||||||
Sudo: pallet_sudo::{Module, Call, Config<T>, Storage, Event<T>},
|
Sudo: pallet_sudo::{Module, Call, Config<T>, Storage, Event<T>},
|
||||||
BridgeEthPoA: pallet_bridge_eth_poa::{Module, Call, Config, Storage},
|
BridgeEthPoA: pallet_bridge_eth_poa::{Module, Call, Config, Storage, ValidateUnsigned},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,45 +17,49 @@
|
|||||||
use sp_runtime::RuntimeDebug;
|
use sp_runtime::RuntimeDebug;
|
||||||
|
|
||||||
/// Header import error.
|
/// Header import error.
|
||||||
#[derive(RuntimeDebug)]
|
#[derive(Clone, Copy, RuntimeDebug)]
|
||||||
#[cfg_attr(feature = "std", derive(PartialEq))]
|
#[cfg_attr(feature = "std", derive(PartialEq))]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// The header is beyound last finalized and can not be imported.
|
/// The header is beyond last finalized and can not be imported.
|
||||||
AncientHeader,
|
AncientHeader = 0,
|
||||||
/// The header is already imported.
|
/// The header is already imported.
|
||||||
KnownHeader,
|
KnownHeader = 1,
|
||||||
/// Seal has an incorrect format.
|
/// Seal has an incorrect format.
|
||||||
InvalidSealArity,
|
InvalidSealArity = 2,
|
||||||
/// Block number isn't sensible.
|
/// Block number isn't sensible.
|
||||||
RidiculousNumber,
|
RidiculousNumber = 3,
|
||||||
/// Block has too much gas used.
|
/// Block has too much gas used.
|
||||||
TooMuchGasUsed,
|
TooMuchGasUsed = 4,
|
||||||
/// Gas limit header field is invalid.
|
/// Gas limit header field is invalid.
|
||||||
InvalidGasLimit,
|
InvalidGasLimit = 5,
|
||||||
/// Extra data is of an invalid length.
|
/// Extra data is of an invalid length.
|
||||||
ExtraDataOutOfBounds,
|
ExtraDataOutOfBounds = 6,
|
||||||
/// Timestamp header overflowed.
|
/// Timestamp header overflowed.
|
||||||
TimestampOverflow,
|
TimestampOverflow = 7,
|
||||||
/// The parent header is missing from the blockchain.
|
/// The parent header is missing from the blockchain.
|
||||||
MissingParentBlock,
|
MissingParentBlock = 8,
|
||||||
/// The header step is missing from the header.
|
/// The header step is missing from the header.
|
||||||
MissingStep,
|
MissingStep = 9,
|
||||||
/// The header signature is missing from the header.
|
/// The header signature is missing from the header.
|
||||||
MissingSignature,
|
MissingSignature = 10,
|
||||||
/// Empty steps are missing from the header.
|
/// Empty steps are missing from the header.
|
||||||
MissingEmptySteps,
|
MissingEmptySteps = 11,
|
||||||
/// The same author issued different votes at the same step.
|
/// The same author issued different votes at the same step.
|
||||||
DoubleVote,
|
DoubleVote = 12,
|
||||||
/// Validation proof insufficient.
|
/// Validation proof insufficient.
|
||||||
InsufficientProof,
|
InsufficientProof = 13,
|
||||||
/// Difficulty header field is invalid.
|
/// Difficulty header field is invalid.
|
||||||
InvalidDifficulty,
|
InvalidDifficulty = 14,
|
||||||
/// The received block is from an incorrect proposer.
|
/// The received block is from an incorrect proposer.
|
||||||
NotValidator,
|
NotValidator = 15,
|
||||||
/// Missing transaction receipts for the operation.
|
/// Missing transaction receipts for the operation.
|
||||||
MissingTransactionsReceipts,
|
MissingTransactionsReceipts = 16,
|
||||||
|
/// Redundant transaction receipts are provided.
|
||||||
|
RedundantTransactionsReceipts = 17,
|
||||||
/// Provided transactions receipts are not matching the header.
|
/// Provided transactions receipts are not matching the header.
|
||||||
TransactionsReceiptsMismatch,
|
TransactionsReceiptsMismatch = 18,
|
||||||
|
/// Can't accept unsigned header from the far future.
|
||||||
|
UnsignedTooFarInTheFuture = 19,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
@@ -78,7 +82,14 @@ impl Error {
|
|||||||
Error::InvalidDifficulty => "Header has invalid difficulty",
|
Error::InvalidDifficulty => "Header has invalid difficulty",
|
||||||
Error::NotValidator => "Header is sealed by unexpected validator",
|
Error::NotValidator => "Header is sealed by unexpected validator",
|
||||||
Error::MissingTransactionsReceipts => "The import operation requires transactions receipts",
|
Error::MissingTransactionsReceipts => "The import operation requires transactions receipts",
|
||||||
|
Error::RedundantTransactionsReceipts => "Redundant transactions receipts are provided",
|
||||||
Error::TransactionsReceiptsMismatch => "Invalid transactions receipts provided",
|
Error::TransactionsReceiptsMismatch => "Invalid transactions receipts provided",
|
||||||
|
Error::UnsignedTooFarInTheFuture => "The unsigned header is too far in future",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return unique error code.
|
||||||
|
pub fn code(&self) -> u8 {
|
||||||
|
*self as u8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,18 @@
|
|||||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::{ancestry, Storage};
|
use crate::Storage;
|
||||||
use primitives::{public_to_address, Address, Header, SealedEmptyStep, H256};
|
use primitives::{public_to_address, Address, Header, SealedEmptyStep, H256};
|
||||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||||
use sp_std::collections::{
|
|
||||||
btree_map::{BTreeMap, Entry},
|
|
||||||
btree_set::BTreeSet,
|
|
||||||
vec_deque::VecDeque,
|
|
||||||
};
|
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
|
use sp_std::{
|
||||||
|
collections::{
|
||||||
|
btree_map::{BTreeMap, Entry},
|
||||||
|
btree_set::BTreeSet,
|
||||||
|
vec_deque::VecDeque,
|
||||||
|
},
|
||||||
|
iter::from_fn,
|
||||||
|
};
|
||||||
|
|
||||||
/// Tries to finalize blocks when given block is imported.
|
/// Tries to finalize blocks when given block is imported.
|
||||||
///
|
///
|
||||||
@@ -190,6 +193,24 @@ fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option
|
|||||||
.map(|public| public_to_address(&public))
|
.map(|public| public_to_address(&public))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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, submitter) = storage.header(&parent_hash)?;
|
||||||
|
if header.number == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = parent_hash.clone();
|
||||||
|
parent_hash = header.parent_hash.clone();
|
||||||
|
Some((hash, header, submitter))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::finality::finalize_blocks;
|
use crate::finality::finalize_blocks;
|
||||||
use crate::validators::{Validators, ValidatorsConfiguration};
|
use crate::validators::{Validators, ValidatorsConfiguration};
|
||||||
use crate::verification::verify_aura_header;
|
use crate::verification::{is_importable_header, verify_aura_header};
|
||||||
use crate::{AuraConfiguration, Storage};
|
use crate::{AuraConfiguration, ChangeToEnact, Storage};
|
||||||
use primitives::{Header, Receipt, H256};
|
use primitives::{Header, Receipt, H256};
|
||||||
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
|
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
|
||||||
|
|
||||||
@@ -105,16 +105,22 @@ pub fn import_header<S: Storage>(
|
|||||||
let (scheduled_change, enacted_change) = validators.extract_validators_change(&header, receipts)?;
|
let (scheduled_change, enacted_change) = validators.extract_validators_change(&header, receipts)?;
|
||||||
|
|
||||||
// check if block finalizes some other blocks and corresponding scheduled validators
|
// check if block finalizes some other blocks and corresponding scheduled validators
|
||||||
|
let validators_set = import_context.validators_set();
|
||||||
let finalized_blocks = finalize_blocks(
|
let finalized_blocks = finalize_blocks(
|
||||||
storage,
|
storage,
|
||||||
&prev_finalized_hash,
|
&prev_finalized_hash,
|
||||||
(import_context.validators_start(), import_context.validators()),
|
(&validators_set.enact_block, &validators_set.validators),
|
||||||
&hash,
|
&hash,
|
||||||
import_context.submitter(),
|
import_context.submitter(),
|
||||||
&header,
|
&header,
|
||||||
aura_config.two_thirds_majority_transition,
|
aura_config.two_thirds_majority_transition,
|
||||||
)?;
|
)?;
|
||||||
let enacted_change = enacted_change.or_else(|| validators.finalize_validators_change(storage, &finalized_blocks));
|
let enacted_change = enacted_change
|
||||||
|
.map(|validators| ChangeToEnact {
|
||||||
|
signal_block: None,
|
||||||
|
validators,
|
||||||
|
})
|
||||||
|
.or_else(|| validators.finalize_validators_change(storage, &finalized_blocks));
|
||||||
|
|
||||||
// NOTE: we can't return Err() from anywhere below this line
|
// NOTE: we can't return Err() from anywhere below this line
|
||||||
// (because otherwise we'll have inconsistent storage if transaction will fail)
|
// (because otherwise we'll have inconsistent storage if transaction will fail)
|
||||||
@@ -157,24 +163,6 @@ pub fn header_import_requires_receipts<S: Storage>(
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that we are able to ***try to** import this header.
|
|
||||||
/// Returns error if we should not try to import this block.
|
|
||||||
/// Returns hash of the header and number of the last finalized block.
|
|
||||||
fn is_importable_header<S: Storage>(storage: &S, header: &Header) -> Result<(H256, H256), Error> {
|
|
||||||
// we never import any header that competes with finalized header
|
|
||||||
let (finalized_block_number, finalized_block_hash) = storage.finalized_block();
|
|
||||||
if header.number <= finalized_block_number {
|
|
||||||
return Err(Error::AncientHeader);
|
|
||||||
}
|
|
||||||
// we never import any header with known hash
|
|
||||||
let hash = header.hash();
|
|
||||||
if storage.header(&hash).is_some() {
|
|
||||||
return Err(Error::KnownHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((hash, finalized_block_hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -19,8 +19,14 @@
|
|||||||
use codec::{Decode, Encode};
|
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::{
|
||||||
use sp_std::{cmp::Ord, collections::btree_map::BTreeMap, iter::from_fn, prelude::*};
|
transaction_validity::{
|
||||||
|
InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionValidity, UnknownTransaction,
|
||||||
|
ValidTransaction,
|
||||||
|
},
|
||||||
|
RuntimeDebug,
|
||||||
|
};
|
||||||
|
use sp_std::{cmp::Ord, collections::btree_map::BTreeMap, prelude::*};
|
||||||
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};
|
||||||
@@ -52,6 +58,18 @@ pub struct AuraConfiguration {
|
|||||||
pub maximum_extra_data_size: u64,
|
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.
|
/// 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<Submitter> {
|
pub struct StoredHeader<Submitter> {
|
||||||
@@ -67,6 +85,32 @@ pub struct StoredHeader<Submitter> {
|
|||||||
/// this is the set that has produced the block itself.
|
/// this is the set that has produced the block itself.
|
||||||
/// The hash is the hash of block where validators set has been enacted.
|
/// The hash is the hash of block where validators set has been enacted.
|
||||||
pub next_validators_set_id: u64,
|
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.
|
/// Header that we're importing.
|
||||||
@@ -83,41 +127,44 @@ pub struct HeaderToImport<Submitter> {
|
|||||||
pub header: Header,
|
pub header: Header,
|
||||||
/// Total chain difficulty at the header.
|
/// Total chain difficulty at the header.
|
||||||
pub total_difficulty: U256,
|
pub total_difficulty: U256,
|
||||||
/// Validators set enacted change, if happened at the header.
|
/// New validators set and the hash of block where it has been scheduled (if applicable).
|
||||||
pub enacted_change: Option<Vec<Address>>,
|
/// Some if set is is enacted by this header.
|
||||||
|
pub enacted_change: Option<ChangeToEnact>,
|
||||||
/// Validators set scheduled change, if happened at the header.
|
/// Validators set scheduled change, if happened at the header.
|
||||||
pub scheduled_change: Option<Vec<Address>>,
|
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.
|
/// 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)]
|
#[derive(RuntimeDebug)]
|
||||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||||
pub struct ImportContext<Submitter> {
|
pub struct ImportContext<Submitter> {
|
||||||
submitter: Option<Submitter>,
|
submitter: Option<Submitter>,
|
||||||
|
parent_hash: H256,
|
||||||
parent_header: Header,
|
parent_header: Header,
|
||||||
parent_total_difficulty: U256,
|
parent_total_difficulty: U256,
|
||||||
next_validators_set_id: u64,
|
parent_scheduled_change: Option<ScheduledChange>,
|
||||||
next_validators_set: (H256, Vec<Address>),
|
validators_set_id: u64,
|
||||||
|
validators_set: ValidatorsSet,
|
||||||
|
last_signal_block: Option<H256>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Submitter> ImportContext<Submitter> {
|
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).
|
/// Returns reference to header submitter (if known).
|
||||||
pub fn submitter(&self) -> Option<&Submitter> {
|
pub fn submitter(&self) -> Option<&Submitter> {
|
||||||
self.submitter.as_ref()
|
self.submitter.as_ref()
|
||||||
@@ -133,19 +180,28 @@ impl<Submitter> ImportContext<Submitter> {
|
|||||||
&self.parent_total_difficulty
|
&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.
|
/// Returns id of the set of validators.
|
||||||
pub fn validators_set_id(&self) -> u64 {
|
pub fn validators_set_id(&self) -> u64 {
|
||||||
self.next_validators_set_id
|
self.validators_set_id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns block whenre validators set has been enacted.
|
/// Returns reference to validators set for the block we're going to import.
|
||||||
pub fn validators_start(&self) -> &H256 {
|
pub fn validators_set(&self) -> &ValidatorsSet {
|
||||||
&self.next_validators_set.0
|
&self.validators_set
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns reference to the set of validators of the block we're going to import.
|
/// Returns reference to the latest block which has signalled change of validators set.
|
||||||
pub fn validators(&self) -> &[Address] {
|
/// This may point to parent if parent has signalled change.
|
||||||
&self.next_validators_set.1
|
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.
|
/// Converts import context into header we're going to import.
|
||||||
@@ -155,7 +211,7 @@ impl<Submitter> ImportContext<Submitter> {
|
|||||||
hash: H256,
|
hash: H256,
|
||||||
header: Header,
|
header: Header,
|
||||||
total_difficulty: U256,
|
total_difficulty: U256,
|
||||||
enacted_change: Option<Vec<Address>>,
|
enacted_change: Option<ChangeToEnact>,
|
||||||
scheduled_change: Option<Vec<Address>>,
|
scheduled_change: Option<Vec<Address>>,
|
||||||
) -> HeaderToImport<Submitter> {
|
) -> HeaderToImport<Submitter> {
|
||||||
HeaderToImport {
|
HeaderToImport {
|
||||||
@@ -191,8 +247,9 @@ pub trait Storage {
|
|||||||
submitter: Option<Self::Submitter>,
|
submitter: Option<Self::Submitter>,
|
||||||
parent_hash: &H256,
|
parent_hash: &H256,
|
||||||
) -> Option<ImportContext<Self::Submitter>>;
|
) -> Option<ImportContext<Self::Submitter>>;
|
||||||
/// Get new validators that are scheduled by given header.
|
/// Get new validators that are scheduled by given header and hash of the previous
|
||||||
fn scheduled_change(&self, hash: &H256) -> Option<Vec<Address>>;
|
/// block that has scheduled change.
|
||||||
|
fn scheduled_change(&self, hash: &H256) -> Option<ScheduledChange>;
|
||||||
/// Insert imported header.
|
/// Insert imported header.
|
||||||
fn insert_header(&mut self, header: HeaderToImport<Self::Submitter>);
|
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.
|
||||||
@@ -235,13 +292,28 @@ pub trait Trait: frame_system::Trait {
|
|||||||
|
|
||||||
decl_module! {
|
decl_module! {
|
||||||
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
|
||||||
/// Import Aura chain headers. Ignores non-fatal errors (like when known
|
/// Import single Aura header. Requires transaction to be **UNSIGNED**.
|
||||||
/// header is provided), rewards for successful headers import and penalizes
|
pub fn import_unsigned_header(origin, header: Header, receipts: Option<Vec<Receipt>>) {
|
||||||
/// for fatal errors.
|
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
|
/// This should be used with caution - passing too many headers could lead to
|
||||||
/// 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_signed_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 mut finalized_headers = BTreeMap::new();
|
||||||
let import_result = import::import_headers(
|
let import_result = import::import_headers(
|
||||||
@@ -293,13 +365,13 @@ decl_storage! {
|
|||||||
/// The ID of next validator set.
|
/// The ID of next validator set.
|
||||||
NextValidatorsSetId: u64;
|
NextValidatorsSetId: u64;
|
||||||
/// Map of validators sets by their id.
|
/// 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
|
/// 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.
|
/// 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.
|
/// When it reaches zero, we are free to prune validator set as well.
|
||||||
ValidatorsSetsRc: map hasher(blake2_256) u64 => Option<u64>;
|
ValidatorsSetsRc: map hasher(blake2_256) u64 => Option<u64>;
|
||||||
/// Map of validators set changes scheduled by given header.
|
/// 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 {
|
add_extra_genesis {
|
||||||
config(initial_header): Header;
|
config(initial_header): Header;
|
||||||
@@ -326,9 +398,14 @@ decl_storage! {
|
|||||||
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,
|
||||||
|
last_signal_block: None,
|
||||||
});
|
});
|
||||||
NextValidatorsSetId::put(1);
|
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);
|
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.
|
/// Runtime bridge storage.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct BridgeStorage<T>(sp_std::marker::PhantomData<T>);
|
struct BridgeStorage<T>(sp_std::marker::PhantomData<T>);
|
||||||
@@ -385,20 +499,23 @@ impl<T: Trait> Storage for BridgeStorage<T> {
|
|||||||
parent_hash: &H256,
|
parent_hash: &H256,
|
||||||
) -> Option<ImportContext<Self::Submitter>> {
|
) -> Option<ImportContext<Self::Submitter>> {
|
||||||
Headers::<T>::get(parent_hash).map(|parent_header| {
|
Headers::<T>::get(parent_hash).map(|parent_header| {
|
||||||
let (next_validators_set_start, next_validators) =
|
let validators_set = 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");
|
let parent_scheduled_change = ScheduledChanges::get(parent_hash);
|
||||||
ImportContext {
|
ImportContext {
|
||||||
submitter,
|
submitter,
|
||||||
|
parent_hash: *parent_hash,
|
||||||
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,
|
parent_scheduled_change,
|
||||||
next_validators_set: (next_validators_set_start, next_validators),
|
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)
|
ScheduledChanges::get(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +524,13 @@ impl<T: Trait> Storage for BridgeStorage<T> {
|
|||||||
BestBlock::put((header.header.number, header.hash, header.total_difficulty));
|
BestBlock::put((header.header.number, header.hash, header.total_difficulty));
|
||||||
}
|
}
|
||||||
if let Some(scheduled_change) = header.scheduled_change {
|
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 {
|
let next_validators_set_id = match header.enacted_change {
|
||||||
Some(enacted_change) => {
|
Some(enacted_change) => {
|
||||||
@@ -416,19 +539,27 @@ impl<T: Trait> Storage for BridgeStorage<T> {
|
|||||||
*set_id += 1;
|
*set_id += 1;
|
||||||
next_set_id
|
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);
|
ValidatorsSetsRc::insert(next_validators_set_id, 1);
|
||||||
next_validators_set_id
|
next_validators_set_id
|
||||||
}
|
}
|
||||||
None => {
|
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 = Some(rc.map(|rc| rc + 1).unwrap_or(1));
|
||||||
*rc
|
*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]);
|
HeadersByNumber::append_or_insert(header.header.number, vec![header.hash]);
|
||||||
Headers::<T>::insert(
|
Headers::<T>::insert(
|
||||||
&header.hash,
|
&header.hash,
|
||||||
@@ -437,6 +568,7 @@ impl<T: Trait> Storage for BridgeStorage<T> {
|
|||||||
header: header.header,
|
header: header.header,
|
||||||
total_difficulty: header.total_difficulty,
|
total_difficulty: header.total_difficulty,
|
||||||
next_validators_set_id,
|
next_validators_set_id,
|
||||||
|
last_signal_block,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -616,27 +748,11 @@ pub fn kovan_validators_config() -> ValidatorsConfiguration {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return iterator of given header ancestors.
|
/// Transaction pool configuration.
|
||||||
pub(crate) fn ancestry<'a, S: Storage>(
|
fn pool_configuration() -> PoolConfiguration {
|
||||||
storage: &'a S,
|
PoolConfiguration {
|
||||||
header: &Header,
|
max_future_number_difference: 10,
|
||||||
) -> 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,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -706,9 +822,9 @@ pub(crate) mod tests {
|
|||||||
headers: HashMap<H256, StoredHeader<AccountId>>,
|
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, ValidatorsSet>,
|
||||||
validators_sets_rc: HashMap<u64, u64>,
|
validators_sets_rc: HashMap<u64, u64>,
|
||||||
scheduled_changes: HashMap<H256, Vec<Address>>,
|
scheduled_changes: HashMap<H256, ScheduledChange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InMemoryStorage {
|
impl InMemoryStorage {
|
||||||
@@ -726,17 +842,82 @@ pub(crate) mod tests {
|
|||||||
header: initial_header,
|
header: initial_header,
|
||||||
total_difficulty: 0.into(),
|
total_difficulty: 0.into(),
|
||||||
next_validators_set_id: 0,
|
next_validators_set_id: 0,
|
||||||
|
last_signal_block: None,
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
next_validators_set_id: 1,
|
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(),
|
validators_sets_rc: vec![(0, 1)].into_iter().collect(),
|
||||||
scheduled_changes: HashMap::new(),
|
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 {
|
pub(crate) fn oldest_unpruned_block(&self) -> u64 {
|
||||||
self.oldest_unpruned_block
|
self.oldest_unpruned_block
|
||||||
}
|
}
|
||||||
@@ -769,19 +950,26 @@ pub(crate) mod tests {
|
|||||||
parent_hash: &H256,
|
parent_hash: &H256,
|
||||||
) -> Option<ImportContext<Self::Submitter>> {
|
) -> 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 validators_set = self
|
||||||
self.validators_sets.get(&parent_header.next_validators_set_id).unwrap();
|
.validators_sets
|
||||||
|
.get(&parent_header.next_validators_set_id)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
let parent_scheduled_change = self.scheduled_changes.get(parent_hash).cloned();
|
||||||
ImportContext {
|
ImportContext {
|
||||||
submitter,
|
submitter,
|
||||||
|
parent_hash: *parent_hash,
|
||||||
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,
|
parent_scheduled_change,
|
||||||
next_validators_set: (*next_validators_set_start, next_validators.clone()),
|
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()
|
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);
|
self.best_block = (header.header.number, header.hash, header.total_difficulty);
|
||||||
}
|
}
|
||||||
if let Some(scheduled_change) = header.scheduled_change {
|
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 {
|
let next_validators_set_id = match header.enacted_change {
|
||||||
Some(enacted_change) => {
|
Some(enacted_change) => {
|
||||||
let next_validators_set_id = self.next_validators_set_id;
|
let next_validators_set_id = self.next_validators_set_id;
|
||||||
self.next_validators_set_id += 1;
|
self.next_validators_set_id += 1;
|
||||||
self.validators_sets
|
self.validators_sets.insert(
|
||||||
.insert(next_validators_set_id, (header.hash, enacted_change));
|
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);
|
self.validators_sets_rc.insert(next_validators_set_id, 1);
|
||||||
next_validators_set_id
|
next_validators_set_id
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
*self
|
*self
|
||||||
.validators_sets_rc
|
.validators_sets_rc
|
||||||
.entry(header.context.next_validators_set_id)
|
.entry(header.context.validators_set_id)
|
||||||
.or_default() += 1;
|
.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
|
self.headers_by_number
|
||||||
.entry(header.header.number)
|
.entry(header.header.number)
|
||||||
.or_default()
|
.or_default()
|
||||||
@@ -821,6 +1022,7 @@ pub(crate) mod tests {
|
|||||||
header: header.header,
|
header: header.header,
|
||||||
total_difficulty: header.total_difficulty,
|
total_difficulty: header.total_difficulty,
|
||||||
next_validators_set_id,
|
next_validators_set_id,
|
||||||
|
last_signal_block,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::Storage;
|
use crate::{ChangeToEnact, Storage};
|
||||||
use primitives::{Address, Header, LogEntry, Receipt, H256, U256};
|
use primitives::{Address, Header, LogEntry, Receipt, H256, U256};
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
|
|
||||||
@@ -183,10 +183,13 @@ impl<'a> Validators<'a> {
|
|||||||
&self,
|
&self,
|
||||||
storage: &mut S,
|
storage: &mut S,
|
||||||
finalized_blocks: &[(u64, H256, Option<S::Submitter>)],
|
finalized_blocks: &[(u64, H256, Option<S::Submitter>)],
|
||||||
) -> Option<Vec<Address>> {
|
) -> Option<ChangeToEnact> {
|
||||||
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(ChangeToEnact {
|
||||||
|
signal_block: Some(*finalized_hash),
|
||||||
|
validators: changes.validators,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -15,39 +15,205 @@
|
|||||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::validators::step_validator;
|
use crate::validators::{step_validator, Validators, ValidatorsConfiguration};
|
||||||
use crate::{AuraConfiguration, ImportContext, Storage};
|
use crate::{AuraConfiguration, ImportContext, PoolConfiguration, ScheduledChange, Storage};
|
||||||
use primitives::{public_to_address, Address, Header, SealedEmptyStep, H256, H520, U128, U256};
|
use codec::Encode;
|
||||||
|
use primitives::{public_to_address, Address, Header, Receipt, SealedEmptyStep, H256, H520, U128, U256};
|
||||||
use sp_io::crypto::secp256k1_ecdsa_recover;
|
use sp_io::crypto::secp256k1_ecdsa_recover;
|
||||||
|
use sp_std::{vec, vec::Vec};
|
||||||
|
|
||||||
|
/// Pre-check to see if should try and import this header.
|
||||||
|
/// Returns error if we should not try to import this block.
|
||||||
|
/// Returns hash of the header and number of the last finalized block otherwise.
|
||||||
|
pub fn is_importable_header<S: Storage>(storage: &S, header: &Header) -> Result<(H256, H256), Error> {
|
||||||
|
// we never import any header that competes with finalized header
|
||||||
|
let (finalized_block_number, finalized_block_hash) = storage.finalized_block();
|
||||||
|
if header.number <= finalized_block_number {
|
||||||
|
return Err(Error::AncientHeader);
|
||||||
|
}
|
||||||
|
// we never import any header with known hash
|
||||||
|
let hash = header.hash();
|
||||||
|
if storage.header(&hash).is_some() {
|
||||||
|
return Err(Error::KnownHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((hash, finalized_block_hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try accept unsigned aura header into transaction pool.
|
||||||
|
pub fn accept_aura_header_into_pool<S: Storage>(
|
||||||
|
storage: &S,
|
||||||
|
config: &AuraConfiguration,
|
||||||
|
validators_config: &ValidatorsConfiguration,
|
||||||
|
pool_config: &PoolConfiguration,
|
||||||
|
header: &Header,
|
||||||
|
receipts: Option<&Vec<Receipt>>,
|
||||||
|
) -> Result<(Vec<Vec<u8>>, Vec<Vec<u8>>), Error> {
|
||||||
|
// check if we can verify further
|
||||||
|
let (hash, _) = is_importable_header(storage, header)?;
|
||||||
|
|
||||||
|
// we can always do contextless checks
|
||||||
|
contextless_checks(config, header)?;
|
||||||
|
|
||||||
|
// we want to avoid having same headers twice in the pool
|
||||||
|
// => we're strict about receipts here - if we need them, we require receipts to be Some,
|
||||||
|
// otherwise we require receipts to be None
|
||||||
|
let receipts_required = Validators::new(validators_config).maybe_signals_validators_change(header);
|
||||||
|
match (receipts_required, receipts.is_some()) {
|
||||||
|
(true, false) => return Err(Error::MissingTransactionsReceipts),
|
||||||
|
(false, true) => return Err(Error::RedundantTransactionsReceipts),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// we do not want to have all future headers in the pool at once
|
||||||
|
// => if we see header with number > maximal ever seen header number + LIMIT,
|
||||||
|
// => we consider this transaction invalid, but only at this moment (we do not want to ban it)
|
||||||
|
// => let's mark it as Unknown transaction
|
||||||
|
let (best_number, best_hash, _) = storage.best_block();
|
||||||
|
let difference = header.number.saturating_sub(best_number);
|
||||||
|
if difference > pool_config.max_future_number_difference {
|
||||||
|
return Err(Error::UnsignedTooFarInTheFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: only accept new headers when we're at the tip of PoA chain
|
||||||
|
// https://github.com/paritytech/parity-bridges-common/issues/38
|
||||||
|
|
||||||
|
// we want to see at most one header with given number from single authority
|
||||||
|
// => every header is providing tag (block_number + authority)
|
||||||
|
// => since only one tx in the pool can provide the same tag, they're auto-deduplicated
|
||||||
|
let provides_number_and_authority_tag = (header.number, header.author).encode();
|
||||||
|
|
||||||
|
// we want to see several 'future' headers in the pool at once, but we may not have access to
|
||||||
|
// previous headers here
|
||||||
|
// => we can at least 'verify' that headers comprise a chain by providing and requiring
|
||||||
|
// tag (header.number, header.hash)
|
||||||
|
let provides_header_number_and_hash_tag = (header.number, hash).encode();
|
||||||
|
|
||||||
|
// depending on whether parent header is available, we either perform full or 'shortened' check
|
||||||
|
let context = storage.import_context(None, &header.parent_hash);
|
||||||
|
let tags = match context {
|
||||||
|
Some(context) => {
|
||||||
|
let header_step = contextual_checks(config, &context, None, header)?;
|
||||||
|
validator_checks(config, &context.validators_set().validators, header, header_step)?;
|
||||||
|
|
||||||
|
// since our parent is already in the storage, we do not require it
|
||||||
|
// to be in the transaction pool
|
||||||
|
(
|
||||||
|
vec![],
|
||||||
|
vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// we know nothing about parent header
|
||||||
|
// => the best thing we can do is to believe that there are no forks in
|
||||||
|
// PoA chain AND that the header is produced either by previous, or next
|
||||||
|
// scheduled validators set change
|
||||||
|
let header_step = header.step().ok_or(Error::MissingStep)?;
|
||||||
|
let best_context = storage.import_context(None, &best_hash).expect(
|
||||||
|
"import context is None only when header is missing from the storage;\
|
||||||
|
best header is always in the storage; qed",
|
||||||
|
);
|
||||||
|
let validators_check_result =
|
||||||
|
validator_checks(config, &best_context.validators_set().validators, header, header_step);
|
||||||
|
if let Err(error) = validators_check_result {
|
||||||
|
find_next_validators_signal(storage, &best_context)
|
||||||
|
.ok_or_else(|| error)
|
||||||
|
.and_then(|next_validators| validator_checks(config, &next_validators, header, header_step))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// since our parent is missing from the storage, we **DO** require it
|
||||||
|
// to be in the transaction pool
|
||||||
|
// (- 1 can't underflow because there's always best block in the header)
|
||||||
|
let requires_header_number_and_hash_tag = (header.number - 1, header.parent_hash).encode();
|
||||||
|
(
|
||||||
|
vec![requires_header_number_and_hash_tag],
|
||||||
|
vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// the heaviest, but rare operation - we do not want invalid receipts in the pool
|
||||||
|
if let Some(receipts) = receipts {
|
||||||
|
if !header.check_transactions_receipts(receipts) {
|
||||||
|
return Err(Error::TransactionsReceiptsMismatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tags)
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify header by Aura rules.
|
/// Verify header by Aura rules.
|
||||||
pub fn verify_aura_header<S: Storage>(
|
pub fn verify_aura_header<S: Storage>(
|
||||||
storage: &S,
|
storage: &S,
|
||||||
params: &AuraConfiguration,
|
config: &AuraConfiguration,
|
||||||
submitter: Option<S::Submitter>,
|
submitter: Option<S::Submitter>,
|
||||||
header: &Header,
|
header: &Header,
|
||||||
) -> Result<ImportContext<S::Submitter>, 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(config, header)?;
|
||||||
|
|
||||||
// the rest of checks requires parent
|
// the rest of checks requires access to the parent header
|
||||||
let context = storage
|
let context = storage
|
||||||
.import_context(submitter, &header.parent_hash)
|
.import_context(submitter, &header.parent_hash)
|
||||||
.ok_or(Error::MissingParentBlock)?;
|
.ok_or(Error::MissingParentBlock)?;
|
||||||
let validators = context.validators();
|
let header_step = contextual_checks(config, &context, None, header)?;
|
||||||
|
validator_checks(config, &context.validators_set().validators, header, header_step)?;
|
||||||
|
|
||||||
|
Ok(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform basic checks that only require header itself.
|
||||||
|
fn contextless_checks(config: &AuraConfiguration, header: &Header) -> Result<(), Error> {
|
||||||
|
let expected_seal_fields = expected_header_seal_fields(config, header);
|
||||||
|
if header.seal.len() != expected_seal_fields {
|
||||||
|
return Err(Error::InvalidSealArity);
|
||||||
|
}
|
||||||
|
if header.number >= u64::max_value() {
|
||||||
|
return Err(Error::RidiculousNumber);
|
||||||
|
}
|
||||||
|
if header.gas_used > header.gas_limit {
|
||||||
|
return Err(Error::TooMuchGasUsed);
|
||||||
|
}
|
||||||
|
if header.gas_limit < config.min_gas_limit {
|
||||||
|
return Err(Error::InvalidGasLimit);
|
||||||
|
}
|
||||||
|
if header.gas_limit > config.max_gas_limit {
|
||||||
|
return Err(Error::InvalidGasLimit);
|
||||||
|
}
|
||||||
|
if header.number != 0 && header.extra_data.len() as u64 > config.maximum_extra_data_size {
|
||||||
|
return Err(Error::ExtraDataOutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can't detect if block is from future in runtime
|
||||||
|
// => let's only do an overflow check
|
||||||
|
if header.timestamp > i32::max_value() as u64 {
|
||||||
|
return Err(Error::TimestampOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform checks that require access to parent header.
|
||||||
|
fn contextual_checks<Submitter>(
|
||||||
|
config: &AuraConfiguration,
|
||||||
|
context: &ImportContext<Submitter>,
|
||||||
|
validators_override: Option<&[Address]>,
|
||||||
|
header: &Header,
|
||||||
|
) -> Result<u64, Error> {
|
||||||
|
let validators = validators_override.unwrap_or_else(|| &context.validators_set().validators);
|
||||||
let header_step = header.step().ok_or(Error::MissingStep)?;
|
let header_step = header.step().ok_or(Error::MissingStep)?;
|
||||||
let parent_step = context.parent_header().step().ok_or(Error::MissingStep)?;
|
let parent_step = context.parent_header().step().ok_or(Error::MissingStep)?;
|
||||||
|
|
||||||
// Ensure header is from the step after context.
|
// Ensure header is from the step after context.
|
||||||
if header_step == parent_step || (header.number >= params.validate_step_transition && header_step <= parent_step) {
|
if header_step == parent_step || (header.number >= config.validate_step_transition && header_step <= parent_step) {
|
||||||
return Err(Error::DoubleVote);
|
return Err(Error::DoubleVote);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If empty step messages are enabled we will validate the messages in the seal, missing messages are not
|
// If empty step messages are enabled we will validate the messages in the seal, missing messages are not
|
||||||
// reported as there's no way to tell whether the empty step message was never sent or simply not included.
|
// reported as there's no way to tell whether the empty step message was never sent or simply not included.
|
||||||
let empty_steps_len = match header.number >= params.empty_steps_transition {
|
let empty_steps_len = match header.number >= config.empty_steps_transition {
|
||||||
true => {
|
true => {
|
||||||
let strict_empty_steps = header.number >= params.strict_empty_steps_transition;
|
let strict_empty_steps = header.number >= config.strict_empty_steps_transition;
|
||||||
let empty_steps = header.empty_steps().ok_or(Error::MissingEmptySteps)?;
|
let empty_steps = header.empty_steps().ok_or(Error::MissingEmptySteps)?;
|
||||||
let empty_steps_len = empty_steps.len();
|
let empty_steps_len = empty_steps.len();
|
||||||
let mut prev_empty_step = 0;
|
let mut prev_empty_step = 0;
|
||||||
@@ -76,13 +242,23 @@ pub fn verify_aura_header<S: Storage>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Validate chain score.
|
// Validate chain score.
|
||||||
if header.number >= params.validate_score_transition {
|
if header.number >= config.validate_score_transition {
|
||||||
let expected_difficulty = calculate_score(parent_step, header_step, empty_steps_len as _);
|
let expected_difficulty = calculate_score(parent_step, header_step, empty_steps_len as _);
|
||||||
if header.difficulty != expected_difficulty {
|
if header.difficulty != expected_difficulty {
|
||||||
return Err(Error::InvalidDifficulty);
|
return Err(Error::InvalidDifficulty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(header_step)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that block is produced by expected validator.
|
||||||
|
fn validator_checks(
|
||||||
|
config: &AuraConfiguration,
|
||||||
|
validators: &[Address],
|
||||||
|
header: &Header,
|
||||||
|
header_step: u64,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let expected_validator = step_validator(validators, header_step);
|
let expected_validator = step_validator(validators, header_step);
|
||||||
if header.author != expected_validator {
|
if header.author != expected_validator {
|
||||||
return Err(Error::NotValidator);
|
return Err(Error::NotValidator);
|
||||||
@@ -90,44 +266,13 @@ pub fn verify_aura_header<S: Storage>(
|
|||||||
|
|
||||||
let validator_signature = header.signature().ok_or(Error::MissingSignature)?;
|
let validator_signature = header.signature().ok_or(Error::MissingSignature)?;
|
||||||
let header_seal_hash = header
|
let header_seal_hash = header
|
||||||
.seal_hash(header.number >= params.empty_steps_transition)
|
.seal_hash(header.number >= config.empty_steps_transition)
|
||||||
.ok_or(Error::MissingEmptySteps)?;
|
.ok_or(Error::MissingEmptySteps)?;
|
||||||
let is_invalid_proposer = !verify_signature(&expected_validator, &validator_signature, &header_seal_hash);
|
let is_invalid_proposer = !verify_signature(&expected_validator, &validator_signature, &header_seal_hash);
|
||||||
if is_invalid_proposer {
|
if is_invalid_proposer {
|
||||||
return Err(Error::NotValidator);
|
return Err(Error::NotValidator);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Perform basic checks that only require header iteself.
|
|
||||||
fn contextless_checks(config: &AuraConfiguration, header: &Header) -> Result<(), Error> {
|
|
||||||
let expected_seal_fields = expected_header_seal_fields(config, header);
|
|
||||||
if header.seal.len() != expected_seal_fields {
|
|
||||||
return Err(Error::InvalidSealArity);
|
|
||||||
}
|
|
||||||
if header.number >= u64::max_value() {
|
|
||||||
return Err(Error::RidiculousNumber);
|
|
||||||
}
|
|
||||||
if header.gas_used > header.gas_limit {
|
|
||||||
return Err(Error::TooMuchGasUsed);
|
|
||||||
}
|
|
||||||
if header.gas_limit < config.min_gas_limit {
|
|
||||||
return Err(Error::InvalidGasLimit);
|
|
||||||
}
|
|
||||||
if header.gas_limit > config.max_gas_limit {
|
|
||||||
return Err(Error::InvalidGasLimit);
|
|
||||||
}
|
|
||||||
if header.number != 0 && header.extra_data.len() as u64 > config.maximum_extra_data_size {
|
|
||||||
return Err(Error::ExtraDataOutOfBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we can't detect if block is from future in runtime
|
|
||||||
// => let's only do an overflow check
|
|
||||||
if header.timestamp > i32::max_value() as u64 {
|
|
||||||
return Err(Error::TimestampOverflow);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,13 +305,47 @@ fn verify_signature(expected_validator: &Address, signature: &H520, message: &H2
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find next unfinalized validators set change after finalized set.
|
||||||
|
fn find_next_validators_signal<S: Storage>(storage: &S, context: &ImportContext<S::Submitter>) -> Option<Vec<Address>> {
|
||||||
|
// that's the earliest block number we may met in following loop
|
||||||
|
// it may be None if that's the first set
|
||||||
|
let best_set_signal_block = context.validators_set().signal_block;
|
||||||
|
|
||||||
|
// if parent schedules validators set change, then it may be our set
|
||||||
|
// else we'll start with last known change
|
||||||
|
let mut current_set_signal_block = context.last_signal_block().cloned();
|
||||||
|
let mut next_scheduled_set: Option<ScheduledChange> = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// if we have reached block that signals finalized change, then
|
||||||
|
// next_current_block_hash points to the block that schedules next
|
||||||
|
// change
|
||||||
|
let current_scheduled_set = match current_set_signal_block {
|
||||||
|
Some(current_set_signal_block) if Some(¤t_set_signal_block) == best_set_signal_block.as_ref() => {
|
||||||
|
return next_scheduled_set.map(|scheduled_set| scheduled_set.validators)
|
||||||
|
}
|
||||||
|
None => return next_scheduled_set.map(|scheduled_set| scheduled_set.validators),
|
||||||
|
Some(current_set_signal_block) => storage.scheduled_change(¤t_set_signal_block).expect(
|
||||||
|
"header that is associated with this change is not pruned;\
|
||||||
|
scheduled changes are only removed when header is pruned; qed",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
current_set_signal_block = current_scheduled_set.prev_signal_block;
|
||||||
|
next_scheduled_set = Some(current_scheduled_set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::kovan_aura_config;
|
use crate::tests::{
|
||||||
use crate::tests::{genesis, signed_header, validator, validators_addresses, AccountId, InMemoryStorage};
|
block_i, custom_block_i, genesis, signed_header, validator, validators_addresses, AccountId, InMemoryStorage,
|
||||||
|
};
|
||||||
|
use crate::validators::{tests::validators_change_recept, ValidatorsSource};
|
||||||
|
use crate::{kovan_aura_config, pool_configuration};
|
||||||
use parity_crypto::publickey::{sign, KeyPair};
|
use parity_crypto::publickey::{sign, KeyPair};
|
||||||
use primitives::{rlp_encode, H520};
|
use primitives::{rlp_encode, TransactionOutcome, H520};
|
||||||
|
|
||||||
fn sealed_empty_step(validators: &[KeyPair], parent_hash: &H256, step: u64) -> SealedEmptyStep {
|
fn sealed_empty_step(validators: &[KeyPair], parent_hash: &H256, step: u64) -> SealedEmptyStep {
|
||||||
let mut empty_step = SealedEmptyStep {
|
let mut empty_step = SealedEmptyStep {
|
||||||
@@ -191,6 +370,35 @@ mod tests {
|
|||||||
verify_with_config(&kovan_aura_config(), header)
|
verify_with_config(&kovan_aura_config(), header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_accept_into_pool(
|
||||||
|
mut make_header: impl FnMut(&mut InMemoryStorage, &[KeyPair]) -> (Header, Option<Vec<Receipt>>),
|
||||||
|
) -> Result<(Vec<Vec<u8>>, Vec<Vec<u8>>), Error> {
|
||||||
|
let validators = vec![validator(0), validator(1), validator(2)];
|
||||||
|
let mut storage = InMemoryStorage::new(genesis(), validators_addresses(3));
|
||||||
|
let block1 = block_i(&storage, 1, &validators);
|
||||||
|
storage.insert(block1);
|
||||||
|
let block2 = block_i(&storage, 2, &validators);
|
||||||
|
let block2_hash = block2.hash();
|
||||||
|
storage.insert(block2);
|
||||||
|
let block3 = block_i(&storage, 3, &validators);
|
||||||
|
let block3_hash = block3.hash();
|
||||||
|
storage.insert(block3);
|
||||||
|
storage.set_finalized_block((2, block2_hash));
|
||||||
|
storage.set_best_block((3, block3_hash));
|
||||||
|
|
||||||
|
let validators_config =
|
||||||
|
ValidatorsConfiguration::Single(ValidatorsSource::Contract(Default::default(), Vec::new()));
|
||||||
|
let (header, receipts) = make_header(&mut storage, &validators);
|
||||||
|
accept_aura_header_into_pool(
|
||||||
|
&storage,
|
||||||
|
&kovan_aura_config(),
|
||||||
|
&validators_config,
|
||||||
|
&pool_configuration(),
|
||||||
|
&header,
|
||||||
|
receipts.as_ref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verifies_seal_count() {
|
fn verifies_seal_count() {
|
||||||
// when there are no seals at all
|
// when there are no seals at all
|
||||||
@@ -433,4 +641,262 @@ mod tests {
|
|||||||
// when everything is OK
|
// when everything is OK
|
||||||
assert_eq!(default_verify(&good_header).map(|_| ()), Ok(()));
|
assert_eq!(default_verify(&good_header).map(|_| ()), Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_verifies_known_blocks() {
|
||||||
|
// when header is known
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| (block_i(storage, 3, validators), None)),
|
||||||
|
Err(Error::KnownHeader),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_verifies_ancient_blocks() {
|
||||||
|
// when header number is less than finalized
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| (
|
||||||
|
custom_block_i(storage, 2, validators, |header| header.gas_limit += 1.into()),
|
||||||
|
None,
|
||||||
|
),),
|
||||||
|
Err(Error::AncientHeader),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_rejects_headers_without_required_receipts() {
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|_, _| (
|
||||||
|
Header {
|
||||||
|
number: 20_000_000,
|
||||||
|
seal: vec![vec![].into(), vec![].into()],
|
||||||
|
gas_limit: kovan_aura_config().min_gas_limit,
|
||||||
|
log_bloom: (&[0xff; 256]).into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),),
|
||||||
|
Err(Error::MissingTransactionsReceipts),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_rejects_headers_with_redundant_receipts() {
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| (
|
||||||
|
block_i(storage, 4, validators),
|
||||||
|
Some(vec![Receipt {
|
||||||
|
gas_used: 1.into(),
|
||||||
|
log_bloom: (&[0xff; 256]).into(),
|
||||||
|
logs: vec![],
|
||||||
|
outcome: TransactionOutcome::Unknown,
|
||||||
|
}]),
|
||||||
|
),),
|
||||||
|
Err(Error::RedundantTransactionsReceipts),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_verifies_future_block_number() {
|
||||||
|
// when header is too far from the future
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| (
|
||||||
|
custom_block_i(storage, 4, validators, |header| header.number = 100),
|
||||||
|
None,
|
||||||
|
),),
|
||||||
|
Err(Error::UnsignedTooFarInTheFuture),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_performs_full_verification_when_parent_is_known() {
|
||||||
|
// if parent is known, then we'll execute contextual_checks, which
|
||||||
|
// checks for DoubleVote
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| (
|
||||||
|
custom_block_i(storage, 4, validators, |header| header.seal[0] =
|
||||||
|
block_i(storage, 3, validators).seal[0].clone()),
|
||||||
|
None,
|
||||||
|
),),
|
||||||
|
Err(Error::DoubleVote),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_performs_validators_checks_when_parent_is_unknown() {
|
||||||
|
// if parent is unknown, then we still need to check if header has required signature
|
||||||
|
// (even if header will be considered invalid/duplicate later, we can use this signature
|
||||||
|
// as a proof of malicious action by this validator)
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|_, validators| (
|
||||||
|
signed_header(
|
||||||
|
validators,
|
||||||
|
Header {
|
||||||
|
author: validators[1].address().as_fixed_bytes().into(),
|
||||||
|
seal: vec![vec![8].into(), vec![].into()],
|
||||||
|
gas_limit: kovan_aura_config().min_gas_limit,
|
||||||
|
parent_hash: [42; 32].into(),
|
||||||
|
number: 8,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
43
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
Err(Error::NotValidator),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_verifies_header_with_known_parent() {
|
||||||
|
let mut hash = None;
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| {
|
||||||
|
let header = block_i(&storage, 4, &validators);
|
||||||
|
hash = Some(header.hash());
|
||||||
|
(header, None)
|
||||||
|
}),
|
||||||
|
Ok((
|
||||||
|
// no tags are required
|
||||||
|
vec![],
|
||||||
|
// header provides two tags
|
||||||
|
vec![
|
||||||
|
(4u64, validators_addresses(3)[1]).encode(),
|
||||||
|
(4u64, hash.unwrap()).encode(),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_verifies_header_with_unknown_parent() {
|
||||||
|
let mut hash = None;
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|_, validators| {
|
||||||
|
let header = signed_header(
|
||||||
|
validators,
|
||||||
|
Header {
|
||||||
|
author: validators[2].address().as_fixed_bytes().into(),
|
||||||
|
seal: vec![vec![47].into(), vec![].into()],
|
||||||
|
gas_limit: kovan_aura_config().min_gas_limit,
|
||||||
|
parent_hash: [42; 32].into(),
|
||||||
|
number: 5,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
47,
|
||||||
|
);
|
||||||
|
hash = Some(header.hash());
|
||||||
|
(header, None)
|
||||||
|
}),
|
||||||
|
Ok((
|
||||||
|
// parent tag required
|
||||||
|
vec![(4u64, [42u8; 32]).encode(),],
|
||||||
|
// header provides two tags
|
||||||
|
vec![
|
||||||
|
(5u64, validators_addresses(3)[2]).encode(),
|
||||||
|
(5u64, hash.unwrap()).encode(),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_uses_next_validators_set_when_finalized_fails() {
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, actual_validators| {
|
||||||
|
// change finalized set at parent header
|
||||||
|
storage.change_validators_set_at(3, validators_addresses(1), None);
|
||||||
|
|
||||||
|
// header is signed using wrong set
|
||||||
|
let header = signed_header(
|
||||||
|
actual_validators,
|
||||||
|
Header {
|
||||||
|
author: actual_validators[2].address().as_fixed_bytes().into(),
|
||||||
|
seal: vec![vec![47].into(), vec![].into()],
|
||||||
|
gas_limit: kovan_aura_config().min_gas_limit,
|
||||||
|
parent_hash: [42; 32].into(),
|
||||||
|
number: 5,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
47,
|
||||||
|
);
|
||||||
|
|
||||||
|
(header, None)
|
||||||
|
}),
|
||||||
|
Err(Error::NotValidator),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut hash = None;
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, actual_validators| {
|
||||||
|
// change finalized set at parent header + signal valid set at parent block
|
||||||
|
storage.change_validators_set_at(3, validators_addresses(10), Some(validators_addresses(3)));
|
||||||
|
|
||||||
|
// header is signed using wrong set
|
||||||
|
let header = signed_header(
|
||||||
|
actual_validators,
|
||||||
|
Header {
|
||||||
|
author: actual_validators[2].address().as_fixed_bytes().into(),
|
||||||
|
seal: vec![vec![47].into(), vec![].into()],
|
||||||
|
gas_limit: kovan_aura_config().min_gas_limit,
|
||||||
|
parent_hash: [42; 32].into(),
|
||||||
|
number: 5,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
47,
|
||||||
|
);
|
||||||
|
hash = Some(header.hash());
|
||||||
|
|
||||||
|
(header, None)
|
||||||
|
}),
|
||||||
|
Ok((
|
||||||
|
// parent tag required
|
||||||
|
vec![(4u64, [42u8; 32]).encode(),],
|
||||||
|
// header provides two tags
|
||||||
|
vec![
|
||||||
|
(5u64, validators_addresses(3)[2]).encode(),
|
||||||
|
(5u64, hash.unwrap()).encode(),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_rejects_headers_with_invalid_receipts() {
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| {
|
||||||
|
let header = custom_block_i(&storage, 4, &validators, |header| {
|
||||||
|
header.log_bloom = (&[0xff; 256]).into();
|
||||||
|
});
|
||||||
|
(header, Some(vec![validators_change_recept(Default::default())]))
|
||||||
|
}),
|
||||||
|
Err(Error::TransactionsReceiptsMismatch),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_accepts_headers_with_valid_receipts() {
|
||||||
|
let mut hash = None;
|
||||||
|
assert_eq!(
|
||||||
|
default_accept_into_pool(|storage, validators| {
|
||||||
|
let header = custom_block_i(&storage, 4, &validators, |header| {
|
||||||
|
header.log_bloom = (&[0xff; 256]).into();
|
||||||
|
header.receipts_root = "81ce88dc524403b796222046bf3daf543978329b87ffd50228f1d3987031dc45"
|
||||||
|
.parse()
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
hash = Some(header.hash());
|
||||||
|
(header, Some(vec![validators_change_recept(Default::default())]))
|
||||||
|
}),
|
||||||
|
Ok((
|
||||||
|
// no tags are required
|
||||||
|
vec![],
|
||||||
|
// header provides two tags
|
||||||
|
vec![
|
||||||
|
(4u64, validators_addresses(3)[1]).encode(),
|
||||||
|
(4u64, hash.unwrap()).encode(),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,22 @@ args:
|
|||||||
value_name: SUB_PORT
|
value_name: SUB_PORT
|
||||||
help: Connect to Substrate node at given port.
|
help: Connect to Substrate node at given port.
|
||||||
takes_value: true
|
takes_value: true
|
||||||
|
- sub-tx-mode:
|
||||||
|
long: sub-tx-mode
|
||||||
|
value_name: MODE
|
||||||
|
help: Submit headers using signed (default) or unsigned transactions. Third mode - backup - submits signed transactions only when we believe that sync has stalled.
|
||||||
|
takes_value: true
|
||||||
|
possible_values:
|
||||||
|
- signed
|
||||||
|
- unsigned
|
||||||
|
- backup
|
||||||
- sub-signer:
|
- sub-signer:
|
||||||
long: sub-signer
|
long: sub-signer
|
||||||
value_name: SUB_SIGNER
|
value_name: SUB_SIGNER
|
||||||
help: The SURI of secret key to use when transactions are submitted to the Substrate node.
|
help: The SURI of secret key to use when transactions are submitted to the Substrate node.
|
||||||
|
takes_value: true
|
||||||
- sub-signer-password:
|
- sub-signer-password:
|
||||||
long: sub-signer-password
|
long: sub-signer-password
|
||||||
value_name: SUB_SIGNER_PASSWORD
|
value_name: SUB_SIGNER_PASSWORD
|
||||||
help: The password for the SURI of secret key to use when transactions are submitted to the Substrate node.
|
help: The password for the SURI of secret key to use when transactions are submitted to the Substrate node.
|
||||||
|
takes_value: true
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::ethereum_headers::QueuedHeaders;
|
use crate::ethereum_headers::QueuedHeaders;
|
||||||
use crate::ethereum_sync_loop::EthereumSyncParams;
|
use crate::ethereum_sync_loop::{EthereumSyncParams, SubstrateTransactionMode};
|
||||||
use crate::ethereum_types::{HeaderId, HeaderStatus, QueuedHeader};
|
use crate::ethereum_types::{HeaderId, HeaderStatus, QueuedHeader};
|
||||||
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
|
use crate::substrate_types::{into_substrate_ethereum_header, into_substrate_ethereum_receipts};
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
@@ -95,7 +95,12 @@ impl HeadersSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Select headers that need to be submitted to the Substrate node.
|
/// Select headers that need to be submitted to the Substrate node.
|
||||||
pub fn select_headers_to_submit(&self) -> Option<Vec<&QueuedHeader>> {
|
pub fn select_headers_to_submit(&self, stalled: bool) -> Option<Vec<&QueuedHeader>> {
|
||||||
|
// if we operate in backup mode, we only submit headers when sync has stalled
|
||||||
|
if self.params.sub_tx_mode == SubstrateTransactionMode::Backup && !stalled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let headers_in_submit_status = self.headers.headers_in_status(HeaderStatus::Submitted);
|
let headers_in_submit_status = self.headers.headers_in_status(HeaderStatus::Submitted);
|
||||||
let headers_to_submit_count = self
|
let headers_to_submit_count = self
|
||||||
.params
|
.params
|
||||||
@@ -224,7 +229,7 @@ mod tests {
|
|||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(101)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(101)));
|
||||||
eth_sync.headers.maybe_receipts_response(&id(101), false);
|
eth_sync.headers.maybe_receipts_response(&id(101), false);
|
||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::Ready), Some(&header(101)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::Ready), Some(&header(101)));
|
||||||
assert_eq!(eth_sync.select_headers_to_submit(), Some(vec![&header(101)]));
|
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(101)]));
|
||||||
|
|
||||||
// and header #102 is ready to be downloaded
|
// and header #102 is ready to be downloaded
|
||||||
assert_eq!(eth_sync.select_new_header_to_download(), Some(102));
|
assert_eq!(eth_sync.select_new_header_to_download(), Some(102));
|
||||||
@@ -238,13 +243,13 @@ mod tests {
|
|||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(102)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(102)));
|
||||||
eth_sync.headers.maybe_receipts_response(&id(102), false);
|
eth_sync.headers.maybe_receipts_response(&id(102), false);
|
||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::Ready), Some(&header(102)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::Ready), Some(&header(102)));
|
||||||
assert_eq!(eth_sync.select_headers_to_submit(), None);
|
assert_eq!(eth_sync.select_headers_to_submit(false), None);
|
||||||
|
|
||||||
// substrate reports that it has imported block #101
|
// substrate reports that it has imported block #101
|
||||||
eth_sync.substrate_best_header_response(id(101));
|
eth_sync.substrate_best_header_response(id(101));
|
||||||
|
|
||||||
// and we are ready to submit #102
|
// and we are ready to submit #102
|
||||||
assert_eq!(eth_sync.select_headers_to_submit(), Some(vec![&header(102)]));
|
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(102)]));
|
||||||
eth_sync.headers.headers_submitted(vec![id(102)]);
|
eth_sync.headers.headers_submitted(vec![id(102)]);
|
||||||
|
|
||||||
// substrate reports that it has imported block #102
|
// substrate reports that it has imported block #102
|
||||||
@@ -269,7 +274,7 @@ mod tests {
|
|||||||
eth_sync.headers.header_response(header(101).header().clone());
|
eth_sync.headers.header_response(header(101).header().clone());
|
||||||
|
|
||||||
// we can't submit header #101, because its parent status is unknown
|
// we can't submit header #101, because its parent status is unknown
|
||||||
assert_eq!(eth_sync.select_headers_to_submit(), None);
|
assert_eq!(eth_sync.select_headers_to_submit(false), None);
|
||||||
|
|
||||||
// instead we are trying to determine status of its parent (#100)
|
// instead we are trying to determine status of its parent (#100)
|
||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeOrphan), Some(&header(101)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeOrphan), Some(&header(101)));
|
||||||
@@ -282,7 +287,7 @@ mod tests {
|
|||||||
eth_sync.headers.header_response(header(100).header().clone());
|
eth_sync.headers.header_response(header(100).header().clone());
|
||||||
|
|
||||||
// we can't submit header #100, because its parent status is unknown
|
// we can't submit header #100, because its parent status is unknown
|
||||||
assert_eq!(eth_sync.select_headers_to_submit(), None);
|
assert_eq!(eth_sync.select_headers_to_submit(false), None);
|
||||||
|
|
||||||
// instead we are trying to determine status of its parent (#99)
|
// instead we are trying to determine status of its parent (#99)
|
||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeOrphan), Some(&header(100)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeOrphan), Some(&header(100)));
|
||||||
@@ -293,13 +298,13 @@ mod tests {
|
|||||||
// and we are ready to submit #100
|
// and we are ready to submit #100
|
||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(100)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(100)));
|
||||||
eth_sync.headers.maybe_receipts_response(&id(100), false);
|
eth_sync.headers.maybe_receipts_response(&id(100), false);
|
||||||
assert_eq!(eth_sync.select_headers_to_submit(), Some(vec![&header(100)]));
|
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(100)]));
|
||||||
eth_sync.headers.headers_submitted(vec![id(100)]);
|
eth_sync.headers.headers_submitted(vec![id(100)]);
|
||||||
|
|
||||||
// and we are ready to submit #101
|
// and we are ready to submit #101
|
||||||
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(101)));
|
assert_eq!(eth_sync.headers.header(HeaderStatus::MaybeReceipts), Some(&header(101)));
|
||||||
eth_sync.headers.maybe_receipts_response(&id(101), false);
|
eth_sync.headers.maybe_receipts_response(&id(101), false);
|
||||||
assert_eq!(eth_sync.select_headers_to_submit(), Some(vec![&header(101)]));
|
assert_eq!(eth_sync.select_headers_to_submit(false), Some(vec![&header(101)]));
|
||||||
eth_sync.headers.headers_submitted(vec![id(101)]);
|
eth_sync.headers.headers_submitted(vec![id(101)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,4 +315,26 @@ mod tests {
|
|||||||
eth_sync.substrate_best_header_response(id(100));
|
eth_sync.substrate_best_header_response(id(100));
|
||||||
assert_eq!(eth_sync.headers.prune_border(), 50);
|
assert_eq!(eth_sync.headers.prune_border(), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_submitting_headers_in_backup_mode_when_stalled() {
|
||||||
|
let mut eth_sync = HeadersSync::new(Default::default());
|
||||||
|
eth_sync.params.sub_tx_mode = SubstrateTransactionMode::Backup;
|
||||||
|
|
||||||
|
// ethereum reports best header #102
|
||||||
|
eth_sync.ethereum_best_header_number_response(102);
|
||||||
|
|
||||||
|
// substrate reports that it is at block #100
|
||||||
|
eth_sync.substrate_best_header_response(id(100));
|
||||||
|
|
||||||
|
// block #101 is downloaded first
|
||||||
|
eth_sync.headers.header_response(header(101).header().clone());
|
||||||
|
eth_sync.headers.maybe_receipts_response(&id(101), false);
|
||||||
|
|
||||||
|
// ensure that headers are not submitted when sync is not stalled
|
||||||
|
assert_eq!(eth_sync.select_headers_to_submit(false), None);
|
||||||
|
|
||||||
|
// ensure that headers are not submitted when sync is stalled
|
||||||
|
assert_eq!(eth_sync.select_headers_to_submit(true), Some(vec![&header(101)]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ const SUBSTRATE_TICK_INTERVAL_MS: u64 = 5_000;
|
|||||||
/// the subscriber will receive every best header (2) reorg won't always lead to sync
|
/// the subscriber will receive every best header (2) reorg won't always lead to sync
|
||||||
/// stall and restart is a heavy operation (we forget all in-memory headers).
|
/// stall and restart is a heavy operation (we forget all in-memory headers).
|
||||||
const STALL_SYNC_TIMEOUT_MS: u64 = 30_000;
|
const STALL_SYNC_TIMEOUT_MS: u64 = 30_000;
|
||||||
|
/// Delay (in milliseconds) after we have seen update of best Ethereum header in Substrate,
|
||||||
|
/// for us to treat sync stalled. ONLY when relay operates in backup mode.
|
||||||
|
const BACKUP_STALL_SYNC_TIMEOUT_MS: u64 = 5 * 60_000;
|
||||||
/// Delay (in milliseconds) after connection-related error happened before we'll try
|
/// Delay (in milliseconds) after connection-related error happened before we'll try
|
||||||
/// reconnection again.
|
/// reconnection again.
|
||||||
const CONNECTION_ERROR_DELAY_MS: u64 = 10_000;
|
const CONNECTION_ERROR_DELAY_MS: u64 = 10_000;
|
||||||
@@ -57,6 +60,8 @@ pub struct EthereumSyncParams {
|
|||||||
pub sub_host: String,
|
pub sub_host: String,
|
||||||
/// Substrate RPC port.
|
/// Substrate RPC port.
|
||||||
pub sub_port: u16,
|
pub sub_port: u16,
|
||||||
|
/// Substrate transactions submission mode.
|
||||||
|
pub sub_tx_mode: SubstrateTransactionMode,
|
||||||
/// Substrate transactions signer.
|
/// Substrate transactions signer.
|
||||||
pub sub_signer: sp_core::sr25519::Pair,
|
pub sub_signer: sp_core::sr25519::Pair,
|
||||||
/// Maximal number of ethereum headers to pre-download.
|
/// Maximal number of ethereum headers to pre-download.
|
||||||
@@ -72,6 +77,18 @@ pub struct EthereumSyncParams {
|
|||||||
pub prune_depth: u64,
|
pub prune_depth: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Substrate transaction mode.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum SubstrateTransactionMode {
|
||||||
|
/// Submit eth headers using signed substrate transactions.
|
||||||
|
Signed,
|
||||||
|
/// Submit eth headers using unsigned substrate transactions.
|
||||||
|
Unsigned,
|
||||||
|
/// Submit eth headers using signed substrate transactions, but only when we
|
||||||
|
/// believe that sync has stalled.
|
||||||
|
Backup,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for EthereumSyncParams {
|
impl std::fmt::Debug for EthereumSyncParams {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
f.debug_struct("EthereumSyncParams")
|
f.debug_struct("EthereumSyncParams")
|
||||||
@@ -79,6 +96,7 @@ impl std::fmt::Debug for EthereumSyncParams {
|
|||||||
.field("eth_port", &self.eth_port)
|
.field("eth_port", &self.eth_port)
|
||||||
.field("sub_host", &self.sub_port)
|
.field("sub_host", &self.sub_port)
|
||||||
.field("sub_port", &self.sub_port)
|
.field("sub_port", &self.sub_port)
|
||||||
|
.field("sub_tx_mode", &self.sub_tx_mode)
|
||||||
.field("max_future_headers_to_download", &self.max_future_headers_to_download)
|
.field("max_future_headers_to_download", &self.max_future_headers_to_download)
|
||||||
.field("max_headers_in_submitted_status", &self.max_headers_in_submitted_status)
|
.field("max_headers_in_submitted_status", &self.max_headers_in_submitted_status)
|
||||||
.field("max_headers_in_single_submit", &self.max_headers_in_single_submit)
|
.field("max_headers_in_single_submit", &self.max_headers_in_single_submit)
|
||||||
@@ -98,6 +116,7 @@ impl Default for EthereumSyncParams {
|
|||||||
eth_port: 8545,
|
eth_port: 8545,
|
||||||
sub_host: "localhost".into(),
|
sub_host: "localhost".into(),
|
||||||
sub_port: 9933,
|
sub_port: 9933,
|
||||||
|
sub_tx_mode: SubstrateTransactionMode::Signed,
|
||||||
sub_signer: sp_keyring::AccountKeyring::Alice.pair(),
|
sub_signer: sp_keyring::AccountKeyring::Alice.pair(),
|
||||||
max_future_headers_to_download: 128,
|
max_future_headers_to_download: 128,
|
||||||
max_headers_in_submitted_status: 128,
|
max_headers_in_submitted_status: 128,
|
||||||
@@ -112,6 +131,10 @@ impl Default for EthereumSyncParams {
|
|||||||
pub fn run(params: EthereumSyncParams) {
|
pub fn run(params: EthereumSyncParams) {
|
||||||
let mut local_pool = futures::executor::LocalPool::new();
|
let mut local_pool = futures::executor::LocalPool::new();
|
||||||
let mut progress_context = (std::time::Instant::now(), None, None);
|
let mut progress_context = (std::time::Instant::now(), None, None);
|
||||||
|
let sign_sub_transactions = match params.sub_tx_mode {
|
||||||
|
SubstrateTransactionMode::Signed | SubstrateTransactionMode::Backup => true,
|
||||||
|
SubstrateTransactionMode::Unsigned => false,
|
||||||
|
};
|
||||||
|
|
||||||
local_pool.run_until(async move {
|
local_pool.run_until(async move {
|
||||||
let eth_uri = format!("http://{}:{}", params.eth_host, params.eth_port);
|
let eth_uri = format!("http://{}:{}", params.eth_host, params.eth_port);
|
||||||
@@ -120,6 +143,7 @@ pub fn run(params: EthereumSyncParams) {
|
|||||||
|
|
||||||
let mut eth_sync = crate::ethereum_sync::HeadersSync::new(params);
|
let mut eth_sync = crate::ethereum_sync::HeadersSync::new(params);
|
||||||
let mut stall_countdown = None;
|
let mut stall_countdown = None;
|
||||||
|
let mut last_update_time = std::time::Instant::now();
|
||||||
|
|
||||||
let mut eth_maybe_client = None;
|
let mut eth_maybe_client = None;
|
||||||
let mut eth_best_block_number_required = false;
|
let mut eth_best_block_number_required = false;
|
||||||
@@ -220,6 +244,9 @@ pub fn run(params: EthereumSyncParams) {
|
|||||||
sub_best_block,
|
sub_best_block,
|
||||||
|sub_best_block| {
|
|sub_best_block| {
|
||||||
let head_updated = eth_sync.substrate_best_header_response(sub_best_block);
|
let head_updated = eth_sync.substrate_best_header_response(sub_best_block);
|
||||||
|
if head_updated {
|
||||||
|
last_update_time = std::time::Instant::now();
|
||||||
|
}
|
||||||
match head_updated {
|
match head_updated {
|
||||||
// IF head is updated AND there are still our transactions:
|
// IF head is updated AND there are still our transactions:
|
||||||
// => restart stall countdown timer
|
// => restart stall countdown timer
|
||||||
@@ -336,7 +363,9 @@ pub fn run(params: EthereumSyncParams) {
|
|||||||
|
|
||||||
sub_existence_status_future
|
sub_existence_status_future
|
||||||
.set(substrate_client::ethereum_header_known(sub_client, parent_id).fuse());
|
.set(substrate_client::ethereum_header_known(sub_client, parent_id).fuse());
|
||||||
} else if let Some(headers) = eth_sync.select_headers_to_submit() {
|
} else if let Some(headers) = eth_sync.select_headers_to_submit(
|
||||||
|
last_update_time.elapsed() > std::time::Duration::from_millis(BACKUP_STALL_SYNC_TIMEOUT_MS),
|
||||||
|
) {
|
||||||
let ids = match headers.len() {
|
let ids = match headers.len() {
|
||||||
1 => format!("{:?}", headers[0].id()),
|
1 => format!("{:?}", headers[0].id()),
|
||||||
2 => format!("[{:?}, {:?}]", headers[0].id(), headers[1].id()),
|
2 => format!("[{:?}, {:?}]", headers[0].id(), headers[1].id()),
|
||||||
@@ -350,7 +379,9 @@ pub fn run(params: EthereumSyncParams) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let headers = headers.into_iter().cloned().collect();
|
let headers = headers.into_iter().cloned().collect();
|
||||||
sub_submit_header_future.set(substrate_client::submit_ethereum_headers(sub_client, headers).fuse());
|
sub_submit_header_future.set(
|
||||||
|
substrate_client::submit_ethereum_headers(sub_client, headers, sign_sub_transactions).fuse(),
|
||||||
|
);
|
||||||
|
|
||||||
// remember that we have submitted some headers
|
// remember that we have submitted some headers
|
||||||
if stall_countdown.is_none() {
|
if stall_countdown.is_none() {
|
||||||
|
|||||||
@@ -99,5 +99,18 @@ fn ethereum_sync_params() -> Result<ethereum_sync_loop::EthereumSyncParams, Stri
|
|||||||
sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password).map_err(|e| format!("{:?}", e))?;
|
sp_core::sr25519::Pair::from_string(sub_signer, sub_signer_password).map_err(|e| format!("{:?}", e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match matches.value_of("sub-tx-mode") {
|
||||||
|
Some("signed") => eth_sync_params.sub_tx_mode = ethereum_sync_loop::SubstrateTransactionMode::Signed,
|
||||||
|
Some("unsigned") => {
|
||||||
|
eth_sync_params.sub_tx_mode = ethereum_sync_loop::SubstrateTransactionMode::Unsigned;
|
||||||
|
|
||||||
|
// tx pool won't accept too much unsigned transactions
|
||||||
|
eth_sync_params.max_headers_in_submitted_status = 10;
|
||||||
|
}
|
||||||
|
Some("backup") => eth_sync_params.sub_tx_mode = ethereum_sync_loop::SubstrateTransactionMode::Backup,
|
||||||
|
Some(mode) => return Err(format!("Invalid sub-tx-mode: {}", mode)),
|
||||||
|
None => eth_sync_params.sub_tx_mode = ethereum_sync_loop::SubstrateTransactionMode::Signed,
|
||||||
|
}
|
||||||
|
|
||||||
Ok(eth_sync_params)
|
Ok(eth_sync_params)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,19 @@ pub async fn ethereum_header_known(
|
|||||||
pub async fn submit_ethereum_headers(
|
pub async fn submit_ethereum_headers(
|
||||||
client: Client,
|
client: Client,
|
||||||
headers: Vec<QueuedEthereumHeader>,
|
headers: Vec<QueuedEthereumHeader>,
|
||||||
) -> (Client, Result<(TransactionHash, Vec<EthereumHeaderId>), Error>) {
|
sign_transactions: bool,
|
||||||
|
) -> (Client, Result<(Vec<TransactionHash>, Vec<EthereumHeaderId>), Error>) {
|
||||||
|
match sign_transactions {
|
||||||
|
true => submit_signed_ethereum_headers(client, headers).await,
|
||||||
|
false => submit_unsigned_ethereum_headers(client, headers).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Submits signed Ethereum header to Substrate runtime.
|
||||||
|
pub async fn submit_signed_ethereum_headers(
|
||||||
|
client: Client,
|
||||||
|
headers: Vec<QueuedEthereumHeader>,
|
||||||
|
) -> (Client, Result<(Vec<TransactionHash>, Vec<EthereumHeaderId>), Error>) {
|
||||||
let ids = headers.iter().map(|header| header.id()).collect();
|
let ids = headers.iter().map(|header| header.id()).collect();
|
||||||
let (client, genesis_hash) = match client.genesis_hash {
|
let (client, genesis_hash) = match client.genesis_hash {
|
||||||
Some(genesis_hash) => (client, genesis_hash),
|
Some(genesis_hash) => (client, genesis_hash),
|
||||||
@@ -152,7 +164,9 @@ pub async fn submit_ethereum_headers(
|
|||||||
Ok(nonce) => nonce,
|
Ok(nonce) => nonce,
|
||||||
Err(err) => return (client, Err(err)),
|
Err(err) => return (client, Err(err)),
|
||||||
};
|
};
|
||||||
let transaction = create_submit_transaction(headers, &client.signer, nonce, genesis_hash);
|
|
||||||
|
let transaction = create_signed_submit_transaction(headers, &client.signer, nonce, genesis_hash);
|
||||||
|
|
||||||
let encoded_transaction = transaction.encode();
|
let encoded_transaction = transaction.encode();
|
||||||
let (client, transaction_hash) = call_rpc(
|
let (client, transaction_hash) = call_rpc(
|
||||||
client,
|
client,
|
||||||
@@ -160,7 +174,39 @@ pub async fn submit_ethereum_headers(
|
|||||||
Params::Array(vec![to_value(Bytes(encoded_transaction)).unwrap()]),
|
Params::Array(vec![to_value(Bytes(encoded_transaction)).unwrap()]),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
(client, transaction_hash.map(|transaction_hash| (transaction_hash, ids)))
|
|
||||||
|
(
|
||||||
|
client,
|
||||||
|
transaction_hash.map(|transaction_hash| (vec![transaction_hash], ids)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Submits unsigned Ethereum header to Substrate runtime.
|
||||||
|
pub async fn submit_unsigned_ethereum_headers(
|
||||||
|
mut client: Client,
|
||||||
|
headers: Vec<QueuedEthereumHeader>,
|
||||||
|
) -> (Client, Result<(Vec<TransactionHash>, Vec<EthereumHeaderId>), Error>) {
|
||||||
|
let ids = headers.iter().map(|header| header.id()).collect();
|
||||||
|
let mut transactions_hashes = Vec::new();
|
||||||
|
for header in headers {
|
||||||
|
let transaction = create_unsigned_submit_transaction(header);
|
||||||
|
|
||||||
|
let encoded_transaction = transaction.encode();
|
||||||
|
let (used_client, transaction_hash) = call_rpc(
|
||||||
|
client,
|
||||||
|
"author_submitExtrinsic",
|
||||||
|
Params::Array(vec![to_value(Bytes(encoded_transaction)).unwrap()]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
client = used_client;
|
||||||
|
transactions_hashes.push(match transaction_hash {
|
||||||
|
Ok(transaction_hash) => transaction_hash,
|
||||||
|
Err(error) => return (client, Err(error)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(client, Ok((transactions_hashes, ids)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get Substrate block hash by its number.
|
/// Get Substrate block hash by its number.
|
||||||
@@ -236,25 +282,26 @@ async fn call_rpc_u64(mut client: Client, method: &'static str, params: Params)
|
|||||||
(client, result)
|
(client, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create Substrate transaction for submitting Ethereum header.
|
/// Create signed Substrate transaction for submitting Ethereum headers.
|
||||||
fn create_submit_transaction(
|
fn create_signed_submit_transaction(
|
||||||
headers: Vec<QueuedEthereumHeader>,
|
headers: Vec<QueuedEthereumHeader>,
|
||||||
signer: &sp_core::sr25519::Pair,
|
signer: &sp_core::sr25519::Pair,
|
||||||
index: node_primitives::Index,
|
index: node_primitives::Index,
|
||||||
genesis_hash: H256,
|
genesis_hash: H256,
|
||||||
) -> bridge_node_runtime::UncheckedExtrinsic {
|
) -> bridge_node_runtime::UncheckedExtrinsic {
|
||||||
let function = bridge_node_runtime::Call::BridgeEthPoA(bridge_node_runtime::BridgeEthPoACall::import_headers(
|
let function =
|
||||||
headers
|
bridge_node_runtime::Call::BridgeEthPoA(bridge_node_runtime::BridgeEthPoACall::import_signed_headers(
|
||||||
.into_iter()
|
headers
|
||||||
.map(|header| {
|
.into_iter()
|
||||||
let (header, receipts) = header.extract();
|
.map(|header| {
|
||||||
(
|
let (header, receipts) = header.extract();
|
||||||
into_substrate_ethereum_header(&header),
|
(
|
||||||
into_substrate_ethereum_receipts(&receipts),
|
into_substrate_ethereum_header(&header),
|
||||||
)
|
into_substrate_ethereum_receipts(&receipts),
|
||||||
})
|
)
|
||||||
.collect(),
|
})
|
||||||
));
|
.collect(),
|
||||||
|
));
|
||||||
|
|
||||||
let extra = |i: node_primitives::Index, f: node_primitives::Balance| {
|
let extra = |i: node_primitives::Index, f: node_primitives::Balance| {
|
||||||
(
|
(
|
||||||
@@ -284,3 +331,15 @@ fn create_submit_transaction(
|
|||||||
|
|
||||||
bridge_node_runtime::UncheckedExtrinsic::new_signed(function, signer.into_account().into(), signature.into(), extra)
|
bridge_node_runtime::UncheckedExtrinsic::new_signed(function, signer.into_account().into(), signature.into(), extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create unsigned Substrate transaction for submitting Ethereum header.
|
||||||
|
fn create_unsigned_submit_transaction(header: QueuedEthereumHeader) -> bridge_node_runtime::UncheckedExtrinsic {
|
||||||
|
let (header, receipts) = header.extract();
|
||||||
|
let function =
|
||||||
|
bridge_node_runtime::Call::BridgeEthPoA(bridge_node_runtime::BridgeEthPoACall::import_unsigned_header(
|
||||||
|
into_substrate_ethereum_header(&header),
|
||||||
|
into_substrate_ethereum_receipts(&receipts),
|
||||||
|
));
|
||||||
|
|
||||||
|
bridge_node_runtime::UncheckedExtrinsic::new_unsigned(function)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user