3

In Substrate docs, it's said: "The maximum block weight should be equivalent to one-third of the target block time, allocating one third for block construction, one third for network propagation, and one third for import and verification."

For example, in the original runtime, total compute time is 2 seconds (2 * WEIGHT_PER_SECOND) while block time is 6s:

...

parameter_types! {
    pub const Version: RuntimeVersion = VERSION;
    pub const BlockHashCount: BlockNumber = 2400;
    /// We allow for 2 seconds of compute with a 6 second average block time.
    pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights
        ::with_sensible_defaults(2 * WEIGHT_PER_SECOND, NORMAL_DISPATCH_RATIO);
    pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength
        ::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
 
...
}

What could happen if started a new chain with decreased block time (1000 ms), but did not change the block weight of 2 seconds?

And after genesis, could those variables be changed with a Forkless upgrade without negative consequences?

2 Answers 2

4

What could happen if started a new chain with decreased block time (1000 ms), but did not change the block weight of 2 seconds?

I don't really know what you mean by block time, but I assume you mean the time we have in maximum one block can take to execute. Here in your case block time is based on the block weight. There isn't any other parameter defining this.

However, I think you mean something like we have a block weight of 2 seconds and a slot duration of 1 second. Meaning we want to produce a block every second. If you have a normal Substrate node, nothing problematic will happen (in most cases). The block production is tracking the time and if it runs over a maximum (depending on the block production part of the slot duration). Let's assume we give the block production 500 ms. Of this 500ms 67% are allocated for the actual block initialization plus transaction execution. If we are now above these 67% of execution time we will stop the block production and finalize the block. Then you send out the block to the network.

The runtime is now tracking also the used block weight and if we for example already used 80% of the block weight and the node tries to push another transaction that would take another 21% block weight, the runtime would "deny" the transaction for now. It would need to be retried in a different block. However, in your case here were block weight is much bigger than the actual time we got for block production it could happen that at 60% of the time we got, we push a transaction that uses a lot of weight. The runtime wouldn't "deny" because it thinks there is still enough time left and then it would start to process the transaction. We don't support aborting transaction that are being applied, this means we would run until the transaction is applied. If that takes too much time we would probably have missed our slot and throw the entire block away. So, we would have wasted all the block production for nothing.

If there is a malicious validator that manages to produce a 2 second block in like 500ms, it could force all other nodes to import this block (we don't abort block import, because the runtime needs to ensure that blocks are not taking infinite time to import). So, someone could maybe spam your network with very big blocks and let it may stall a little bit.

And after genesis, could those variables be changed with a Forkless upgrade without negative consequences?

Yes you can change the block weight without any consequences. Only the SLOT_DURATION can not be changed that easily!

4

How exactly it behaves depends on the consensus engine that's being used, but when blocks are consistently overfull it causes a slowdown of the chain. In certain consensus engines, it might lead to a stall.

In consensus engines like BABE and Aura, where a slot leader unilaterally authors and distributes a block, things just slow down. You can imagine a node X starting to create a block B1 against some parent block B0, but not finishing it before the next time slot, where a node Y starts authoring. Y also starts authoring a block B1' on top of B0, because it's not aware of X's block. X finishes authoring its block B1 before the next time slot, and then the next node Z starts building B2 on top of B1. Y's block B1' becomes an orphaned fork when it's completed. And so on - lots of orphaned forks, lots of skipped blocks. The overfull blocks start to work against the throughput of the chain.

In some consensus engines, like Tendermint or HotStuff, where blocks are proposed and need to be signed off on by 2/3 of validators before being accepted, block execution might just time out, and rounds of consensus would proceed with no block being accepted, and the chain would not make progress. Most implementations would work around this by raising timeouts exponentially over time, which would mean that the chain just slows down.

Weight is a fuzzy mechanism. In the world of consensus, network latency and execution time can be treated similarly. If network latencies are low, execution times can go up. But if network latencies are high, you can't budget as much time for execution. Consensus mechanisms have differing assumptions about the network conditions they function under, and different ways of dealing with the worst cases of those network assumptions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.