|
|
|
@@ -21,14 +21,8 @@
|
|
|
|
|
use crate::finality::{CachedFinalityVotes, FinalityVotes};
|
|
|
|
|
use bp_eth_poa::{Address, AuraHeader, HeaderId, RawTransaction, RawTransactionReceipt, Receipt, H256, U256};
|
|
|
|
|
use codec::{Decode, Encode};
|
|
|
|
|
use frame_support::{decl_module, decl_storage, traits::Get};
|
|
|
|
|
use sp_runtime::{
|
|
|
|
|
transaction_validity::{
|
|
|
|
|
InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionSource, TransactionValidity,
|
|
|
|
|
UnknownTransaction, ValidTransaction,
|
|
|
|
|
},
|
|
|
|
|
RuntimeDebug,
|
|
|
|
|
};
|
|
|
|
|
use frame_support::traits::Get;
|
|
|
|
|
use sp_runtime::RuntimeDebug;
|
|
|
|
|
use sp_std::{cmp::Ord, collections::btree_map::BTreeMap, prelude::*};
|
|
|
|
|
|
|
|
|
|
pub use validators::{ValidatorsConfiguration, ValidatorsSource};
|
|
|
|
@@ -369,34 +363,53 @@ impl<AccountId> OnHeadersSubmitted<AccountId> for () {
|
|
|
|
|
fn on_valid_headers_finalized(_submitter: AccountId, _finalized: u64) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The module configuration trait.
|
|
|
|
|
pub trait Config<I = DefaultInstance>: frame_system::Config {
|
|
|
|
|
/// Aura configuration.
|
|
|
|
|
type AuraConfiguration: Get<AuraConfiguration>;
|
|
|
|
|
/// Validators configuration.
|
|
|
|
|
type ValidatorsConfiguration: Get<validators::ValidatorsConfiguration>;
|
|
|
|
|
pub use pallet::*;
|
|
|
|
|
|
|
|
|
|
/// Interval (in blocks) for for finality votes caching.
|
|
|
|
|
/// If None, cache is disabled.
|
|
|
|
|
///
|
|
|
|
|
/// Ideally, this should either be None (when we are sure that there won't
|
|
|
|
|
/// be any significant finalization delays), or something that is bit larger
|
|
|
|
|
/// than average finalization delay.
|
|
|
|
|
type FinalityVotesCachingInterval: Get<Option<u64>>;
|
|
|
|
|
/// Headers pruning strategy.
|
|
|
|
|
type PruningStrategy: PruningStrategy;
|
|
|
|
|
/// Header timestamp verification against current on-chain time.
|
|
|
|
|
type ChainTime: ChainTime;
|
|
|
|
|
#[frame_support::pallet]
|
|
|
|
|
pub mod pallet {
|
|
|
|
|
use super::*;
|
|
|
|
|
use frame_support::pallet_prelude::*;
|
|
|
|
|
use frame_system::pallet_prelude::*;
|
|
|
|
|
|
|
|
|
|
/// Handler for headers submission result.
|
|
|
|
|
type OnHeadersSubmitted: OnHeadersSubmitted<Self::AccountId>;
|
|
|
|
|
}
|
|
|
|
|
#[pallet::config]
|
|
|
|
|
pub trait Config<I: 'static = ()>: frame_system::Config {
|
|
|
|
|
/// Aura configuration.
|
|
|
|
|
type AuraConfiguration: Get<AuraConfiguration>;
|
|
|
|
|
/// Validators configuration.
|
|
|
|
|
type ValidatorsConfiguration: Get<validators::ValidatorsConfiguration>;
|
|
|
|
|
|
|
|
|
|
decl_module! {
|
|
|
|
|
pub struct Module<T: Config<I>, I: Instance = DefaultInstance> for enum Call where origin: T::Origin {
|
|
|
|
|
/// Interval (in blocks) for for finality votes caching.
|
|
|
|
|
/// If None, cache is disabled.
|
|
|
|
|
///
|
|
|
|
|
/// Ideally, this should either be None (when we are sure that there won't
|
|
|
|
|
/// be any significant finalization delays), or something that is bit larger
|
|
|
|
|
/// than average finalization delay.
|
|
|
|
|
type FinalityVotesCachingInterval: Get<Option<u64>>;
|
|
|
|
|
/// Headers pruning strategy.
|
|
|
|
|
type PruningStrategy: PruningStrategy;
|
|
|
|
|
/// Header timestamp verification against current on-chain time.
|
|
|
|
|
type ChainTime: ChainTime;
|
|
|
|
|
|
|
|
|
|
/// Handler for headers submission result.
|
|
|
|
|
type OnHeadersSubmitted: OnHeadersSubmitted<Self::AccountId>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[pallet::pallet]
|
|
|
|
|
#[pallet::generate_store(pub(super) trait Store)]
|
|
|
|
|
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
|
|
|
|
|
|
|
|
|
|
#[pallet::hooks]
|
|
|
|
|
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {}
|
|
|
|
|
|
|
|
|
|
#[pallet::call]
|
|
|
|
|
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
|
|
|
|
/// Import single Aura header. Requires transaction to be **UNSIGNED**.
|
|
|
|
|
#[weight = 0] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78)
|
|
|
|
|
pub fn import_unsigned_header(origin, header: AuraHeader, receipts: Option<Vec<Receipt>>) {
|
|
|
|
|
#[pallet::weight(0)] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78)
|
|
|
|
|
pub fn import_unsigned_header(
|
|
|
|
|
origin: OriginFor<T>,
|
|
|
|
|
header: AuraHeader,
|
|
|
|
|
receipts: Option<Vec<Receipt>>,
|
|
|
|
|
) -> DispatchResult {
|
|
|
|
|
frame_system::ensure_none(origin)?;
|
|
|
|
|
|
|
|
|
|
import::import_header(
|
|
|
|
@@ -408,7 +421,10 @@ decl_module! {
|
|
|
|
|
header,
|
|
|
|
|
&T::ChainTime::default(),
|
|
|
|
|
receipts,
|
|
|
|
|
).map_err(|e| e.msg())?;
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.msg())?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Import Aura chain headers in a single **SIGNED** transaction.
|
|
|
|
@@ -417,8 +433,11 @@ decl_module! {
|
|
|
|
|
///
|
|
|
|
|
/// This should be used with caution - passing too many headers could lead to
|
|
|
|
|
/// enormous block production/import time.
|
|
|
|
|
#[weight = 0] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78)
|
|
|
|
|
pub fn import_signed_headers(origin, headers_with_receipts: Vec<(AuraHeader, Option<Vec<Receipt>>)>) {
|
|
|
|
|
#[pallet::weight(0)] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78)
|
|
|
|
|
pub fn import_signed_headers(
|
|
|
|
|
origin: OriginFor<T>,
|
|
|
|
|
headers_with_receipts: Vec<(AuraHeader, Option<Vec<Receipt>>)>,
|
|
|
|
|
) -> DispatchResult {
|
|
|
|
|
let submitter = frame_system::ensure_signed(origin)?;
|
|
|
|
|
let mut finalized_headers = BTreeMap::new();
|
|
|
|
|
let import_result = import::import_headers(
|
|
|
|
@@ -435,77 +454,147 @@ decl_module! {
|
|
|
|
|
// if we have finalized some headers, we will reward their submitters even
|
|
|
|
|
// if current submitter has provided some invalid headers
|
|
|
|
|
for (f_submitter, f_count) in finalized_headers {
|
|
|
|
|
T::OnHeadersSubmitted::on_valid_headers_finalized(
|
|
|
|
|
f_submitter,
|
|
|
|
|
f_count,
|
|
|
|
|
);
|
|
|
|
|
T::OnHeadersSubmitted::on_valid_headers_finalized(f_submitter, f_count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now track/penalize current submitter for providing new headers
|
|
|
|
|
match import_result {
|
|
|
|
|
Ok((useful, useless)) =>
|
|
|
|
|
T::OnHeadersSubmitted::on_valid_headers_submitted(submitter, useful, useless),
|
|
|
|
|
Ok((useful, useless)) => T::OnHeadersSubmitted::on_valid_headers_submitted(submitter, useful, useless),
|
|
|
|
|
Err(error) => {
|
|
|
|
|
// even though we may have accept some headers, we do not want to reward someone
|
|
|
|
|
// who provides invalid headers
|
|
|
|
|
T::OnHeadersSubmitted::on_invalid_headers_submitted(submitter);
|
|
|
|
|
return Err(error.msg().into());
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[pallet::validate_unsigned]
|
|
|
|
|
impl<T: Config<I>, I: 'static> ValidateUnsigned for Pallet<T, I> {
|
|
|
|
|
type Call = Call<T, I>;
|
|
|
|
|
|
|
|
|
|
fn validate_unsigned(_source: TransactionSource, 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, I>::new(),
|
|
|
|
|
&T::AuraConfiguration::get(),
|
|
|
|
|
&T::ValidatorsConfiguration::get(),
|
|
|
|
|
&pool_configuration(),
|
|
|
|
|
header,
|
|
|
|
|
&T::ChainTime::default(),
|
|
|
|
|
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(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
decl_storage! {
|
|
|
|
|
trait Store for Pallet<T: Config<I>, I: Instance = DefaultInstance> as Bridge {
|
|
|
|
|
/// Best known block.
|
|
|
|
|
BestBlock: (HeaderId, U256);
|
|
|
|
|
/// Best finalized block.
|
|
|
|
|
FinalizedBlock: HeaderId;
|
|
|
|
|
/// Range of blocks that we want to prune.
|
|
|
|
|
BlocksToPrune: PruningRange;
|
|
|
|
|
/// Map of imported headers by hash.
|
|
|
|
|
Headers: map hasher(identity) H256 => Option<StoredHeader<T::AccountId>>;
|
|
|
|
|
/// Map of imported header hashes by number.
|
|
|
|
|
HeadersByNumber: map hasher(blake2_128_concat) u64 => Option<Vec<H256>>;
|
|
|
|
|
/// Map of cached finality data by header hash.
|
|
|
|
|
FinalityCache: map hasher(identity) H256 => Option<FinalityVotes<T::AccountId>>;
|
|
|
|
|
/// The ID of next validator set.
|
|
|
|
|
NextValidatorsSetId: u64;
|
|
|
|
|
/// Map of validators sets by their id.
|
|
|
|
|
ValidatorsSets: map hasher(twox_64_concat) u64 => Option<ValidatorsSet>;
|
|
|
|
|
/// Validators sets reference count. Each header that is authored by this set increases
|
|
|
|
|
/// the reference count. When we prune this header, we decrease the reference count.
|
|
|
|
|
/// When it reaches zero, we are free to prune validator set as well.
|
|
|
|
|
ValidatorsSetsRc: map hasher(twox_64_concat) u64 => Option<u64>;
|
|
|
|
|
/// Map of validators set changes scheduled by given header.
|
|
|
|
|
ScheduledChanges: map hasher(identity) H256 => Option<AuraScheduledChange>;
|
|
|
|
|
/// Best known block.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type BestBlock<T: Config<I>, I: 'static = ()> = StorageValue<_, (HeaderId, U256), ValueQuery>;
|
|
|
|
|
|
|
|
|
|
/// Best finalized block.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type FinalizedBlock<T: Config<I>, I: 'static = ()> = StorageValue<_, HeaderId, ValueQuery>;
|
|
|
|
|
|
|
|
|
|
/// Range of blocks that we want to prune.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type BlocksToPrune<T: Config<I>, I: 'static = ()> = StorageValue<_, PruningRange, ValueQuery>;
|
|
|
|
|
|
|
|
|
|
/// Map of imported headers by hash.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type Headers<T: Config<I>, I: 'static = ()> = StorageMap<_, Identity, H256, StoredHeader<T::AccountId>>;
|
|
|
|
|
|
|
|
|
|
/// Map of imported header hashes by number.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type HeadersByNumber<T: Config<I>, I: 'static = ()> = StorageMap<_, Blake2_128Concat, u64, Vec<H256>>;
|
|
|
|
|
|
|
|
|
|
/// Map of cached finality data by header hash.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type FinalityCache<T: Config<I>, I: 'static = ()> =
|
|
|
|
|
StorageMap<_, Identity, H256, FinalityVotes<T::AccountId>>;
|
|
|
|
|
|
|
|
|
|
/// The ID of next validator set.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type NextValidatorsSetId<T: Config<I>, I: 'static = ()> = StorageValue<_, u64, ValueQuery>;
|
|
|
|
|
|
|
|
|
|
/// Map of validators sets by their id.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type ValidatorsSets<T: Config<I>, I: 'static = ()> = StorageMap<_, Twox64Concat, u64, ValidatorsSet>;
|
|
|
|
|
|
|
|
|
|
/// Validators sets reference count. Each header that is authored by this set increases
|
|
|
|
|
/// the reference count. When we prune this header, we decrease the reference count.
|
|
|
|
|
/// When it reaches zero, we are free to prune validator set as well.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type ValidatorsSetsRc<T: Config<I>, I: 'static = ()> = StorageMap<_, Twox64Concat, u64, u64>;
|
|
|
|
|
|
|
|
|
|
/// Map of validators set changes scheduled by given header.
|
|
|
|
|
#[pallet::storage]
|
|
|
|
|
pub(super) type ScheduledChanges<T: Config<I>, I: 'static = ()> =
|
|
|
|
|
StorageMap<_, Identity, H256, AuraScheduledChange>;
|
|
|
|
|
|
|
|
|
|
#[pallet::genesis_config]
|
|
|
|
|
pub struct GenesisConfig {
|
|
|
|
|
/// PoA header to start with.
|
|
|
|
|
pub initial_header: AuraHeader,
|
|
|
|
|
/// Initial PoA chain difficulty.
|
|
|
|
|
pub initial_difficulty: U256,
|
|
|
|
|
/// Initial PoA validators set.
|
|
|
|
|
pub initial_validators: Vec<Address>,
|
|
|
|
|
}
|
|
|
|
|
add_extra_genesis {
|
|
|
|
|
config(initial_header): AuraHeader;
|
|
|
|
|
config(initial_difficulty): U256;
|
|
|
|
|
config(initial_validators): Vec<Address>;
|
|
|
|
|
build(|config| {
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "std")]
|
|
|
|
|
impl Default for GenesisConfig {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
initial_header: Default::default(),
|
|
|
|
|
initial_difficulty: Default::default(),
|
|
|
|
|
initial_validators: Default::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[pallet::genesis_build]
|
|
|
|
|
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig {
|
|
|
|
|
fn build(&self) {
|
|
|
|
|
// the initial blocks should be selected so that:
|
|
|
|
|
// 1) it doesn't signal validators changes;
|
|
|
|
|
// 2) there are no scheduled validators changes from previous blocks;
|
|
|
|
|
// 3) (implied) all direct children of initial block are authored by the same validators set.
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
!config.initial_validators.is_empty(),
|
|
|
|
|
!self.initial_validators.is_empty(),
|
|
|
|
|
"Initial validators set can't be empty",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
initialize_storage::<T, I>(
|
|
|
|
|
&config.initial_header,
|
|
|
|
|
config.initial_difficulty,
|
|
|
|
|
&config.initial_validators,
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
initialize_storage::<T, I>(&self.initial_header, self.initial_difficulty, &self.initial_validators);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Config<I>, I: Instance> Pallet<T, I> {
|
|
|
|
|
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
|
|
|
|
/// Returns number and hash of the best block known to the bridge module.
|
|
|
|
|
/// The caller should only submit `import_header` transaction that makes
|
|
|
|
|
/// (or leads to making) other header the best one.
|
|
|
|
@@ -542,49 +631,11 @@ impl<T: Config<I>, I: Instance> Pallet<T, I> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Config<I>, I: Instance> frame_support::unsigned::ValidateUnsigned for Pallet<T, I> {
|
|
|
|
|
type Call = Call<T, I>;
|
|
|
|
|
|
|
|
|
|
fn validate_unsigned(_source: TransactionSource, 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, I>::new(),
|
|
|
|
|
&T::AuraConfiguration::get(),
|
|
|
|
|
&T::ValidatorsConfiguration::get(),
|
|
|
|
|
&pool_configuration(),
|
|
|
|
|
header,
|
|
|
|
|
&T::ChainTime::default(),
|
|
|
|
|
receipts.as_ref(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
match accept_result {
|
|
|
|
|
Ok((requires, provides)) => Ok(ValidTransaction {
|
|
|
|
|
priority: TransactionPriority::max_value(),
|
|
|
|
|
requires,
|
|
|
|
|
provides,
|
|
|
|
|
longevity: TransactionLongevity::max_value(),
|
|
|
|
|
propagate: true,
|
|
|
|
|
}),
|
|
|
|
|
// UnsignedTooFarInTheFuture is the special error code used to limit
|
|
|
|
|
// number of transactions in the pool - we do not want to ban transaction
|
|
|
|
|
// in this case (see verification.rs for details)
|
|
|
|
|
Err(error::Error::UnsignedTooFarInTheFuture) => {
|
|
|
|
|
UnknownTransaction::Custom(error::Error::UnsignedTooFarInTheFuture.code()).into()
|
|
|
|
|
}
|
|
|
|
|
Err(error) => InvalidTransaction::Custom(error.code()).into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => InvalidTransaction::Call.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Runtime bridge storage.
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct BridgeStorage<T, I = DefaultInstance>(sp_std::marker::PhantomData<(T, I)>);
|
|
|
|
|
pub struct BridgeStorage<T, I: 'static = ()>(sp_std::marker::PhantomData<(T, I)>);
|
|
|
|
|
|
|
|
|
|
impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
impl<T: Config<I>, I: 'static> BridgeStorage<T, I> {
|
|
|
|
|
/// Create new BridgeStorage.
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
BridgeStorage(sp_std::marker::PhantomData::<(T, I)>::default())
|
|
|
|
@@ -592,7 +643,7 @@ impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
|
|
|
|
|
/// Prune old blocks.
|
|
|
|
|
fn prune_blocks(&self, mut max_blocks_to_prune: u64, finalized_number: u64, prune_end: u64) {
|
|
|
|
|
let pruning_range = BlocksToPrune::<I>::get();
|
|
|
|
|
let pruning_range = BlocksToPrune::<T, I>::get();
|
|
|
|
|
let mut new_pruning_range = pruning_range.clone();
|
|
|
|
|
|
|
|
|
|
// update oldest block we want to keep
|
|
|
|
@@ -611,7 +662,7 @@ impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// read hashes of blocks with given number and try to prune these blocks
|
|
|
|
|
let blocks_at_number = HeadersByNumber::<I>::take(number);
|
|
|
|
|
let blocks_at_number = HeadersByNumber::<T, I>::take(number);
|
|
|
|
|
if let Some(mut blocks_at_number) = blocks_at_number {
|
|
|
|
|
self.prune_blocks_by_hashes(
|
|
|
|
|
&mut max_blocks_to_prune,
|
|
|
|
@@ -622,7 +673,7 @@ impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
|
|
|
|
|
// if we haven't pruned all blocks, remember unpruned
|
|
|
|
|
if !blocks_at_number.is_empty() {
|
|
|
|
|
HeadersByNumber::<I>::insert(number, blocks_at_number);
|
|
|
|
|
HeadersByNumber::<T, I>::insert(number, blocks_at_number);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -638,7 +689,7 @@ impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
|
|
|
|
|
// update pruning range in storage
|
|
|
|
|
if pruning_range != new_pruning_range {
|
|
|
|
|
BlocksToPrune::<I>::put(new_pruning_range);
|
|
|
|
|
BlocksToPrune::<T, I>::put(new_pruning_range);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -651,7 +702,7 @@ impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
blocks_at_number: &mut Vec<H256>,
|
|
|
|
|
) {
|
|
|
|
|
// ensure that unfinalized headers we want to prune do not have scheduled changes
|
|
|
|
|
if number > finalized_number && blocks_at_number.iter().any(ScheduledChanges::<I>::contains_key) {
|
|
|
|
|
if number > finalized_number && blocks_at_number.iter().any(ScheduledChanges::<T, I>::contains_key) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -665,10 +716,10 @@ impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
hash,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ScheduledChanges::<I>::remove(hash);
|
|
|
|
|
ScheduledChanges::<T, I>::remove(hash);
|
|
|
|
|
FinalityCache::<T, I>::remove(hash);
|
|
|
|
|
if let Some(header) = header {
|
|
|
|
|
ValidatorsSetsRc::<I>::mutate(header.next_validators_set_id, |rc| match *rc {
|
|
|
|
|
ValidatorsSetsRc::<T, I>::mutate(header.next_validators_set_id, |rc| match *rc {
|
|
|
|
|
Some(rc) if rc > 1 => Some(rc - 1),
|
|
|
|
|
_ => None,
|
|
|
|
|
});
|
|
|
|
@@ -683,15 +734,15 @@ impl<T: Config<I>, I: Instance> BridgeStorage<T, I> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
impl<T: Config<I>, I: 'static> Storage for BridgeStorage<T, I> {
|
|
|
|
|
type Submitter = T::AccountId;
|
|
|
|
|
|
|
|
|
|
fn best_block(&self) -> (HeaderId, U256) {
|
|
|
|
|
BestBlock::<I>::get()
|
|
|
|
|
BestBlock::<T, I>::get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn finalized_block(&self) -> HeaderId {
|
|
|
|
|
FinalizedBlock::<I>::get()
|
|
|
|
|
FinalizedBlock::<T, I>::get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn header(&self, hash: &H256) -> Option<(AuraHeader, Option<Self::Submitter>)> {
|
|
|
|
@@ -750,9 +801,9 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
parent_hash: &H256,
|
|
|
|
|
) -> Option<ImportContext<Self::Submitter>> {
|
|
|
|
|
Headers::<T, I>::get(parent_hash).map(|parent_header| {
|
|
|
|
|
let validators_set = ValidatorsSets::<I>::get(parent_header.next_validators_set_id)
|
|
|
|
|
let validators_set = ValidatorsSets::<T, I>::get(parent_header.next_validators_set_id)
|
|
|
|
|
.expect("validators set is only pruned when last ref is pruned; there is a ref; qed");
|
|
|
|
|
let parent_scheduled_change = ScheduledChanges::<I>::get(parent_hash);
|
|
|
|
|
let parent_scheduled_change = ScheduledChanges::<T, I>::get(parent_hash);
|
|
|
|
|
ImportContext {
|
|
|
|
|
submitter,
|
|
|
|
|
parent_hash: *parent_hash,
|
|
|
|
@@ -767,15 +818,15 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn scheduled_change(&self, hash: &H256) -> Option<AuraScheduledChange> {
|
|
|
|
|
ScheduledChanges::<I>::get(hash)
|
|
|
|
|
ScheduledChanges::<T, I>::get(hash)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn insert_header(&mut self, header: HeaderToImport<Self::Submitter>) {
|
|
|
|
|
if header.is_best {
|
|
|
|
|
BestBlock::<I>::put((header.id, header.total_difficulty));
|
|
|
|
|
BestBlock::<T, I>::put((header.id, header.total_difficulty));
|
|
|
|
|
}
|
|
|
|
|
if let Some(scheduled_change) = header.scheduled_change {
|
|
|
|
|
ScheduledChanges::<I>::insert(
|
|
|
|
|
ScheduledChanges::<T, I>::insert(
|
|
|
|
|
&header.id.hash,
|
|
|
|
|
AuraScheduledChange {
|
|
|
|
|
validators: scheduled_change,
|
|
|
|
@@ -785,12 +836,12 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
}
|
|
|
|
|
let next_validators_set_id = match header.enacted_change {
|
|
|
|
|
Some(enacted_change) => {
|
|
|
|
|
let next_validators_set_id = NextValidatorsSetId::<I>::mutate(|set_id| {
|
|
|
|
|
let next_validators_set_id = NextValidatorsSetId::<T, I>::mutate(|set_id| {
|
|
|
|
|
let next_set_id = *set_id;
|
|
|
|
|
*set_id += 1;
|
|
|
|
|
next_set_id
|
|
|
|
|
});
|
|
|
|
|
ValidatorsSets::<I>::insert(
|
|
|
|
|
ValidatorsSets::<T, I>::insert(
|
|
|
|
|
next_validators_set_id,
|
|
|
|
|
ValidatorsSet {
|
|
|
|
|
validators: enacted_change.validators,
|
|
|
|
@@ -798,11 +849,11 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
signal_block: enacted_change.signal_block,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
ValidatorsSetsRc::<I>::insert(next_validators_set_id, 1);
|
|
|
|
|
ValidatorsSetsRc::<T, I>::insert(next_validators_set_id, 1);
|
|
|
|
|
next_validators_set_id
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
ValidatorsSetsRc::<I>::mutate(header.context.validators_set_id, |rc| {
|
|
|
|
|
ValidatorsSetsRc::<T, I>::mutate(header.context.validators_set_id, |rc| {
|
|
|
|
|
*rc = Some(rc.map(|rc| rc + 1).unwrap_or(1));
|
|
|
|
|
*rc
|
|
|
|
|
});
|
|
|
|
@@ -826,7 +877,7 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let last_signal_block = header.context.last_signal_block();
|
|
|
|
|
HeadersByNumber::<I>::append(header.id.number, header.id.hash);
|
|
|
|
|
HeadersByNumber::<T, I>::append(header.id.number, header.id.hash);
|
|
|
|
|
Headers::<T, I>::insert(
|
|
|
|
|
&header.id.hash,
|
|
|
|
|
StoredHeader {
|
|
|
|
@@ -844,7 +895,7 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
let finalized_number = finalized
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|f| f.number)
|
|
|
|
|
.unwrap_or_else(|| FinalizedBlock::<I>::get().number);
|
|
|
|
|
.unwrap_or_else(|| FinalizedBlock::<T, I>::get().number);
|
|
|
|
|
if let Some(finalized) = finalized {
|
|
|
|
|
log::trace!(
|
|
|
|
|
target: "runtime",
|
|
|
|
@@ -853,7 +904,7 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
finalized.hash,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FinalizedBlock::<I>::put(finalized);
|
|
|
|
|
FinalizedBlock::<T, I>::put(finalized);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// and now prune headers if we need to
|
|
|
|
@@ -863,7 +914,7 @@ impl<T: Config<I>, I: Instance> Storage for BridgeStorage<T, I> {
|
|
|
|
|
|
|
|
|
|
/// Initialize storage.
|
|
|
|
|
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
|
|
|
|
|
pub(crate) fn initialize_storage<T: Config<I>, I: Instance>(
|
|
|
|
|
pub(crate) fn initialize_storage<T: Config<I>, I: 'static>(
|
|
|
|
|
initial_header: &AuraHeader,
|
|
|
|
|
initial_difficulty: U256,
|
|
|
|
|
initial_validators: &[Address],
|
|
|
|
@@ -880,13 +931,13 @@ pub(crate) fn initialize_storage<T: Config<I>, I: Instance>(
|
|
|
|
|
number: initial_header.number,
|
|
|
|
|
hash: initial_hash,
|
|
|
|
|
};
|
|
|
|
|
BestBlock::<I>::put((initial_id, initial_difficulty));
|
|
|
|
|
FinalizedBlock::<I>::put(initial_id);
|
|
|
|
|
BlocksToPrune::<I>::put(PruningRange {
|
|
|
|
|
BestBlock::<T, I>::put((initial_id, initial_difficulty));
|
|
|
|
|
FinalizedBlock::<T, I>::put(initial_id);
|
|
|
|
|
BlocksToPrune::<T, I>::put(PruningRange {
|
|
|
|
|
oldest_unpruned_block: initial_header.number,
|
|
|
|
|
oldest_block_to_keep: initial_header.number,
|
|
|
|
|
});
|
|
|
|
|
HeadersByNumber::<I>::insert(initial_header.number, vec![initial_hash]);
|
|
|
|
|
HeadersByNumber::<T, I>::insert(initial_header.number, vec![initial_hash]);
|
|
|
|
|
Headers::<T, I>::insert(
|
|
|
|
|
initial_hash,
|
|
|
|
|
StoredHeader {
|
|
|
|
@@ -897,8 +948,8 @@ pub(crate) fn initialize_storage<T: Config<I>, I: Instance>(
|
|
|
|
|
last_signal_block: None,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
NextValidatorsSetId::<I>::put(1);
|
|
|
|
|
ValidatorsSets::<I>::insert(
|
|
|
|
|
NextValidatorsSetId::<T, I>::put(1);
|
|
|
|
|
ValidatorsSets::<T, I>::insert(
|
|
|
|
|
0,
|
|
|
|
|
ValidatorsSet {
|
|
|
|
|
validators: initial_validators.to_vec(),
|
|
|
|
@@ -906,7 +957,7 @@ pub(crate) fn initialize_storage<T: Config<I>, I: Instance>(
|
|
|
|
|
enact_block: initial_id,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
ValidatorsSetsRc::<I>::insert(0, 1);
|
|
|
|
|
ValidatorsSetsRc::<T, I>::insert(0, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Verify that transaction is included into given finalized block.
|
|
|
|
@@ -1112,7 +1163,7 @@ pub(crate) mod tests {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if i == 7 && j == 1 {
|
|
|
|
|
ScheduledChanges::<DefaultInstance>::insert(
|
|
|
|
|
ScheduledChanges::<TestRuntime, ()>::insert(
|
|
|
|
|
hash,
|
|
|
|
|
AuraScheduledChange {
|
|
|
|
|
validators: validators_addresses(5),
|
|
|
|
@@ -1121,7 +1172,7 @@ pub(crate) mod tests {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
HeadersByNumber::<DefaultInstance>::insert(i, headers_by_number);
|
|
|
|
|
HeadersByNumber::<TestRuntime, ()>::insert(i, headers_by_number);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f(BridgeStorage::new())
|
|
|
|
@@ -1131,16 +1182,16 @@ pub(crate) mod tests {
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocks_are_not_pruned_if_range_is_empty() {
|
|
|
|
|
with_headers_to_prune(|storage| {
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::put(PruningRange {
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::put(PruningRange {
|
|
|
|
|
oldest_unpruned_block: 5,
|
|
|
|
|
oldest_block_to_keep: 5,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// try to prune blocks [5; 10)
|
|
|
|
|
storage.prune_blocks(0xFFFF, 10, 5);
|
|
|
|
|
assert_eq!(HeadersByNumber::<DefaultInstance>::get(&5).unwrap().len(), 5);
|
|
|
|
|
assert_eq!(HeadersByNumber::<TestRuntime, ()>::get(&5).unwrap().len(), 5);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::get(),
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::get(),
|
|
|
|
|
PruningRange {
|
|
|
|
|
oldest_unpruned_block: 5,
|
|
|
|
|
oldest_block_to_keep: 5,
|
|
|
|
@@ -1152,7 +1203,7 @@ pub(crate) mod tests {
|
|
|
|
|
#[test]
|
|
|
|
|
fn blocks_to_prune_never_shrinks_from_the_end() {
|
|
|
|
|
with_headers_to_prune(|storage| {
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::put(PruningRange {
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::put(PruningRange {
|
|
|
|
|
oldest_unpruned_block: 0,
|
|
|
|
|
oldest_block_to_keep: 5,
|
|
|
|
|
});
|
|
|
|
@@ -1160,7 +1211,7 @@ pub(crate) mod tests {
|
|
|
|
|
// try to prune blocks [5; 10)
|
|
|
|
|
storage.prune_blocks(0xFFFF, 10, 3);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::get(),
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::get(),
|
|
|
|
|
PruningRange {
|
|
|
|
|
oldest_unpruned_block: 5,
|
|
|
|
|
oldest_block_to_keep: 5,
|
|
|
|
@@ -1174,12 +1225,12 @@ pub(crate) mod tests {
|
|
|
|
|
with_headers_to_prune(|storage| {
|
|
|
|
|
// try to prune blocks [0; 10)
|
|
|
|
|
storage.prune_blocks(0, 10, 10);
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&0).is_some());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&1).is_some());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&2).is_some());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&3).is_some());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&0).is_some());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&1).is_some());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&2).is_some());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&3).is_some());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::get(),
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::get(),
|
|
|
|
|
PruningRange {
|
|
|
|
|
oldest_unpruned_block: 0,
|
|
|
|
|
oldest_block_to_keep: 10,
|
|
|
|
@@ -1194,13 +1245,13 @@ pub(crate) mod tests {
|
|
|
|
|
// try to prune blocks [0; 10)
|
|
|
|
|
storage.prune_blocks(7, 10, 10);
|
|
|
|
|
// 1 headers with number = 0 is pruned (1 total)
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&0).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&0).is_none());
|
|
|
|
|
// 5 headers with number = 1 are pruned (6 total)
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&1).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&1).is_none());
|
|
|
|
|
// 1 header with number = 2 are pruned (7 total)
|
|
|
|
|
assert_eq!(HeadersByNumber::<DefaultInstance>::get(&2).unwrap().len(), 4);
|
|
|
|
|
assert_eq!(HeadersByNumber::<TestRuntime, ()>::get(&2).unwrap().len(), 4);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::get(),
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::get(),
|
|
|
|
|
PruningRange {
|
|
|
|
|
oldest_unpruned_block: 2,
|
|
|
|
|
oldest_block_to_keep: 10,
|
|
|
|
@@ -1210,13 +1261,13 @@ pub(crate) mod tests {
|
|
|
|
|
// try to prune blocks [2; 10)
|
|
|
|
|
storage.prune_blocks(11, 10, 10);
|
|
|
|
|
// 4 headers with number = 2 are pruned (4 total)
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&2).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&2).is_none());
|
|
|
|
|
// 5 headers with number = 3 are pruned (9 total)
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&3).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&3).is_none());
|
|
|
|
|
// 2 headers with number = 4 are pruned (11 total)
|
|
|
|
|
assert_eq!(HeadersByNumber::<DefaultInstance>::get(&4).unwrap().len(), 3);
|
|
|
|
|
assert_eq!(HeadersByNumber::<TestRuntime, ()>::get(&4).unwrap().len(), 3);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::get(),
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::get(),
|
|
|
|
|
PruningRange {
|
|
|
|
|
oldest_unpruned_block: 4,
|
|
|
|
|
oldest_block_to_keep: 10,
|
|
|
|
@@ -1233,16 +1284,16 @@ pub(crate) mod tests {
|
|
|
|
|
// and one of blocks#7 has scheduled change
|
|
|
|
|
// => we won't prune any block#7 at all
|
|
|
|
|
storage.prune_blocks(0xFFFF, 5, 10);
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&0).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&1).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&2).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&3).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&4).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&5).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<DefaultInstance>::get(&6).is_none());
|
|
|
|
|
assert_eq!(HeadersByNumber::<DefaultInstance>::get(&7).unwrap().len(), 5);
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&0).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&1).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&2).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&3).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&4).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&5).is_none());
|
|
|
|
|
assert!(HeadersByNumber::<TestRuntime, ()>::get(&6).is_none());
|
|
|
|
|
assert_eq!(HeadersByNumber::<TestRuntime, ()>::get(&7).unwrap().len(), 5);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::get(),
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::get(),
|
|
|
|
|
PruningRange {
|
|
|
|
|
oldest_unpruned_block: 7,
|
|
|
|
|
oldest_block_to_keep: 10,
|
|
|
|
@@ -1272,7 +1323,7 @@ pub(crate) mod tests {
|
|
|
|
|
assert!(FinalityCache::<TestRuntime>::get(&header_with_entry_hash).is_some());
|
|
|
|
|
|
|
|
|
|
// when we later prune this header, cache entry is removed
|
|
|
|
|
BlocksToPrune::<DefaultInstance>::put(PruningRange {
|
|
|
|
|
BlocksToPrune::<TestRuntime, ()>::put(PruningRange {
|
|
|
|
|
oldest_unpruned_block: interval - 1,
|
|
|
|
|
oldest_block_to_keep: interval - 1,
|
|
|
|
|
});
|
|
|
|
@@ -1359,7 +1410,7 @@ pub(crate) mod tests {
|
|
|
|
|
insert_header(&mut storage, header1s);
|
|
|
|
|
|
|
|
|
|
// header1 is finalized
|
|
|
|
|
FinalizedBlock::<DefaultInstance>::put(header1_id);
|
|
|
|
|
FinalizedBlock::<TestRuntime, ()>::put(header1_id);
|
|
|
|
|
|
|
|
|
|
// trying to get finality votes when importing header2 -> header1 succeeds
|
|
|
|
|
assert!(
|
|
|
|
|