# What causes the runtime error: `Timestamp must match 'CurrentSlot'` ? In order to understand the error you should understand how a block is built, however in this post I will not dive in the formal definitions of BABE but I will put the reference to the [Polkadot Protocol Specification] (https://spec.polkadot.network/#sect-block-production) for you to dive into yourself. The Polkadot block production mechanism is the [BABE Protocol](https://research.web3.foundation/en/latest/polkadot/block-production/Babe.html). For this post, its important to know that its' execution is based on sequential, non-overlapping phases known as an _epoch_. An epoch is a period with a pre-known start time and a fixed length duration. For each epoch, the set of block producers will remain constant. Each epoch is divided into equal-length periods known as block production slots. For example, in the image below we have an epoch with a fixed-length duration of 60 seconds and a slot duration of 6 seconds each, so in the example epoch we can fit 10 slots in 1 epoch. ![](https://i.imgur.com/vIdHmGI.png) Each slot is assigned to a block producer through a lottery process executed at the beginning of every epoch. This means that after the lottery each block producer knows the slots that it is assigned to produce a new block. Basically at the very beginning of each epoch the host is aware of the amount of slots in the epoch as well as their numbers and the epochs start time. In the Gossamer context we use a `timer` to wait for the exact authoring slots for which we are the author to start to initialize the block production engine. In the following image we can see that we as an authority node we are allowed to produce blocks at slots 2, 4 and 8, for example. ![](https://i.imgur.com/lqabtwr.png) Basically we will wait untill the start of slots number 2 (12s), 4 (24s) and 8 (48s) to start our block production engine and produce a block. ![](https://i.imgur.com/r9mVRdJ.png) ### The block production engine The process of built a block, which for a more precise understanding you can refer to [Block Building Process in the Polkadot Specification](https://spec.polkadot.network/#sect-block-building), calls the following runtime functions: - `Core_initialize_block` - `BlockBuilder_inherent_extrinsics` - `BlockBuilder_apply_extrinsics` - `BlockBuilder_finalize_block` The runtime error `Timestamp must match 'CurrentSlot'` is related with two of them: `Core_initialize_block` and `BlockBuilder_apply_extrinsics`. - When its our time to produce a block we build the block header using the parent block hash, block number, and the block digest which contains the **slot number** and then we call the runtime function `Core_initialize_block` which endups calling `fn initialize(now: T::BlockNumber)` function from the [BABE frame pallet ](https://github.com/paritytech/substrate/blob/master/frame/babe/src/lib.rs#L753). Inside the runtime function `fn initialize(now: T::BlockNumber)`, among other things, it extracts the `slot_number` from the block header digest and saves it in the `CurrentSlot` runtime storage value. - In the same context but now in the host side, after calling the runtime the next step is to build the block inherents. Block inherents are a set of key-values data produced only by the Polkadot Host (Gossamer). In this step we set the `timstap0` inherent key that holds an unix epoch time. It's value is important for the runtime as the runtime will perform a set of assertions to ensure a correct progression as well as assert that it matches with the `CurrentSlot` storage value. > The difference between extrinsics to inherent data is that inherent data are broadcasted as part of the produced block rather than individual extrinsics. [Polkadot Specification, Preliminaries](https://spec.polkadot.network/#id-preliminaries) - Instead of going straight to the checks I would first like to explain a constant that the timestamp frame pallet has. The constant is called `MinimumPeriod` and it stores half of the slot duration value, eg. in the [Westend Runtime the `SLOT_DURATION`](https://github.com/paritytech/polkadot/blob/master/runtime/westend/constants/src/lib.rs#L44) is 6000 (6s), so for the same runtime the `MinimumPeriod` will be 3000 (3s) ```rust // polkadot/runtime/westend/constants/src/lib.rs pub const MILLISECS_PER_BLOCK: Moment = 6000; pub const SLOT_DURATION: Moment = MILLISECS_PER_BLOCK; ``` ```rust // polkadot/runtime/westend/src/lib.rs parameter_types! { pub const MinimumPeriod: u64 = SLOT_DURATION / 2; } impl pallet_timestamp::Config for Runtime { ... type MinimumPeriod = MinimumPeriod; ... } ``` - Secondly, I would like to explain an important relationship between `BABE` and `Timestamp` pallets where `Timestamp` activate the `on_timestamp_set` hook in the `BABE` pallet when it sets a new timestamp given the `timstap0` inherent key. - `BABE` implements the trait [`OnTimestampSet`](https://github.com/paritytech/substrate/blob/3bc3742d5c0c5269353d7809d9f8f91104a93273/frame/support/src/traits/hooks.rs#L354) which has only one method called `on_timestamp_set(moment: Moment)` - `Timestamp` has in its pallet configuration the field [`OnTimestampSet`](https://github.com/paritytech/substrate/blob/3bc3742d5c0c5269353d7809d9f8f91104a93273/frame/timestamp/src/lib.rs#L140) which expects an implementation of the `OnTimestampSet` trait ```rust // polkadot/runtime/westend/src/lib.rs impl pallet_timestamp::Config for Runtime { ... type OnTimestampSet = Babe; type MinimumPeriod = MinimumPeriod; ... } ``` - So... how does the hook activation happen? Let's take a look at the [Timestamp frame pallet](https://github.com/paritytech/substrate/blob/3bc3742d5c0c5269353d7809d9f8f91104a93273/frame/timestamp/src/lib.rs#L121), there we can see a couple functions within the macro `#[pallet::inherent]`, to be more specific we will analyse the function `create_inherent` which receives the inherent data as argument and extracts only the `timstap0` inherent value and does the comparision: `let next_time = cmp::max(data, Self::now() + T::MinimumPeriod::get());`, the expression [`Self::now()` is a getter](https://github.com/paritytech/substrate/blob/3bc3742d5c0c5269353d7809d9f8f91104a93273/frame/timestamp/src/lib.rs#L160) for the `Now` storage value that holds the time for the current block, however since we have not applied the inherent value yet it will return the time for the previous block (if it is the very first block `Self::now()` returns `0`). - Back to the comparision, it enforces some sort of progress, because if the time provided by the inherent data don't advance at least by the `MinimumPeriod` then the runtime itself provides the time ignoring the inherent value. In the next line the `Timestamp` calls the [`set` function](https://github.com/paritytech/substrate/blob/3bc3742d5c0c5269353d7809d9f8f91104a93273/frame/timestamp/src/lib.rs#L206) that exists within the pallet, and passes the outcome of the comparision as the argument. The next function does a couple of actions including: - Store the argument in the `Now` storage value - Calls the `on_timestamp_set` from the `OnTimestampSet` implementation passing the comparision outcome as the argument - Now that we know how the hook is activated lets see how the implementor acts once the hook happens. [The implementation of `on_timestamp_set`](https://github.com/paritytech/substrate/blob/master/frame/babe/src/lib.rs#L881) is quite simple lets break down this function: - The first thing it does is to call a function called [`Self::slot_duration()`](https://github.com/paritytech/substrate/blob/129fee774a6d185d117a57fd1e81b3d0d05ad747/frame/babe/src/lib.rs#L523) and this function simply returns the constant value `MinimumPeriod` from the `Timestamp` frame pallet multiplied by 2, so let's take `Westend` runtime as an example againg: its `MinimumPeriod` is 3000 (3s) so the returned value of the `Self::slot_duration()` function will be 6000 (6s); - In the following line it calculates a variable called `timestamp_slot` using the `moment` argument (which was provided by the `Timestamp` pallet) divided by the `slot_duration`, the `timestamp_slot` is nothing more than the slot number, and this value **must** be the same as the **slot number** value we informed earlier in the block header digest otherwise we will get the error `Timestamp slot must match 'CurrentSlot'`. > The slot number calculation is defined by the [Polkadot Specification at session 5.3: Slot Number Calculation](https://spec.polkadot.network/#sect-slot-number-calculation) So this is how one gets the error `Timestamp slot must match 'CurrentSlot'`, but how does that actually impact us in gossamer? - For 1, it makes us think about the concept of "our time" vs "their time." The runtime itself stores the time of each block and tracks the progess which the host must satisfy, and the timing is based upon the epoch start time, so it is all predetermined. It is important for Gossamer nodes to have the same sense of slot start time as the runtime has, which is why we recently made a change to [`use the predetermined slot start time rather than time.Now()`](https://github.com/ChainSafe/gossamer/pull/3070). We still use a timer to determine that correct start time for a slot to avoid overlap, but use the predetermined start time rather than `time.Now()` to set the start time of the slot. - Another reason that understanding how time is handled with regard to slots is important is for testing purposes. Let's bring a Gossamer BABE integration test, where we need to be able to craft specific instances to test BABE functionality. An example of which would be a block author submitting multiple blocks in the same slot to simulate a equivocation. There are two different ways that this can play, using a hardcoded slot start time and using the current time with `time.Now()`. Both cases are run on one node network with 6 seconds of slot duration. - Lets elaborate on the first case where we use the a deterministic time to build a block in our test. So while building the block number 1 we set in its **header digest** the **slot number 0** and in its inherent data we set the `timstap0` inherent key as 1. ![](https://i.imgur.com/xsXtPmD.png) - As presented before we know that the slot number in the block header digest is stored in the `BABE CurrentSlot` storage value, in this case the value stored is `0`, so let's proceed to the next step, now in the `Timestamp` pallet a comparision happens between `timstap0` inherent value (`1`) and `Self::now() + MinimumPeriod`, here as it's the very first block `Self::now()` evaluates to `0` and `MinimumPeriod` is half of a slot duration which evaluates to `3` so the outcome of `cmp::max(1, 3)` is `3` which is 1) stored in the `Timestamp::Now` storage value and is used as the argument to the hook `on_timestamp_set` activation in `BABE` pallet, there it retrieves the slot duration value `6`, [which is simply the `MinimumPeriod (3)` multiplied by `2`](https://github.com/paritytech/substrate/blob/5d0867c7dcdc50572422611128853ccff63ebd4d/frame/babe/src/lib.rs#L523), and it does the slot number calculation dividing the argument (`3`) by the slot duration (`6`) and since it gets the integer part the outcome is `0` and by comparing this value with what is stored in the `BABE CurrentSlot` we get true which is the happy path! - Now we will try to produce another block but within the same **slot number 0**, so the new block will contains in its header digest the slot number 0 and this value will be stored in the `BABE CurrentSlot` storage value (through the `Core_initialize_block` runtime function), now let's introduce a new value within the `0-5` range for the `timstap0` inherent key let's say `4`, using this value we will create a failure situation I will explain why. In the `Timestamp` pallet the comparision will be made and now since we have some values already stored in the `Now` storage value the values compared will be `cmp::max(4, 6)`, `Self::now()` evaluates to `3` and `MinimumPeriod` evaluates to `3` as well, so the outcome of the comparision is `6`, after storing `6` in `Timestamp Now` storage value it will be used as the argument for `on_timestamp_set` function in the `BABE` pallet, there the slot number calculation is made by dividing the argument by the slot duration, `6 / 6` which gives us `1` and this does not match with the slot number we provided in the block header digest (`0`) which will leads to a runtime error. ![](https://i.imgur.com/Q3oBdmO.png) - To avoid the previous mentioned error and at the same time allowing the test to produce two blocks in the same slot number, we should make sure to inject a timestamp at the beginning of the **next** slot. 1. The first case: We initialize the epoch, and claim slot 1 at t = 6, submit it to the runtime and all is well. Note that since this is the first block authored, `time.Now()` is zero and then gets updated to the time we send, which is 6. We then make another block to submit with the same slot number, now at time t = 7. The runtime does a check for `cmp::max(7, time.Now() + minimumPeriod = 9)` Since the first slot occupies time t = 6 - 11, we have successfully created a situation where the block author has authored two blocks in the same slot, which is the situation we wanted to create! 4. Now for the second case: We initialize the epoch, and since we are using time.now(), we claim the slot at time t = 5. Now remember, the first slot is only time 0 - 6. But we know this right?! So when we create the second slot with the same number, we artificially use the same time as the first slot at t = 5, and send that to the runtime. Since we sent a block for slot at time t = 5 for both this should be the exact same as the above scenario right? Nope. Remember this line from earlier in the Timestamp pallat `let next_time = cmp::max(data, Self::now() + T::MinimumPeriod::get());`. So we hit this line, `time.Now()` is now (`5`), and next_time evaluates to `Self::now() + T::MinimumPeriod::get());` meaning that according the runtime the timestamp is t = 8, which is in the next slot! The point here is that even thought our whole test ran in under 6 seconds, its success is entirely dependant on what time it is when we start executing the test! - The takeaway here is that when we are testing babe and manually creating slots, we need to inject a timestamp rather than use time.Now() to achieve consistent results.