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:
Jordan Hrycaj 2021-08-03 08:15:32 +01:00 committed by GitHub
parent cc89d40a41
commit ec9354d2d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 289 additions and 90 deletions

View File

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

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

View File

@ -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],

View File

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

View File

@ -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 = "["

View File

@ -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 @[]

View File

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