nimbus-eth1/nimbus/p2p/clique/clique_verify.nim

349 lines
13 KiB
Nim
Raw Normal View History

Renamed source file clique_utils => clique_helpers (#762) * Renamed source file clique_utils => clique_helpers why: New name is more in line with other modules where local libraries are named similarly. * re-implemented PoA verification module as clique_verify.nim details: The verification code was ported from the go sources and provisionally stored in the clique_misc.nim source file. todo: Bring it to life. * re-design Snapshot descriptor as: ref object why: Avoids some copying descriptor objects details: The snapshot management in clique_snapshot.nim has been cleaned up. todo: There is a lot of unnecessary copying & sub-list manipulation of seq[BlockHeader] lists which needs to be simplified by managing index intervals. * optimised sequence handling for Clique/PoA why: To much ado about nothing details: * Working with shallow sequences inside PoA processing avoids unnecessary copying. * Using degenerate lists in the cliqueVerify() batch where only the parent (and no other ancestor) is needed. todo: Expose only functions that are needed, shallow sequences should be handles with care. * fix var-parameter function argument * Activate PoA engine -- currently proof of concept details: PoA engine is activated with newChain(extraValidation = true) applied to a PoA network. status and todo: The extraValidation flag on the Chain object can be set at a later state which allows to pre-load parts of the block chain without verification. Setting it later will only go back the block chain to the latest epoch checkpoint. This is inherent to the Clique protocol, needs testing though. PoA engine works in fine weather mode on Goerli replay. With the canonical eip-225 tests, there are quite a few fringe conditions that fail. These can easily fudged over to make things work but need some more work to understand and correct properly. * Make the last offending verification header available why: Makes some fringe case tests work. details: Within a failed transaction comprising several blocks, this feature help to identify the offending block if there was a PoA verification error. * Make PoA header verifier store the final snapshot why: The last snapshot needed by the verifier is the one of the parent but the list of authorised signer is derived from the current snapshot. So updating to the latest snapshot provides the latest signers list. details: Also, PoA processing has been implemented as transaction in persistBlocks() with Clique state rollback. Clique tests succeed now. * Avoiding double yields in iterator => replaced by template why: Tanks to Andri who observed it (see #762) * Calibrate logging interval and fix logging event detection why: Logging interval as copied from Go implementation was too large and needed re-calibration. Elapsed time calculation was bonkers, negative the wrong way round.
2021-07-21 13:31:52 +00:00
# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
##
## Verify Headers for Clique PoA Consensus Protocol
## ================================================
##
## Note that mining in currently unsupported by `NIMBUS`
##
## For details see
## `EIP-225 <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-225.md>`_
## and
## `go-ethereum <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-225.md>`_
##
import
std/[sequtils, strformat, tables, times],
../../chain_config,
../../constants,
../../db/db_chain,
../../utils,
../gaslimit,
./clique_cfg,
./clique_defs,
./clique_desc,
./clique_helpers,
./clique_snapshot,
./snapshot/[ballot, snapshot_desc, snapshot_misc],
chronicles,
eth/common,
stew/results
{.push raises: [Defect].}
logScope:
topics = "clique PoA verify header"
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
# consensus/misc/forks.go(30): func VerifyForkHashes(config [..]
proc verifyForkHashes(c: Clique; header: BlockHeader): CliqueOkResult
{.inline, raises: [Defect,ValueError].} =
## Verify that blocks conforming to network hard-forks do have the correct
## hashes, to avoid clients going off on different chains.
if c.db.config.eip150Block.isZero or
c.db.config.eip150Block != header.blockNumber:
return ok()
# If the homestead reprice hash is set, validate it
let
eip150 = c.db.config.eip150Hash
hash = header.hash
if eip150 == hash:
return ok()
err((errCliqueGasRepriceFork,
&"Homestead gas reprice fork: have {eip150}, want {hash}"))
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
# clique/clique.go(463): func (c *Clique) verifySeal(chain [..]
proc verifySeal(c: Clique; header: BlockHeader): CliqueOkResult
{.inline, raises: [Defect,CatchableError].} =
## Check whether the signature contained in the header satisfies the
## consensus protocol requirements. The method accepts an optional list of
## parent headers that aren't yet part of the local blockchain to generate
## the snapshots from.
# Verifying the genesis block is not supported
if header.blockNumber.isZero:
return err((errUnknownBlock,""))
# Get current snapshot
let snapshot = c.snapshot
# Verify availability of the cached snapshot
doAssert snapshot.blockHash == header.parentHash
# Resolve the authorization key and check against signers
let signer = c.cfg.ecRecover(header)
if signer.isErr:
return err(signer.error)
if not snapshot.isSigner(signer.value):
return err((errUnauthorizedSigner,""))
let seen = snapshot.recent(signer.value)
if seen.isOk:
# Signer is among recents, only fail if the current block does not
# shift it out
if header.blockNumber - snapshot.signersThreshold.u256 < seen.value:
return err((errRecentlySigned,""))
# Ensure that the difficulty corresponds to the turn-ness of the signer
if not c.fakeDiff:
if snapshot.inTurn(header.blockNumber, signer.value):
if header.difficulty != DIFF_INTURN:
return err((errWrongDifficulty,""))
else:
if header.difficulty != DIFF_NOTURN:
return err((errWrongDifficulty,""))
ok()
# clique/clique.go(314): func (c *Clique) verifyCascadingFields(chain [..]
proc verifyCascadingFields(c: Clique; header: BlockHeader;
parents: var seq[BlockHeader]): CliqueOkResult
{.inline, raises: [Defect,CatchableError].} =
## Verify all the header fields that are not standalone, rather depend on a
## batch of previous headers. The caller may optionally pass in a batch of
## parents (ascending order) to avoid looking those up from the database.
## This is useful for concurrently verifying a batch of new headers.
# The genesis block is the always valid dead-end
if header.blockNumber.isZero:
return ok()
# Ensure that the block's timestamp isn't too close to its parent
var parent: BlockHeader
if 0 < parents.len:
parent = parents[^1]
elif not c.db.getBlockHeader(header.blockNumber-1, parent):
return err((errUnknownAncestor,""))
if parent.blockNumber != header.blockNumber-1 or
parent.hash != header.parentHash:
return err((errUnknownAncestor,""))
# clique/clique.go(330): if parent.Time+c.config.Period > header.Time {
if header.timestamp < parent.timestamp + c.cfg.period:
return err((errInvalidTimestamp,""))
# Verify that the gasUsed is <= gasLimit
block:
# clique/clique.go(333): if header.GasUsed > header.GasLimit {
let (used, limit) = (header.gasUsed, header.gasLimit)
if limit < used:
return err((errCliqueExceedsGasLimit,
&"invalid gasUsed: have {used}, gasLimit {limit}"))
# Verify `GasLimit` or `BaseFee` depending on whether before or after
# EIP-1559/London fork.
block:
# clique/clique.go(337): if !chain.Config().IsLondon(header.Number) {
let rc = c.db.validateGasLimitOrBaseFee(header, parent)
if rc.isErr:
return err((errCliqueGasLimitOrBaseFee, rc.error))
# Retrieve the snapshot needed to verify this header and cache it
block:
# clique/clique.go(350): snap, err := c.snapshot(chain, number-1, ..
let rc = c.cliqueSnapshotSeq(header.parentHash, parents)
if rc.isErr:
return err(rc.error)
# If the block is a checkpoint block, verify the signer list
if (header.blockNumber mod c.cfg.epoch.u256) == 0:
if c.snapshot.ballot.authSigners != header.extraData.extraDataAddresses:
return err((errMismatchingCheckpointSigners,""))
# All basic checks passed, verify the seal and return
return c.verifySeal(header)
proc verifyHeaderFields(c: Clique; header: BlockHeader): CliqueOkResult
{.inline, raises: [Defect,CatchableError].} =
## Check header fields, the ones that do not depend on a parent block.
# clique/clique.go(250): number := header.Number.Uint64()
# Don't waste time checking blocks from the future
if getTime() < header.timestamp:
return err((errFutureBlock,""))
# Checkpoint blocks need to enforce zero beneficiary
let isCheckPoint = (header.blockNumber mod c.cfg.epoch.u256) == 0
if isCheckPoint and not header.coinbase.isZero:
return err((errInvalidCheckpointBeneficiary,""))
# Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
if header.nonce != NONCE_AUTH and header.nonce != NONCE_DROP:
return err((errInvalidVote,""))
if isCheckPoint and header.nonce != NONCE_DROP:
return err((errInvalidCheckpointVote,""))
# Check that the extra-data contains both the vanity and signature
if header.extraData.len < EXTRA_VANITY:
return err((errMissingVanity,""))
if header.extraData.len < EXTRA_VANITY + EXTRA_SEAL:
return err((errMissingSignature,""))
# Ensure that the extra-data contains a signer list on a checkpoint,
# but none otherwise
let signersBytes = header.extraData.len - EXTRA_VANITY - EXTRA_SEAL
if not isCheckPoint:
if signersBytes != 0:
return err((errExtraSigners,""))
elif (signersBytes mod EthAddress.len) != 0:
return err((errInvalidCheckpointSigners,""))
# Ensure that the mix digest is zero as we do not have fork protection
# currently
if not header.mixDigest.isZero:
return err((errInvalidMixDigest,""))
# Ensure that the block does not contain any uncles which are meaningless
# in PoA
if header.ommersHash != EMPTY_UNCLE_HASH:
return err((errInvalidUncleHash,""))
# Ensure that the block's difficulty is meaningful (may not be correct at
# this point)
if not header.blockNumber.isZero:
# Note that neither INTURN or NOTURN should be zero (but this might be
# subject to change as it is explicitely checked for in `clique.go`)
let diffy = header.difficulty
# clique/clique.go(246): if header.Difficulty == nil || (header.Difficulty..
if diffy.isZero or (diffy != DIFF_INTURN and diffy != DIFF_NOTURN):
return err((errInvalidDifficulty,""))
# verify that the gas limit is <= 2^63-1
when header.gasLimit.typeof isnot int64:
if int64.high < header.gasLimit:
return err((errCliqueExceedsGasLimit,
&"invalid gasLimit: have {header.gasLimit}, must be int64"))
ok()
# clique/clique.go(246): func (c *Clique) verifyHeader(chain [..]
proc cliqueVerifyImpl*(c: Clique; header: BlockHeader;
parents: var seq[BlockHeader]): CliqueOkResult
{.gcsafe, raises: [Defect,CatchableError].} =
## Check whether a header conforms to the consensus rules. The caller may
## optionally pass in a batch of parents (ascending order) to avoid looking
## those up from the database. This is useful for concurrently verifying
## a batch of new headers.
c.failed = (ZERO_HASH32,cliqueNoError)
block:
# Check header fields independent of parent blocks
let rc = c.verifyHeaderFields(header)
if rc.isErr:
c.failed = (header.hash, rc.error)
return err(rc.error)
block:
# If all checks passed, validate any special fields for hard forks
let rc = c.verifyForkHashes(header)
if rc.isErr:
c.failed = (header.hash, rc.error)
return err(rc.error)
# All basic checks passed, verify cascading fields
result = c.verifyCascadingFields(header, parents)
if result.isErr:
c.failed = (header.hash, result.error)
# ------------------------------------------------------------------------------
# Public function
# ------------------------------------------------------------------------------
proc cliqueVerifySeq*(c: Clique; header: BlockHeader;
parents: var seq[BlockHeader]): CliqueOkResult
{.gcsafe, raises: [Defect,CatchableError].} =
## Check whether a header conforms to the consensus rules. The caller may
## optionally pass in a batch of parents (ascending order) to avoid looking
## those up from the database. This is useful for concurrently verifying
## a batch of new headers.
##
## On success, the latest authorised signers list is available via the
## fucntion `c.cliqueSigners()`. Otherwise, the latest error is also stored
## in the `Clique` descriptor
##
## If there is an error, this error is also stored within the `Clique`
## descriptor and can be retrieved via `c.failed` along with the hash/ID of
## the failed block header.
block:
let rc = c.cliqueVerifyImpl(header, parents)
if rc.isErr:
return rc
# Adjust current shapshot (the function `cliqueVerifyImpl()` typically
# works with the parent snapshot.
block:
let rc = c.cliqueSnapshotSeq(header, parents)
if rc.isErr:
return err(rc.error)
ok()
proc cliqueVerifySeq*(c: Clique;
headers: var seq[BlockHeader]): CliqueOkResult
{.gcsafe, raises: [Defect,CatchableError].} =
## This function verifies a batch of headers checking each header for
## consensus rules conformance. The `headers` list is supposed to
## contain a chain of headers, i e. `headers[i]` is parent to `headers[i+1]`.
##
## On success, the latest authorised signers list is available via the
## fucntion `c.cliqueSigners()`. Otherwise, the latest error is also stored
## in the `Clique` descriptor
##
## If there is an error, this error is also stored within the `Clique`
## descriptor and can be retrieved via `c.failed` along with the hash/ID of
## the failed block header.
##
## Note that the sequence argument must be write-accessible, even though it
## will be left untouched by this function.
if 0 < headers.len:
headers.shallow
block:
var blind: seq[BlockHeader]
let rc = c.cliqueVerifyImpl(headers[0],blind)
if rc.isErr:
return rc
for n in 1 ..< headers.len:
var parent = headers[n-1 .. n-1] # is actually a single item squence
let rc = c.cliqueVerifyImpl(headers[n],parent)
if rc.isErr:
return rc
# Adjust current shapshot (the function `cliqueVerifyImpl()` typically
# works with the parent snapshot.
block:
let rc = c.cliqueSnapshot(headers[^1])
if rc.isErr:
return err(rc.error)
ok()
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------