diff --git a/bridges/bin/millau/node/src/chain_spec.rs b/bridges/bin/millau/node/src/chain_spec.rs index a3e95398fda9..9d29d8ca7b46 100644 --- a/bridges/bin/millau/node/src/chain_spec.rs +++ b/bridges/bin/millau/node/src/chain_spec.rs @@ -155,7 +155,10 @@ fn testnet_genesis( pallet_grandpa: Some(GrandpaConfig { authorities: Vec::new(), }), - pallet_substrate_bridge: load_rialto_bridge_config(), + pallet_substrate_bridge: Some(BridgeRialtoConfig { + // We'll initialize the pallet with a dispatchable instead. + init_data: None, + }), pallet_sudo: Some(SudoConfig { key: root_key }), pallet_session: Some(SessionConfig { keys: initial_authorities @@ -165,12 +168,3 @@ fn testnet_genesis( }), } } - -fn load_rialto_bridge_config() -> Option { - Some(BridgeRialtoConfig { - initial_header: Some(millau_runtime::rialto::initial_header()), - initial_authority_list: millau_runtime::rialto::initial_authority_set().authorities, - initial_set_id: millau_runtime::rialto::initial_authority_set().set_id, - first_scheduled_change: None, - }) -} diff --git a/bridges/bin/rialto/node/src/chain_spec.rs b/bridges/bin/rialto/node/src/chain_spec.rs index 4b66d656f839..b55a75854433 100644 --- a/bridges/bin/rialto/node/src/chain_spec.rs +++ b/bridges/bin/rialto/node/src/chain_spec.rs @@ -186,9 +186,6 @@ fn load_kovan_bridge_config() -> Option { fn load_millau_bridge_config() -> Option { Some(BridgeMillauConfig { - initial_header: Some(rialto_runtime::millau::initial_header()), - initial_authority_list: rialto_runtime::millau::initial_authority_set().authorities, - initial_set_id: rialto_runtime::millau::initial_authority_set().set_id, - first_scheduled_change: None, + init_data: Some(rialto_runtime::millau::init_data()), }) } diff --git a/bridges/bin/rialto/runtime/src/millau.rs b/bridges/bin/rialto/runtime/src/millau.rs index 1ca819331906..8da062ec59b2 100644 --- a/bridges/bin/rialto/runtime/src/millau.rs +++ b/bridges/bin/rialto/runtime/src/millau.rs @@ -18,11 +18,23 @@ use bp_rialto::Header; use hex_literal::hex; -use pallet_substrate_bridge::AuthoritySet; +use pallet_substrate_bridge::{AuthoritySet, InitializationData}; use sp_core::crypto::Public; use sp_finality_grandpa::AuthorityId; use sp_std::vec; +/// Information about where the bridge palelt should start syncing from. This includes things like +/// the initial header and the initial authorities of the briged chain. +pub fn init_data() -> InitializationData
{ + let authority_set = initial_authority_set(); + InitializationData { + header: initial_header(), + authority_list: authority_set.authorities, + set_id: authority_set.set_id, + scheduled_change: None, + } +} + /// The first header known to the pallet. /// /// Note that this does not need to be the genesis header of the Millau diff --git a/bridges/modules/ethereum/src/lib.rs b/bridges/modules/ethereum/src/lib.rs index 2328864957ad..ace34f38edb8 100644 --- a/bridges/modules/ethereum/src/lib.rs +++ b/bridges/modules/ethereum/src/lib.rs @@ -120,7 +120,7 @@ pub struct ValidatorsSet { /// Validators set change as it is stored in the runtime storage. #[derive(Encode, Decode, PartialEq, RuntimeDebug)] #[cfg_attr(test, derive(Clone))] -pub struct ScheduledChange { +pub struct AuraScheduledChange { /// Validators of this set. pub validators: Vec
, /// Hash of the block which has emitted previous validators change signal. @@ -187,7 +187,7 @@ pub struct ImportContext { parent_hash: H256, parent_header: AuraHeader, parent_total_difficulty: U256, - parent_scheduled_change: Option, + parent_scheduled_change: Option, validators_set_id: u64, validators_set: ValidatorsSet, last_signal_block: Option, @@ -210,7 +210,7 @@ impl ImportContext { } /// Returns the validator set change if the parent header has signaled a change. - pub fn parent_scheduled_change(&self) -> Option<&ScheduledChange> { + pub fn parent_scheduled_change(&self) -> Option<&AuraScheduledChange> { self.parent_scheduled_change.as_ref() } @@ -293,7 +293,7 @@ pub trait Storage { ) -> Option>; /// Get new validators that are scheduled by given header and hash of the previous /// block that has scheduled change. - fn scheduled_change(&self, hash: &H256) -> Option; + fn scheduled_change(&self, hash: &H256) -> Option; /// Insert imported header. fn insert_header(&mut self, header: HeaderToImport); /// Finalize given block and schedules pruning of all headers @@ -479,7 +479,7 @@ decl_storage! { /// When it reaches zero, we are free to prune validator set as well. ValidatorsSetsRc: map hasher(twox_64_concat) u64 => Option; /// Map of validators set changes scheduled by given header. - ScheduledChanges: map hasher(identity) H256 => Option; + ScheduledChanges: map hasher(identity) H256 => Option; } add_extra_genesis { config(initial_header): AuraHeader; @@ -766,7 +766,7 @@ impl, I: Instance> Storage for BridgeStorage { }) } - fn scheduled_change(&self, hash: &H256) -> Option { + fn scheduled_change(&self, hash: &H256) -> Option { ScheduledChanges::::get(hash) } @@ -777,7 +777,7 @@ impl, I: Instance> Storage for BridgeStorage { if let Some(scheduled_change) = header.scheduled_change { ScheduledChanges::::insert( &header.id.hash, - ScheduledChange { + AuraScheduledChange { validators: scheduled_change, prev_signal_block: header.context.last_signal_block, }, @@ -1119,7 +1119,7 @@ pub(crate) mod tests { if i == 7 && j == 1 { ScheduledChanges::::insert( hash, - ScheduledChange { + AuraScheduledChange { validators: validators_addresses(5), prev_signal_block: None, }, diff --git a/bridges/modules/ethereum/src/validators.rs b/bridges/modules/ethereum/src/validators.rs index ede479fd3788..dc09b48d5506 100644 --- a/bridges/modules/ethereum/src/validators.rs +++ b/bridges/modules/ethereum/src/validators.rs @@ -277,7 +277,7 @@ pub(crate) mod tests { use super::*; use crate::mock::{run_test, validators_addresses, validators_change_receipt, TestRuntime}; use crate::DefaultInstance; - use crate::{BridgeStorage, Headers, ScheduledChange, ScheduledChanges, StoredHeader}; + use crate::{AuraScheduledChange, BridgeStorage, Headers, ScheduledChanges, StoredHeader}; use bp_eth_poa::compute_merkle_root; use frame_support::StorageMap; @@ -428,7 +428,7 @@ pub(crate) mod tests { next_validators_set_id: 0, last_signal_block: scheduled_at, }; - let scheduled_change = ScheduledChange { + let scheduled_change = AuraScheduledChange { validators: validators_addresses(1), prev_signal_block: None, }; diff --git a/bridges/modules/ethereum/src/verification.rs b/bridges/modules/ethereum/src/verification.rs index 90d3f2a4748b..b762a280f584 100644 --- a/bridges/modules/ethereum/src/verification.rs +++ b/bridges/modules/ethereum/src/verification.rs @@ -16,7 +16,7 @@ use crate::error::Error; use crate::validators::{Validators, ValidatorsConfiguration}; -use crate::{AuraConfiguration, ChainTime, ImportContext, PoolConfiguration, ScheduledChange, Storage}; +use crate::{AuraConfiguration, AuraScheduledChange, ChainTime, ImportContext, PoolConfiguration, Storage}; use bp_eth_poa::{ public_to_address, step_validator, Address, AuraHeader, HeaderId, Receipt, SealedEmptyStep, H256, H520, U128, U256, }; @@ -341,7 +341,7 @@ fn find_next_validators_signal(storage: &S, context: &ImportContext< // 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(); - let mut next_scheduled_set: Option = None; + let mut next_scheduled_set: Option = None; loop { // if we have reached block that signals finalized change, then @@ -456,7 +456,7 @@ mod tests { }); ScheduledChanges::::insert( header.header.parent_hash, - ScheduledChange { + AuraScheduledChange { validators: signalled_set, prev_signal_block: None, }, diff --git a/bridges/modules/substrate/src/lib.rs b/bridges/modules/substrate/src/lib.rs index 0bd3423e9218..cf7d4fbc6ffe 100644 --- a/bridges/modules/substrate/src/lib.rs +++ b/bridges/modules/substrate/src/lib.rs @@ -33,15 +33,15 @@ use crate::storage::ImportedHeader; use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf}; -use frame_support::{decl_error, decl_module, decl_storage, dispatch::DispatchResult}; -use frame_system::ensure_signed; +use frame_support::{decl_error, decl_module, decl_storage, dispatch::DispatchResult, ensure}; +use frame_system::{ensure_root, ensure_signed}; use sp_runtime::traits::Header as HeaderT; use sp_runtime::RuntimeDebug; use sp_std::{marker::PhantomData, prelude::*}; use sp_trie::StorageProof; // Re-export since the node uses these when configuring genesis -pub use storage::{AuthoritySet, ScheduledChange}; +pub use storage::{AuthoritySet, InitializationData, ScheduledChange}; pub use justification::decode_justification_target; pub use storage_proof::StorageProofChecker; @@ -105,53 +105,18 @@ decl_storage! { // Grandpa doesn't require there to always be a pending change. In fact, most of the time // there will be no pending change available. NextScheduledChange: map hasher(identity) BridgedBlockHash => Option>>; + /// Whether or not the bridge has been initialized. + /// + /// This is important to know to ensure that we don't try and initialize the bridge twice + /// and create an inconsistent genesis state. + IsInitialized: bool; } add_extra_genesis { - config(initial_header): Option>; - config(initial_authority_list): sp_finality_grandpa::AuthorityList; - config(initial_set_id): sp_finality_grandpa::SetId; - config(first_scheduled_change): Option>>; + config(init_data): Option>>; build(|config| { - assert!( - !config.initial_authority_list.is_empty(), - "An initial authority list is needed." - ); - - let initial_header = config - .initial_header - .clone() - .expect("An initial header is needed"); - let initial_hash = initial_header.hash(); - - >::put(initial_header.number()); - >::put(vec![initial_hash]); - >::put(initial_hash); - - let authority_set = - AuthoritySet::new(config.initial_authority_list.clone(), config.initial_set_id); - CurrentAuthoritySet::put(authority_set); - - let mut signal_hash = None; - if let Some(ref change) = config.first_scheduled_change { - assert!( - change.height > *initial_header.number(), - "Changes must be scheduled past initial header." - ); - - signal_hash = Some(initial_hash); - >::insert(initial_hash, change); - }; - - >::insert( - initial_hash, - ImportedHeader { - header: initial_header, - requires_justification: false, - is_finalized: true, - signal_hash, - }, - ); - + if let Some(init_data) = config.init_data.clone() { + initialize_bridge::(init_data); + } }) } } @@ -164,6 +129,8 @@ decl_error! { UnfinalizedHeader, /// The header is unknown. UnknownHeader, + /// The pallet has already been initialized. + AlreadyInitialized, /// The storage proof doesn't contains storage root. So it is invalid for given header. StorageRootMismatch, /// Error when trying to fetch storage value from the proof. @@ -225,6 +192,27 @@ decl_module! { Ok(()) } + + /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. + /// + /// The initial configuration provided does not need to be the genesis header of the bridged + /// chain, it can be any arbirary header. You can also provide the next scheduled set change + /// if it is already know. + /// + /// This function is only allowed to be called from a trusted origin and writes to storage + /// with practically no checks in terms of the validity of the data. It is important that + /// you ensure that valid data is being passed in. + //TODO: Update weights [#78] + #[weight = 0] + pub fn initialize( + origin, + init_data: InitializationData>, + ) { + let _ = ensure_root(origin)?; + let init_allowed = !IsInitialized::get(); + ensure!(init_allowed, >::AlreadyInitialized); + initialize_bridge::(init_data); + } } } @@ -300,6 +288,49 @@ impl Module { } } +// Since this writes to storage with no real checks this should only be used in functions that were +// called by a trusted origin. +fn initialize_bridge(init_params: InitializationData>) { + let InitializationData { + header, + authority_list, + set_id, + scheduled_change, + } = init_params; + + let initial_hash = header.hash(); + + let mut signal_hash = None; + if let Some(ref change) = scheduled_change { + assert!( + change.height > *header.number(), + "Changes must be scheduled past initial header." + ); + + signal_hash = Some(initial_hash); + >::insert(initial_hash, change); + }; + + >::put(header.number()); + >::put(vec![initial_hash]); + >::put(initial_hash); + + let authority_set = AuthoritySet::new(authority_list, set_id); + CurrentAuthoritySet::put(authority_set); + + >::insert( + initial_hash, + ImportedHeader { + header, + requires_justification: false, + is_finalized: true, + signal_hash, + }, + ); + + IsInitialized::put(true); +} + /// Expected interface for interacting with bridge pallet storage. // TODO: This should be split into its own less-Substrate-dependent crate pub trait BridgeStorage { @@ -468,8 +499,75 @@ impl BridgeStorage for PalletStorage { #[cfg(test)] mod tests { use super::*; - use crate::mock::{helpers::unfinalized_header, run_test, TestRuntime}; + use crate::mock::helpers::{authority_list, test_header, unfinalized_header}; + use crate::mock::{run_test, Origin, TestRuntime}; use frame_support::{assert_noop, assert_ok}; + use sp_runtime::DispatchError; + + #[test] + fn only_root_origin_can_initialize_pallet() { + run_test(|| { + let init_data = InitializationData { + header: test_header(1), + authority_list: authority_list(), + set_id: 1, + scheduled_change: None, + }; + + assert_noop!( + Module::::initialize(Origin::signed(1), init_data.clone()), + DispatchError::BadOrigin, + ); + + assert_ok!(Module::::initialize(Origin::root(), init_data)); + }) + } + + #[test] + fn can_only_initialize_pallet_once() { + run_test(|| { + let init_data = InitializationData { + header: test_header(1), + authority_list: authority_list(), + set_id: 1, + scheduled_change: None, + }; + + assert_ok!(Module::::initialize(Origin::root(), init_data.clone())); + assert_noop!( + Module::::initialize(Origin::root(), init_data,), + >::AlreadyInitialized, + ); + }) + } + + #[test] + fn storage_entries_are_correctly_initialized() { + run_test(|| { + let init_data = InitializationData { + header: test_header(1), + authority_list: authority_list(), + set_id: 1, + scheduled_change: None, + }; + + assert_ok!(Module::::initialize(Origin::root(), init_data.clone())); + + let storage = PalletStorage::::new(); + + assert!(IsInitialized::get()); + assert!(storage.header_exists(init_data.header.hash())); + assert_eq!( + storage.best_headers()[0], + crate::HeaderId { + number: *init_data.header.number(), + hash: init_data.header.hash() + } + ); + assert_eq!(storage.best_finalized_header().hash(), init_data.header.hash()); + assert_eq!(storage.current_authority_set().authorities, init_data.authority_list); + }) + } #[test] fn parse_finalized_storage_proof_rejects_proof_on_unknown_header() { diff --git a/bridges/modules/substrate/src/storage.rs b/bridges/modules/substrate/src/storage.rs index fb98228c6a6a..24c42482955c 100644 --- a/bridges/modules/substrate/src/storage.rs +++ b/bridges/modules/substrate/src/storage.rs @@ -24,6 +24,22 @@ use sp_finality_grandpa::{AuthorityList, SetId}; use sp_runtime::traits::Header as HeaderT; use sp_runtime::RuntimeDebug; +/// Data required for initializing the bridge pallet. +/// +/// The bridge needs to know where to start its sync from, and this provides that initial context. +#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct InitializationData { + /// The header from which we should start syncing. + pub header: H, + /// The initial authorities of the pallet. + pub authority_list: AuthorityList, + /// The ID of the initial authority set. + pub set_id: SetId, + /// The first scheduled authority set change of the pallet. + pub scheduled_change: Option>, +} + /// A Grandpa Authority List and ID. #[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]