nimbus-eth1/nimbus/p2p/clique/clique_miner.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

399 lines
15 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.
##
## Mining Support 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, tables, times],
../../constants,
../../db/[db_chain, state_db],
../../utils,
./clique_cfg,
./clique_defs,
./clique_desc,
./clique_helpers,
./clique_snapshot,
./clique_signers,
./clique_verify,
./snapshot/[snapshot_desc, snapshot_misc],
chronicles,
chronos,
eth/[common, keys, rlp],
nimcrypto
{.push raises: [Defect].}
logScope:
topics = "clique PoA Mining"
type
CliqueSyncDefect* = object of Defect
## Defect raised with lock/unlock problem
# ------------------------------------------------------------------------------
# Private Helpers
# ------------------------------------------------------------------------------
template syncExceptionWrap(action: untyped) =
try:
action
except:
raise (ref CliqueSyncDefect)(msg: getCurrentException().msg)
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
# clique/clique.go(681): func calcDifficulty(snap [..]
proc calcDifficulty(snap: Snapshot; signer: EthAddress): DifficultyInt =
if snap.inTurn(snap.blockNumber + 1, signer):
DIFF_INTURN
else:
DIFF_NOTURN
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
# clique/clique.go(212): func (c *Clique) Author(header [..]
proc author*(c: Clique; header: BlockHeader): Result[EthAddress,CliqueError]
{.gcsafe, raises: [Defect,CatchableError].} =
## For the Consensus Engine, `author()` retrieves the Ethereum address of the
## account that minted the given block, which may be different from the
## header's coinbase if a consensus engine is based on signatures.
##
## This implementation returns the Ethereum address recovered from the
## signature in the header's extra-data section.
c.cfg.ecRecover(header)
# clique/clique.go(217): func (c *Clique) VerifyHeader(chain [..]
proc verifyHeader*(c: Clique; header: BlockHeader): CliqueOkResult
{.gcsafe, raises: [Defect,CatchableError].} =
## For the Consensus Engine, `verifyHeader()` checks whether a header
## conforms to the consensus rules of a given engine. Verifying the seal
## may be done optionally here, or explicitly via the `verifySeal()` method.
##
## This implementation checks whether a header conforms to the consensus
## rules.
c.cliqueVerify(header)
# clique/clique.go(224): func (c *Clique) VerifyHeader(chain [..]
proc verifyHeaders*(c: Clique; headers: openArray[BlockHeader]):
Future[seq[CliqueOkResult]] {.async,gcsafe.} =
## For the Consensus Engine, `verifyHeader()` s similar to VerifyHeader, but
## verifies a batch of headers concurrently. This method is accompanied
## by a `stopVerifyHeader()` method that can abort the operations.
##
## This implementation checks whether a header conforms to the consensus
## rules. It verifies a batch of headers. If running in the background,
## the process can be stopped by calling the `stopVerifyHeader()` function.
syncExceptionWrap:
c.doExclusively:
c.stopVHeaderReq = false
for n in 0 ..< headers.len:
c.doExclusively:
let isStopRequest = c.stopVHeaderReq
if isStopRequest:
result.add cliqueResultErr((errCliqueStopped,""))
break
result.add c.cliqueVerify(headers[n], headers[0 ..< n])
c.doExclusively:
c.stopVHeaderReq = false
proc stopVerifyHeader*(c: Clique): bool {.discardable.} =
## Activate the stop flag for running `verifyHeader()` function.
## Returns `true` if the stop flag could be activated.
syncExceptionWrap:
c.doExclusively:
if not c.stopVHeaderReq:
c.stopVHeaderReq = true
result = true
# clique/clique.go(450): func (c *Clique) VerifyUncles(chain [..]
proc verifyUncles*(c: Clique; ethBlock: EthBlock): CliqueOkResult =
## For the Consensus Engine, `verifyUncles()` verifies that the given
## block's uncles conform to the consensus rules of a given engine.
##
## This implementation always returns an error for existing uncles as this
## consensus mechanism doesn't permit uncles.
if 0 < ethBlock.uncles.len:
return err((errCliqueUnclesNotAllowed,""))
result = ok()
# clique/clique.go(506): func (c *Clique) Prepare(chain [..]
proc prepare*(c: Clique; header: var BlockHeader): CliqueOkResult
{.gcsafe, raises: [Defect,CatchableError].} =
## For the Consensus Engine, `prepare()` initializes the consensus fields
## of a block header according to the rules of a particular engine. The
## changes are executed inline.
##
## This implementation prepares all the consensus fields of the header for
## running the transactions on top.
# If the block isn't a checkpoint, cast a random vote (good enough for now)
header.coinbase.reset
header.nonce.reset
# Assemble the voting snapshot to check which votes make sense
let rc = c.cliqueSnapshot(header.parentHash, @[])
if rc.isErr:
return err(rc.error)
if (header.blockNumber mod c.cfg.epoch) != 0:
c.doExclusively:
# Gather all the proposals that make sense voting on
var addresses: seq[EthAddress]
for (address,authorize) in c.proposals.pairs:
if c.snapshot.isValidVote(address, authorize):
addresses.add address
# If there's pending proposals, cast a vote on them
if 0 < addresses.len:
header.coinbase = addresses[c.cfg.rand(addresses.len-1)]
header.nonce = if header.coinbase in c.proposals: NONCE_AUTH
else: NONCE_DROP
# Set the correct difficulty
header.difficulty = c.snapshot.calcDifficulty(c.signer)
# Ensure the extra data has all its components
header.extraData.setLen(EXTRA_VANITY)
if (header.blockNumber mod c.cfg.epoch) == 0:
header.extraData.add c.cliqueSigners.mapIt(toSeq(it)).concat
header.extraData.add 0.byte.repeat(EXTRA_SEAL)
# Mix digest is reserved for now, set to empty
header.mixDigest.reset
# Ensure the timestamp has the correct delay
var parent: BlockHeader
if not c.db.getBlockHeader(header.blockNumber-1, parent):
return err((errUnknownAncestor,""))
header.timestamp = parent.timestamp + c.cfg.period
if header.timestamp < getTime():
header.timestamp = getTime()
return ok()
# clique/clique.go(571): func (c *Clique) Finalize(chain [..]
proc finalize*(c: Clique; header: BlockHeader; db: AccountStateDB) =
## For the Consensus Engine, `finalize()` runs any post-transaction state
## modifications (e.g. block rewards) but does not assemble the block.
##
## Note: The block header and state database might be updated to reflect any
## consensus rules that happen at finalization (e.g. block rewards).
##
## Not implemented here, raises `AssertionDefect`
raiseAssert "Not implemented"
#
# ## This implementation ensures no uncles are set, nor block rewards given.
# # No block rewards in PoA, so the state remains as is and uncles are dropped
# let deleteEmptyObjectsOk = c.cfg.config.eip158block <= header.blockNumber
# header.stateRoot = db.intermediateRoot(deleteEmptyObjectsOk)
# header.ommersHash = EMPTY_UNCLE_HASH
# clique/clique.go(579): func (c *Clique) FinalizeAndAssemble(chain [..]
proc finalizeAndAssemble*(c: Clique; header: BlockHeader;
db: AccountStateDB; txs: openArray[Transaction];
receipts: openArray[Receipt]):
Result[EthBlock,CliqueError] =
## For the Consensus Engine, `finalizeAndAssemble()` runs any
## post-transaction state modifications (e.g. block rewards) and assembles
## the final block.
##
## Note: The block header and state database might be updated to reflect any
## consensus rules that happen at finalization (e.g. block rewards).
##
## Not implemented here, raises `AssertionDefect`
raiseAssert "Not implemented"
# ## Ensuring no uncles are set, nor block rewards given, and returns the
# ## final block.
#
# # Finalize block
# c.finalize(header, state, txs, uncles)
#
# # Assemble and return the final block for sealing
# return types.NewBlock(header, txs, nil, receipts,
# trie.NewStackTrie(nil)), nil
# clique/clique.go(589): func (c *Clique) Authorize(signer [..]
proc authorize*(c: Clique; signer: EthAddress; signFn: CliqueSignerFn) =
## Injects private key into the consensus engine to mint new blocks with.
syncExceptionWrap:
c.doExclusively:
c.signer = signer
c.signFn = signFn
# clique/clique.go(724): func CliqueRLP(header [..]
proc cliqueRlp*(header: BlockHeader): seq[byte] =
## Returns the rlp bytes which needs to be signed for the proof-of-authority
## sealing. The RLP to sign consists of the entire header apart from the 65
## byte signature contained at the end of the extra data.
##
## Note, the method requires the extra data to be at least 65 bytes,
## otherwise it panics. This is done to avoid accidentally using both forms
## (signature present or not), which could be abused to produce different
##hashes for the same header.
header.encodeSealHeader
# clique/clique.go(688): func SealHash(header *types.Header) common.Hash {
proc sealHash*(header: BlockHeader): Hash256 =
## For the Consensus Engine, `sealHash()` returns the hash of a block prior
## to it being sealed.
##
## This implementation returns the hash of a block prior to it being sealed.
header.hashSealHeader
# clique/clique.go(599): func (c *Clique) Seal(chain [..]
proc seal*(c: Clique; ethBlock: EthBlock):
Future[Result[EthBlock,CliqueError]] {.async,gcsafe.} =
## For the Consensus Engine, `seal()` generates a new sealing request for
## the given input block and pushes the result into the given channel.
##
## Note, the method returns immediately and will send the result async. More
## than one result may also be returned depending on the consensus algorithm.
##
## This implementation attempts to create a sealed block using the local
## signing credentials. If running in the background, the process can be
## stopped by calling the `stopSeal()` function.
c.doExclusively:
c.stopSealReq = false
var header = ethBlock.header
# Sealing the genesis block is not supported
if header.blockNumber.isZero:
return err((errUnknownBlock,""))
# For 0-period chains, refuse to seal empty blocks (no reward but would spin
# sealing)
if c.cfg.period.isZero and ethBlock.txs.len == 0:
info $nilCliqueSealNoBlockYet
return err((nilCliqueSealNoBlockYet,""))
# Don't hold the signer fields for the entire sealing procedure
c.doExclusively:
let
signer = c.signer
signFn = c.signFn
# Bail out if we're unauthorized to sign a block
let rc = c.cliqueSnapshot(header.parentHash)
if rc.isErr:
return err(rc.error)
if not c.snapshot.isSigner(signer):
return err((errUnauthorizedSigner,""))
# If we're amongst the recent signers, wait for the next block
let seen = c.snapshot.recent(signer)
if seen.isOk:
# Signer is among recents, only wait if the current block does not
# shift it out
if header.blockNumber < seen.value + c.snapshot.signersThreshold.u256:
info $nilCliqueSealSignedRecently
return err((nilCliqueSealSignedRecently,""))
# Sweet, the protocol permits us to sign the block, wait for our time
var delay = header.timestamp - getTime()
if header.difficulty == DIFF_NOTURN:
# It's not our turn explicitly to sign, delay it a bit
let wiggle = c.snapshot.signersThreshold.int64 * WIGGLE_TIME
# Kludge for limited rand() argument range
if wiggle.inSeconds < (int.high div 1000).int64:
let rndWiggleMs = c.cfg.rand(wiggle.inMilliSeconds.int)
delay += initDuration(milliseconds = rndWiggleMs)
else:
let rndWiggleSec = c.cfg.rand((wiggle.inSeconds and int.high).int)
delay += initDuration(seconds = rndWiggleSec)
trace "Out-of-turn signing requested",
wiggle = $wiggle
# Sign all the things!
let sigHash = signFn(signer,header.cliqueRlp)
if sigHash.isErr:
return err((errCliqueSealSigFn,$sigHash.error))
let extraLen = header.extraData.len
if EXTRA_SEAL < extraLen:
header.extraData.setLen(extraLen - EXTRA_SEAL)
header.extraData.add sigHash.value.data
# Wait until sealing is terminated or delay timeout.
trace "Waiting for slot to sign and propagate",
delay = $delay
# FIXME: double check
let timeOutTime = getTime() + delay
while getTime() < timeOutTime:
c.doExclusively:
let isStopRequest = c.stopVHeaderReq
if isStopRequest:
warn "Sealing result is not read by miner",
sealhash = sealHash(header)
return err((errCliqueStopped,""))
poll()
c.doExclusively:
c.stopSealReq = false
return ok(ethBlock.withHeader(header))
proc stopSeal*(c: Clique): bool {.discardable.} =
## Activate the stop flag for running `seal()` function.
## Returns `true` if the stop flag could be activated.
syncExceptionWrap:
c.doExclusively:
if not c.stopSealReq:
c.stopSealReq = true
result =true
# clique/clique.go(673): func (c *Clique) CalcDifficulty(chain [..]
proc calcDifficulty(c: Clique;
parent: BlockHeader): Result[DifficultyInt,CliqueError]
{.gcsafe, raises: [Defect,CatchableError].} =
## For the Consensus Engine, `calcDifficulty()` is the difficulty adjustment
## algorithm. It returns the difficulty that a new block should have.
##
## This implementation returns the difficulty that a new block should have:
## * DIFF_NOTURN(2) if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX
## * DIFF_INTURN(1) if BLOCK_NUMBER % SIGNER_COUNT == SIGNER_INDEX
let rc = c.cliqueSnapshot(parent)
if rc.isErr:
return err(rc.error)
return ok(c.snapshot.calcDifficulty(c.signer))
# # clique/clique.go(710): func (c *Clique) SealHash(header [..]
# proc sealHash(c: Clique; header: BlockHeader): Hash256 =
# ## SealHash returns the hash of a block prior to it being sealed.
# header.encodeSigHeader.keccakHash
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------