mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-11 21:04:11 +00:00
Jordan/poa voting header (#782)
* Provide PoA voting header generator why: Handy for hive/smoke test details: Header generator is a re-implementation of the generator previously used for the canonical reference tests. * try fixing ci out-of-mem condition why: for some reason, the ci began behaving like a real win7/i386 machine where gcc is limited to 64k optimiser space * fix comments, typos ..
This commit is contained in:
parent
cc89d40a41
commit
ec9354d2d0
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -128,6 +128,11 @@ jobs:
|
||||
elif [[ '${{ matrix.target.evmc }}' == 'nimvm2' ]]; then
|
||||
echo "ENABLE_EVMC=0" >> $GITHUB_ENV
|
||||
echo "ENABLE_VM2=1" >> $GITHUB_ENV
|
||||
case '${{ matrix.target.cpu }}:${{ runner.os }}' in
|
||||
'i386:Windows')
|
||||
# on a real win7/i386, there is only 64k optimiser space for gcc
|
||||
echo "ENABLE_VM2LOWMEM=1" >> $GITHUB_ENV
|
||||
esac
|
||||
else
|
||||
echo "ENABLE_EVMC=0" >> $GITHUB_ENV
|
||||
echo "ENABLE_VM2=0" >> $GITHUB_ENV
|
||||
|
209
nimbus/p2p/clique/clique_genvote.nim
Normal file
209
nimbus/p2p/clique/clique_genvote.nim
Normal file
@ -0,0 +1,209 @@
|
||||
# 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.
|
||||
|
||||
##
|
||||
## Generate PoA Voting Header
|
||||
## ==========================
|
||||
##
|
||||
## 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, times],
|
||||
../../constants,
|
||||
../../db/db_chain,
|
||||
./clique_cfg,
|
||||
./clique_defs,
|
||||
./clique_desc,
|
||||
./clique_helpers,
|
||||
eth/[common, keys]
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/snapshot_test.go(49): func (ap *testerAccountPool) [..]
|
||||
proc extraCheckPoint(header: var BlockHeader; signers: openArray[EthAddress]) =
|
||||
## creates a Clique checkpoint signer section from the provided list
|
||||
## of authorized signers and embeds it into the provided header.
|
||||
header.extraData.setLen(EXTRA_VANITY)
|
||||
header.extraData.add signers.mapIt(toSeq(it)).concat
|
||||
header.extraData.add 0.byte.repeat(EXTRA_SEAL)
|
||||
|
||||
# clique/snapshot_test.go(77): func (ap *testerAccountPool) sign(header n[..]
|
||||
proc sign(header: var BlockHeader; signer: PrivateKey) {.inline.} =
|
||||
## sign calculates a Clique digital signature for the given block and embeds
|
||||
## it back into the header.
|
||||
#
|
||||
# Sign the header and embed the signature in extra data
|
||||
let
|
||||
hashData = header.hashSealHeader.data
|
||||
signature = signer.sign(SkMessage(hashData)).toRaw
|
||||
extraLen = header.extraData.len
|
||||
header.extraData.setLen(extraLen - EXTRA_SEAL)
|
||||
header.extraData.add signature
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/snapshot_test.go(415): blocks, _ := core.GenerateChain(&config, [..]
|
||||
proc cliqueGenvote*(
|
||||
c: Clique;
|
||||
voter: EthAddress; # new voter account/identity
|
||||
seal: PrivateKey; # signature key
|
||||
parent: BlockHeader;
|
||||
elapsed = initDuration();
|
||||
voteInOk = false; # vote in the new voter if `true`
|
||||
outOfTurn = false;
|
||||
checkPoint: seq[EthAddress] = @[]): BlockHeader =
|
||||
## Generate PoA voting header (as opposed to `epoch` synchronisation header.)
|
||||
## The function arguments are as follows:
|
||||
##
|
||||
## :c:
|
||||
## Clique descriptor. see the `newClique()` object constructor.
|
||||
##
|
||||
## :voter:
|
||||
## New voter account address to vote in or out (see `voteInOk`). A trivial
|
||||
## example for the first block #1 header would be choosing one of the
|
||||
## accounts listed in the `extraData` field fo the genesis header (note
|
||||
## that Goerli has exactly one of those accounts.) This trivial example
|
||||
## has no effect on the authorised voters' list.
|
||||
##
|
||||
## :seal:
|
||||
## Private key related to an authorised voter account. Again, a trivial
|
||||
## example for the block #1 header would be to (know and) use the
|
||||
## associated key for one of the accounts listed in the `extraData` field
|
||||
## fo the genesis header.
|
||||
##
|
||||
## :parent:
|
||||
## parent header to chain with (not necessarily on block chain yet). For
|
||||
## a block #1 header as a trivial example, this would be the genesis
|
||||
## header.
|
||||
##
|
||||
## :elapsed:
|
||||
## Optional timestamp distance from parent. This value defaults to valid
|
||||
## minimum time interval `c.cfg.period`
|
||||
##
|
||||
## :voteInOk:
|
||||
## Role of voting account. If `true`, the `voter` account address is voted
|
||||
## in to be accepted as authorised account. If `false`, the `voter` account
|
||||
## is voted to be removed (if it exists as authorised account, at all.)
|
||||
##
|
||||
## :outOfTurn:
|
||||
## Must be `false` if the `voter` is `in-turn` which is defined as the
|
||||
## property of a header block number retrieving the `seal` account address
|
||||
## when used as list index (modulo list-length) into the (internally
|
||||
## calculated and sorted) list of authorised signers. Absence of this
|
||||
## property is called `out-of-turn`.
|
||||
##
|
||||
## The classification `in-turn` and `out-of-turn` is used only with a
|
||||
## multi mining strategy where an `in-turn` block is slightly preferred.
|
||||
## Nevertheless, this property is to be locked into the block chain. In a
|
||||
## trivial example of an authorised signers list with exactly one entry,
|
||||
## all block numbers are zero modulo one, so are `in-turn`, and
|
||||
## `outOfTurn` would be left `false`.
|
||||
##
|
||||
## :checkPoint:
|
||||
## List of currently authorised signers. According to the Clique protocol
|
||||
## EIP-225, this list must be the same as the internally computed list of
|
||||
## authorised signers from the block chain.
|
||||
##
|
||||
## This list must appear on an `epoch` block and nowhere else. An `epoch`
|
||||
## block is a block where the block number is a multiple of `c.cfg.epoch`.
|
||||
## Typically, `c.cfg.epoch` is initialised as `30'000`.
|
||||
##
|
||||
let timeElapsed = if elapsed == initDuration(): c.cfg.period else: elapsed
|
||||
|
||||
result = BlockHeader(
|
||||
parentHash: parent.blockHash,
|
||||
ommersHash: EMPTY_UNCLE_HASH,
|
||||
stateRoot: parent.stateRoot,
|
||||
timestamp: parent.timestamp + timeElapsed,
|
||||
txRoot: BLANK_ROOT_HASH,
|
||||
receiptRoot: BLANK_ROOT_HASH,
|
||||
blockNumber: parent.blockNumber + 1,
|
||||
gasLimit: parent.gasLimit,
|
||||
#
|
||||
# clique/snapshot_test.go(417): gen.SetCoinbase(accounts.address( [..]
|
||||
coinbase: voter,
|
||||
#
|
||||
# clique/snapshot_test.go(418): if tt.votes[j].auth {
|
||||
nonce: if voteInOk: NONCE_AUTH else: NONCE_DROP,
|
||||
#
|
||||
# clique/snapshot_test.go(436): header.Difficulty = diffInTurn [..]
|
||||
difficulty: if outOfTurn: DIFF_NOTURN else: DIFF_INTURN,
|
||||
#
|
||||
extraData: 0.byte.repeat(EXTRA_VANITY + EXTRA_SEAL))
|
||||
|
||||
# clique/snapshot_test.go(432): if auths := tt.votes[j].checkpoint; [..]
|
||||
if 0 < checkPoint.len:
|
||||
result.extraCheckPoint(checkPoint)
|
||||
|
||||
# Generate the signature and embed it into the header
|
||||
result.sign(seal)
|
||||
|
||||
|
||||
proc cliqueGenvote*(
|
||||
c: Clique; voter: EthAddress; seal: PrivateKey;
|
||||
elapsed = initDuration();
|
||||
voteInOk = false;
|
||||
outOfTurn = false;
|
||||
checkPoint: seq[EthAddress] = @[]): BlockHeader
|
||||
{.gcsafe, raises: [Defect,CatchableError].} =
|
||||
## Variant of `clique_genvote()` where the `parent` is the canonical head
|
||||
## on the the block chain database.
|
||||
##
|
||||
## Trivial example (aka smoke test):
|
||||
##
|
||||
## :signature: `S`
|
||||
## :account address: `a(S)`
|
||||
## :genesis: extraData contains exactly one signer `a(S)`
|
||||
##
|
||||
## [..]
|
||||
##
|
||||
## | import pkg/[times], ..
|
||||
## | import p2p/[chain,clique], p2p/clique/clique_genvote, ..
|
||||
##
|
||||
## [..]
|
||||
##
|
||||
## | var db: BaseChainDB = ...
|
||||
## | var c = db.newChain
|
||||
##
|
||||
##
|
||||
## | \# overwrite, typically initialised at 15s
|
||||
## | const threeSecs = initDuration(seconds = 3)
|
||||
## | c.clique.cfg.period = threeSecs
|
||||
##
|
||||
##
|
||||
## | \# create first block (assuming empty block chain), mind `a(S)`, `S`
|
||||
## | let header = c.clique.clique_genvote(`a(S)`, `S`, elapsed = threeSecs)
|
||||
##
|
||||
## [..]
|
||||
##
|
||||
## let ok = c.persistBlocks(@[header],@[BlockBody()])
|
||||
##
|
||||
## [..]
|
||||
##
|
||||
c.clique_genvote(voter, seal,
|
||||
parent = c.cfg.db.getCanonicalHead,
|
||||
elapsed = elapsed,
|
||||
voteInOk = voteInOk,
|
||||
outOfTurn = outOfTurn,
|
||||
checkPoint = checkPoint)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
@ -9,16 +9,14 @@
|
||||
# according to those terms.
|
||||
|
||||
##
|
||||
## Tuoole & Utils for Clique PoA Consensus Protocol
|
||||
## ================================================
|
||||
## Tools & 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
|
||||
std/[algorithm, times],
|
||||
|
@ -102,11 +102,11 @@ proc isSigner*(s: Snapshot; address: EthAddress): bool {.inline.} =
|
||||
|
||||
# clique/snapshot.go(319): func (s *Snapshot) inturn(number [..]
|
||||
proc inTurn*(s: Snapshot; number: BlockNumber, signer: EthAddress): bool =
|
||||
## Returns `true` if a signer at a given block height is in-turn or not.
|
||||
## Returns `true` if a signer at a given block height is in-turn.
|
||||
let ascSignersList = s.ballot.authSigners
|
||||
for offset in 0 ..< ascSignersList.len:
|
||||
if ascSignersList[offset] == signer:
|
||||
return (number mod ascSignersList.len.u256) == offset.u256
|
||||
if 0 < ascSignersList.len:
|
||||
let offset = (number mod ascSignersList.len.u256).truncate(int64)
|
||||
return ascSignersList[offset] == signer
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
|
@ -40,7 +40,7 @@ logScope:
|
||||
proc say(s: Snapshot; v: varargs[string,`$`]) {.inline.} =
|
||||
discard
|
||||
# uncomment body to enable
|
||||
s.cfg.say v
|
||||
#s.cfg.say v
|
||||
|
||||
proc pp(a: openArray[BlockHeader]; first, last: int): string {.inline.} =
|
||||
result = "["
|
||||
|
@ -15,6 +15,7 @@ import
|
||||
../../nimbus/p2p/[chain,
|
||||
clique,
|
||||
clique/clique_desc,
|
||||
clique/clique_genvote,
|
||||
clique/clique_helpers,
|
||||
clique/clique_snapshot,
|
||||
clique/snapshot/snapshot_desc],
|
||||
@ -304,39 +305,6 @@ proc address*(ap: TesterPool; account: string): EthAddress =
|
||||
if account != "":
|
||||
result = ap.privateKey(account).toPublicKey.toCanonicalAddress
|
||||
|
||||
|
||||
# clique/snapshot_test.go(49): func (ap *testerAccountPool) [..]
|
||||
proc checkpoint*(ap: TesterPool;
|
||||
header: var BlockHeader; signers: openArray[string]) =
|
||||
## creates a Clique checkpoint signer section from the provided list
|
||||
## of authorized signers and embeds it into the provided header.
|
||||
header.extraData.setLen(EXTRA_VANITY)
|
||||
header.extraData.add signers
|
||||
.mapIt(ap.address(it))
|
||||
.sorted(EthAscending)
|
||||
.mapIt(toSeq(it))
|
||||
.concat
|
||||
header.extraData.add 0.byte.repeat(EXTRA_SEAL)
|
||||
|
||||
|
||||
# clique/snapshot_test.go(77): func (ap *testerAccountPool) sign(header n[..]
|
||||
proc sign*(ap: TesterPool; header: var BlockHeader; signer: string) =
|
||||
## sign calculates a Clique digital signature for the given block and embeds
|
||||
## it back into the header.
|
||||
#
|
||||
# Sign the header and embed the signature in extra data
|
||||
let
|
||||
hashData = header.hashSealHeader.data
|
||||
signature = ap.privateKey(signer).sign(SkMessage(hashData)).toRaw
|
||||
extraLen = header.extraData.len
|
||||
header.extraData.setLen(extraLen - EXTRA_SEAL)
|
||||
header.extraData.add signature
|
||||
#
|
||||
# Register for debugging
|
||||
ap.xSeals[signature] = XSealValue(
|
||||
blockNumber: header.blockNumber.truncate(uint64),
|
||||
account: signer)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public: set up & manage voter database
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -378,34 +346,25 @@ proc appendVoter*(ap: TesterPool;
|
||||
else:
|
||||
ap.batch[^1][^1]
|
||||
|
||||
var header = BlockHeader(
|
||||
parentHash: parent.blockHash,
|
||||
ommersHash: EMPTY_UNCLE_HASH,
|
||||
stateRoot: parent.stateRoot,
|
||||
timestamp: parent.timestamp + initDuration(seconds = 100),
|
||||
txRoot: BLANK_ROOT_HASH,
|
||||
receiptRoot: BLANK_ROOT_HASH,
|
||||
blockNumber: parent.blockNumber + 1,
|
||||
gasLimit: parent.gasLimit,
|
||||
#
|
||||
# clique/snapshot_test.go(417): gen.SetCoinbase(accounts.address( [..]
|
||||
coinbase: ap.address(voter.voted),
|
||||
#
|
||||
# clique/snapshot_test.go(418): if tt.votes[j].auth {
|
||||
nonce: if voter.auth: NONCE_AUTH else: NONCE_DROP,
|
||||
#
|
||||
# clique/snapshot_test.go(436): header.Difficulty = diffInTurn [..]
|
||||
difficulty: if voter.noTurn: DIFF_NOTURN else: DIFF_INTURN,
|
||||
#
|
||||
extraData: 0.byte.repeat(EXTRA_VANITY + EXTRA_SEAL))
|
||||
let header = ap.chain.clique.cliqueGenvote(
|
||||
voter = ap.address(voter.voted),
|
||||
seal = ap.privateKey(voter.signer),
|
||||
parent = parent,
|
||||
elapsed = initDuration(seconds = 100),
|
||||
voteInOk = voter.auth,
|
||||
outOfTurn = voter.noTurn,
|
||||
checkPoint = voter.checkpoint.mapIt(ap.address(it)).sorted(EthAscending))
|
||||
|
||||
# clique/snapshot_test.go(432): if auths := tt.votes[j].checkpoint; [..]
|
||||
if 0 < voter.checkpoint.len:
|
||||
doAssert (header.blockNumber mod ap.clique.cfg.epoch) == 0
|
||||
ap.checkpoint(header,voter.checkpoint)
|
||||
|
||||
# Generate the signature, embed it into the header and the block
|
||||
ap.sign(header, voter.signer)
|
||||
# Register for debugging
|
||||
let
|
||||
extraLen = header.extraData.len
|
||||
extraSeal = header.extraData[extraLen - EXTRA_SEAL ..< extraLen]
|
||||
ap.xSeals[toArray(XSealKey.len,extraSeal)] = XSealValue(
|
||||
blockNumber: header.blockNumber.truncate(uint64),
|
||||
account: voter.signer)
|
||||
|
||||
if voter.newbatch:
|
||||
ap.batch.add @[]
|
||||
|
@ -17,32 +17,60 @@ type
|
||||
TesterVote* = object ## VoterBlock represents a single block signed by a
|
||||
## particular account, where the account may or may not
|
||||
## have cast a Clique vote.
|
||||
signer*: string ## Account that signed this particular block
|
||||
voted*: string ## Optional value if the signer voted on
|
||||
## adding/removing ## someone
|
||||
auth*: bool ## Whether the vote was to authorize (or
|
||||
## deauthorize)
|
||||
checkpoint*: seq[string] ## List of authorized signers if this is an epoch
|
||||
## block
|
||||
noTurn*: bool ## initialise `NOTURN` it `true`, otherwise
|
||||
## `INTURN` (not part of Go ref implementation,
|
||||
## used here to avoid `fakeDiff` kludge in the
|
||||
## Go implementation)
|
||||
signer*: string ##\
|
||||
## Account that signed this particular block
|
||||
|
||||
voted*: string ##\
|
||||
## Optional value if the signer voted on adding/removing ## someone
|
||||
|
||||
auth*: bool ##\
|
||||
## Whether the vote was to authorize (or deauthorize)
|
||||
|
||||
checkpoint*: seq[string] ##\
|
||||
## List of authorized signers if this is an epoch block
|
||||
|
||||
noTurn*: bool ##\
|
||||
## Initialise `NOTURN` if `true`, otherwise use `INTURN`. This is not
|
||||
## part of Go ref test implementation. The flag used here to avoid what
|
||||
## is implemented as `fakeDiff` kludge in the Go ref test implementation.
|
||||
##
|
||||
## Note that the `noTurn` value depends on the sort order of the
|
||||
## calculated authorised signers account address list. These account
|
||||
## addresses in turn (no pun intended) depend on the private keys of
|
||||
## these accounts. Now, the private keys are generated on-the-fly by a
|
||||
## PRNG which re-seeded the same for each test. So the sort order is
|
||||
## predictable and the correct value of the the `noTurn` flag can be set
|
||||
## by sort of experimenting with the tests (and/or refering to earlier
|
||||
## woking test specs.)
|
||||
|
||||
newbatch*: bool
|
||||
|
||||
TestSpecs* = object ## Define the various voting scenarios to test
|
||||
id*: int ## Test id
|
||||
info*: string ## Test description
|
||||
epoch*: int ## Number of blocks in an epoch (unset = 30000)
|
||||
runBack*: bool ## Set `applySnapsMinBacklog` flag
|
||||
signers*: seq[string] ## Initial list of authorized signers in the
|
||||
## genesis
|
||||
votes*: seq[TesterVote] ## Chain of signed blocks, potentially influencing
|
||||
## auths
|
||||
results*: seq[string] ## Final list of authorized signers after all
|
||||
## blocks
|
||||
failure*: CliqueErrorType ## Failure if some block is invalid according to
|
||||
## the rules
|
||||
|
||||
TestSpecs* = object ## Defining genesis and the various voting scenarios
|
||||
## to test (see `votes`.)
|
||||
id*: int ##\
|
||||
## Test id
|
||||
|
||||
info*: string ##\
|
||||
## Test description
|
||||
|
||||
epoch*: int ##\
|
||||
## Number of blocks in an epoch (unset = 30000)
|
||||
|
||||
runBack*: bool ##\
|
||||
## Set `applySnapsMinBacklog` flag
|
||||
|
||||
signers*: seq[string] ##\
|
||||
## Initial list of authorized signers in the genesis
|
||||
|
||||
votes*: seq[TesterVote] ##\
|
||||
## Chain of signed blocks, potentially influencing auths
|
||||
|
||||
results*: seq[string] ##\
|
||||
## Final list of authorized signers after all blocks
|
||||
|
||||
failure*: CliqueErrorType ##\
|
||||
## Failure if some block is invalid according to the rules
|
||||
|
||||
const
|
||||
# Define the various voting scenarios to test
|
||||
|
Loading…
x
Reference in New Issue
Block a user