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

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