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

397 lines
14 KiB
Nim

# 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, strutils, tables, times],
../../chain_config,
../../constants,
../../db/db_chain,
../../utils,
../gaslimit,
./clique_cfg,
./clique_defs,
./clique_desc,
./clique_helpers,
./clique_snapshot,
./snapshot/[ballot, snapshot_desc],
chronicles,
eth/common,
stew/results
{.push raises: [Defect].}
logScope:
topics = "clique PoA verify header"
# ------------------------------------------------------------------------------
# Private helpers, pretty printing
# ------------------------------------------------------------------------------
proc say(c: Clique; v: varargs[string,`$`]) {.inline.} =
discard
# uncomment body to enable
#c.cfg.say v
proc pp(c: Clique; a: AddressHistory): string
{.inline, raises: [Defect,CatchableError].} =
"(" & toSeq(a.pairs).mapIt(&"#{it[0]}:{c.pp(it[1])}").join(" ") & ")"
# ------------------------------------------------------------------------------
# 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.blockHash
if eip150 == hash:
return ok()
err((errCliqueGasRepriceFork,
&"Homestead gas reprice fork: have {eip150}, want {hash}"))
proc signersThreshold*(s: Snapshot): int {.inline.} =
## Minimum number of authorised signers needed.
s.ballot.authSignersThreshold
proc recentBlockNumber*(s: Snapshot;
a: EthAddress): Result[BlockNumber,void] {.inline.} =
## Return `BlockNumber` for `address` argument (if any)
for (number,recent) in s.recents.pairs:
if recent == a:
return ok(number)
return err()
proc isSigner*(s: Snapshot; address: EthAddress): bool {.inline.} =
## Checks whether argukment ``address` is in signers list
s.ballot.isAuthSigner(address)
# clique/snapshot.go(319): func (s *Snapshot) inturn(number [..]
proc inTurn*(s: Snapshot; number: BlockNumber, signer: EthAddress): bool =
## Returns `true` if a signer at a given block height is in-turn.
let ascSignersList = s.ballot.authSigners
if 0 < ascSignersList.len:
let offset = (number mod ascSignersList.len.u256).truncate(int64)
return ascSignersList[offset] == signer
# ------------------------------------------------------------------------------
# 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((errEcRecover,$signer.error))
if not snapshot.isSigner(signer.value):
return err((errUnauthorizedSigner,""))
c.say "verifySeal signer=", c.pp(signer.value), " ", c.pp(snapshot.recents)
let seen = snapshot.recentBlockNumber(signer.value)
if seen.isOk:
c.say "verifySeal signer=#", seen.value,
" header=#", header.blockNumber,
" threshold=#", snapshot.signersThreshold.u256
# Signer is among recents, only fail if the current block does not
# shift it out
# clique/clique.go(486): if limit := uint64(len(snap.Signers)/2 + 1); [..]
if header.blockNumber - snapshot.signersThreshold.u256 < seen.value:
return err((errRecentlySigned,""))
# Ensure that the difficulty corresponds to the turn-ness of the signer
if snapshot.inTurn(header.blockNumber, signer.value):
if header.difficulty != DIFF_INTURN:
return err((errWrongDifficulty,"INTURN expected"))
else:
if header.difficulty != DIFF_NOTURN:
return err((errWrongDifficulty,"NOTURN expected"))
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.blockHash != 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:
var addrList = header.extraData.extraDataAddresses
# not using `authSigners()` here as it is too slow
if c.snapshot.ballot.authSignersLen != addrList.len or
not c.snapshot.ballot.isAuthSigner(addrList):
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_HASH256,cliqueNoError)
block:
# Check header fields independent of parent blocks
let rc = c.verifyHeaderFields(header)
if rc.isErr:
c.failed = (header.blockHash, 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.blockHash, rc.error)
return err(rc.error)
# All basic checks passed, verify cascading fields
result = c.verifyCascadingFields(header, parents)
if result.isErr:
c.failed = (header.blockHash, 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
# ------------------------------------------------------------------------------