mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-27 12:35:00 +00:00
Eip225 clique/PoA consensus protocol
details: formal port from go-lang sources, compiles but will not do anything useful yet
This commit is contained in:
parent
aef7a25174
commit
491149c6d5
@ -10,6 +10,7 @@ type
|
|||||||
canonicalHeadHash
|
canonicalHeadHash
|
||||||
slotHashToSlot
|
slotHashToSlot
|
||||||
contractHash
|
contractHash
|
||||||
|
cliqueSnapshot
|
||||||
|
|
||||||
DbKey* = object
|
DbKey* = object
|
||||||
# The first byte stores the key type. The rest are key-specific values
|
# The first byte stores the key type. The rest are key-specific values
|
||||||
@ -52,6 +53,11 @@ proc contractHashKey*(h: Hash256): DbKey {.inline.} =
|
|||||||
result.data[1 .. 32] = h.data
|
result.data[1 .. 32] = h.data
|
||||||
result.dataEndPos = uint8 32
|
result.dataEndPos = uint8 32
|
||||||
|
|
||||||
|
proc cliqueSnapshotKey*(h: Hash256): DbKey {.inline.} =
|
||||||
|
result.data[0] = byte ord(cliqueSnapshot)
|
||||||
|
result.data[1 .. 32] = h.data
|
||||||
|
result.dataEndPos = uint8 32
|
||||||
|
|
||||||
template toOpenArray*(k: DbKey): openarray[byte] =
|
template toOpenArray*(k: DbKey): openarray[byte] =
|
||||||
k.data.toOpenArray(0, int(k.dataEndPos))
|
k.data.toOpenArray(0, int(k.dataEndPos))
|
||||||
|
|
||||||
|
591
nimbus/p2p/clique.nim
Normal file
591
nimbus/p2p/clique.nim
Normal file
@ -0,0 +1,591 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
##
|
||||||
|
## EIP-225 Clique PoA Consensus Protocol
|
||||||
|
## =====================================
|
||||||
|
##
|
||||||
|
## 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
|
||||||
|
../db/db_chain,
|
||||||
|
../utils,
|
||||||
|
./clique/[clique_cfg, clique_defs, clique_utils, ec_recover, recent_snaps],
|
||||||
|
chronicles,
|
||||||
|
chronos,
|
||||||
|
eth/[common, keys, rlp],
|
||||||
|
# ethash,
|
||||||
|
nimcrypto,
|
||||||
|
random,
|
||||||
|
sequtils,
|
||||||
|
strformat,
|
||||||
|
tables,
|
||||||
|
times
|
||||||
|
|
||||||
|
type
|
||||||
|
# clique/clique.go(142): type SignerFn func(signer [..]
|
||||||
|
CliqueSignerFn* = ## Hashes and signs the data to be signed by
|
||||||
|
## a backing account
|
||||||
|
proc(signer: EthAddress;
|
||||||
|
message: openArray[byte]): Result[Hash256,cstring] {.gcsafe.}
|
||||||
|
|
||||||
|
Proposals = Table[EthAddress,bool]
|
||||||
|
|
||||||
|
# clique/clique.go(172): type Clique struct { [..]
|
||||||
|
Clique* = object ## Clique is the proof-of-authority consensus engine
|
||||||
|
## proposed to support the Ethereum testnet following
|
||||||
|
## the Ropsten attacks.
|
||||||
|
cfg: CliqueCfg ## Consensus engine parameters to fine tune behaviour
|
||||||
|
|
||||||
|
recents: RecentSnaps ## Snapshots for recent block to speed up reorgs
|
||||||
|
# signatures => see CliqueCfg
|
||||||
|
|
||||||
|
proposals: Proposals ## Current list of proposals we are pushing
|
||||||
|
|
||||||
|
signer: EthAddress ## Ethereum address of the signing key
|
||||||
|
signFn: CliqueSignerFn ## Signer function to authorize hashes with
|
||||||
|
lock: AsyncLock ## Protects the signer fields
|
||||||
|
|
||||||
|
fakeDiff: bool ## Testing only: skip difficulty verifications
|
||||||
|
stopSealReq: bool ## Stop running `seal()` function
|
||||||
|
stopVHeaderReq: bool ## Stop running `verifyHeader()` function
|
||||||
|
|
||||||
|
{.push raises: [Defect,CatchableError].}
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "clique PoA"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private Helpers
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template doExclusively(c: var Clique; action: untyped) =
|
||||||
|
waitFor c.lock.acquire
|
||||||
|
action
|
||||||
|
c.lock.release
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# clique/clique.go(145): func ecrecover(header [..]
|
||||||
|
proc ecrecover(header: BlockHeader;
|
||||||
|
sigcache: var EcRecover): Result[EthAddress,CliqueError] =
|
||||||
|
## ecrecover extracts the Ethereum account address from a signed header.
|
||||||
|
sigcache.getEcRecover(header)
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(369): func (c *Clique) snapshot(chain [..]
|
||||||
|
proc snapshot(c: var Clique; blockNumber: BlockNumber; hash: Hash256;
|
||||||
|
parents: openArray[Blockheader]): Result[Snapshot,CliqueError] =
|
||||||
|
## snapshot retrieves the authorization snapshot at a given point in time.
|
||||||
|
c.recents.getRecentSnaps:
|
||||||
|
RecentArgs(blockHash: hash,
|
||||||
|
blockNumber: blockNumber,
|
||||||
|
parents: toSeq(parents))
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(463): func (c *Clique) verifySeal(chain [..]
|
||||||
|
proc verifySeal(c: var Clique; header: BlockHeader;
|
||||||
|
parents: openArray[BlockHeader]): CliqueResult =
|
||||||
|
## 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,""))
|
||||||
|
|
||||||
|
# Retrieve the snapshot needed to verify this header and cache it
|
||||||
|
var snap = c.snapshot(header.blockNumber-1, header.parentHash, parents)
|
||||||
|
if snap.isErr:
|
||||||
|
return err(snap.error)
|
||||||
|
|
||||||
|
# Resolve the authorization key and check against signers
|
||||||
|
let signer = ecrecover(header,c.cfg.signatures)
|
||||||
|
if signer.isErr:
|
||||||
|
return err(signer.error)
|
||||||
|
|
||||||
|
if not snap.value.isSigner(signer.value):
|
||||||
|
return err((errUnauthorizedSigner,""))
|
||||||
|
|
||||||
|
let seen = snap.value.recent(signer.value)
|
||||||
|
if seen.isOk:
|
||||||
|
# Signer is among recents, only fail if the current block does not
|
||||||
|
# shift it out
|
||||||
|
if header.blockNumber - snap.value.signersThreshold.u256 < seen.value:
|
||||||
|
return err((errRecentlySigned,""))
|
||||||
|
|
||||||
|
# Ensure that the difficulty corresponds to the turn-ness of the signer
|
||||||
|
if not c.fakeDiff:
|
||||||
|
if snap.value.inTurn(header.blockNumber, signer.value):
|
||||||
|
if header.difficulty != DIFF_INTURN:
|
||||||
|
return err((errWrongDifficulty,""))
|
||||||
|
else:
|
||||||
|
if header.difficulty != DIFF_NOTURN:
|
||||||
|
return err((errWrongDifficulty,""))
|
||||||
|
|
||||||
|
return ok()
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(314): func (c *Clique) verifyCascadingFields(chain [..]
|
||||||
|
proc verifyCascadingFields(c: var Clique; header: BlockHeader;
|
||||||
|
parents: openArray[BlockHeader]): CliqueResult =
|
||||||
|
## 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 err((errZeroBlockNumberRejected,""))
|
||||||
|
|
||||||
|
# Ensure that the block's timestamp isn't too close to its parent
|
||||||
|
var parent: BlockHeader
|
||||||
|
if 0 < parents.len:
|
||||||
|
parent = parents[^1]
|
||||||
|
else:
|
||||||
|
let rc = c.cfg.dbChain.getBlockHeaderResult(header.blockNumber-1)
|
||||||
|
if rc.isErr:
|
||||||
|
return err((errUnknownAncestor,""))
|
||||||
|
parent = rc.value
|
||||||
|
|
||||||
|
if parent.blockNumber != header.blockNumber-1 or
|
||||||
|
parent.hash != header.parentHash:
|
||||||
|
return err((errUnknownAncestor,""))
|
||||||
|
|
||||||
|
if header.timestamp < parent.timestamp + c.cfg.period:
|
||||||
|
return err((errInvalidTimestamp,""))
|
||||||
|
|
||||||
|
# Verify that the gasUsed is <= gasLimit
|
||||||
|
if header.gasLimit < header.gasUsed:
|
||||||
|
return err((errCliqueExceedsGasLimit,
|
||||||
|
&"invalid gasUsed: have {header.gasUsed}, " &
|
||||||
|
&"gasLimit {header.gasLimit}"))
|
||||||
|
|
||||||
|
if not c.cfg.dbChain.config.isLondonOrLater(header.blockNumber):
|
||||||
|
# Verify BaseFee not present before EIP-1559 fork.
|
||||||
|
if not header.baseFee.isZero:
|
||||||
|
return err((errCliqueUnsupportedBaseFee,
|
||||||
|
"invalid baseFee before London fork: have " &
|
||||||
|
&"{header.baseFee}, want <0>"))
|
||||||
|
let rc = c.cfg.dbChain.validateGasLimit(header)
|
||||||
|
if rc.isErr:
|
||||||
|
return err(rc.error)
|
||||||
|
else:
|
||||||
|
let rc = c.cfg.dbChain.config.verify1559Header(parent = parent,
|
||||||
|
header = header)
|
||||||
|
if rc.isErr:
|
||||||
|
return err(rc.error)
|
||||||
|
|
||||||
|
# Retrieve the snapshot needed to verify this header and cache it
|
||||||
|
var snap = c.snapshot(header.blockNumber-1, header.parentHash, parents)
|
||||||
|
if snap.isErr:
|
||||||
|
return err(snap.error)
|
||||||
|
|
||||||
|
# If the block is a checkpoint block, verify the signer list
|
||||||
|
if (header.blockNumber mod c.cfg.epoch.u256) == 0:
|
||||||
|
let
|
||||||
|
signersList = snap.value.signers
|
||||||
|
extraList = header.extraData.extraDataSigners
|
||||||
|
if signersList != extraList:
|
||||||
|
return err((errMismatchingCheckpointSigners,""))
|
||||||
|
|
||||||
|
# All basic checks passed, verify the seal and return
|
||||||
|
return c.verifySeal(header, parents)
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(145): func ecrecover(header [..]
|
||||||
|
proc verifyHeader(c: var Clique; header: BlockHeader;
|
||||||
|
parents: openArray[BlockHeader]): CliqueResult =
|
||||||
|
## 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.
|
||||||
|
if header.blockNumber.isZero:
|
||||||
|
return err((errUnknownBlock,""))
|
||||||
|
|
||||||
|
# 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 checkpoint,
|
||||||
|
# but none otherwise
|
||||||
|
let signersBytes = header.extraData.len - EXTRA_VANITY - EXTRA_SEAL
|
||||||
|
if not isCheckPoint and signersBytes != 0:
|
||||||
|
return err((errExtraSigners,""))
|
||||||
|
|
||||||
|
if isCheckPoint and (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 != 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:
|
||||||
|
if header.difficulty.isZero or
|
||||||
|
(header.difficulty != DIFF_INTURN and
|
||||||
|
header.difficulty != 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"))
|
||||||
|
|
||||||
|
# If all checks passed, validate any special fields for hard forks
|
||||||
|
let rc = c.cfg.dbChain.config.verifyForkHashes(header)
|
||||||
|
if rc.isErr:
|
||||||
|
return err(rc.error)
|
||||||
|
|
||||||
|
# All basic checks passed, verify cascading fields
|
||||||
|
return c.verifyCascadingFields(header, parents)
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(681): func calcDifficulty(snap [..]
|
||||||
|
proc calcDifficulty(snap: var Snapshot; signer: EthAddress): DifficultyInt =
|
||||||
|
if snap.inTurn(snap.blockNumber + 1, signer):
|
||||||
|
DIFF_INTURN
|
||||||
|
else:
|
||||||
|
DIFF_NOTURN
|
||||||
|
|
||||||
|
# clique/clique.go(730): func encodeSigHeader(w [..]
|
||||||
|
proc encodeSigHeader(header: BlockHeader): seq[byte] =
|
||||||
|
## Cut sigature off `extraData` header field and consider new `baseFee`
|
||||||
|
## field for Eip1559.
|
||||||
|
doAssert EXTRA_SEAL < header.extraData.len
|
||||||
|
|
||||||
|
var rlpHeader = header
|
||||||
|
rlpHeader.extraData.setLen(header.extraData.len - EXTRA_SEAL)
|
||||||
|
|
||||||
|
rlpHeader.encode1559
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# clique/clique.go(191): func New(config [..]
|
||||||
|
proc initClique*(c: var Clique; cfg: CliqueCfg) =
|
||||||
|
## Initialiser for Clique proof-of-authority consensus engine with the
|
||||||
|
## initial signers set to the ones provided by the user.
|
||||||
|
c.cfg = cfg
|
||||||
|
c.recents = initRecentSnaps(cfg)
|
||||||
|
c.proposals = initTable[EthAddress,bool]()
|
||||||
|
c.lock = newAsyncLock()
|
||||||
|
|
||||||
|
proc initClique*(cfg: CliqueCfg): Clique =
|
||||||
|
result.initClique(cfg)
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(212): func (c *Clique) Author(header [..]
|
||||||
|
proc author*(c: var Clique;
|
||||||
|
header: BlockHeader): Result[EthAddress,CliqueError] =
|
||||||
|
## Implements consensus.Engine, returning the Ethereum address recovered
|
||||||
|
## from the signature in the header's extra-data section.
|
||||||
|
ecrecover(header, c.cfg.signatures)
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(217): func (c *Clique) VerifyHeader(chain [..]
|
||||||
|
proc verifyHeader*(c: var Clique; header: BlockHeader): CliqueResult =
|
||||||
|
## Checks whether a header conforms to the consensus rules.
|
||||||
|
c.verifyHeader(header, @[])
|
||||||
|
|
||||||
|
# clique/clique.go(224): func (c *Clique) VerifyHeader(chain [..]
|
||||||
|
proc verifyHeader*(c: var Clique; headers: openArray[BlockHeader]):
|
||||||
|
Future[seq[CliqueResult]] {.async,gcsafe.} =
|
||||||
|
## 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.
|
||||||
|
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.verifyHeader(headers[n], headers[0 ..< n])
|
||||||
|
c.doExclusively:
|
||||||
|
c.stopVHeaderReq = false
|
||||||
|
|
||||||
|
proc stopVerifyHeader*(c: var Clique): bool {.discardable.} =
|
||||||
|
## Activate the stop flag for running `verifyHeader()` function.
|
||||||
|
## Returns `true` if the stop flag could be activated.
|
||||||
|
c.doExclusively:
|
||||||
|
if not c.stopVHeaderReq:
|
||||||
|
c.stopVHeaderReq = true
|
||||||
|
result = true
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(450): func (c *Clique) VerifyUncles(chain [..]
|
||||||
|
proc verifyUncles*(c: var Clique; ethBlock: EthBlock): CliqueResult =
|
||||||
|
## Always returning an error for any 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: var Clique; header: var BlockHeader): CliqueResult =
|
||||||
|
## Peparing 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
|
||||||
|
var snap = c.snapshot(header.blockNumber-1, header.parentHash, @[])
|
||||||
|
if snap.isErr:
|
||||||
|
return err(snap.error)
|
||||||
|
|
||||||
|
if (header.blockNumber mod c.cfg.epoch.u256) != 0:
|
||||||
|
c.doExclusively:
|
||||||
|
# Gather all the proposals that make sense voting on
|
||||||
|
var addresses: seq[EthAddress]
|
||||||
|
for (address,authorize) in c.proposals.pairs:
|
||||||
|
if snap.value.validVote(address, authorize):
|
||||||
|
addresses.add address
|
||||||
|
|
||||||
|
# If there's pending proposals, cast a vote on them
|
||||||
|
if 0 < addresses.len:
|
||||||
|
header.coinbase = addresses[c.cfg.prng.rand(addresses.len-1)]
|
||||||
|
header.nonce = if header.coinbase in c.proposals: NONCE_AUTH
|
||||||
|
else: NONCE_DROP
|
||||||
|
|
||||||
|
# Set the correct difficulty
|
||||||
|
header.difficulty = snap.value.calcDifficulty(c.signer)
|
||||||
|
|
||||||
|
# Ensure the extra data has all its components
|
||||||
|
header.extraData.setLen(EXTRA_VANITY)
|
||||||
|
|
||||||
|
if (header.blockNumber mod c.cfg.epoch.u256) == 0:
|
||||||
|
for a in snap.value.signers:
|
||||||
|
header.extraData.add a
|
||||||
|
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
|
||||||
|
let parent = c.cfg.dbChain.getBlockHeaderResult(header.blockNumber-1)
|
||||||
|
if parent.isErr:
|
||||||
|
return err((errUnknownAncestor,""))
|
||||||
|
|
||||||
|
header.timestamp = parent.value.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: var Clique; header: BlockHeader; state: StateDB;
|
||||||
|
# txs: openArray[Transaction]; uncles: openArray[BlockHeader]) =
|
||||||
|
# ## Ensuring no uncles are set, nor block rewards given.
|
||||||
|
#
|
||||||
|
# # No block rewards in PoA, so the state remains as is and uncles are dropped
|
||||||
|
# header.Root =
|
||||||
|
# state.intermediateRoot(c.cfg.config.eip158block <= header.BlockNumber)
|
||||||
|
# header.UncleHash = types.CalcUncleHash(nil)
|
||||||
|
|
||||||
|
# clique/clique.go(579): func (c *Clique) FinalizeAndAssemble(chain [..]
|
||||||
|
#proc finalizeAndAssemble*(c: var Clique; header: BlockHeader; state: StateDB;
|
||||||
|
# txs: openArray[Transaction];
|
||||||
|
# uncles: openArray[BlockHeader];
|
||||||
|
# receipts: openArray[Receipts]):
|
||||||
|
# Result[EthBlock,CliqueError] =
|
||||||
|
# ## 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: var Clique; signer: EthAddress; signFn: CliqueSignerFn) =
|
||||||
|
## Injects private key into the consensus engine to mint new blocks with.
|
||||||
|
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.encodeSigHeader
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(688): func SealHash(header *types.Header) common.Hash {
|
||||||
|
proc sealHash(header: BlockHeader): Hash256 =
|
||||||
|
## SealHash returns the hash of a block prior to it being sealed.
|
||||||
|
header.encodeSigHeader.keccakHash
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(599): func (c *Clique) Seal(chain [..]
|
||||||
|
proc seal*(c: var Clique; ethBlock: EthBlock):
|
||||||
|
Future[Result[EthBlock,CliqueError]] {.async,gcsafe.} =
|
||||||
|
## Attempt 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
|
||||||
|
var snap = c.snapshot(header.blockNumber-1, header.parentHash, @[])
|
||||||
|
if snap.isErr:
|
||||||
|
return err(snap.error)
|
||||||
|
if not snap.value.isSigner(signer):
|
||||||
|
return err((errUnauthorizedSigner,""))
|
||||||
|
|
||||||
|
# If we're amongst the recent signers, wait for the next block
|
||||||
|
let seen = snap.value.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 + snap.value.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 = snap.value.signersThreshold.int64 * WIGGLE_TIME
|
||||||
|
# Kludge for limited rand() argument range
|
||||||
|
if wiggle.inSeconds < (int.high div 1000).int64:
|
||||||
|
let rndWiggleMs = c.cfg.prng.rand(wiggle.inMilliSeconds.int)
|
||||||
|
delay += initDuration(milliseconds = rndWiggleMs)
|
||||||
|
else:
|
||||||
|
let rndWiggleSec = c.cfg.prng.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: var Clique): bool {.discardable.} =
|
||||||
|
## Activate the stop flag for running `seal()` function.
|
||||||
|
## Returns `true` if the stop flag could be activated.
|
||||||
|
c.doExclusively:
|
||||||
|
if not c.stopSealReq:
|
||||||
|
c.stopSealReq = true
|
||||||
|
result =true
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(673): func (c *Clique) CalcDifficulty(chain [..]
|
||||||
|
proc calcDifficulty(c: var Clique;
|
||||||
|
parent: BlockHeader): Result[DifficultyInt,CliqueError] =
|
||||||
|
## The difficulty adjustment algorithm. It 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
|
||||||
|
var snap = c.snapshot(parent.blockNumber, parent.blockHash, @[])
|
||||||
|
if snap.isErr:
|
||||||
|
return err(snap.error)
|
||||||
|
return ok(snap.value.calcDifficulty(c.signer))
|
||||||
|
|
||||||
|
|
||||||
|
# # clique/clique.go(710): func (c *Clique) SealHash(header [..]
|
||||||
|
# proc sealHash(c: var Clique; header: BlockHeader): Hash256 =
|
||||||
|
# ## SealHash returns the hash of a block prior to it being sealed.
|
||||||
|
# header.encodeSigHeader.keccakHash
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
56
nimbus/p2p/clique/clique_cfg.nim
Normal file
56
nimbus/p2p/clique/clique_cfg.nim
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 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
|
||||||
|
../../db/db_chain,
|
||||||
|
./clique_defs,
|
||||||
|
./ec_recover,
|
||||||
|
random,
|
||||||
|
times
|
||||||
|
|
||||||
|
const
|
||||||
|
prngSeed = 42
|
||||||
|
|
||||||
|
type
|
||||||
|
CliqueCfg* = ref object
|
||||||
|
dbChain*: BaseChainDB
|
||||||
|
signatures*: EcRecover ## Recent block signatures to speed up mining
|
||||||
|
period*: Duration ## time between blocks to enforce
|
||||||
|
epoch*: uint64 ## Epoch length to reset votes and checkpoint
|
||||||
|
prng*: Rand ## PRNG state
|
||||||
|
|
||||||
|
{.push raises: [Defect,CatchableError].}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc newCliqueCfg*(dbChain: BaseChainDB;
|
||||||
|
period = BLOCK_PERIOD; epoch = EPOCH_LENGTH): CliqueCfg =
|
||||||
|
CliqueCfg(
|
||||||
|
dbChain: dbChain,
|
||||||
|
period: period,
|
||||||
|
epoch: epoch,
|
||||||
|
signatures: initEcRecover(),
|
||||||
|
prng: initRand(prngSeed))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
281
nimbus/p2p/clique/clique_defs.nim
Normal file
281
nimbus/p2p/clique/clique_defs.nim
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
# 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 Constants & Types
|
||||||
|
## ============================
|
||||||
|
##
|
||||||
|
## 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>`_
|
||||||
|
##
|
||||||
|
|
||||||
|
const
|
||||||
|
# debugging, enable with: nim c -r -d:noisy:3 ...
|
||||||
|
noisy {.intdefine.}: int = 0
|
||||||
|
isMainOk {.used.} = noisy > 2
|
||||||
|
|
||||||
|
import
|
||||||
|
eth/common,
|
||||||
|
ethash,
|
||||||
|
nimcrypto,
|
||||||
|
stew/results,
|
||||||
|
times
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Constants copied from eip-225 specs & implementation
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# clique/clique.go(48): const ( [..]
|
||||||
|
const
|
||||||
|
CHECKPOINT_INTERVAL* = ## Number of blocks after which to save the vote
|
||||||
|
## snapshot to the database
|
||||||
|
1024
|
||||||
|
|
||||||
|
INMEMORY_SNAPSHOTS* = ## Number of recent vote snapshots to keep in memory
|
||||||
|
128
|
||||||
|
|
||||||
|
INMEMORY_SIGNATURES* = ## Number of recent block signatures to keep in memory
|
||||||
|
4096
|
||||||
|
|
||||||
|
WIGGLE_TIME* = ## Random delay (per signer) to allow concurrent
|
||||||
|
## signers
|
||||||
|
initDuration(seconds = 0, milliseconds = 500)
|
||||||
|
|
||||||
|
# clique/clique.go(57): var ( [..]
|
||||||
|
const
|
||||||
|
EPOCH_LENGTH* = ## 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.
|
||||||
|
ethash.EPOCH_LENGTH.uint64
|
||||||
|
|
||||||
|
BLOCK_PERIOD* = ## Minimum difference in seconds between two consecutive
|
||||||
|
## block's timestamps. Suggested 15s for the testnet to
|
||||||
|
## remain analogous to the mainnet ethash target.
|
||||||
|
initDuration(seconds = 15)
|
||||||
|
|
||||||
|
EXTRA_VANITY* = ## Fixed number of extra-data prefix bytes reserved for
|
||||||
|
## signer vanity. Suggested 32 bytes to retain the current
|
||||||
|
## extra-data allowance and/or use.
|
||||||
|
32
|
||||||
|
|
||||||
|
EXTRA_SEAL* = ## Fixed number of extra-data suffix bytes reserved for
|
||||||
|
## signer seal. 65 bytes fixed as signatures are based on
|
||||||
|
## the standard secp256k1 curve.
|
||||||
|
65
|
||||||
|
|
||||||
|
NONCE_AUTH* = ## Magic nonce number 0xffffffffffffffff to vote on adding a
|
||||||
|
## new signer.
|
||||||
|
0xffffffffffffffffu64.toBlockNonce
|
||||||
|
|
||||||
|
NONCE_DROP* = ## Magic nonce number 0x0000000000000000 to vote on removing
|
||||||
|
## a signer.
|
||||||
|
0x0000000000000000u64.toBlockNonce
|
||||||
|
|
||||||
|
UNCLE_HASH* = ## Always Keccak256(RLP([])) as uncles are meaningless
|
||||||
|
## outside of PoW.
|
||||||
|
rlpHash[seq[BlockHeader]](@[])
|
||||||
|
|
||||||
|
DIFF_NOTURN* = ## Block score (difficulty) for blocks containing out-of-turn
|
||||||
|
## signatures. Suggested 1 since it just needs to be an
|
||||||
|
## arbitrary baseline constant.
|
||||||
|
1.u256
|
||||||
|
|
||||||
|
DIFF_INTURN* = ## Block score (difficulty) for blocks containing in-turn
|
||||||
|
## signatures. Suggested 2 to show a slight preference over
|
||||||
|
## out-of-turn signatures.
|
||||||
|
2.u256
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Additional constants copied from eip-225 go implementation
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const
|
||||||
|
# params/network_params.go(60): FullImmutabilityThreshold = 90000
|
||||||
|
FULL_IMMUTABILITY_THRESHOLD* = ## Number of blocks after which a chain
|
||||||
|
## segment is considered immutable
|
||||||
|
## (i.e. 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.
|
||||||
|
90000
|
||||||
|
|
||||||
|
# params/protocol_params.go(121): BaseFeeChangeDenominator = 8 [..]
|
||||||
|
EIP1559_BASE_FEE_CHANGE_DENOMINATOR* = ## Bounds the amount the base fee can
|
||||||
|
## change between blocks.
|
||||||
|
8
|
||||||
|
|
||||||
|
# params/protocol_params.go(122): ElasticityMultiplier = 2 [..]
|
||||||
|
EIP1559_ELASTICITY_MULTIPLIER* = ## Bounds the maximum gas limit an
|
||||||
|
## EIP-1559 block may have.
|
||||||
|
2
|
||||||
|
|
||||||
|
# params/protocol_params.go(123): InitialBaseFee = 1000000000 [..]
|
||||||
|
EIP1559_INITIAL_BASE_FEE* = ## Initial base fee for Eip1559 blocks.
|
||||||
|
1000000000i64
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Error tokens
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# clique/clique.go(76): var ( [..]
|
||||||
|
type
|
||||||
|
CliqueErrorType* = enum
|
||||||
|
errUnknownBlock = ## is returned when the list of signers is
|
||||||
|
## requested for a block that is not part of
|
||||||
|
## the local blockchain.
|
||||||
|
"unknown block"
|
||||||
|
|
||||||
|
errInvalidCheckpointBeneficiary = ## is returned if a checkpoint/epoch
|
||||||
|
## transition block has a beneficiary
|
||||||
|
## set to non-zeroes.
|
||||||
|
"beneficiary in checkpoint block non-zero"
|
||||||
|
|
||||||
|
errInvalidVote = ## is returned if a nonce value is something
|
||||||
|
## else that the two allowed constants of
|
||||||
|
## 0x00..0 or 0xff..f.
|
||||||
|
"vote nonce not 0x00..0 or 0xff..f"
|
||||||
|
|
||||||
|
errInvalidCheckpointVote = ## is returned if a checkpoint/epoch
|
||||||
|
## transition block has a vote nonce set to
|
||||||
|
## non-zeroes.
|
||||||
|
"vote nonce in checkpoint block non-zero"
|
||||||
|
|
||||||
|
errMissingVanity = ## is returned if a block's extra-data section
|
||||||
|
## is shorter than 32 bytes, which is required
|
||||||
|
## to store the signer vanity.
|
||||||
|
"extra-data 32 byte vanity prefix missing"
|
||||||
|
|
||||||
|
errMissingSignature = ## is returned if a block's extra-data section
|
||||||
|
## doesn't seem to contain a 65 byte secp256k1
|
||||||
|
## signature.
|
||||||
|
"extra-data 65 byte signature suffix missing"
|
||||||
|
|
||||||
|
errExtraSigners = ## is returned if non-checkpoint block contain
|
||||||
|
## signer data in their extra-data fields.
|
||||||
|
"non-checkpoint block contains extra signer list"
|
||||||
|
|
||||||
|
errInvalidCheckpointSigners = ## is returned if a checkpoint block contains
|
||||||
|
## an invalid list of signers (i.e. non
|
||||||
|
## divisible by 20 bytes).
|
||||||
|
"invalid signer list on checkpoint block"
|
||||||
|
|
||||||
|
errMismatchingCheckpointSigners = ## is returned if a checkpoint block
|
||||||
|
## contains a list of signers different
|
||||||
|
## than the one the local node calculated.
|
||||||
|
"mismatching signer list on checkpoint block"
|
||||||
|
|
||||||
|
errInvalidMixDigest = ## is returned if a block's mix digest is
|
||||||
|
## non-zero.
|
||||||
|
"non-zero mix digest"
|
||||||
|
|
||||||
|
errInvalidUncleHash = ## is returned if a block contains an
|
||||||
|
## non-empty uncle list.
|
||||||
|
"non empty uncle hash"
|
||||||
|
|
||||||
|
errInvalidDifficulty = ## is returned if the difficulty of a block
|
||||||
|
## neither 1 or 2.
|
||||||
|
"invalid difficulty"
|
||||||
|
|
||||||
|
errWrongDifficulty = ## is returned if the difficulty of a block
|
||||||
|
## doesn't match the turn of the signer.
|
||||||
|
"wrong difficulty"
|
||||||
|
|
||||||
|
errInvalidTimestamp = ## is returned if the timestamp of a block is
|
||||||
|
## lower than the previous block's timestamp
|
||||||
|
## + the minimum block period.
|
||||||
|
"invalid timestamp"
|
||||||
|
|
||||||
|
errInvalidVotingChain = ## is returned if an authorization list is
|
||||||
|
## attempted to be modified via out-of-range
|
||||||
|
## or non-contiguous headers.
|
||||||
|
"invalid voting chain"
|
||||||
|
|
||||||
|
errUnauthorizedSigner = ## is returned if a header is signed by a
|
||||||
|
## non-authorized entity.
|
||||||
|
"unauthorized signer"
|
||||||
|
|
||||||
|
errRecentlySigned = ## is returned if a header is signed by an
|
||||||
|
## authorized entity that already signed a
|
||||||
|
## header recently, thus is temporarily not
|
||||||
|
## allowed to.
|
||||||
|
"recently signed"
|
||||||
|
|
||||||
|
|
||||||
|
# additional errors sources elsewhere
|
||||||
|
# -----------------------------------
|
||||||
|
|
||||||
|
errPublicKeyToShort = ## Cannot retrieve public key
|
||||||
|
"cannot retrieve public key: too short"
|
||||||
|
|
||||||
|
# imported from consensus/errors.go
|
||||||
|
errUnknownAncestor = ## is returned when validating a block
|
||||||
|
## requires an ancestor that is unknown.
|
||||||
|
"unknown ancestor"
|
||||||
|
|
||||||
|
#errPrunedAncestor = ## is returned when validating a block
|
||||||
|
# ## requires an ancestor that is known, but
|
||||||
|
# ## the state of which is not available.
|
||||||
|
# "pruned ancestor"
|
||||||
|
|
||||||
|
errFutureBlock = ## is returned when a block's timestamp is in
|
||||||
|
## the future according to the current node.
|
||||||
|
"block in the future"
|
||||||
|
|
||||||
|
#errInvalidNumber = ## is returned if a block's number doesn't
|
||||||
|
# ## equal its parent's plus one.
|
||||||
|
# "invalid block number"
|
||||||
|
|
||||||
|
|
||||||
|
# additional errors, manually added
|
||||||
|
# ---------------------------------
|
||||||
|
|
||||||
|
errZeroBlockNumberRejected =
|
||||||
|
"Block number must not be Zero"
|
||||||
|
|
||||||
|
errSkSigResult ## eth/keys subsytem error: signature
|
||||||
|
errSkPubKeyResult ## eth/keys subsytem error: public key
|
||||||
|
errSnapshotLoad ## DB subsytem error
|
||||||
|
errSnapshotStore ## ..
|
||||||
|
errSnapshotClone
|
||||||
|
|
||||||
|
errCliqueExceedsGasLimit
|
||||||
|
errCliqueUnsupportedBaseFee
|
||||||
|
errCliqueBaseFeeError
|
||||||
|
errCliqueGasRepriceFork
|
||||||
|
errCliqueSealSigFn
|
||||||
|
|
||||||
|
errCliqueStopped = "Process was interrupted"
|
||||||
|
errCliqueExpectedBaseFee = "header is missing baseFee"
|
||||||
|
errCliqueGasLimitTooLow = "gas limit is too low"
|
||||||
|
errCliqueGasLimitTooHigh = "gas limit is too high"
|
||||||
|
errCliqueUnclesNotAllowed = "uncles not allowed"
|
||||||
|
|
||||||
|
# not really an error
|
||||||
|
nilCliqueSealNoBlockYet = "Sealing paused, waiting for transactions"
|
||||||
|
nilCliqueSealSignedRecently = "Signed recently, must wait for others"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# More types
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type
|
||||||
|
CliqueError* = (CliqueErrorType,string)
|
||||||
|
CliqueResult* = Result[void,CliqueError]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
173
nimbus/p2p/clique/clique_poll.nim
Normal file
173
nimbus/p2p/clique/clique_poll.nim
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
##
|
||||||
|
## Votes Management for Clique PoA Consensus Protocol
|
||||||
|
## =================================================
|
||||||
|
##
|
||||||
|
## 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
|
||||||
|
algorithm,
|
||||||
|
eth/common,
|
||||||
|
sequtils,
|
||||||
|
tables
|
||||||
|
|
||||||
|
type
|
||||||
|
Vote* = object ## Vote represent single votes that an authorized
|
||||||
|
## signer made to modify the list of authorizations.
|
||||||
|
signer*: EthAddress ## authorized signer that cast this vote
|
||||||
|
address*: EthAddress ## account being voted on to change its
|
||||||
|
## authorization type (`true` or `false`)
|
||||||
|
blockNumber*: BlockNumber ## block number the vote was cast in
|
||||||
|
## (expire old votes)
|
||||||
|
authorize*: bool ## authorization type, whether to authorize or
|
||||||
|
## deauthorize the voted account
|
||||||
|
|
||||||
|
Tally = tuple
|
||||||
|
authorize: bool
|
||||||
|
signers: Table[EthAddress,Vote]
|
||||||
|
|
||||||
|
CliquePoll* = object
|
||||||
|
votes: Table[EthAddress,Tally] ## votes by account -> signer
|
||||||
|
authSig: Table[EthAddress,bool] ## currently authorised signers
|
||||||
|
authRemoved: bool ## last `addVote()` action was removing an
|
||||||
|
## authorised signer from the `authSig` list
|
||||||
|
|
||||||
|
{.push raises: [Defect,CatchableError].}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc initCliquePoll*(t: var CliquePoll) =
|
||||||
|
## Ininialise an empty `CliquePoll` descriptor.
|
||||||
|
t.votes = initTable[EthAddress,Tally]()
|
||||||
|
t.authSig = initTable[EthAddress,bool]()
|
||||||
|
|
||||||
|
proc initCliquePoll*(t: var CliquePoll; signers: openArray[EthAddress]) =
|
||||||
|
## Ininialise `CliquePoll` with a given authorised signers list
|
||||||
|
t.initCliquePoll
|
||||||
|
for a in signers:
|
||||||
|
t.authSig[a] = true
|
||||||
|
|
||||||
|
proc authSigners*(t: var CliquePoll): seq[EthAddress] =
|
||||||
|
## Sorted ascending list of authorised signer addresses
|
||||||
|
result = toSeq(t.authSig.keys)
|
||||||
|
result.sort do (x, y: EthAddress) -> int:
|
||||||
|
for n in 0 ..< x.len:
|
||||||
|
if x[n] < y[n]:
|
||||||
|
return -1
|
||||||
|
elif y[n] < x[n]:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
proc isAuthSigner*(t: var CliquePoll; address: EthAddress): bool =
|
||||||
|
## Check whether `address` is an authorised signer
|
||||||
|
address in t.authSig
|
||||||
|
|
||||||
|
proc authSignersShrunk*(t: var CliquePoll): bool =
|
||||||
|
## Check whether the authorised signers list was shrunk recently after
|
||||||
|
## appying `addVote()`
|
||||||
|
t.authRemoved
|
||||||
|
|
||||||
|
proc authSignersThreshold*(t: var CliquePoll): int =
|
||||||
|
## Returns the minimum number of authorised signers needed for authorising
|
||||||
|
## a addres for voting. This is currently
|
||||||
|
## ::
|
||||||
|
## 1 + half of the number of authorised signers
|
||||||
|
##
|
||||||
|
1 + (t.authSig.len div 2)
|
||||||
|
|
||||||
|
|
||||||
|
proc delVote*(t: var CliquePoll; signer, address: EthAddress) =
|
||||||
|
## Remove a particular previously added vote.
|
||||||
|
if address in t.votes:
|
||||||
|
if signer in t.votes[address].signers:
|
||||||
|
if t.votes[address].signers.len <= 1:
|
||||||
|
t.votes.del(address)
|
||||||
|
else:
|
||||||
|
t.votes[address].signers.del(signer)
|
||||||
|
|
||||||
|
|
||||||
|
# clique/snapshot.go(141): func (s *Snapshot) validVote(address [..]
|
||||||
|
proc validVote*(t: var CliquePoll; address: EthAddress; authorize: bool): bool =
|
||||||
|
## Check whether voting would have an effect in `addVote()`
|
||||||
|
if address in t.authSig: not authorize else: authorize
|
||||||
|
|
||||||
|
|
||||||
|
proc addVote*(t: var CliquePoll; vote: Vote) =
|
||||||
|
## Add a new vote collecting the signers for the particular voting address.
|
||||||
|
##
|
||||||
|
## Unless it is the first vote for this address, the authorisation type
|
||||||
|
## `true` or `false` of the vote must match the previous one. For the first
|
||||||
|
## vote, the authorisation type `true` is accepted if the address is not an
|
||||||
|
## authorised signer, and `false` if it is an authorised signer. Otherwise
|
||||||
|
## the vote is ignored.
|
||||||
|
##
|
||||||
|
## If the number of signers for the particular address are at least
|
||||||
|
## `authSignersThreshold()`, the status of this address will change as
|
||||||
|
## follows.
|
||||||
|
## * If the authorisation type is `true`, the address is added
|
||||||
|
## to the list of authorised signers.
|
||||||
|
## * If the authorisation type is `false`, the address is removed
|
||||||
|
## from the list of authorised signers.
|
||||||
|
t.authRemoved = false
|
||||||
|
|
||||||
|
# clique/snapshot.go(147): if !s.validVote(address, [..]
|
||||||
|
if not t.validVote(vote.address, vote.authorize):
|
||||||
|
# Voting has no effect
|
||||||
|
return
|
||||||
|
|
||||||
|
# clique/snapshot.go(253): if snap.cast(header.Coinbase, [..]
|
||||||
|
|
||||||
|
# Collect vote
|
||||||
|
var numVotes = 0
|
||||||
|
if not t.votes.hasKey(vote.address):
|
||||||
|
t.votes[vote.address] = (vote.authorize, {vote.signer: vote}.toTable)
|
||||||
|
numVotes = 1
|
||||||
|
elif t.votes[vote.address].authorize == vote.authorize:
|
||||||
|
t.votes[vote.address].signers[vote.signer] = vote
|
||||||
|
numVotes = t.votes[vote.address].signers.len
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
# clique/snapshot.go(262): if tally := snap.Tally[header.Coinbase]; [..]
|
||||||
|
|
||||||
|
# Vote passed, update the list of authorised signers if enough votes
|
||||||
|
if numVotes < t.authSignersThreshold:
|
||||||
|
return
|
||||||
|
|
||||||
|
var obsolete = @[vote.address]
|
||||||
|
if vote.authorize:
|
||||||
|
# Has minimum votes, so add it
|
||||||
|
t.authSig[vote.address] = true
|
||||||
|
else:
|
||||||
|
# clique/snapshot.go(266): delete(snap.Signers, [..]
|
||||||
|
t.authSig.del(vote.address)
|
||||||
|
t.authRemoved = true
|
||||||
|
|
||||||
|
# Not a signer anymore => remove it everywhere
|
||||||
|
for key,value in t.votes.mpairs:
|
||||||
|
if vote.address in value.signers:
|
||||||
|
if 1 < value.signers.len:
|
||||||
|
value.signers.del(vote.address)
|
||||||
|
else:
|
||||||
|
obsolete.add key
|
||||||
|
|
||||||
|
for key in obsolete:
|
||||||
|
t.votes.del(key)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
216
nimbus/p2p/clique/clique_utils.nim
Normal file
216
nimbus/p2p/clique/clique_utils.nim
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
##
|
||||||
|
## Tuoole & Utils for Clique PoA Consensus Protocol
|
||||||
|
## ================================================
|
||||||
|
##
|
||||||
|
## 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>`_
|
||||||
|
##
|
||||||
|
## Caveat: Not supporting RLP serialisation encode()/decode()
|
||||||
|
##
|
||||||
|
|
||||||
|
import
|
||||||
|
../../chain_config,
|
||||||
|
../../config,
|
||||||
|
../../constants,
|
||||||
|
../../db/db_chain,
|
||||||
|
../../utils,
|
||||||
|
../../vm_types2,
|
||||||
|
./clique_defs,
|
||||||
|
eth/[common, rlp],
|
||||||
|
stew/results,
|
||||||
|
stint,
|
||||||
|
strformat,
|
||||||
|
times
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private helpers
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc toEthAddress(a: openArray[byte]; start: int): EthAddress =
|
||||||
|
## Concert seq[..] => Array[..]
|
||||||
|
doAssert start + EthAddress.len <= a.len
|
||||||
|
for n in 0 ..< EthAddress.len:
|
||||||
|
result[n] = a[start + n]
|
||||||
|
|
||||||
|
proc gasLimitBounds(limit: GasInt): (GasInt, GasInt) =
|
||||||
|
## See also utils.header.gasLimitBounds()
|
||||||
|
let
|
||||||
|
bndRange = limit div GAS_LIMIT_ADJUSTMENT_FACTOR
|
||||||
|
upperBound = if GAS_LIMIT_MAXIMUM - bndRange < limit: GAS_LIMIT_MAXIMUM
|
||||||
|
else: limit + bndRange
|
||||||
|
lowerBound = max(GAS_LIMIT_MINIMUM, limit - bndRange)
|
||||||
|
|
||||||
|
return (lowerBound, upperBound)
|
||||||
|
|
||||||
|
proc validateGasLimit(header: BlockHeader; limit: GasInt): CliqueResult =
|
||||||
|
let (lowBound, highBound) = gasLimitBounds(limit)
|
||||||
|
if header.gasLimit < lowBound:
|
||||||
|
return err((errCliqueGasLimitTooLow,""))
|
||||||
|
if highBound < header.gasLimit:
|
||||||
|
return err((errCliqueGasLimitTooHigh,""))
|
||||||
|
return ok()
|
||||||
|
|
||||||
|
func zeroItem[T](t: typedesc[T]): T {.inline.} =
|
||||||
|
discard
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc isZero*[T: EthAddress|Hash256|Duration](a: T): bool =
|
||||||
|
## `true` if `a` is all zero
|
||||||
|
a == zeroItem(T)
|
||||||
|
|
||||||
|
proc cliqueResultErr*(w: CliqueError): CliqueResult =
|
||||||
|
## Return error result (syntactic sugar)
|
||||||
|
err(w)
|
||||||
|
|
||||||
|
|
||||||
|
proc extraDataSigners*(extraData: Blob): seq[EthAddress] =
|
||||||
|
## Extract signer addresses from extraData header field
|
||||||
|
if EXTRA_VANITY + EXTRA_SEAL < extraData.len:
|
||||||
|
var addrOffset = EXTRA_VANITY
|
||||||
|
while addrOffset + EthAddress.len <= EXTRA_SEAL:
|
||||||
|
result.add extraData.toEthAddress(addrOffset)
|
||||||
|
addrOffset += EthAddress.len
|
||||||
|
|
||||||
|
proc getBlockHeaderResult*(c: BaseChainDB;
|
||||||
|
number: BlockNumber): Result[BlockHeader,void] =
|
||||||
|
## Slightly re-phrased dbChain.getBlockHeader(..) command
|
||||||
|
var header: BlockHeader
|
||||||
|
if c.getBlockHeader(number, header):
|
||||||
|
return ok(header)
|
||||||
|
result = err()
|
||||||
|
|
||||||
|
# core/types/block.go(343): func (b *Block) WithSeal(header [..]
|
||||||
|
proc withHeader*(b: EthBlock; header: BlockHeader): EthBlock =
|
||||||
|
## New block with the data from `b` but the header replaced with the
|
||||||
|
## argument one.
|
||||||
|
EthBlock(
|
||||||
|
header: header,
|
||||||
|
txs: b.txs,
|
||||||
|
uncles: b.uncles)
|
||||||
|
|
||||||
|
# consensus/misc/forks.go(30): func VerifyForkHashes(config [..]
|
||||||
|
proc verifyForkHashes*(c: var ChainConfig; header: BlockHeader): CliqueResult =
|
||||||
|
## Verify that blocks conforming to network hard-forks do have the correct
|
||||||
|
## hashes, to avoid clients going off on different chains.
|
||||||
|
|
||||||
|
# If the homestead reprice hash is set, validate it
|
||||||
|
if c.eip150Block.isZero or c.eip150Block != header.blockNumber:
|
||||||
|
return ok()
|
||||||
|
|
||||||
|
let hash = header.hash
|
||||||
|
if c.eip150Hash.isZero or c.eip150Hash == hash:
|
||||||
|
return ok()
|
||||||
|
|
||||||
|
return err((errCliqueGasRepriceFork,
|
||||||
|
&"Homestead gas reprice fork: have {c.eip150Hash}, want {hash}"))
|
||||||
|
|
||||||
|
proc validateGasLimit*(c: var BaseChainDB; header: BlockHeader): CliqueResult =
|
||||||
|
## See also private function p2p.validate.validateGasLimit()
|
||||||
|
let parent = c.getBlockHeader(header.parentHash)
|
||||||
|
header.validateGasLimit(parent.gasLimit)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Eip 1559 support
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# params/config.go(450): func (c *ChainConfig) IsLondon(num [..]
|
||||||
|
proc isLondonOrLater*(c: var ChainConfig; number: BlockNumber): bool =
|
||||||
|
## FIXME: London is not defined yet, will come after Berlin
|
||||||
|
FkBerlin < c.toFork(number)
|
||||||
|
|
||||||
|
proc baseFee*(header: BlockHeader): GasInt =
|
||||||
|
# FIXME: `baseFee` header field not defined before `London` fork
|
||||||
|
0.GasInt
|
||||||
|
|
||||||
|
# clique/clique.go(730): func encodeSigHeader(w [..]
|
||||||
|
proc encode1559*(header: BlockHeader): seq[byte] =
|
||||||
|
## Encode header field and considering new `baseFee` field for Eip1559.
|
||||||
|
var writer = initRlpWriter()
|
||||||
|
writer.append(header)
|
||||||
|
if not header.baseFee.isZero:
|
||||||
|
writer.append(header.baseFee)
|
||||||
|
result = writer.finish
|
||||||
|
|
||||||
|
# consensus/misc/eip1559.go(55): func CalcBaseFee(config [..]
|
||||||
|
proc calc1599BaseFee*(c: var ChainConfig; parent: BlockHeader): GasInt =
|
||||||
|
## calculates the basefee of the header.
|
||||||
|
|
||||||
|
# If the current block is the first EIP-1559 block, return the
|
||||||
|
# initial base fee.
|
||||||
|
if not c.isLondonOrLater(parent.blockNumber):
|
||||||
|
return EIP1559_INITIAL_BASE_FEE
|
||||||
|
|
||||||
|
let parentGasTarget = parent.gasLimit div EIP1559_ELASTICITY_MULTIPLIER
|
||||||
|
|
||||||
|
# If the parent gasUsed is the same as the target, the baseFee remains
|
||||||
|
# unchanged.
|
||||||
|
if parent.gasUsed == parentGasTarget:
|
||||||
|
return parent.baseFee
|
||||||
|
|
||||||
|
let parentGasDenom = parentGasTarget.i128 *
|
||||||
|
EIP1559_BASE_FEE_CHANGE_DENOMINATOR.i128
|
||||||
|
|
||||||
|
if parentGasTarget < parent.gasUsed:
|
||||||
|
# If the parent block used more gas than its target, the baseFee should
|
||||||
|
# increase.
|
||||||
|
let
|
||||||
|
gasUsedDelta = (parent.gasUsed - parentGasTarget).i128
|
||||||
|
baseFeeDelta = (parent.baseFee.i128 * gasUsedDelta) div parentGasDenom
|
||||||
|
|
||||||
|
return parent.baseFee + max(baseFeeDelta.truncate(GasInt), 1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Otherwise if the parent block used less gas than its target, the
|
||||||
|
# baseFee should decrease.
|
||||||
|
let
|
||||||
|
gasUsedDelta = (parentGasTarget - parent.gasUsed).i128
|
||||||
|
baseFeeDelta = (parent.baseFee.i128 * gasUsedDelta) div parentGasDenom
|
||||||
|
|
||||||
|
return max(parent.baseFee - baseFeeDelta.truncate(GasInt), 0)
|
||||||
|
|
||||||
|
|
||||||
|
# consensus/misc/eip1559.go(32): func VerifyEip1559Header(config [..]
|
||||||
|
proc verify1559Header*(c: var ChainConfig;
|
||||||
|
parent, header: BlockHeader): CliqueResult =
|
||||||
|
## Verify that the gas limit remains within allowed bounds
|
||||||
|
let limit = if c.isLondonOrLater(parent.blockNumber):
|
||||||
|
parent.gasLimit
|
||||||
|
else:
|
||||||
|
parent.gasLimit * EIP1559_ELASTICITY_MULTIPLIER
|
||||||
|
let rc = header.validateGasLimit(limit)
|
||||||
|
if rc.isErr:
|
||||||
|
return err(rc.error)
|
||||||
|
|
||||||
|
# Verify the header is not malformed
|
||||||
|
if header.baseFee.isZero:
|
||||||
|
return err((errCliqueExpectedBaseFee,""))
|
||||||
|
|
||||||
|
# Verify the baseFee is correct based on the parent header.
|
||||||
|
var expectedBaseFee = c.calc1599BaseFee(parent)
|
||||||
|
if header.baseFee != expectedBaseFee:
|
||||||
|
return err((errCliqueBaseFeeError,
|
||||||
|
&"invalid baseFee: have {expectedBaseFee}, "&
|
||||||
|
&"want {header.baseFee}, " &
|
||||||
|
&"parent.baseFee {parent.baseFee}, "&
|
||||||
|
&"parent.gasUsed {parent.gasUsed}"))
|
||||||
|
|
||||||
|
return ok()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
100
nimbus/p2p/clique/ec_recover.nim
Normal file
100
nimbus/p2p/clique/ec_recover.nim
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
##
|
||||||
|
## Address Cache for Clique PoA Consensus Protocol
|
||||||
|
## ===============================================
|
||||||
|
##
|
||||||
|
## 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
|
||||||
|
../../utils,
|
||||||
|
../../utils/lru_cache,
|
||||||
|
./clique_defs,
|
||||||
|
eth/[common, keys, rlp],
|
||||||
|
stint
|
||||||
|
|
||||||
|
type
|
||||||
|
# simplify Hash256 for rlp serialisation
|
||||||
|
EcKey32 = array[32, byte]
|
||||||
|
|
||||||
|
EcRecover* = LruCache[BlockHeader,EcKey32,EthAddress,CliqueError]
|
||||||
|
|
||||||
|
{.push raises: [Defect,CatchableError].}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc initEcRecover*(cache: var EcRecover) {.gcsafe, raises: [Defect].} =
|
||||||
|
|
||||||
|
var toKey: LruKey[BlockHeader,EcKey32] =
|
||||||
|
|
||||||
|
# Use the seal hash for cache lookup
|
||||||
|
proc(header:BlockHeader): EcKey32 =
|
||||||
|
## If the signature's already cached, return that
|
||||||
|
# clique/clique.go(148): hash := header.Hash()
|
||||||
|
header.hash.data
|
||||||
|
|
||||||
|
var toValue: LruValue[BlockHeader,EthAddress,CliqueError] =
|
||||||
|
|
||||||
|
# Retrieve signature from the header's extra data fields
|
||||||
|
proc(header: BlockHeader): Result[EthAddress,CliqueError] =
|
||||||
|
|
||||||
|
# Extract signature from extra data field (last 65 bytes)
|
||||||
|
let msg = header.extraData
|
||||||
|
|
||||||
|
# clique/clique.go(153): if len(header.Extra) < extraSeal {
|
||||||
|
if msg.len < EXTRA_SEAL:
|
||||||
|
return err((errMissingSignature,""))
|
||||||
|
let signature = Signature.fromRaw(
|
||||||
|
msg.toOpenArray(msg.len - EXTRA_SEAL, msg.high))
|
||||||
|
if signature.isErr:
|
||||||
|
return err((errSkSigResult,$signature.error))
|
||||||
|
|
||||||
|
# Recover the public key from signature and seal hash
|
||||||
|
# clique/clique.go(159): pubkey, err := crypto.Ecrecover( [..]
|
||||||
|
let pubKey = recover(signature.value, SKMessage(header.hash.data))
|
||||||
|
if pubKey.isErr:
|
||||||
|
return err((errSkPubKeyResult,$pubKey.error))
|
||||||
|
|
||||||
|
# Convert public key to address.
|
||||||
|
return ok(pubKey.value.toCanonicalAddress)
|
||||||
|
|
||||||
|
cache.initLruCache(toKey, toValue, INMEMORY_SIGNATURES)
|
||||||
|
|
||||||
|
proc initEcRecover*: EcRecover {.gcsafe, raises: [Defect].} =
|
||||||
|
result.initEcRecover
|
||||||
|
|
||||||
|
|
||||||
|
# clique/clique.go(145): func ecrecover(header [..]
|
||||||
|
proc getEcRecover*(addrCache: var EcRecover; header: BlockHeader): auto =
|
||||||
|
## extract Ethereum account address from a signed header block, the relevant
|
||||||
|
## signature used is appended to the re-purposed extra data field
|
||||||
|
addrCache.getLruItem(header)
|
||||||
|
|
||||||
|
|
||||||
|
proc append*(rw: var RlpWriter; ecRec: EcRecover) {.inline.} =
|
||||||
|
## Generic support for `rlp.encode(ecRec)`
|
||||||
|
rw.append(ecRec.data)
|
||||||
|
|
||||||
|
proc read*(rlp: var Rlp; Q: type EcRecover): Q {.inline.} =
|
||||||
|
## Generic support for `rlp.decode(bytes)` for loading the cache from a
|
||||||
|
## serialised data stream.
|
||||||
|
result.initEcRecover
|
||||||
|
result.data = rlp.read(type result.data)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
206
nimbus/p2p/clique/recent_snaps.nim
Normal file
206
nimbus/p2p/clique/recent_snaps.nim
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
##
|
||||||
|
## Snapshot Cache for Clique PoA Consensus Protocol
|
||||||
|
## ================================================
|
||||||
|
##
|
||||||
|
## 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>`_
|
||||||
|
##
|
||||||
|
## Caveat: Not supporting RLP serialisation encode()/decode()
|
||||||
|
##
|
||||||
|
|
||||||
|
import
|
||||||
|
../../utils,
|
||||||
|
../../utils/lru_cache,
|
||||||
|
./clique_cfg,
|
||||||
|
./clique_defs,
|
||||||
|
./clique_utils,
|
||||||
|
./snapshot,
|
||||||
|
chronicles,
|
||||||
|
eth/[common, keys],
|
||||||
|
nimcrypto,
|
||||||
|
stint
|
||||||
|
|
||||||
|
export
|
||||||
|
snapshot
|
||||||
|
|
||||||
|
type
|
||||||
|
RecentArgs* = ref object
|
||||||
|
blockHash*: Hash256
|
||||||
|
blockNumber*: BlockNumber
|
||||||
|
parents*: seq[BlockHeader]
|
||||||
|
|
||||||
|
# Internal, temporary state variables
|
||||||
|
LocalArgs = ref object
|
||||||
|
headers: seq[BlockHeader]
|
||||||
|
|
||||||
|
# Internal type, simplify Hash256 for rlp serialisation
|
||||||
|
RecentKey = array[32, byte]
|
||||||
|
|
||||||
|
# Internal descriptor used by toValue()
|
||||||
|
RecentDesc = object
|
||||||
|
cfg: CliqueCfg
|
||||||
|
args: RecentArgs
|
||||||
|
local: LocalArgs
|
||||||
|
|
||||||
|
RecentSnaps* = object
|
||||||
|
cfg: CliqueCfg
|
||||||
|
cache: LruCache[RecentDesc,RecentKey,Snapshot,CliqueError]
|
||||||
|
|
||||||
|
{.push raises: [Defect,CatchableError].}
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "clique snap cache"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private helpers
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# clique/clique.go(394): if number == 0 || (number%c.config.Epoch [..]
|
||||||
|
proc canDiskCheckPointOk(d: RecentDesc): bool =
|
||||||
|
# If we're at the genesis, snapshot the initial state.
|
||||||
|
if d.args.blockNumber.isZero:
|
||||||
|
return true
|
||||||
|
|
||||||
|
# Alternatively if we're at a checkpoint block without a parent
|
||||||
|
# (light client CHT), or we have piled up more headers than allowed
|
||||||
|
# to be re-orged (chain reinit from a freezer), consider the
|
||||||
|
# checkpoint trusted and snapshot it.
|
||||||
|
if (d.args.blockNumber mod d.cfg.epoch.u256) == 0:
|
||||||
|
if (FULL_IMMUTABILITY_THRESHOLD < d.local.headers.len) or
|
||||||
|
d.cfg.dbChain.getBlockHeaderResult(d.args.blockNumber - 1).isErr:
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc isCheckPointOk(number: BlockNumber): bool =
|
||||||
|
number mod CHECKPOINT_INTERVAL == 0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# clique/clique.go(383): if number%checkpointInterval == 0 [..]
|
||||||
|
proc tryDiskSnapshot(d: RecentDesc; snap: var Snapshot): bool =
|
||||||
|
if d.args.blockNumber.isCheckPointOk:
|
||||||
|
if snap.loadSnapshot(d.cfg, d.args.blockHash).isOk:
|
||||||
|
trace "Loaded voting snapshot from disk",
|
||||||
|
blockNumber = d.args.blockNumber,
|
||||||
|
blockHash = d.args.blockHash
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc tryDiskCheckPoint(d: RecentDesc; snap: var Snapshot): bool =
|
||||||
|
if d.canDiskCheckPointOk:
|
||||||
|
# clique/clique.go(395): checkpoint := chain.GetHeaderByNumber [..]
|
||||||
|
let checkPoint = d.cfg.dbChain.getBlockHeaderResult(d.args.blockNumber)
|
||||||
|
if checkPoint.isErr:
|
||||||
|
return false
|
||||||
|
let
|
||||||
|
hash = checkPoint.value.hash
|
||||||
|
signersList = checkPoint.value.extraData.extraDataSigners
|
||||||
|
snap.initSnapshot(d.cfg, d.args.blockNumber, hash, signersList)
|
||||||
|
|
||||||
|
if snap.storeSnapshot.isOk:
|
||||||
|
info "Stored checkpoint snapshot to disk",
|
||||||
|
blockNumber = d.args.blockNumber,
|
||||||
|
blockHash = hash
|
||||||
|
return true
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc initRecentSnaps*(rs: var RecentSnaps;
|
||||||
|
cfg: CliqueCfg) {.gcsafe,raises: [Defect].} =
|
||||||
|
|
||||||
|
var toKey: LruKey[RecentDesc,RecentKey] =
|
||||||
|
proc(d: RecentDesc): RecentKey =
|
||||||
|
d.args.blockHash.data
|
||||||
|
|
||||||
|
var toValue: LruValue[RecentDesc,Snapshot,CliqueError] =
|
||||||
|
proc(d: RecentDesc): Result[Snapshot,CliqueError] =
|
||||||
|
var snap: Snapshot
|
||||||
|
|
||||||
|
while true:
|
||||||
|
# If an on-disk checkpoint snapshot can be found, use that
|
||||||
|
if d.tryDiskSnapshot(snap):
|
||||||
|
# clique/clique.go(386): snap = s
|
||||||
|
break
|
||||||
|
|
||||||
|
# Save checkpoint e.g. when at the genesis ..
|
||||||
|
if d.tryDiskCheckPoint(snap):
|
||||||
|
# clique/clique.go(407): log.Info("Stored [..]
|
||||||
|
break
|
||||||
|
|
||||||
|
# No snapshot for this header, gather the header and move backward
|
||||||
|
var header: BlockHeader
|
||||||
|
if 0 < d.args.parents.len:
|
||||||
|
# If we have explicit parents, pick from there (enforced)
|
||||||
|
header = d.args.parents[^1]
|
||||||
|
|
||||||
|
# clique/clique.go(416): if header.Hash() != hash [..]
|
||||||
|
if header.hash != d.args.blockHash and
|
||||||
|
header.blockNumber != d.args.blockNumber:
|
||||||
|
return err((errUnknownAncestor,""))
|
||||||
|
d.args.parents.setLen(d.args.parents.len-1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# No explicit parents (or no more left), reach out to the database
|
||||||
|
let rc = d.cfg.dbChain.getBlockHeaderResult(d.args.blockNumber)
|
||||||
|
if rc.isErr:
|
||||||
|
return err((errUnknownAncestor,""))
|
||||||
|
header = rc.value
|
||||||
|
|
||||||
|
d.local.headers.add header
|
||||||
|
d.args.blockNumber -= 1.u256
|
||||||
|
d.args.blockHash = header.parentHash
|
||||||
|
# => while loop
|
||||||
|
|
||||||
|
# Previous snapshot found, apply any pending headers on top of it
|
||||||
|
for i in 0 ..< d.local.headers.len div 2:
|
||||||
|
swap(d.local.headers[i], d.local.headers[^(1+i)])
|
||||||
|
block:
|
||||||
|
# clique/clique.go(434): snap, err := snap.apply(headers)
|
||||||
|
let rc = snap.applySnapshot(d.local.headers)
|
||||||
|
if rc.isErr:
|
||||||
|
return err(rc.error)
|
||||||
|
|
||||||
|
# If we've generated a new checkpoint snapshot, save to disk
|
||||||
|
if snap.blockNumber.isCheckPointOk and 0 < d.local.headers.len:
|
||||||
|
var rc = snap.storeSnapshot
|
||||||
|
if rc.isErr:
|
||||||
|
return err(rc.error)
|
||||||
|
trace "Stored voting snapshot to disk",
|
||||||
|
blockNumber = d.blockNumber,
|
||||||
|
blockHash = hash
|
||||||
|
|
||||||
|
# clique/clique.go(438): c.recents.Add(snap.Hash, snap)
|
||||||
|
return ok(snap)
|
||||||
|
|
||||||
|
rs.cfg = cfg
|
||||||
|
rs.cache.initLruCache(toKey, toValue, INMEMORY_SNAPSHOTS)
|
||||||
|
|
||||||
|
|
||||||
|
proc initRecentSnaps*(cfg: CliqueCfg): RecentSnaps {.gcsafe,raises: [Defect].} =
|
||||||
|
result.initRecentSnaps(cfg)
|
||||||
|
|
||||||
|
|
||||||
|
proc getRecentSnaps*(rs: var RecentSnaps; args: RecentArgs): auto =
|
||||||
|
## Get snapshot from cache or disk
|
||||||
|
rs.cache.getLruItem:
|
||||||
|
RecentDesc(cfg: rs.cfg,
|
||||||
|
args: args,
|
||||||
|
local: LocalArgs())
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
285
nimbus/p2p/clique/snapshot.nim
Normal file
285
nimbus/p2p/clique/snapshot.nim
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
##
|
||||||
|
## Snapshot Structure for Clique PoA Consensus Protocol
|
||||||
|
## ====================================================
|
||||||
|
##
|
||||||
|
## 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>`_
|
||||||
|
##
|
||||||
|
|
||||||
|
const
|
||||||
|
# debugging, enable with: nim c -r -d:noisy:3 ...
|
||||||
|
noisy {.intdefine.}: int = 0
|
||||||
|
isMainOk {.used.} = noisy > 2
|
||||||
|
|
||||||
|
import
|
||||||
|
../../db/[storage_types, db_chain],
|
||||||
|
../../utils/lru_cache,
|
||||||
|
./clique_cfg,
|
||||||
|
./clique_defs,
|
||||||
|
./clique_poll,
|
||||||
|
./ec_recover,
|
||||||
|
chronicles,
|
||||||
|
eth/[common, rlp, trie/db],
|
||||||
|
sequtils,
|
||||||
|
tables,
|
||||||
|
times
|
||||||
|
|
||||||
|
type
|
||||||
|
AddressHistory = Table[BlockNumber,EthAddress]
|
||||||
|
|
||||||
|
SnapshotData* = object
|
||||||
|
blockNumber: BlockNumber ## truncated block num where snapshot was created
|
||||||
|
blockHash: Hash256 ## block hash where snapshot was created
|
||||||
|
recents: AddressHistory ## recent signers for spam protections
|
||||||
|
|
||||||
|
# clique/snapshot.go(58): Recents map[uint64]common.Address [..]
|
||||||
|
ballot: CliquePoll ## Votes => authorised signers
|
||||||
|
|
||||||
|
# clique/snapshot.go(50): type Snapshot struct [..]
|
||||||
|
Snapshot* = object ## Snapshot is the state of the authorization voting at
|
||||||
|
## a given point in time.
|
||||||
|
cfg: CliqueCfg ## parameters to fine tune behavior
|
||||||
|
data*: SnapshotData ## real snapshot
|
||||||
|
|
||||||
|
{.push raises: [Defect,CatchableError].}
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "clique snapshot"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Private functions needed to support RLP conversion
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
proc append[K,V](rw: var RlpWriter; tab: Table[K,V]) {.inline.} =
|
||||||
|
rw.startList(tab.len)
|
||||||
|
for key,value in tab.pairs:
|
||||||
|
rw.append((key,value))
|
||||||
|
|
||||||
|
proc read[K,V](rlp: var Rlp;
|
||||||
|
Q: type Table[K,V]): Q {.inline, raises: [Defect,CatchableError].} =
|
||||||
|
for w in rlp.items:
|
||||||
|
let (key,value) = w.read((K,V))
|
||||||
|
result[key] = value
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Public functions
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# clique/snapshot.go(72): func newSnapshot(config [..]
|
||||||
|
proc initSnapshot*(s: var Snapshot; cfg: CliqueCfg;
|
||||||
|
number: BlockNumber; hash: Hash256; signers: openArray[EthAddress]) =
|
||||||
|
## This creates a new snapshot with the specified startup parameters. The
|
||||||
|
## method does not initialize the set of recent signers, so only ever use
|
||||||
|
## if for the genesis block.
|
||||||
|
s.cfg = cfg
|
||||||
|
s.data.blockNumber = number
|
||||||
|
s.data.blockHash = hash
|
||||||
|
s.data.recents = initTable[BlockNumber,EthAddress]()
|
||||||
|
s.data.ballot.initCliquePoll(signers)
|
||||||
|
|
||||||
|
proc initSnapshot*(cfg: CliqueCfg; number: BlockNumber; hash: Hash256;
|
||||||
|
signers: openArray[EthAddress]): Snapshot =
|
||||||
|
result.initSnapshot(cfg, number, hash, signers)
|
||||||
|
|
||||||
|
|
||||||
|
proc blockNumber*(s: var Snapshot): BlockNumber =
|
||||||
|
## Getter
|
||||||
|
s.data.blockNumber
|
||||||
|
|
||||||
|
# clique/snapshot.go(88): func loadSnapshot(config [..]
|
||||||
|
proc loadSnapshot*(s: var Snapshot; cfg: CliqueCfg;
|
||||||
|
hash: Hash256): CliqueResult {.gcsafe, raises: [Defect].} =
|
||||||
|
## Load an existing snapshot from the database.
|
||||||
|
try:
|
||||||
|
let
|
||||||
|
key = hash.cliqueSnapshotKey
|
||||||
|
value = cfg.dbChain.db.get(key.toOpenArray)
|
||||||
|
s.data = value.decode(SnapshotData)
|
||||||
|
s.cfg = cfg
|
||||||
|
except CatchableError as e:
|
||||||
|
return err((errSnapshotLoad,e.msg))
|
||||||
|
result = ok()
|
||||||
|
|
||||||
|
|
||||||
|
# clique/snapshot.go(104): func (s *Snapshot) store(db [..]
|
||||||
|
proc storeSnapshot*(s: var Snapshot): CliqueResult {.gcsafe,raises: [Defect].} =
|
||||||
|
## Insert the snapshot into the database.
|
||||||
|
try:
|
||||||
|
let
|
||||||
|
key = s.data.blockHash.cliqueSnapshotKey
|
||||||
|
value = rlp.encode(s.data)
|
||||||
|
s.cfg.dbChain.db.put(key.toOpenArray, value)
|
||||||
|
except CatchableError as e:
|
||||||
|
return err((errSnapshotStore,e.msg))
|
||||||
|
result = ok()
|
||||||
|
|
||||||
|
|
||||||
|
# clique/snapshot.go(185): func (s *Snapshot) apply(headers [..]
|
||||||
|
proc applySnapshot*(s: var Snapshot;
|
||||||
|
headers: openArray[BlockHeader]): CliqueResult =
|
||||||
|
## Initialises an authorization snapshot `snap` by applying the `headers`
|
||||||
|
## to the argument snapshot `s`.
|
||||||
|
|
||||||
|
# Allow passing in no headers for cleaner code
|
||||||
|
if headers.len == 0:
|
||||||
|
return ok()
|
||||||
|
|
||||||
|
# Sanity check that the headers can be applied
|
||||||
|
if headers[0].blockNumber != s.data.blockNumber + 1:
|
||||||
|
return err((errInvalidVotingChain,""))
|
||||||
|
for i in 0 ..< headers.len:
|
||||||
|
if headers[i+1].blockNumber != headers[i].blockNumber+1:
|
||||||
|
return err((errInvalidVotingChain,""))
|
||||||
|
|
||||||
|
# Iterate through the headers and create a new snapshot
|
||||||
|
let
|
||||||
|
start = getTime()
|
||||||
|
logInterval = initDuration(seconds = 8)
|
||||||
|
var
|
||||||
|
logged = start
|
||||||
|
|
||||||
|
# clique/snapshot.go(206): for i, header := range headers [..]
|
||||||
|
for headersIndex in 0 ..< headers.len:
|
||||||
|
let
|
||||||
|
# headersIndex => also used for logging at the end of this loop
|
||||||
|
header = headers[headersIndex]
|
||||||
|
number = header.blockNumber
|
||||||
|
|
||||||
|
# Remove any votes on checkpoint blocks
|
||||||
|
if number mod s.cfg.epoch.u256 == 0:
|
||||||
|
s.data.ballot.initCliquePoll
|
||||||
|
|
||||||
|
# Delete the oldest signer from the recent list to allow it signing again
|
||||||
|
block:
|
||||||
|
let limit = s.data.ballot.authSignersThreshold.u256
|
||||||
|
if limit <= number:
|
||||||
|
s.data.recents.del(number - limit)
|
||||||
|
|
||||||
|
# Resolve the authorization key and check against signers
|
||||||
|
let signer = ? s.cfg.signatures.getEcRecover(header)
|
||||||
|
if not s.data.ballot.isAuthSigner(signer):
|
||||||
|
return err((errUnauthorizedSigner,""))
|
||||||
|
for recent in s.data.recents.values:
|
||||||
|
if recent == signer:
|
||||||
|
return err((errRecentlySigned,""))
|
||||||
|
s.data.recents[number] = signer
|
||||||
|
|
||||||
|
# Header authorized, discard any previous vote from the signer
|
||||||
|
s.data.ballot.delVote(signer = signer, address = header.coinbase)
|
||||||
|
|
||||||
|
# Tally up the new vote from the signer
|
||||||
|
var authOk = false
|
||||||
|
if header.nonce == NONCE_AUTH:
|
||||||
|
authOk = true
|
||||||
|
elif header.nonce != NONCE_DROP:
|
||||||
|
return err((errInvalidVote,""))
|
||||||
|
s.data.ballot.addVote:
|
||||||
|
Vote(address: header.coinbase,
|
||||||
|
signer: signer,
|
||||||
|
blockNumber: number,
|
||||||
|
authorize: authOk)
|
||||||
|
|
||||||
|
# clique/snapshot.go(269): if limit := uint64(len(snap.Signers)/2 [..]
|
||||||
|
if s.data.ballot.authSignersShrunk:
|
||||||
|
# Signer list shrunk, delete any leftover recent caches
|
||||||
|
let limit = s.data.ballot.authSignersThreshold.u256
|
||||||
|
if limit <= number:
|
||||||
|
s.data.recents.del(number - limit)
|
||||||
|
|
||||||
|
# If we're taking too much time (ecrecover), notify the user once a while
|
||||||
|
if logInterval < logged - getTime():
|
||||||
|
info "Reconstructing voting history",
|
||||||
|
processed = headersIndex,
|
||||||
|
total = headers.len,
|
||||||
|
elapsed = start - getTime()
|
||||||
|
logged = getTime()
|
||||||
|
|
||||||
|
let sinceStart = start - getTime()
|
||||||
|
if logInterval < sinceStart:
|
||||||
|
info "Reconstructed voting history",
|
||||||
|
processed = headers.len,
|
||||||
|
elapsed = sinceStart
|
||||||
|
|
||||||
|
# clique/snapshot.go(303): snap.Number += uint64(len(headers))
|
||||||
|
s.data.blockNumber += headers.len.u256
|
||||||
|
s.data.blockHash = headers[^1].blockHash
|
||||||
|
result = ok()
|
||||||
|
|
||||||
|
proc validVote*(s: var Snapshot; address: EthAddress; authorize: bool): bool =
|
||||||
|
## Returns `true` if voting makes sense, at all.
|
||||||
|
s.data.ballot.validVote(address, authorize)
|
||||||
|
|
||||||
|
proc recent*(s: var Snapshot; address: EthAddress): Result[BlockNumber,void] =
|
||||||
|
## Return `BlockNumber` for `address` argument (if any)
|
||||||
|
for (number,recent) in s.data.recents.pairs:
|
||||||
|
if recent == address:
|
||||||
|
return ok(number)
|
||||||
|
return err()
|
||||||
|
|
||||||
|
proc signersThreshold*(s: var Snapshot): int =
|
||||||
|
## Forward to `CliquePoll`: Minimum number of authorised signers needed.
|
||||||
|
s.data.ballot.authSignersThreshold
|
||||||
|
|
||||||
|
proc isSigner*(s: var Snapshot; address: EthAddress): bool =
|
||||||
|
## Checks whether argukment ``address` is in signers list
|
||||||
|
s.data.ballot.isAuthSigner(address)
|
||||||
|
|
||||||
|
proc signers*(s: var Snapshot): seq[EthAddress] =
|
||||||
|
## Retrieves the sorted list of authorized signers
|
||||||
|
s.data.ballot.authSigners
|
||||||
|
|
||||||
|
|
||||||
|
# clique/snapshot.go(319): func (s *Snapshot) inturn(number [..]
|
||||||
|
proc inTurn*(s: var Snapshot; number: BlockNumber, signer: EthAddress): bool =
|
||||||
|
## Returns `true` if a signer at a given block height is in-turn or not.
|
||||||
|
let ascSignersList = s.data.ballot.authSigners
|
||||||
|
for offset in 0 ..< ascSignersList.len:
|
||||||
|
if ascSignersList[offset] == signer:
|
||||||
|
return (number mod ascSignersList.len.u256) == offset.u256
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Debugging/testing
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
when isMainModule and isMainOK:
|
||||||
|
|
||||||
|
var
|
||||||
|
cfg = newMemoryDB().newBaseChainDB.newCliqueCfg
|
||||||
|
ssh, ss1, ss2: Snapshot
|
||||||
|
key: Hash256
|
||||||
|
hdr: BlockHeader
|
||||||
|
|
||||||
|
ssh.init(cfg, 0.u256, key, @[])
|
||||||
|
ssh.data.blockNumber = 77.u256
|
||||||
|
key = ssh.data.blockHash
|
||||||
|
|
||||||
|
ssh.store.expect("store failed")
|
||||||
|
echo ">>> ", rlp.encode(ssh.data)
|
||||||
|
|
||||||
|
ss2.init(cfg, 0.u256, key, @[])
|
||||||
|
ss2.load(cfg,key).expect("load failed")
|
||||||
|
|
||||||
|
echo ">>> ", rlp.encode(ss2.data)
|
||||||
|
|
||||||
|
doAssert rlp.encode(ssh.data) == rlp.encode(ss2.data)
|
||||||
|
#discard ss1.data.sigcache.getEcRecover(hdr)
|
||||||
|
|
||||||
|
ss1 = ss2
|
||||||
|
echo "ss1.data: ", ss1.data.repr
|
||||||
|
echo "ss2.data: ", ss2.data.repr
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# End
|
||||||
|
# ------------------------------------------------------------------------------
|
Loading…
x
Reference in New Issue
Block a user