Blocknumber vs Timestamps: Should we abandon blocktimes altogether?

I am aware of at least 3 different instances where Polkadot today relies heavily on counting blocks vs timestamps:

  • Vesting Pallet defines vesting per block
  • Governance has voting periods defined in blocks (at least gov1 - maybe that was addressed in gov2)
  • Multisig has a delay feature that accepts a block count

With asynchronous backing blocktimes will change, with parathreads and the core time changes we will see blocktimes becoming much more dynamic again. Therefore I think we should abandon the idea of using block counts and blockheight in any core pallet. Has there been any discussion of this already or a move by the fellowship and other parachain builders in this direction?

2 Likes

These pallets should be generalized over some kind of Moment or Timestamp - a BlockNumber is a timestamp, of a kind, although it’s a bad clock for anything which doesn’t progress consistently.

Here are a few possible Moments which can be used:

  1. The parachain block number - this is a bad clock after asynchronous backing and agile coretime and should be avoided
  2. The parachain timestamp - this is a pretty good clock, as long as consensus pallets validate it against the relay chain block number (Aura for parachains does)
  3. The relay chain block number - this is a good clock, but is only available after inherents, not during on_initialize. the last relay chain block number
  4. The relay chain slot - likewise.

The relay-chain block number, which is available to parachain execution, is a reasonable Moment, although it’s not available during on_initialize and only during/after inherents. The relay-chain timestamp is even better.

You mention the relay chain block number isn’t available in inherent and on_initialize, but couldn’t you look at the last block and just add one to it?

Or is there some deeper problem here I am missing?

I have always recommended to other blockchain developers that they should prefer writing code which relies on a block number rather than the timestamp since the block number is a native part of the blockchain, and the timestamp is an externally reported, potentially inaccurate (to some limit) value.

Indeed, block number is inferior to many of the “user experience” things that want to be designed, like “execute some transaction exactly in one year”, since delays to block production may mean that a block number representation of that time period is not perfect, but to me this is also fine, and could be repaired with upgrades / governance.

Are there scenarios where it is strictly better for a developer to design a system around the timestamp?

In this case, having parachains rely on the relay chain block number seems like the best choice to me. In the current design of Polkadot, it has a pretty consistent block time of 6 seconds. This stuff is reported to the parachains, and all logic could stem from this as a universal marker of time.

When designing on-demand parachain logic, developers need to remove all assumptions around a fixed block production time, and instead write logic which incentivizes blocks to be pushed to the relay chain at certain relay chain blocks, or to look at the current relay chain block for the truth about the current time.

Example: an on-demand parachain for vesting DOT balances.

Polkadot already has a pallet for managing vesting DOT directly on the relay chain. Most of the time, the logic in this pallet is inactive. Users can initiate transactions to the pallet, and based on the current relay chain block number, some DOT balance will be unlocked.

If we were to design this as an on-demand parachain, there would be no need for a local timestamp or consistent block time. Instead, users will have their balances vesting based on the relay chain block number. A block is only created when a user wants to unlock their DOT (which could be months apart), and simply checking the latest relay chain block number would allow the logic to unlock the correct amount.

I think you could think of a very similar pattern for a Domain Name Service On-Demand Parachain, where most of the time, the DNS logic is inactive, and only when users buy / trade / renew / re-claim their domain, blocks will need to be made. We should not require that this chain produce consistent empty blocks just to keep track of time, instead we again rely on the block number of the relay chain.

1 Like

That’s why I was pushing to use traits instead of reading time/block number from system pallet directly.

In that way, the runtime can configure those values. It could be local block number, relay block number, local timestamp, relay timestamp, or whatever.

We have ~30’000 addresses with vesting on Centrifuge. This would lead us having to upgrade them every time we change the block time. If we momentarily buy another core to process end of month peak of transactions and therefore temporarily cut the bock time in half it would mean we’d have to upgrade twice. In addition, I’ve already outlined 3 separate places where block numbers are used today and that number is likely only growing. I think “repairing it with upgrades” becomes a big hurdle pretty soon.

You are right though, we could instead just rely on the relay blocknumber and hope that Polkadot won’t frequently change block times. How sure are we that they will stay relatively consistent?

@xlc and @rphmeier what do you think of that solution?

There’s no requirement post-asynchronous-backing that the relay-parent necessarily increases, but using the relay-chain block number as of the parent parachain block is probably fine, though it will lag slightly behind ‘real’ time.

That’s why we are using relaychain block number for vesting. I see no reason to adjust relaychain block time and will definitely push it back if others want to change it without a very very very good reason.

This will work fine until you’ll see the first parachain moving from Kusama to Polkadot :slight_smile: But yes this seems reasonable overall.

Is this the solution you mean? If so, yes, I think it’s likely the best approach (balancing reliability & ease-of-implementation) and is currently available, though with a less-than-ideal API via the parachain-system pallet in Cumulus. As I mentioned above, the current relay-chain block number is only available after inherents, while the last relay chain block number will be stable throughout the entire block. It will be slightly out of date, but never by more than the amount of time between parachain blocks.

I’ve just opened this PR to expose an API for parachain runtimes to read this value: expose the last relay chain block number as an API from parachain-system by rphmeier · Pull Request #1761 · paritytech/polkadot-sdk · GitHub . This API will work during all stages of block execution.

With respect to relay chain block times changing - that would be a huge undertaking and a large breaking change within the entire ecosystem. The only motivation for them to get shorter is to reduce time to finality, but it will likely lower the throughput of the network. There may be some motivation to make them longer to make that tradeoff in the opposite direction. I loosely advocated for shortening them in the past, but I no longer consider it important on any timeline.

2 Likes

A simple offset can be used to counter the difference of block numbers between Kusama and Polkadot.

Thanks for adding the API. We will make sure we use relay chain blocks for things like governance and vesting going forward. I think using the previous block would be incompatible with on demand cores in the future which would be less than ideal.

I agree it’s not ideal, but it would still be compatible. It might just be several minutes behind. There are better solutions, just not ones that can be completed with such small code changes.

We can use the current relay chain block number if it is communicated to the runtime with a pre-runtime digest rather than an inherent.