Inspiration

The Kotti Classic and Görli testnets running different implementations of the Clique engine got stuck multiple times due to minor issues discovered. These issues were partially addressed on the mono-client Rinkeby network by optimizing the Geth code.

However, optimizations across multiple clients should be adequately specified and discussed. This working document is a result of a couple of months testing and running cross-client Clique networks, especially with the feedback gathered by several Pantheon, Nethermind, Parity Ethereum, and Geth engineers on different channels.

The overall goal is to simplify the setup and configuration of proof-of-authority networks, ensure testnets avoid getting stuck and mimicking mainnet conditions.

What it does

Cliquey is the second iteration of the Clique proof-of-authority consensus protocol, previously discussed as "Clique v2". It comes with some usability and stability optimizations gained from creating the Görli and Kotti Classic cross-client proof-of-authority networks that were implemented in Geth, Parity Ethereum, Pantheon, Nethermind, and various other clients.

How we built it

This section specifies the Cliquey proof-of-authority engine. To quickly understand the differences from Clique, please refer to the Rationale further below.

Constants

We define the following constants:

  • EPOCH_LENGTH: The number of blocks after which to checkpoint and reset the pending votes. It is suggested to remain analogous to the mainnet ethash proof-of-work epoch (30_000).
  • BLOCK_PERIOD: The minimum difference between two consecutive block's timestamps. It is suggested to remain analogous to the mainnet ethash proof-of-work block time target (15 seconds).
  • EXTRA_VANITY: The fixed number of extra-data prefix bytes reserved for signer vanity. It is suggested to retain the current extra-data allowance and use (32 bytes).
  • EXTRA_SEAL: The fixed number of extra-data suffix bytes reserved for signer seal: 65 bytes fixed as signatures are based on the standard secp256k1 curve.
  • NONCE_AUTH: Magic nonce number 0xffffffffffffffff to vote on adding a new signer.
  • NONCE_DROP: Magic nonce number 0x0000000000000000 to vote on removing a signer.
  • UNCLE_HASH: Always Keccak256(RLP([])) as uncles are meaningless outside of proof-of-work.
  • DIFF_NOTURN: Block score (difficulty) for blocks containing out-of-turn signatures. It should be set to 1 since it just needs to be an arbitrary baseline constant.
  • DIFF_INTURN: Block score (difficulty) for blocks containing in-turn signatures. It should be 3 to show preference over out-of-turn signatures.
  • MIN_WAIT: The minimum time to wait for an out-of-turn block to be published. It is suggested to set it to BLOCK_PERIOD / 2.

We also define the following per-block constants:

  • BLOCK_NUMBER: The block height in the chain, where the height of the genesis is block 0.
  • SIGNER_COUNT: The number of authorized signers valid at a particular instance in the chain.
  • SIGNER_INDEX: The index of the block signer in the sorted list of currently authorized signers.
  • SIGNER_LIMIT: The number of signers required to govern the list of authorities. It must be floor(SIGNER_COUNT / 2) + 1 to enforce majority consensus on a proof-of-authority chain.

We repurpose the ethash header fields as follows:

  • beneficiary: The address to propose modifying the list of authorized signers with.
    • Should be filled with zeroes normally, modified only while voting.
    • Arbitrary values are permitted nonetheless (even meaningless ones such as voting out non-signers) to avoid extra complexity in implementations around voting mechanics.
    • It must be filled with zeroes on checkpoint (i.e., epoch transition) blocks.
    • Transaction execution must use the actual block signer (see extraData) for the COINBASE opcode.
  • nonce: The signer proposal regarding the account defined by the beneficiary field.
    • It should be NONCE_DROP to propose deauthorizing beneficiary as an existing signer.
    • It should be NONCE_AUTH to propose authorizing beneficiary as a new signer.
    • It must be filled with zeroes on checkpoint (i.e., on epoch transition) blocks.
    • It must not take up any other value apart from the two above.
  • extraData: Combined field for signer vanity, checkpointing and signer signatures.
    • The first EXTRA_VANITY fixed bytes may contain arbitrary signer vanity data.
    • The last EXTRA_SEAL fixed bytes are the signer's signature sealing the header.
    • Checkpoint blocks must contain a list of signers (SIGNER_COUNT*20 bytes) in between, omitted otherwise.
    • The list of signers in checkpoint block extra-data sections must be sorted in ascending order.
  • mixHash: Reserved for fork protection logic, similar to the extra-data during the DAO.
    • It must be filled with zeroes during regular operation.
  • ommersHash: It must be UNCLE_HASH as uncles are meaningless outside of proof-of-work.
  • timestamp: It must be greater than the parent timestamp.
  • difficulty: It contains the standalone score of the block to derive the quality of a chain.
    • It must be DIFF_NOTURN if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX
    • It must be DIFF_INTURN if BLOCK_NUMBER % SIGNER_COUNT == SIGNER_INDEX

Validator List

The initial validator list can be specified in the configuration at genesis, i.e., by appending it to the Clique config in Geth:

"clique":{
  "period": 15,
  "epoch": 30000,
  "validators": [
    "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
    "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
  ]
}

By using this list, for convenience, the extraData field of the genesis block only has to contain the 32 bytes of EXTRA_VANITY. The client automatically converts and appends the list of signers to the block 0 extra-data as specified in EIP-225 at checkpoint blocks (appending SIGNER_COUNT*20 bytes).

Sealing

For a detailed specification of the block authorization logic, please refer to EIP-225 by honoring the constants defined above. However, the following changes should be highlighted:

  • Each singer is allowed to sign any number of consecutive blocks. The order is not fixed, but in-turn signing weighs more (DIFF_INTURN) than out-of-turn one (DIFF_NOTURN). In case an out-of-turn block is received, an in-turn signer should continue to publish their block to ensure the chain always prefers in-turn blocks in any case. This strategy prevents in-turn validators from being hindered from publishing their block and potential network halting.

    • If a signer is allowed to sign a block, i.e., is on the authorized list:
    • Calculate the Gaussian random signing time of the next block: parent_timestamp + BLOCK_PERIOD + r, where r is a uniform random value in rand(-BLOCK_PERIOD/4, BLOCK_PERIOD/4).
    • If the signer is in-turn, wait for the exact time to arrive, sign and broadcast immediately.
    • If the signer is out-of-turn, delay signing by MIN_WAIT + rand(0, SIGNER_COUNT * 500ms).

This strategy will always ensure that an in-turn signer has a substantial advantage to sign and propagate versus the out-of-turn signers.

Voting

The voting logic is unchanged and can be adapted straight from EIP-225.

Challenges we ran into

The following changes were introduced over Clique EIP-225 and should be discussed briefly.

  • Cliquey introduces a MIN_WAIT period for out-of-turn block to be published which is not present for Clique. This addresses the issue of out-of-turn blocks often getting pushed into the network too fast causing a lot of short reorganizations and in some rare cases causing the network to come to a halt. By holding back out-of-turn blocks, Cliquey allows in-turn validators to seal blocks even under non-optimal network conditions, such as high network latency or validators with unsynchronized clocks.
  • To further strengthen the role of in-turn blocks, an authority should continue to publish in-turn blocks even if an out-of-turn block was already received on the network. This prevents in-turn validators being hindered from publishing their block and potential network problems, such as reorganizations or the network getting stuck.
  • Additionally, the DIFF_INTURN was increased from 2 to 3 to avoid situations where two different chain heads have the same total difficulty. This prevents the network from getting stuck by making in-turn blocks significantly more heavy than out-of-turn blocks.
  • The SIGNER_LIMIT was removed from block sealing logic and is only required for voting. This allows the network to continue sealing blocks even if all but one of the validators are offline. The voting governance is not affected and still requires signer majority.
  • The block period should be less strict and slightly randomized to mimic mainnet conditions. Therefore, it is slightly randomized in the uniform range of [-BLOCK_PERIOD/4, BLOCK_PERIOD/4]. With this, the average block time will still hover around BLOCK_PERIOD.
  • The block time-stamp no longer requires to be greater than the parent block time plus the BLOCK_PERIOD. We propose a simple sanity check on the time-stamp to be greater than the parent time stamp to be sufficient here.

Finally, without changing any consensus logic, we propose the ability to specify an initial list of validators at genesis configuration without tampering with the extraData.

What's next for Cliquey

Implementing the specification in Geth and Parity Ethereum and hardforking Görli and Kotti Classic testnets to upgrade for Cliquey.

Share this project:
×

Updates