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
|
2021-07-30 14:06:51 +00:00
|
|
|
std/[sequtils, strformat, strutils, tables, times],
|
2021-07-21 13:31:52 +00:00
|
|
|
../../chain_config,
|
|
|
|
../../constants,
|
|
|
|
../../db/db_chain,
|
|
|
|
../../utils,
|
|
|
|
../gaslimit,
|
|
|
|
./clique_cfg,
|
|
|
|
./clique_defs,
|
|
|
|
./clique_desc,
|
|
|
|
./clique_helpers,
|
|
|
|
./clique_snapshot,
|
2021-07-30 14:06:51 +00:00
|
|
|
./snapshot/[ballot, snapshot_desc],
|
2021-07-21 13:31:52 +00:00
|
|
|
chronicles,
|
|
|
|
eth/common,
|
|
|
|
stew/results
|
|
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "clique PoA verify header"
|
|
|
|
|
2021-07-30 14:06:51 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# 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(" ") & ")"
|
|
|
|
|
2021-07-21 13:31:52 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# 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
|
2021-07-30 14:06:51 +00:00
|
|
|
hash = header.blockHash
|
2021-07-21 13:31:52 +00:00
|
|
|
|
|
|
|
if eip150 == hash:
|
|
|
|
return ok()
|
|
|
|
|
|
|
|
err((errCliqueGasRepriceFork,
|
|
|
|
&"Homestead gas reprice fork: have {eip150}, want {hash}"))
|
|
|
|
|
2021-07-30 14:06:51 +00:00
|
|
|
|
|
|
|
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 =
|
2021-08-03 07:15:32 +00:00
|
|
|
## Returns `true` if a signer at a given block height is in-turn.
|
2021-07-30 14:06:51 +00:00
|
|
|
let ascSignersList = s.ballot.authSigners
|
2021-08-03 07:15:32 +00:00
|
|
|
if 0 < ascSignersList.len:
|
|
|
|
let offset = (number mod ascSignersList.len.u256).truncate(int64)
|
|
|
|
return ascSignersList[offset] == signer
|
2021-07-30 14:06:51 +00:00
|
|
|
|
2021-07-21 13:31:52 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# 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
|
2021-07-30 14:06:51 +00:00
|
|
|
let signer = c.cfg.ecRecover(header)
|
2021-07-21 13:31:52 +00:00
|
|
|
if signer.isErr:
|
2021-07-27 11:28:05 +00:00
|
|
|
return err((errEcRecover,$signer.error))
|
2021-07-21 13:31:52 +00:00
|
|
|
|
|
|
|
if not snapshot.isSigner(signer.value):
|
|
|
|
return err((errUnauthorizedSigner,""))
|
|
|
|
|
2021-07-30 14:06:51 +00:00
|
|
|
c.say "verifySeal signer=", c.pp(signer.value), " ", c.pp(snapshot.recents)
|
|
|
|
let seen = snapshot.recentBlockNumber(signer.value)
|
2021-07-21 13:31:52 +00:00
|
|
|
if seen.isOk:
|
2021-07-30 14:06:51 +00:00
|
|
|
c.say "verifySeal signer=#", seen.value,
|
|
|
|
" header=#", header.blockNumber,
|
|
|
|
" threshold=#", snapshot.signersThreshold.u256
|
2021-07-21 13:31:52 +00:00
|
|
|
# Signer is among recents, only fail if the current block does not
|
|
|
|
# shift it out
|
2021-07-30 14:06:51 +00:00
|
|
|
# clique/clique.go(486): if limit := uint64(len(snap.Signers)/2 + 1); [..]
|
2021-07-21 13:31:52 +00:00
|
|
|
if header.blockNumber - snapshot.signersThreshold.u256 < seen.value:
|
|
|
|
return err((errRecentlySigned,""))
|
|
|
|
|
|
|
|
# Ensure that the difficulty corresponds to the turn-ness of the signer
|
2021-07-30 14:06:51 +00:00
|
|
|
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"))
|
2021-07-21 13:31:52 +00:00
|
|
|
|
|
|
|
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
|
2021-07-30 14:06:51 +00:00
|
|
|
parent.blockHash != header.parentHash:
|
2021-07-21 13:31:52 +00:00
|
|
|
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:
|
2021-07-30 14:06:51 +00:00
|
|
|
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):
|
2021-07-21 13:31:52 +00:00
|
|
|
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.
|
2021-06-11 11:48:22 +00:00
|
|
|
c.failed = (ZERO_HASH256,cliqueNoError)
|
2021-07-21 13:31:52 +00:00
|
|
|
|
|
|
|
block:
|
|
|
|
# Check header fields independent of parent blocks
|
|
|
|
let rc = c.verifyHeaderFields(header)
|
|
|
|
if rc.isErr:
|
2021-07-30 14:06:51 +00:00
|
|
|
c.failed = (header.blockHash, rc.error)
|
2021-07-21 13:31:52 +00:00
|
|
|
return err(rc.error)
|
|
|
|
|
|
|
|
block:
|
|
|
|
# If all checks passed, validate any special fields for hard forks
|
|
|
|
let rc = c.verifyForkHashes(header)
|
|
|
|
if rc.isErr:
|
2021-07-30 14:06:51 +00:00
|
|
|
c.failed = (header.blockHash, rc.error)
|
2021-07-21 13:31:52 +00:00
|
|
|
return err(rc.error)
|
|
|
|
|
|
|
|
# All basic checks passed, verify cascading fields
|
|
|
|
result = c.verifyCascadingFields(header, parents)
|
|
|
|
if result.isErr:
|
2021-07-30 14:06:51 +00:00
|
|
|
c.failed = (header.blockHash, result.error)
|
2021-07-21 13:31:52 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# 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
|
|
|
|
# ------------------------------------------------------------------------------
|