Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FRAME] Prepare pallets for dynamic block durations #3268

Open
ggwpez opened this issue Feb 8, 2024 · 11 comments
Open

[FRAME] Prepare pallets for dynamic block durations #3268

ggwpez opened this issue Feb 8, 2024 · 11 comments

Comments

@ggwpez
Copy link
Member

ggwpez commented Feb 8, 2024

Currently we just use System::block_number() in many pallet and derive a timestamp from it. This will not work anymore when parachains have changing block times, either from async backing or coretime.

Possible Solution

(the naming here are just placeholders)

  • Create a new system config item: BlockNumberProvider which can then either be configured to RelaychainDataProvider when its a parachain runtime or () for relay runtimes.
  • Add a function System::provided_block_number() -> Number
  • Add a function System::local_block_number() -> Number (for migrations and to avoid ambiguity)
  • Deprecate System::block_number()

We then need to adapt a ton of pallets and check whether their storage needs to be migrated.

@ggwpez ggwpez changed the title [FRAME] Prepare pallets for dynamic block times [FRAME] Prepare pallets for dynamic block durations Feb 8, 2024
@kianenigma
Copy link
Contributor

kianenigma commented Feb 8, 2024

This is more or less the proposed solution that I extracted from the call, mostly my attempt to capture an idea that @gupnik expressed:

diff --git a/substrate/frame/scheduler/src/lib.rs b/substrate/frame/scheduler/src/lib.rs
index e94f154eee..bcb5aff728 100644
--- a/substrate/frame/scheduler/src/lib.rs
+++ b/substrate/frame/scheduler/src/lib.rs
@@ -334,7 +334,9 @@ pub mod pallet {
 		#[pallet::weight(<T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))]
 		pub fn schedule(
 			origin: OriginFor<T>,
-			when: BlockNumberFor<T>,
+			// we provide this type to all dispatchables wishing to reference the future. Later on,
+			// `fn passed` can be used to check if this time is already passed.
+			when: T::RuntimeTime,
 			maybe_periodic: Option<schedule::Period<BlockNumberFor<T>>>,
 			priority: schedule::Priority,
 			call: Box<<T as Config>::RuntimeCall>,
diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs
index 069217bcee..ab725a7dd5 100644
--- a/substrate/frame/system/src/lib.rs
+++ b/substrate/frame/system/src/lib.rs
@@ -570,6 +570,64 @@ pub mod pallet {
 
 		/// The maximum number of consumers allowed on a single account.
 		type MaxConsumers: ConsumerLimits;
+
+		/// Something that can represent a notion of time within this runtime.
+		///
+		/// It can be one's block number, timestamp or similar, depending on wether this is being
+		/// used in a parachain or relay chain context, and with or without async backing.
+		///
+		/// Be aware that changing this type midflight probably has a lot of consequences.
+		type RuntimeTime: RuntimeTime;
+	}
+
+	/// An operation that can happen far in the future.
+	trait RuntimeTime:
+		// This type should be storage-friendly..
+		codec::Codec
+		// dispatchable	friendly..
+		+ Parameter
+		+ Member
+		// and compare-able ..
+		+ core::cmp::PartialOrd
+		+ core::cmp::Eq
+		// and subtract-able.
+		+ sp_runtime::traits::CheckedSub
+	{
+		/// Return the notion of time "now".
+		fn now() -> Self;
+
+		/// Just a shorthand for `now() >= other`.
+		fn passed(&self, other: &Self::Time) -> bool {
+			*self >= *other
+		}
+
+		/// Just a shorthand for `now() - other`.
+		fn remaining(&self, other: &Self::Time) -> Self::Time {
+			self.now() - *other
+		}
+	}
+
+	/// Use my own block number.
+	pub struct SelfBlockNumber<T>(BlockNumberFor<T>);
+	impl<T: Config> RuntimeTime for SelfBlockNumber<T> {
+		fn now() -> Self {
+			Self(Pallet::<T>::deprecated_dont_use_block_number())
+		}
+	}
+
+	/// TOOD: should be provided by parachain-system, not here.
+	pub struct RelayBlockNumber<T>(BlockNumberFor<T>);
+	impl<T: Config> RuntimeTime for RelayBlockNumber<T> {
+		fn now() -> Self {
+			unimplemented!("read from some hardcoded key?")
+		}
+	}
+
+	pub struct Timestamp<T>(u64);
+	impl<T: Config> RuntimeTime for Timestamp<T> {
+		fn now() -> Self {
+			unimplemented!("call into pallet-timestamp")
+		}
 	}
 
 	#[pallet::pallet]
@@ -869,7 +927,7 @@ pub mod pallet {
 	/// The current block number being processed. Set by `execute_block`.
 	#[pallet::storage]
 	#[pallet::whitelist_storage]
-	#[pallet::getter(fn block_number)]
+	#[pallet::getter(fn deprecated_dont_use_block_number)]
 	pub(super) type Number<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
 
 	/// Hash of the previous block.

If it works, it looks elegant and future-proof to me, but I would be open to a simpler solution as well. @ggwpez's proposed solution seems more aligned with the goal of simplicity.

@ggwpez
Copy link
Member Author

ggwpez commented Feb 8, 2024

Yea the RuntimeTime (or Instant maybe) does still make sense to me. Its not prevented by the BlockNumberProvider proposal.

I dont know how much effort it is to refactor the pallets to use the new Instant type. Otherwise we can use BlockNumberProvider for legacy pallets to quickly port them and then the Instant for new pallets?
But if its easy to port them, then just having the Instant would be better i think.

@xlc
Copy link
Contributor

xlc commented Feb 9, 2024

I am not sure if we want a central config. In fact, a well written pallet should not be reading System::block_number and instead have its own config type to allow the runtime to specify the block number / timestamp. So this doesn't apply to all the well written pallets.

For other pallets, yeah, they need to migrate. But instead of migrate to System::provided_block_number or System::local_block_number, they really should be just switch to a BlockNumberProvider in config.

@ggwpez
Copy link
Member Author

ggwpez commented Feb 9, 2024

I am not sure if we want a central config. In fact, a well written pallet should not be reading System::block_number and instead have its own config type to allow the runtime to specify the block number / timestamp. So this doesn't apply to all the well written pallets.

But this will lead to all pallets having a BlockNumberProvider config item, which is why it could be de-duplicated by just putting into System.
It also ensure that all pallets use the same BlockNumberProvider, since otherwise it could lead to issues.

@ggwpez
Copy link
Member Author

ggwpez commented Feb 9, 2024

Actually this does not work since there can be multiple para blocks per relay block...
Gav mentioned that we could use the relay timeslot. So it would basically be Kians suggestion with the Opaque type.

@xlc
Copy link
Contributor

xlc commented Feb 10, 2024

But this will lead to all pallets having a BlockNumberProvider config item, which is why it could be de-duplicated by just putting into System.

No. Only pallets depends on BlockNumberProvider will need to add it. Many pallets don't need it. And explicit dependency is a good thing.

It also ensure that all pallets use the same BlockNumberProvider, since otherwise it could lead to issues.

I argue exact the opposite. For some pallet, it is perfectly fine to use local block number and for some, they should use relay, and for some others, they should use timestamp. It is just not possible to come up something that works for all the pallets.

@ggwpez
Copy link
Member Author

ggwpez commented Feb 10, 2024

I argue exact the opposite. For some pallet, it is perfectly fine to use local block number and for some, they should use relay, and for some others, they should use timestamp. It is just not possible to come up something that works for all the pallets.

Okay makes sense. Then i assume a generic InstantProvider (or TimestampProvider) on a per-pallet basis could work?
This could then be configured to use local blocknumer, relay blocknumber of some timestamp inherent. If you have something else in mind then please make a concrete proposal.
I will add a point to the next Fellowship call agenda.

@xlc
Copy link
Contributor

xlc commented Feb 11, 2024

Yes. I don’t see much changes needed in frame system. We just need to migrate each pallet one by one and maybe add some helpers.

@kianenigma
Copy link
Contributor

It is just not possible to come up something that works for all the pallets.

I see the point in this, yeah. I was hoping by making this be part of frame-system we can make the process of using it easier, but I don't see a better way for now either.

For such cases, there could perhaps be "opinionated" versions of frame-system that has all this stuff configured for you, and you won't need to infiltrate each pallet with new types to use common functionalities such as BlockNumber or even a basic currency.

@xlc
Copy link
Contributor

xlc commented Feb 11, 2024

Most of the pallet doesn’t need to access block number / timestamp so it should be perfectly fine to require few more lines for those

@ggwpez
Copy link
Member Author

ggwpez commented Feb 12, 2024

I put a draft up here: #3298
You can treat it as a scratch pad. Its just what i came up with now, so probably needs improvements.

Lets settle on the basic types first before implementing it in runtime/pallets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Draft
Status: Backlog
Development

No branches or pull requests

3 participants