mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 22:11:06 +00:00
Allow Substrate Pallet to be Initialized via Dipatchable (#481)
* Add dispatchable for intializing pallet * Add Polkadot JS types for Substrate bridge pallet * Ensure Root is the only one that can initialize the pallet * Add some tests * Pack initialization data into struct * Only allow pallet to be initialized once * Use new initialization config in nodes * Rename ScheduledChange in Ethereum pallet We're renaming it to prevent clashes with Substrate bridge pallet type of the same name. This is relevant when importing types to Polkadot JS Apps. * Move all Polkadot JS types into one file * Appease Clippy
This commit is contained in:
committed by
Bastian Köcher
parent
d4fc7bebdc
commit
cac8319480
@@ -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<T> => Option<ScheduledChange<BridgedBlockNumber<T>>>;
|
||||
/// 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<BridgedHeader<T>>;
|
||||
config(initial_authority_list): sp_finality_grandpa::AuthorityList;
|
||||
config(initial_set_id): sp_finality_grandpa::SetId;
|
||||
config(first_scheduled_change): Option<ScheduledChange<BridgedBlockNumber<T>>>;
|
||||
config(init_data): Option<InitializationData<BridgedHeader<T>>>;
|
||||
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();
|
||||
|
||||
<BestHeight<T>>::put(initial_header.number());
|
||||
<BestHeaders<T>>::put(vec![initial_hash]);
|
||||
<BestFinalized<T>>::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);
|
||||
<NextScheduledChange<T>>::insert(initial_hash, change);
|
||||
};
|
||||
|
||||
<ImportedHeaders<T>>::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::<T>(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<BridgedHeader<T>>,
|
||||
) {
|
||||
let _ = ensure_root(origin)?;
|
||||
let init_allowed = !IsInitialized::get();
|
||||
ensure!(init_allowed, <Error<T>>::AlreadyInitialized);
|
||||
initialize_bridge::<T>(init_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +288,49 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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<T: Trait>(init_params: InitializationData<BridgedHeader<T>>) {
|
||||
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);
|
||||
<NextScheduledChange<T>>::insert(initial_hash, change);
|
||||
};
|
||||
|
||||
<BestHeight<T>>::put(header.number());
|
||||
<BestHeaders<T>>::put(vec![initial_hash]);
|
||||
<BestFinalized<T>>::put(initial_hash);
|
||||
|
||||
let authority_set = AuthoritySet::new(authority_list, set_id);
|
||||
CurrentAuthoritySet::put(authority_set);
|
||||
|
||||
<ImportedHeaders<T>>::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<T: Trait> BridgeStorage for PalletStorage<T> {
|
||||
#[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::<TestRuntime>::initialize(Origin::signed(1), init_data.clone()),
|
||||
DispatchError::BadOrigin,
|
||||
);
|
||||
|
||||
assert_ok!(Module::<TestRuntime>::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::<TestRuntime>::initialize(Origin::root(), init_data.clone()));
|
||||
assert_noop!(
|
||||
Module::<TestRuntime>::initialize(Origin::root(), init_data,),
|
||||
<Error<TestRuntime>>::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::<TestRuntime>::initialize(Origin::root(), init_data.clone()));
|
||||
|
||||
let storage = PalletStorage::<TestRuntime>::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() {
|
||||
|
||||
@@ -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<H: HeaderT> {
|
||||
/// 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<ScheduledChange<H::Number>>,
|
||||
}
|
||||
|
||||
/// A Grandpa Authority List and ID.
|
||||
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
|
||||
Reference in New Issue
Block a user