mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-02-21 08:18:31 +00:00
* 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.
301 lines
10 KiB
Nim
301 lines
10 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.
|
|
|
|
##
|
|
## Clique PoA Conmmon Config
|
|
## =========================
|
|
##
|
|
## Constants used by Clique proof-of-authority consensus protocol, 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/[random, sequtils, strutils, times],
|
|
../../db/db_chain,
|
|
./clique_cfg/ec_recover,
|
|
./clique_defs,
|
|
eth/common,
|
|
ethash,
|
|
stew/results,
|
|
stint
|
|
|
|
const
|
|
prngSeed = 42
|
|
|
|
type
|
|
SimpleTypePP = BlockNonce|EthAddress|Blob|BlockHeader
|
|
SeqTypePP = EthAddress|BlockHeader
|
|
|
|
PrettyPrintDefect* = object of Defect
|
|
## Defect raised with `pp()` problems, should be used for debugging only
|
|
|
|
PrettyPrinters* = object ## Set of pretty printers for debugging
|
|
nonce*: proc(v: BlockNonce):
|
|
string {.gcsafe,raises: [Defect,CatchableError].}
|
|
address*: proc(v: EthAddress):
|
|
string {.gcsafe,raises: [Defect,CatchableError].}
|
|
extraData*: proc(v: Blob):
|
|
string {.gcsafe,raises: [Defect,CatchableError].}
|
|
blockHeader*: proc(v: BlockHeader; delim: string):
|
|
string {.gcsafe,raises: [Defect,CatchableError].}
|
|
|
|
CliqueCfg* = ref object of RootRef
|
|
db*: BaseChainDB ##\
|
|
## All purpose (incl. blockchain) database.
|
|
|
|
period: Duration ##\
|
|
## Time between blocks to enforce.
|
|
|
|
ckpInterval: int ##\
|
|
## Number of blocks after which to save the vote snapshot to the
|
|
## database.
|
|
|
|
roThreshold: int ##\
|
|
## Number of blocks after which a chain segment is considered immutable
|
|
## (ie. soft finality). It is used by the downloader as a hard limit
|
|
## against deep ancestors, by the blockchain against deep reorgs, by the
|
|
## freezer as the cutoff threshold and by clique as the snapshot trust
|
|
## limit.
|
|
|
|
prng: Rand ##\
|
|
## PRNG state for internal random generator. This PRNG is
|
|
## cryptographically insecure but with reproducible data stream.
|
|
|
|
signatures: EcRecover ##\
|
|
## Recent block signatures cached to speed up mining.
|
|
|
|
epoch: int ##\
|
|
## The number of blocks after which to checkpoint and reset the pending
|
|
## votes.Suggested 30000 for the testnet to remain analogous to the
|
|
## mainnet ethash epoch.
|
|
|
|
logInterval: Duration ##\
|
|
## Time interval after which the `snapshotApply()` function main loop
|
|
## produces logging entries.
|
|
|
|
debug*: bool ##\
|
|
## Debug mode flag
|
|
|
|
prettyPrint*: PrettyPrinters ##\
|
|
## debugging support
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public constructor
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc newCliqueCfg*(db: BaseChainDB): CliqueCfg =
|
|
result = CliqueCfg(
|
|
db: db,
|
|
epoch: EPOCH_LENGTH,
|
|
period: BLOCK_PERIOD,
|
|
ckpInterval: CHECKPOINT_INTERVAL,
|
|
roThreshold: FULL_IMMUTABILITY_THRESHOLD,
|
|
logInterval: SNAPS_LOG_INTERVAL_MICSECS,
|
|
signatures: initEcRecover(),
|
|
prng: initRand(prngSeed),
|
|
prettyPrint: PrettyPrinters(
|
|
nonce: proc(v:BlockNonce): string = $v,
|
|
address: proc(v:EthAddress): string = $v,
|
|
extraData: proc(v:Blob): string = $v,
|
|
blockHeader: proc(v:BlockHeader; delim:string): string = $v))
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public helper funcion
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# clique/clique.go(145): func ecrecover(header [..]
|
|
proc ecRecover*(cfg: CliqueCfg; header: BlockHeader): auto
|
|
{.gcsafe, raises: [Defect,CatchableError].} =
|
|
cfg.signatures.getEcRecover(header)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public setters
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc `epoch=`*(cfg: CliqueCfg; epoch: SomeInteger) {.inline.} =
|
|
## Setter
|
|
cfg.epoch = if 0 < epoch: epoch
|
|
else: EPOCH_LENGTH
|
|
|
|
proc `period=`*(cfg: CliqueCfg; period: Duration) {.inline.} =
|
|
## Setter
|
|
cfg.period = if period != Duration(): period
|
|
else: BLOCK_PERIOD
|
|
|
|
proc `ckpInterval=`*(cfg: CliqueCfg; numBlocks: SomeInteger) {.inline.} =
|
|
## Setter
|
|
cfg.ckpInterval = if 0 < numBlocks: numBlocks
|
|
else: CHECKPOINT_INTERVAL
|
|
|
|
proc `roThreshold=`*(cfg: CliqueCfg; numBlocks: SomeInteger) {.inline.} =
|
|
## Setter
|
|
cfg.roThreshold = if 0 < numBlocks: numBlocks
|
|
else: FULL_IMMUTABILITY_THRESHOLD
|
|
|
|
proc `logInterval=`*(cfg: CliqueCfg; duration: Duration) {.inline.} =
|
|
## Setter
|
|
cfg.logInterval = if duration != Duration(): duration
|
|
else: SNAPS_LOG_INTERVAL_MICSECS
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public PRNG, may be overloaded
|
|
# ------------------------------------------------------------------------------
|
|
|
|
method rand*(cfg: CliqueCfg; max: Natural): int {.gcsafe,base.} =
|
|
## The method returns a random number base on an internal PRNG providing a
|
|
## reproducible stream of random data. This function is supposed to be used
|
|
## exactly when repeatability comes in handy. Never to be used for crypto key
|
|
## generation or like (except testing.)
|
|
cfg.prng.rand(max)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public getter
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc epoch*(cfg: CliqueCfg): auto {.inline.} =
|
|
## Getter
|
|
cfg.epoch.u256
|
|
|
|
proc period*(cfg: CliqueCfg): auto {.inline.} =
|
|
## Getter
|
|
cfg.period
|
|
|
|
proc ckpInterval*(cfg: CliqueCfg): auto {.inline.} =
|
|
## Getter
|
|
cfg.ckpInterval.u256
|
|
|
|
proc roThreshold*(cfg: CliqueCfg): auto {.inline.} =
|
|
## Getter
|
|
cfg.roThreshold
|
|
|
|
proc logInterval*(cfg: CliqueCfg): auto {.inline.} =
|
|
## Getter
|
|
cfg.logInterval
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Debugging
|
|
# ------------------------------------------------------------------------------
|
|
|
|
template ppExceptionWrap*(body: untyped) =
|
|
## Exception relay to `PrettyPrintDefect`, intended to be used with `pp()`
|
|
## related functions.
|
|
try:
|
|
body
|
|
except:
|
|
raise (ref PrettyPrintDefect)(msg: getCurrentException().msg)
|
|
|
|
proc say*(cfg: CliqueCfg; v: varargs[string,`$`]) {.inline.} =
|
|
## Debugging output
|
|
ppExceptionWrap:
|
|
if cfg.debug: stderr.write "*** " & v.join & "\n"
|
|
|
|
|
|
proc pp*(v: CliqueError): string =
|
|
## Pretty print error
|
|
result = $v[0]
|
|
if v[1] != "":
|
|
result &= " => " & v[1]
|
|
|
|
proc pp*(v: CliqueOkResult): string =
|
|
## Pretty print result
|
|
if v.isOk:
|
|
"OK"
|
|
else:
|
|
v.error.pp
|
|
|
|
|
|
proc pp*(p: var PrettyPrinters; v: BlockNonce): string =
|
|
## Pretty print nonce (for debugging)
|
|
ppExceptionWrap: p.nonce(v)
|
|
|
|
proc pp*(p: var PrettyPrinters; v: EthAddress): string =
|
|
## Pretty print address (for debugging)
|
|
ppExceptionWrap: p.address(v)
|
|
|
|
proc pp*(p: var PrettyPrinters; v: openArray[EthAddress]): seq[string] =
|
|
## Pretty print address list
|
|
toSeq(v).mapIt(p.pp(it))
|
|
|
|
proc pp*(p: var PrettyPrinters; v: Blob): string =
|
|
## Visualise `extraData` field
|
|
ppExceptionWrap: p.extraData(v)
|
|
|
|
proc pp*(p: var PrettyPrinters; v: BlockHeader; delim: string): string =
|
|
## Pretty print block header
|
|
ppExceptionWrap: p.blockHeader(v, delim)
|
|
|
|
proc pp*(p: var PrettyPrinters; v: BlockHeader; indent = 3): string =
|
|
## Pretty print block header, NL delimited, indented fields
|
|
let delim = if 0 < indent: "\n" & ' '.repeat(indent) else: " "
|
|
p.pp(v,delim)
|
|
|
|
proc pp*(p: var PrettyPrinters; v: openArray[BlockHeader]): seq[string] =
|
|
## Pretty print list of block headers
|
|
toSeq(v).mapIt(p.pp(it,","))
|
|
|
|
|
|
proc pp*[T;V: SimpleTypePP](t: T; v: V): string =
|
|
## Generic pretty printer, requires `getPrettyPrinters()` function:
|
|
## ::
|
|
## proc getPrettyPrinters(t: SomeLocalType): var PrettyPrinters
|
|
##
|
|
mixin getPrettyPrinters
|
|
ppExceptionWrap: t.getPrettyPrinters.pp(v)
|
|
|
|
proc pp*[T;V: var SimpleTypePP](t: var T; v: V): string =
|
|
## Generic pretty printer, requires `getPrettyPrinters()` function:
|
|
## ::
|
|
## proc getPrettyPrinters(t: var SomeLocalType): var PrettyPrinters
|
|
##
|
|
mixin getPrettyPrinters
|
|
ppExceptionWrap: t.getPrettyPrinters.pp(v)
|
|
|
|
|
|
proc pp*[T;V: SeqTypePP](t: T; v: openArray[V]): seq[string] =
|
|
## Generic pretty printer, requires `getPrettyPrinters()` function:
|
|
## ::
|
|
## proc getPrettyPrinters(t: SomeLocalType): var PrettyPrinters
|
|
##
|
|
mixin getPrettyPrinters
|
|
ppExceptionWrap: t.getPrettyPrinters.pp(v)
|
|
|
|
proc pp*[T;V: SeqTypePP](t: var T; v: openArray[V]): seq[string] =
|
|
## Generic pretty printer, requires `getPrettyPrinters()` function:
|
|
## ::
|
|
## proc getPrettyPrinters(t: var SomeLocalType): var PrettyPrinters
|
|
##
|
|
mixin getPrettyPrinters
|
|
ppExceptionWrap: t.getPrettyPrinters.pp(v)
|
|
|
|
|
|
proc pp*[T;X: int|string](t: T; v: BlockHeader; sep: X): string =
|
|
## Generic pretty printer, requires `getPrettyPrinters()` function:
|
|
## ::
|
|
## proc getPrettyPrinters(t: SomeLocalType): var PrettyPrinters
|
|
##
|
|
mixin getPrettyPrinters
|
|
ppExceptionWrap: t.getPrettyPrinters.pp(v,sep)
|
|
|
|
proc pp*[T;X: int|string](t: var T; v: BlockHeader; sep: X): string =
|
|
## Generic pretty printer, requires `getPrettyPrinters()` function:
|
|
## ::
|
|
## proc getPrettyPrinters(t: var SomeLocalType): var PrettyPrinters
|
|
##
|
|
mixin getPrettyPrinters
|
|
ppExceptionWrap: t.getPrettyPrinters.pp(v,sep)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|