mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
srml: grandpa: Pause/resume current authority set (#3068)
* grandpa: add pause/resume signals to runtime module * grandpa: add tests for srml pause/resume transitions * node: bump spec_version * Apply suggestions from code review * Update core/finality-grandpa/primitives/src/lib.rs * Update core/finality-grandpa/primitives/src/lib.rs
This commit is contained in:
@@ -93,6 +93,14 @@ pub enum ConsensusLog<N: Codec> {
|
||||
/// Note that the authority with given index is disabled until the next change.
|
||||
#[codec(index = "3")]
|
||||
OnDisabled(AuthorityIndex),
|
||||
/// A signal to pause the current authority set after the given delay.
|
||||
/// After finalizing the block at _delay_ the authorities should stop voting.
|
||||
#[codec(index = "4")]
|
||||
Pause(N),
|
||||
/// A signal to resume the current authority set after the given delay.
|
||||
/// After authoring the block at _delay_ the authorities should resume voting.
|
||||
#[codec(index = "5")]
|
||||
Resume(N),
|
||||
}
|
||||
|
||||
impl<N: Codec> ConsensusLog<N> {
|
||||
@@ -100,7 +108,7 @@ impl<N: Codec> ConsensusLog<N> {
|
||||
pub fn try_into_change(self) -> Option<ScheduledChange<N>> {
|
||||
match self {
|
||||
ConsensusLog::ScheduledChange(change) => Some(change),
|
||||
ConsensusLog::ForcedChange(_, _) | ConsensusLog::OnDisabled(_) => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +116,23 @@ impl<N: Codec> ConsensusLog<N> {
|
||||
pub fn try_into_forced_change(self) -> Option<(N, ScheduledChange<N>)> {
|
||||
match self {
|
||||
ConsensusLog::ForcedChange(median, change) => Some((median, change)),
|
||||
ConsensusLog::ScheduledChange(_) | ConsensusLog::OnDisabled(_) => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to cast the log entry as a contained pause signal.
|
||||
pub fn try_into_pause(self) -> Option<N> {
|
||||
match self {
|
||||
ConsensusLog::Pause(delay) => Some(delay),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to cast the log entry as a contained resume signal.
|
||||
pub fn try_into_resume(self) -> Option<N> {
|
||||
match self {
|
||||
ConsensusLog::Resume(delay) => Some(delay),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
// and set impl_version to equal spec_version. If only runtime
|
||||
// implementation changes and behavior does not, then leave spec_version as
|
||||
// is and increment impl_version.
|
||||
spec_version: 113,
|
||||
spec_version: 114,
|
||||
impl_version: 114,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
@@ -91,10 +91,42 @@ impl<N: Decode> Decode for StoredPendingChange<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Current state of the GRANDPA authority set. State transitions must happen in
|
||||
/// the same order of states defined below, e.g. `Paused` implies a prior
|
||||
/// `PendingPause`.
|
||||
#[derive(Decode, Encode)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub enum StoredState<N> {
|
||||
/// The current authority set is live, and GRANDPA is enabled.
|
||||
Live,
|
||||
/// There is a pending pause event which will be enacted at the given block
|
||||
/// height.
|
||||
PendingPause {
|
||||
/// Block at which the intention to pause was scheduled.
|
||||
scheduled_at: N,
|
||||
/// Number of blocks after which the change will be enacted.
|
||||
delay: N
|
||||
},
|
||||
/// The current GRANDPA authority set is paused.
|
||||
Paused,
|
||||
/// There is a pending resume event which will be enacted at the given block
|
||||
/// height.
|
||||
PendingResume {
|
||||
/// Block at which the intention to resume was scheduled.
|
||||
scheduled_at: N,
|
||||
/// Number of blocks after which the change will be enacted.
|
||||
delay: N,
|
||||
},
|
||||
}
|
||||
|
||||
decl_event!(
|
||||
pub enum Event {
|
||||
/// New authority set has been applied.
|
||||
NewAuthorities(Vec<(AuthorityId, u64)>),
|
||||
/// Current authority set has been paused.
|
||||
Paused,
|
||||
/// Current authority set has been resumed.
|
||||
Resumed,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -103,6 +135,9 @@ decl_storage! {
|
||||
/// The current authority set.
|
||||
Authorities get(authorities) config(): Vec<(AuthorityId, AuthorityWeight)>;
|
||||
|
||||
/// State of the current authority set.
|
||||
State get(state): StoredState<T::BlockNumber> = StoredState::Live;
|
||||
|
||||
/// Pending change: (signaled at, scheduled change).
|
||||
PendingChange: Option<StoredPendingChange<T::BlockNumber>>;
|
||||
|
||||
@@ -125,12 +160,14 @@ decl_module! {
|
||||
}
|
||||
|
||||
fn on_finalize(block_number: T::BlockNumber) {
|
||||
// check for scheduled pending authority set changes
|
||||
if let Some(pending_change) = <PendingChange<T>>::get() {
|
||||
// emit signal if we're at the block that scheduled the change
|
||||
if block_number == pending_change.scheduled_at {
|
||||
if let Some(median) = pending_change.forced {
|
||||
Self::deposit_log(ConsensusLog::ForcedChange(
|
||||
median,
|
||||
ScheduledChange{
|
||||
ScheduledChange {
|
||||
delay: pending_change.delay,
|
||||
next_authorities: pending_change.next_authorities.clone(),
|
||||
}
|
||||
@@ -145,6 +182,7 @@ decl_module! {
|
||||
}
|
||||
}
|
||||
|
||||
// enact the change if we've reached the enacting block
|
||||
if block_number == pending_change.scheduled_at + pending_change.delay {
|
||||
Authorities::put(&pending_change.next_authorities);
|
||||
Self::deposit_event(
|
||||
@@ -153,6 +191,35 @@ decl_module! {
|
||||
<PendingChange<T>>::kill();
|
||||
}
|
||||
}
|
||||
|
||||
// check for scheduled pending state changes
|
||||
match <State<T>>::get() {
|
||||
StoredState::PendingPause { scheduled_at, delay } => {
|
||||
// signal change to pause
|
||||
if block_number == scheduled_at {
|
||||
Self::deposit_log(ConsensusLog::Pause(delay));
|
||||
}
|
||||
|
||||
// enact change to paused state
|
||||
if block_number == scheduled_at + delay {
|
||||
<State<T>>::put(StoredState::Paused);
|
||||
Self::deposit_event(Event::Paused);
|
||||
}
|
||||
},
|
||||
StoredState::PendingResume { scheduled_at, delay } => {
|
||||
// signal change to resume
|
||||
if block_number == scheduled_at {
|
||||
Self::deposit_log(ConsensusLog::Resume(delay));
|
||||
}
|
||||
|
||||
// enact change to live state
|
||||
if block_number == scheduled_at + delay {
|
||||
<State<T>>::put(StoredState::Live);
|
||||
Self::deposit_event(Event::Resumed);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,6 +230,36 @@ impl<T: Trait> Module<T> {
|
||||
Authorities::get()
|
||||
}
|
||||
|
||||
pub fn schedule_pause(in_blocks: T::BlockNumber) -> Result {
|
||||
if let StoredState::Live = <State<T>>::get() {
|
||||
let scheduled_at = system::ChainContext::<T>::default().current_height();
|
||||
<State<T>>::put(StoredState::PendingPause {
|
||||
delay: in_blocks,
|
||||
scheduled_at,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Attempt to signal GRANDPA pause when the authority set isn't live \
|
||||
(either paused or already pending pause).")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule_resume(in_blocks: T::BlockNumber) -> Result {
|
||||
if let StoredState::Paused = <State<T>>::get() {
|
||||
let scheduled_at = system::ChainContext::<T>::default().current_height();
|
||||
<State<T>>::put(StoredState::PendingResume {
|
||||
delay: in_blocks,
|
||||
scheduled_at,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Attempt to signal GRANDPA resume when the authority set isn't paused \
|
||||
(either live or already pending resume).")
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule a change in the authorities.
|
||||
///
|
||||
/// The change will be applied at the end of execution of the block
|
||||
@@ -232,6 +329,18 @@ impl<T: Trait> Module<T> {
|
||||
{
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_forced_change())
|
||||
}
|
||||
|
||||
pub fn pending_pause(digest: &DigestOf<T>)
|
||||
-> Option<T::BlockNumber>
|
||||
{
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_pause())
|
||||
}
|
||||
|
||||
pub fn pending_resume(digest: &DigestOf<T>)
|
||||
-> Option<T::BlockNumber>
|
||||
{
|
||||
Self::grandpa_log(digest).and_then(|signal| signal.try_into_resume())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
@@ -254,6 +363,7 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_disabled(i: usize) {
|
||||
Self::deposit_log(ConsensusLog::OnDisabled(i as u64))
|
||||
}
|
||||
|
||||
@@ -202,3 +202,83 @@ fn dispatch_forced_change() {
|
||||
let _ = header;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_pause_only_when_live() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
// we schedule a pause at block 1 with delay of 1
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_pause(1).unwrap();
|
||||
|
||||
// we've switched to the pending pause state
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::PendingPause {
|
||||
scheduled_at: 1u64,
|
||||
delay: 1,
|
||||
},
|
||||
);
|
||||
|
||||
Grandpa::on_finalize(1);
|
||||
let _ = System::finalize();
|
||||
|
||||
System::initialize(&2, &Default::default(), &Default::default(), &Default::default());
|
||||
|
||||
// signaling a pause now should fail
|
||||
assert!(Grandpa::schedule_pause(1).is_err());
|
||||
|
||||
Grandpa::on_finalize(2);
|
||||
let _ = System::finalize();
|
||||
|
||||
// after finalizing block 2 the set should have switched to paused state
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Paused,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_resume_only_when_paused() {
|
||||
with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
|
||||
System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
|
||||
|
||||
// the set is currently live, resuming it is an error
|
||||
assert!(Grandpa::schedule_resume(1).is_err());
|
||||
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Live,
|
||||
);
|
||||
|
||||
// we schedule a pause to be applied instantly
|
||||
Grandpa::schedule_pause(0).unwrap();
|
||||
Grandpa::on_finalize(1);
|
||||
let _ = System::finalize();
|
||||
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Paused,
|
||||
);
|
||||
|
||||
// we schedule the set to go back live in 2 blocks
|
||||
System::initialize(&2, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::schedule_resume(2).unwrap();
|
||||
Grandpa::on_finalize(2);
|
||||
let _ = System::finalize();
|
||||
|
||||
System::initialize(&3, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::on_finalize(3);
|
||||
let _ = System::finalize();
|
||||
|
||||
System::initialize(&4, &Default::default(), &Default::default(), &Default::default());
|
||||
Grandpa::on_finalize(4);
|
||||
let _ = System::finalize();
|
||||
|
||||
// it should be live at block 4
|
||||
assert_eq!(
|
||||
Grandpa::state(),
|
||||
StoredState::Live,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user