This repository was archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Delay session changes' effects on parachains by 1 block #1354
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
3f4b89b
note that the initializer is responsible for buffering session changes
rphmeier a2b7f17
amend initializer definition to include session change buffering
rphmeier 61eb5c7
support buffered changes before `on_initialize`
rphmeier 1e558e3
implement and test session buffering
rphmeier 85bf8d3
Update roadmap/implementors-guide/src/runtime/README.md
rphmeier a7a406e
expand on how this affects misbehavior reports
rphmeier 299d0ed
Merge branch 'master' into rh-delay-session
rphmeier 93960d1
fix typo
rphmeier f7afc70
Merge branch 'master' into rh-delay-session
rphmeier File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,8 @@ use primitives::{ | |
| use frame_support::{ | ||
| decl_storage, decl_module, decl_error, traits::Randomness, | ||
| }; | ||
| use sp_runtime::traits::One; | ||
| use codec::{Encode, Decode}; | ||
| use crate::{configuration::{self, HostConfiguration}, paras, scheduler, inclusion}; | ||
|
|
||
| /// Information about a session change that has just occurred. | ||
|
|
@@ -46,6 +48,14 @@ pub struct SessionChangeNotification<BlockNumber> { | |
| pub session_index: sp_staking::SessionIndex, | ||
| } | ||
|
|
||
| #[derive(Encode, Decode)] | ||
| struct BufferedSessionChange<N> { | ||
| apply_at: N, | ||
| validators: Vec<ValidatorId>, | ||
| queued: Vec<ValidatorId>, | ||
| session_index: sp_staking::SessionIndex, | ||
| } | ||
|
|
||
| pub trait Trait: | ||
| system::Trait + configuration::Trait + paras::Trait + scheduler::Trait + inclusion::Trait | ||
| { | ||
|
|
@@ -64,6 +74,14 @@ decl_storage! { | |
| /// them writes to the trie and one does not. This confusion makes `Option<()>` more suitable for | ||
| /// the semantics of this variable. | ||
| HasInitialized: Option<()>; | ||
| /// Buffered session changes along with the block number at which they should be applied. | ||
| /// | ||
| /// Typically this will be empty or one element long, with the single element having a block | ||
| /// number of the next block. | ||
| /// | ||
| /// However this is a `Vec` regardless to handle various edge cases that may occur at runtime | ||
| /// upgrade boundaries or if governance intervenes. | ||
| BufferedSessionChanges: Vec<BufferedSessionChange<T::BlockNumber>>; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -77,6 +95,21 @@ decl_module! { | |
| type Error = Error<T>; | ||
|
|
||
| fn on_initialize(now: T::BlockNumber) -> Weight { | ||
| // Apply buffered session changes before initializing modules, so they | ||
| // can be initialized with respect to the current validator set. | ||
| <BufferedSessionChanges<T>>::mutate(|v| { | ||
| let drain_up_to = v.iter().take_while(|b| b.apply_at <= now).count(); | ||
|
|
||
| // apply only the last session as all others lasted less than a block (weirdly). | ||
| if let Some(buffered) = v.drain(..drain_up_to).last() { | ||
| Self::apply_new_session( | ||
| buffered.session_index, | ||
| buffered.validators, | ||
| buffered.queued, | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| // The other modules are initialized in this order: | ||
| // - Configuration | ||
| // - Paras | ||
|
|
@@ -106,27 +139,11 @@ decl_module! { | |
| } | ||
|
|
||
| impl<T: Trait> Module<T> { | ||
| /// Should be called when a new session occurs. Forwards the session notification to all | ||
| /// wrapped modules. If `queued` is `None`, the `validators` are considered queued. | ||
| /// | ||
| /// Panics if the modules have already been initialized. | ||
| fn on_new_session<'a, I: 'a>( | ||
| _changed: bool, | ||
| fn apply_new_session( | ||
| session_index: sp_staking::SessionIndex, | ||
| validators: I, | ||
| queued: Option<I>, | ||
| ) | ||
| where I: Iterator<Item=(&'a T::AccountId, ValidatorId)> | ||
| { | ||
| assert!(HasInitialized::get().is_none()); | ||
|
|
||
| let validators: Vec<_> = validators.map(|(_, v)| v).collect(); | ||
| let queued: Vec<_> = if let Some(queued) = queued { | ||
| queued.map(|(_, v)| v).collect() | ||
| } else { | ||
| validators.clone() | ||
| }; | ||
|
|
||
| validators: Vec<ValidatorId>, | ||
| queued: Vec<ValidatorId>, | ||
| ) { | ||
| let prev_config = <configuration::Module<T>>::config(); | ||
|
|
||
| let random_seed = { | ||
|
|
@@ -156,6 +173,31 @@ impl<T: Trait> Module<T> { | |
| scheduler::Module::<T>::initializer_on_new_session(¬ification); | ||
| inclusion::Module::<T>::initializer_on_new_session(¬ification); | ||
| } | ||
|
|
||
| /// Should be called when a new session occurs. Buffers the session notification to be applied | ||
| /// at the next block. If `queued` is `None`, the `validators` are considered queued. | ||
| fn on_new_session<'a, I: 'a>( | ||
| _changed: bool, | ||
| session_index: sp_staking::SessionIndex, | ||
| validators: I, | ||
| queued: Option<I>, | ||
| ) | ||
| where I: Iterator<Item=(&'a T::AccountId, ValidatorId)> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may not matter, but |
||
| { | ||
| let validators: Vec<_> = validators.map(|(_, v)| v).collect(); | ||
| let queued: Vec<_> = if let Some(queued) = queued { | ||
| queued.map(|(_, v)| v).collect() | ||
| } else { | ||
| validators.clone() | ||
| }; | ||
|
|
||
| <BufferedSessionChanges<T>>::mutate(|v| v.push(BufferedSessionChange { | ||
| apply_at: <system::Module<T>>::block_number() + One::one(), | ||
| validators, | ||
| queued, | ||
| session_index, | ||
| })); | ||
| } | ||
| } | ||
|
|
||
| impl<T: Trait> sp_runtime::BoundToRuntimeAppPublic for Module<T> { | ||
|
|
@@ -184,21 +226,47 @@ impl<T: session::Trait + Trait> session::OneSessionHandler<T::AccountId> for Mod | |
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use crate::mock::{new_test_ext, Initializer}; | ||
| use crate::mock::{new_test_ext, Initializer, Test, System}; | ||
|
|
||
| use frame_support::traits::{OnFinalize, OnInitialize}; | ||
|
|
||
| #[test] | ||
| #[should_panic] | ||
| fn panics_if_session_changes_after_on_initialize() { | ||
| fn session_change_before_initialize_is_still_buffered_after() { | ||
| new_test_ext(Default::default()).execute_with(|| { | ||
| Initializer::on_new_session( | ||
| false, | ||
| 1, | ||
| Vec::new().into_iter(), | ||
| Some(Vec::new().into_iter()), | ||
| ); | ||
|
|
||
| let now = System::block_number(); | ||
| Initializer::on_initialize(now); | ||
|
|
||
| let v = <BufferedSessionChanges<Test>>::get(); | ||
| assert_eq!(v.len(), 1); | ||
|
|
||
| let apply_at = now + 1; | ||
| assert_eq!(v[0].apply_at, apply_at); | ||
| }); | ||
| } | ||
|
|
||
| #[test] | ||
| fn session_change_applied_on_initialize() { | ||
| new_test_ext(Default::default()).execute_with(|| { | ||
| Initializer::on_initialize(1); | ||
|
|
||
| let now = System::block_number(); | ||
| Initializer::on_new_session( | ||
| false, | ||
| 1, | ||
| Vec::new().into_iter(), | ||
| Some(Vec::new().into_iter()), | ||
| ); | ||
|
|
||
| Initializer::on_initialize(now + 1); | ||
|
|
||
| assert!(<BufferedSessionChanges<Test>>::get().is_empty()); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.