nimbus-eth1/nimbus/p2p/clique/clique_cfg.nim
Jordan Hrycaj a0d0e35a70
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 14:31:52 +01:00

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
# ------------------------------------------------------------------------------