aleth icon indicating copy to clipboard operation
aleth copied to clipboard

Clique PoA implementation plan

Open gumb0 opened this issue 6 years ago • 11 comments

https://eips.ethereum.org/EIPS/eip-225

Keeping track of signers

Each block has a corresponding list of currently authorized signers and the list of current pending votes. This signers/voting state may be represented as a separate class/struct and CliqueSealEngine will store the map of recently used block hash => clique voting state (Parity has LRU cache with 128 items for this)

  • The block processing has an additional step of creating a voting state for this block (a new virtual method should be added to SealEngineFace for this). More exactly it does the following:

    • For regular block
      • Update vote list (Only the latest proposal per target beneficiary is kept from a single signer)
      • if proposal reaches SIGNER_LIMIT, modify signers list, and discard all votes for this beneficiary
    • For the epoch transition block
      • Discard all pending votes
  • When required voting state is not found in the cache, it is reconstructed from the latest epoch transition block (checkpoint block)

    • Checkpoint state (signer list) is deserialized from extra-data of the checkpoint block header
    • The following blocks' state is replayed (by block processing procedure above) until we reach required block
  • Handling of reorgs - nothing special is required to handle reorgs, when we have the cache of <block hash => voting state> for the last MAX_REORG_DEPTH of blocks. If reorg goes further than MAX_REORG_DEPTH, it will also work by replaying the required voting state from the latest checkpoint block.

  • Tests for signer list logic listed in EIP

  • (optional, I think this is not required for the first implementation) To get rid of heavy replaying voting from the checkpoint, we can serialize signers and votes into DB periodically.

    • This requires modifications in database format, adding some kind of new metadata for the block.
    • Saving them for each block might be wasteful, so a better strategy should be devised.
    • (go-ethereum apparently is saving signers/votes each time it flushes the state trie to disk)

Block validation

These checks should go into verify method of Clique's implementation of SealEngineFace.

  • mixhash is 0
  • uncle list is empty
  • timestamp >= parent.timestamp + 15s
  • check that signer is in the list of authorized signers
  • check that signer's last signed block is more than SIGNER_LIMIT blocks ago
  • difficulty is either 1 or 2, depending on signer index
  • For epoch transition block
    • beneficiary is 0
    • nonce is 0
    • check the list of signers in the block header is equivalent to our calculated list
  • For regular blocks
    • nonce is either 0 or 0xff..f
    • extra-data size is 32 + 65 bytes

Other important things

  • COINBASE opcode should return the signer and not the contents of beneficiary. Probably this may be done by asking CliqueSealEngine for the signer of the block before enacting it and putting it into EnvInfo that will be used by EVM.

CLI

  • Config for Goerli
  • Config for Rinkeby
  • Command line arguments --goerli and --rinkeby

(optional) Sealing

  • Check that we have a private key for the address given through --author

  • This should probably go into CliqueSealEngine::populateFromParent() method:

    • difficulty = 1 if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX, 2 otherwise
    • Epoch transition
      • set beneficiary and nonce to 0
      • include sorted list of signers
    • set mixhash to 0, set uncles to empty list (should happen automatically if we populate from parent)
  • Signing should probably happen in CliqueSealEngine::generateSeal()

  • Make sure we sign only once in SIGNER_LIMIT blocks and only if we're in the signer's list (Probably we'll need to add new virtual methods to SealEngineFace which will decide whether we should sign current block. For Ethash it's simpler because any block can be mined)

  • Time of signing strategy (add random delay for out-of-turn sign)

  • Make sure --extra-data CLI parameter sets only first 32 bytes

(optional) Voting

  • CLI argument for the list of votes or RPC method to enable vote (geth has RPC)
  • add one of the votes from the list of active votes into the block when signing

(optional) Support Goerli in Warp sync

  • Initialize list of signers from the snapshot block

gumb0 avatar Mar 14 '19 16:03 gumb0

Oh awesome, I was just thinking of putting together a plan for this. Great timing!

halfalicious avatar Mar 15 '19 01:03 halfalicious

cc @jwasinger

axic avatar Mar 15 '19 12:03 axic

A note on sealing (and block import) implementation:

The block coinbase field contains the vote (or is zeroed). During contract execution, it must be set to the author of the block and reset to the original value (the vote) after contract execution (and before the block is signed). Difficulty must also be set before contract execution.

jwasinger avatar Mar 15 '19 13:03 jwasinger

@jwasinger yeah I've mentioned coinbase change in "Other important things" part. And DIFFICULTY opcode I think will work the same as for Ethash (will get it from the header)

gumb0 avatar Mar 15 '19 16:03 gumb0

My point was mainly to illustrate that you need to set the COINBASE to a different value when executing contracts, and reset it afterwards to the vote value.

jwasinger avatar Mar 16 '19 17:03 jwasinger

@jwasinger if this is not clarified in https://eips.ethereum.org/EIPS/eip-225 I suggest to create a PR improving the EIP.

axic avatar Mar 16 '19 20:03 axic

Good point. Done https://github.com/ethereum/EIPs/pull/1846

jwasinger avatar Mar 17 '19 00:03 jwasinger

Serializing signers and votes - the lists should be saved into DB periodically (the simplest solution - save them for each block, though this might be wasteful)

I've read through Parity's implementation and they do something simpler, though probably less efficient - just having an in-memory cache of vote states for a number (128) of blocks. In case required state is not present in the cache, it is reconstructed from the latest checkpoint block (by replaying all votes in blocks between checkpoint and required one)

I think it would be easier for us to start with something similar, too.

gumb0 avatar Jun 20 '19 12:06 gumb0

Updated Keeping track of signers section now with a better understanding.

gumb0 avatar Jun 20 '19 13:06 gumb0

Added a bit of more info in other sections, too.

gumb0 avatar Jun 20 '19 13:06 gumb0

Some highlights of Parity's implementation that I find important/relevant for us:

PR with all changes https://github.com/paritytech/parity-ethereum/pull/9981

Higher-level description of how the code works https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/mod.rs#L17-L59

Keeping track of signers

Data structure to keep the Clique signers/voting state https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/block_state.rs#L60

Processing the votes or checkpoint block (update Clique state from the new header) https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/block_state.rs#L163

Cache storing 128 recently used Clique states https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/mod.rs#L164

Function that reconstructs Clique state from checkpoint block if not found in cache https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/mod.rs#L272

Block validation

Validating the header https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/mod.rs#L536

Sealing

Filling the block to seal https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/mod.rs#L381 Another piece of filling the block, including setting the difficulty field https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/mod.rs#L699-L735

Calculating the timestamp when to seal the next block (including random delay for out-of-turn seal) https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/block_state.rs#L268

Other

Optimization for recovery of signer addresses from block signatures: cache that stores recently recovered signer addresses https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/util.rs#L34

Signer address recovery function https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/util.rs#L38

Function to extract the signer list from checkpoint block header https://github.com/paritytech/parity-ethereum/blob/78d0a8696ffec4eb6b0e2214b476fed388e14bed/ethcore/src/engines/clique/util.rs#L85

gumb0 avatar Jun 20 '19 17:06 gumb0