rm Clique consensus method support and Goerli network (#2219)
* rm Clique consensus method support and Goerli network * rm a few more SealingEngineRef and GoerliNets
This commit is contained in:
parent
72912626a2
commit
e895c0baeb
|
@ -18,7 +18,6 @@ import
|
|||
../../../nimbus/[
|
||||
config,
|
||||
constants,
|
||||
core/sealer,
|
||||
core/chain,
|
||||
core/tx_pool,
|
||||
core/tx_pool/tx_item,
|
||||
|
@ -45,7 +44,6 @@ type
|
|||
com : CommonRef
|
||||
node : EthereumNode
|
||||
server : RpcHttpServer
|
||||
sealer : SealingEngineRef
|
||||
ttd : DifficultyInt
|
||||
client : RpcHttpClient
|
||||
sync : BeaconSyncRef
|
||||
|
@ -93,7 +91,7 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E
|
|||
chain = newChain(com)
|
||||
|
||||
com.initializeEmptyDb()
|
||||
let txPool = TxPoolRef.new(com, conf.engineSigner)
|
||||
let txPool = TxPoolRef.new(com, ZERO_ADDRESS)
|
||||
|
||||
node.addEthHandlerCapability(
|
||||
node.peerPool,
|
||||
|
@ -117,9 +115,6 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E
|
|||
echo "Failed to create rpc server: ", error
|
||||
quit(QuitFailure)
|
||||
|
||||
sealer = SealingEngineRef.new(
|
||||
chain, ctx, conf.engineSigner,
|
||||
txPool, EngineStopped)
|
||||
sync = if com.ttd().isSome:
|
||||
BeaconSyncRef.init(node, chain, ctx.rng, conf.maxPeers, id=conf.tcpPort.int)
|
||||
else:
|
||||
|
@ -135,8 +130,6 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E
|
|||
if chainFile.len > 0:
|
||||
if not importRlpBlock(chainFolder / chainFile, com):
|
||||
quit(QuitFailure)
|
||||
elif not enableAuth:
|
||||
sealer.start()
|
||||
|
||||
server.start()
|
||||
|
||||
|
@ -153,7 +146,6 @@ proc newEngineEnv*(conf: var NimbusConf, chainFile: string, enableAuth: bool): E
|
|||
com : com,
|
||||
node : node,
|
||||
server : server,
|
||||
sealer : sealer,
|
||||
client : client,
|
||||
sync : sync,
|
||||
txPool : txPool,
|
||||
|
@ -165,7 +157,6 @@ proc close*(env: EngineEnv) =
|
|||
if not env.sync.isNil:
|
||||
env.sync.stop()
|
||||
waitFor env.client.close()
|
||||
waitFor env.sealer.stop()
|
||||
waitFor env.server.closeWait()
|
||||
|
||||
proc setRealTTD*(env: EngineEnv) =
|
||||
|
|
|
@ -17,7 +17,6 @@ import
|
|||
transaction,
|
||||
vm_state,
|
||||
vm_types,
|
||||
core/clique,
|
||||
core/dao,
|
||||
core/validate,
|
||||
core/chain/chain_desc,
|
||||
|
@ -107,9 +106,6 @@ proc setBlock*(c: ChainRef; header: BlockHeader;
|
|||
let dbTx = c.db.beginTransaction()
|
||||
defer: dbTx.dispose()
|
||||
|
||||
var cliqueState = c.clique.cliqueSave
|
||||
defer: c.clique.cliqueRestore(cliqueState)
|
||||
|
||||
c.com.hardForkTransition(header)
|
||||
|
||||
# Needed for figuring out whether KVT cleanup is due (see at the end)
|
||||
|
|
|
@ -84,7 +84,7 @@ proc main() =
|
|||
)
|
||||
|
||||
com.initializeEmptyDb()
|
||||
let txPool = TxPoolRef.new(com, conf.engineSigner)
|
||||
let txPool = TxPoolRef.new(com, ZERO_ADDRESS)
|
||||
discard importRlpBlock(blocksFile, com)
|
||||
let ctx = setupGraphqlContext(com, ethNode, txPool)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import
|
|||
../../../nimbus/config,
|
||||
../../../nimbus/rpc,
|
||||
../../../nimbus/utils/utils,
|
||||
../../../nimbus/core/[chain, tx_pool, sealer],
|
||||
../../../nimbus/core/[chain, tx_pool],
|
||||
../../../tests/test_helpers,
|
||||
./vault
|
||||
|
||||
|
@ -28,7 +28,6 @@ type
|
|||
vault*: Vault
|
||||
rpcClient*: RpcClient
|
||||
rpcServer: RpcServer
|
||||
sealingEngine: SealingEngineRef
|
||||
stopServer: StopServerProc
|
||||
|
||||
const
|
||||
|
@ -85,28 +84,20 @@ proc setupEnv*(): TestEnv =
|
|||
com.initializeEmptyDb()
|
||||
|
||||
let chainRef = newChain(com)
|
||||
let txPool = TxPoolRef.new(com, conf.engineSigner)
|
||||
let txPool = TxPoolRef.new(com, ZERO_ADDRESS)
|
||||
|
||||
# txPool must be informed of active head
|
||||
# so it can know the latest account state
|
||||
let head = com.db.getCanonicalHead()
|
||||
doAssert txPool.smartHead(head)
|
||||
|
||||
let sealingEngine = SealingEngineRef.new(
|
||||
chainRef, ethCtx, conf.engineSigner,
|
||||
txPool, EngineStopped
|
||||
)
|
||||
|
||||
let rpcServer = setupRpcServer(ethCtx, com, ethNode, txPool, conf)
|
||||
let rpcClient = newRpcHttpClient()
|
||||
waitFor rpcClient.connect("127.0.0.1", Port(8545), false)
|
||||
let stopServer = stopRpcHttpServer
|
||||
|
||||
sealingEngine.start()
|
||||
|
||||
let t = TestEnv(
|
||||
rpcClient: rpcClient,
|
||||
sealingEngine: sealingEngine,
|
||||
rpcServer: rpcServer,
|
||||
vault : newVault(chainID, gasPrice, rpcClient),
|
||||
stopServer: stopServer
|
||||
|
@ -116,5 +107,4 @@ proc setupEnv*(): TestEnv =
|
|||
|
||||
proc stopEnv*(t: TestEnv) =
|
||||
waitFor t.rpcClient.close()
|
||||
waitFor t.sealingEngine.stop()
|
||||
t.stopServer(t.rpcServer)
|
||||
|
|
|
@ -54,7 +54,6 @@ const
|
|||
CustomNet* = 0.NetworkId
|
||||
# these are public network id
|
||||
MainNet* = 1.NetworkId
|
||||
GoerliNet* = 5.NetworkId
|
||||
SepoliaNet* = 11155111.NetworkId
|
||||
HoleskyNet* = 17000.NetworkId
|
||||
|
||||
|
@ -356,10 +355,6 @@ proc validateChainConfig*(conf: ChainConfig): bool =
|
|||
if cur.time.isSome:
|
||||
lastTimeBasedFork = cur
|
||||
|
||||
if conf.clique.period.isSome or
|
||||
conf.clique.epoch.isSome:
|
||||
conf.consensusType = ConsensusType.POA
|
||||
|
||||
proc parseGenesis*(data: string): Genesis
|
||||
{.gcsafe.} =
|
||||
try:
|
||||
|
@ -473,29 +468,6 @@ proc chainConfigForNetwork*(id: NetworkId): ChainConfig =
|
|||
terminalTotalDifficulty: some(mainNetTTD),
|
||||
shanghaiTime: some(1_681_338_455.EthTime)
|
||||
)
|
||||
of GoerliNet:
|
||||
ChainConfig(
|
||||
clique: CliqueOptions(period: some(15), epoch: some(30000)),
|
||||
consensusType: ConsensusType.POA,
|
||||
chainId: GoerliNet.ChainId,
|
||||
# Genesis: # 2015-07-30 15:26:13 UTC
|
||||
homesteadBlock: some(0.toBlockNumber), # Included in genesis
|
||||
daoForkSupport: false,
|
||||
eip150Block: some(0.toBlockNumber), # Included in genesis
|
||||
eip150Hash: toDigest("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
eip155Block: some(0.toBlockNumber), # Included in genesis
|
||||
eip158Block: some(0.toBlockNumber), # Included in genesis
|
||||
byzantiumBlock: some(0.toBlockNumber), # Included in genesis
|
||||
constantinopleBlock: some(0.toBlockNumber), # Included in genesis
|
||||
petersburgBlock: some(0.toBlockNumber), # Included in genesis
|
||||
istanbulBlock: some(1_561_651.toBlockNumber), # 2019-10-30 13:53:05 UTC
|
||||
muirGlacierBlock: some(4_460_644.toBlockNumber), # Skipped in Goerli
|
||||
berlinBlock: some(4_460_644.toBlockNumber), # 2021-03-18 05:29:51 UTC
|
||||
londonBlock: some(5_062_605.toBlockNumber), # 2021-07-01 03:19:39 UTC
|
||||
terminalTotalDifficulty: some(10790000.u256),
|
||||
shanghaiTime: some(1_678_832_736.EthTime),
|
||||
cancunTime: some(1_705_473_120.EthTime), # 2024-01-17 06:32:00
|
||||
)
|
||||
of SepoliaNet:
|
||||
const sepoliaTTD = parse("17000000000000000",UInt256)
|
||||
ChainConfig(
|
||||
|
@ -553,15 +525,6 @@ proc genesisBlockForNetwork*(id: NetworkId): Genesis
|
|||
difficulty: 17179869184.u256,
|
||||
alloc: decodePrealloc(mainnetAllocData)
|
||||
)
|
||||
of GoerliNet:
|
||||
Genesis(
|
||||
nonce: 0.toBlockNonce,
|
||||
timestamp: EthTime(0x5c51a607),
|
||||
extraData: hexToSeqByte("0x22466c6578692069732061207468696e6722202d204166726900000000000000e0a2bd4258d2768837baa26a28fe71dc079f84c70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
gasLimit: 0xa00000,
|
||||
difficulty: 1.u256,
|
||||
alloc: decodePrealloc(goerliAllocData)
|
||||
)
|
||||
of SepoliaNet:
|
||||
Genesis(
|
||||
nonce: 0.toBlockNonce,
|
||||
|
|
|
@ -13,7 +13,7 @@ import
|
|||
std/[options],
|
||||
chronicles,
|
||||
eth/trie/trie_defs,
|
||||
../core/[pow, clique, casper],
|
||||
../core/[pow, casper],
|
||||
../db/[core_db, ledger, storage_types],
|
||||
../utils/[utils, ec_recover],
|
||||
".."/[constants, errors],
|
||||
|
@ -91,9 +91,6 @@ type
|
|||
pow: PowRef
|
||||
## Wrapper around `hashimotoLight()` and lookup cache
|
||||
|
||||
poa: Clique
|
||||
## For non-PoA networks this descriptor is ignored.
|
||||
|
||||
pos: CasperRef
|
||||
## Proof Of Stake descriptor
|
||||
|
||||
|
@ -161,10 +158,6 @@ proc init(com : CommonRef,
|
|||
com.ldgType = (if ldgType == LedgerType(0): LedgerCache else: ldgType)
|
||||
com.pruneHistory= pruneHistory
|
||||
|
||||
# Initalise the PoA state regardless of whether it is needed on the current
|
||||
# network. For non-PoA networks this descriptor is ignored.
|
||||
com.poa = newClique(com.db, com.cliquePeriod, com.cliqueEpoch)
|
||||
|
||||
# Always initialise the PoW epoch cache even though it migh no be used
|
||||
com.pow = PowRef.new
|
||||
com.pos = CasperRef.new
|
||||
|
@ -281,7 +274,6 @@ proc clone*(com: CommonRef, db: CoreDbRef): CommonRef =
|
|||
currentFork : com.currentFork,
|
||||
consensusType: com.consensusType,
|
||||
pow : com.pow,
|
||||
poa : com.poa,
|
||||
pos : com.pos,
|
||||
ldgType : com.ldgType,
|
||||
pruneHistory : com.pruneHistory)
|
||||
|
@ -353,19 +345,10 @@ func forkGTE*(com: CommonRef, fork: HardFork): bool =
|
|||
com.currentFork >= fork
|
||||
|
||||
# TODO: move this consensus code to where it belongs
|
||||
proc minerAddress*(com: CommonRef; header: BlockHeader): EthAddress
|
||||
func minerAddress*(com: CommonRef; header: BlockHeader): EthAddress
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
if com.consensusType != ConsensusType.POA:
|
||||
# POW and POS return header.coinbase
|
||||
return header.coinbase
|
||||
|
||||
# POA return ecRecover
|
||||
let account = header.ecRecover
|
||||
if account.isErr:
|
||||
let msg = "Could not recover account address: " & $account.error
|
||||
raise newException(ValidationError, msg)
|
||||
|
||||
account.value
|
||||
# POW and POS return header.coinbase
|
||||
return header.coinbase
|
||||
|
||||
func forkId*(com: CommonRef, head, time: uint64): ForkID {.gcsafe.} =
|
||||
## EIP 2364/2124
|
||||
|
@ -436,10 +419,6 @@ func startOfHistory*(com: CommonRef): Hash256 =
|
|||
## Getter
|
||||
com.startOfHistory
|
||||
|
||||
func poa*(com: CommonRef): Clique =
|
||||
## Getter
|
||||
com.poa
|
||||
|
||||
func pow*(com: CommonRef): PowRef =
|
||||
## Getter
|
||||
com.pow
|
||||
|
|
|
@ -23,10 +23,6 @@ type
|
|||
# algorithm: Ethash
|
||||
POW
|
||||
|
||||
# Proof of Authority
|
||||
# algorithm: Clique
|
||||
POA
|
||||
|
||||
# Proof of Stake
|
||||
# algorithm: Casper
|
||||
POS
|
||||
|
|
|
@ -189,14 +189,6 @@ type
|
|||
abbr: "e"
|
||||
name: "import-key" }: InputFile
|
||||
|
||||
engineSigner* {.
|
||||
desc: "Set the signer address(as 20 bytes hex) and enable sealing engine to run and " &
|
||||
"producing blocks at specified interval (only PoA/Clique supported)"
|
||||
defaultValue: ZERO_ADDRESS
|
||||
defaultValueDesc: ""
|
||||
abbr: "s"
|
||||
name: "engine-signer" }: EthAddress
|
||||
|
||||
verifyFrom* {.
|
||||
desc: "Enable extra verification when current block number greater than verify-from"
|
||||
defaultValueDesc: ""
|
||||
|
@ -616,7 +608,6 @@ proc getNetworkId(conf: NimbusConf): Option[NetworkId] =
|
|||
let network = toLowerAscii(conf.network)
|
||||
case network
|
||||
of "mainnet": return some MainNet
|
||||
of "goerli" : return some GoerliNet
|
||||
of "sepolia": return some SepoliaNet
|
||||
of "holesky": return some HoleskyNet
|
||||
else:
|
||||
|
@ -693,8 +684,6 @@ proc getBootNodes*(conf: NimbusConf): seq[ENode] =
|
|||
case conf.networkId
|
||||
of MainNet:
|
||||
bootstrapNodes.setBootnodes(MainnetBootnodes)
|
||||
of GoerliNet:
|
||||
bootstrapNodes.setBootnodes(GoerliBootnodes)
|
||||
of SepoliaNet:
|
||||
bootstrapNodes.setBootnodes(SepoliaBootnodes)
|
||||
of HoleskyNet:
|
||||
|
|
|
@ -14,8 +14,7 @@ import
|
|||
../../common/common,
|
||||
../../utils/utils,
|
||||
../../vm_types,
|
||||
../pow,
|
||||
../clique
|
||||
../pow
|
||||
|
||||
export
|
||||
common
|
||||
|
@ -62,12 +61,12 @@ proc newChain*(com: CommonRef,
|
|||
vmState: vmState,
|
||||
)
|
||||
|
||||
proc newChain*(com: CommonRef): ChainRef =
|
||||
func newChain*(com: CommonRef): ChainRef =
|
||||
## Constructor for the `Chain` descriptor object. All sub-object descriptors
|
||||
## are initialised with defaults. So is extra block chain validation
|
||||
## * `enabled` for PoA networks (such as Goerli)
|
||||
## * `disabled` for non-PaA networks
|
||||
let extraValidation = com.consensus in {ConsensusType.POA, ConsensusType.POS}
|
||||
let extraValidation = com.consensus == ConsensusType.POS
|
||||
ChainRef(
|
||||
com: com,
|
||||
validateBlock: true,
|
||||
|
@ -81,10 +80,6 @@ proc vmState*(c: ChainRef): BaseVMState =
|
|||
## Getter
|
||||
c.vmState
|
||||
|
||||
proc clique*(c: ChainRef): Clique =
|
||||
## Getter
|
||||
c.com.poa
|
||||
|
||||
proc pow*(c: ChainRef): PowRef =
|
||||
## Getter
|
||||
c.com.pow
|
||||
|
|
|
@ -14,8 +14,6 @@ import
|
|||
../../db/ledger,
|
||||
../../vm_state,
|
||||
../../vm_types,
|
||||
../clique/clique_verify,
|
||||
../clique,
|
||||
../executor,
|
||||
../validate,
|
||||
./chain_desc,
|
||||
|
@ -79,9 +77,6 @@ proc persistBlocksImpl(c: ChainRef; headers: openArray[BlockHeader];
|
|||
let dbTx = c.db.beginTransaction()
|
||||
defer: dbTx.dispose()
|
||||
|
||||
var cliqueState = c.clique.cliqueSave
|
||||
defer: c.clique.cliqueRestore(cliqueState)
|
||||
|
||||
c.com.hardForkTransition(headers[0])
|
||||
|
||||
# Note that `0 < headers.len`, assured when called from `persistBlocks()`
|
||||
|
@ -110,15 +105,14 @@ proc persistBlocksImpl(c: ChainRef; headers: openArray[BlockHeader];
|
|||
if c.validateBlock and c.extraValidation and
|
||||
c.verifyFrom <= header.blockNumber:
|
||||
|
||||
if c.com.consensus != ConsensusType.POA:
|
||||
let res = c.com.validateHeaderAndKinship(
|
||||
header,
|
||||
body,
|
||||
checkSealOK = false) # TODO: how to checkseal from here
|
||||
if res.isErr:
|
||||
debug "block validation error",
|
||||
msg = res.error
|
||||
return ValidationResult.Error
|
||||
let res = c.com.validateHeaderAndKinship(
|
||||
header,
|
||||
body,
|
||||
checkSealOK = false) # TODO: how to checkseal from here
|
||||
if res.isErr:
|
||||
debug "block validation error",
|
||||
msg = res.error
|
||||
return ValidationResult.Error
|
||||
|
||||
if c.generateWitness:
|
||||
vmState.generateWitness = true
|
||||
|
@ -137,21 +131,6 @@ proc persistBlocksImpl(c: ChainRef; headers: openArray[BlockHeader];
|
|||
if validationResult != ValidationResult.OK:
|
||||
return validationResult
|
||||
|
||||
if c.validateBlock and c.extraValidation and
|
||||
c.verifyFrom <= header.blockNumber:
|
||||
|
||||
if c.com.consensus == ConsensusType.POA:
|
||||
var parent = if 0 < i: @[headers[i-1]] else: @[]
|
||||
let rc = c.clique.cliqueVerify(c.com, header, parent)
|
||||
if rc.isOk:
|
||||
# mark it off so it would not auto-restore previous state
|
||||
c.clique.cliqueDispose(cliqueState)
|
||||
else:
|
||||
debug "PoA header verification failed",
|
||||
blockNumber = header.blockNumber,
|
||||
msg = $rc.error
|
||||
return ValidationResult.Error
|
||||
|
||||
if c.generateWitness:
|
||||
let dbTx = c.db.beginTransaction()
|
||||
defer: dbTx.dispose()
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2024 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
|
||||
./clique/[clique_cfg, clique_defs, clique_desc],
|
||||
./clique/snapshot/[ballot, snapshot_desc],
|
||||
stew/results
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
# Note that mining is unsupported. Unused code ported from the Go
|
||||
# implementation is stashed into the `clique_unused` module.
|
||||
export
|
||||
clique_cfg,
|
||||
clique_defs,
|
||||
clique_desc.Clique
|
||||
|
||||
type
|
||||
CliqueState* = ##\
|
||||
## Descriptor state snapshot which can be used for implementing
|
||||
## transaction trasnaction handling. Nore the `Snapshot` type
|
||||
## inside the `Result[]` is most probably opaque.
|
||||
Result[Snapshot,void]
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc newClique*(db: CoreDbRef, cliquePeriod: EthTime, cliqueEpoch: int): Clique =
|
||||
## Constructor for a new Clique proof-of-authority consensus engine. The
|
||||
## initial state of the engine is `empty`, there are no authorised signers.
|
||||
##
|
||||
## If chain_config provides `Period` or `Epoch`, then `Period` or `Epoch`
|
||||
## will be taken from chain_config. Otherwise, default value in `newCliqueCfg`
|
||||
## will be used
|
||||
|
||||
let cfg = db.newCliqueCfg
|
||||
if cliquePeriod > 0:
|
||||
cfg.period = cliquePeriod
|
||||
if cliqueEpoch > 0:
|
||||
cfg.epoch = cliqueEpoch
|
||||
cfg.newClique
|
||||
|
||||
proc cliqueSave*(c: Clique): CliqueState =
|
||||
## Save current `Clique` state. This state snapshot saves the internal
|
||||
## data that make up the list of authorised signers (see `cliqueSigners()`
|
||||
## below.)
|
||||
ok(c.snapshot)
|
||||
|
||||
proc cliqueRestore*(c: Clique; state: var CliqueState) =
|
||||
## Restore current `Clique` state from a saved snapshot.
|
||||
##
|
||||
## For the particular `state` argument this fuction is disabled with
|
||||
## `cliqueDispose()`. So it can be savely wrapped in a `defer:` statement.
|
||||
## In transaction lingo, this would then be the rollback function.
|
||||
if state.isOk:
|
||||
c.snapshot = state.value
|
||||
|
||||
proc cliqueDispose*(c: Clique; state: var CliqueState) =
|
||||
## Disable the function `cliqueDispose()` for the particular `state`
|
||||
## argument.
|
||||
##
|
||||
## In transaction lingo, this would be the commit function if
|
||||
## `cliqueRestore()` was wrapped in a `defer:` statement.
|
||||
state = err(CliqueState)
|
||||
|
||||
proc cliqueSigners*(c: Clique): seq[EthAddress] =
|
||||
## Retrieve the sorted list of authorized signers for the current state
|
||||
## of the `Clique` descriptor.
|
||||
##
|
||||
## Note that the return argument list is sorted on-the-fly each time this
|
||||
## function is invoked.
|
||||
c.snapshot.ballot.authSigners
|
||||
|
||||
proc cliqueSignersLen*(c: Clique): int =
|
||||
## Get the number of authorized signers for the current state of the
|
||||
## `Clique` descriptor. The result is equivalent to `c.cliqueSigners.len`.
|
||||
c.snapshot.ballot.authSignersLen
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,168 +0,0 @@
|
|||
# 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>`_
|
||||
##
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[random, times],
|
||||
ethash,
|
||||
../../db/core_db,
|
||||
../../utils/ec_recover,
|
||||
./clique_defs
|
||||
|
||||
export
|
||||
core_db
|
||||
|
||||
const
|
||||
prngSeed = 42
|
||||
|
||||
type
|
||||
CliqueCfg* = ref object of RootRef
|
||||
db*: CoreDbRef
|
||||
## All purpose (incl. blockchain) database.
|
||||
|
||||
nSnaps*: uint64
|
||||
## Number of snapshots stored on disk (for logging troublesshoting)
|
||||
|
||||
snapsData*: uint64
|
||||
## Raw payload stored on disk (for logging troublesshoting)
|
||||
|
||||
period: EthTime
|
||||
## Time between blocks to enforce.
|
||||
|
||||
ckpInterval: int
|
||||
## Number of blocks after which to save the vote snapshot to the
|
||||
## disk database.
|
||||
|
||||
roThreshold: int
|
||||
## Number of blocks after which a chain segment is considered immutable
|
||||
## (ie. 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.
|
||||
|
||||
prng: Rand
|
||||
## PRNG state for internal random generator. This PRNG is
|
||||
## cryptographically insecure but with reproducible data stream.
|
||||
|
||||
signatures: EcRecover
|
||||
## Recent block signatures cached to speed up mining.
|
||||
|
||||
epoch: int
|
||||
## The 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.
|
||||
|
||||
logInterval: Duration
|
||||
## Time interval after which the `snapshotApply()` function main loop
|
||||
## produces logging entries.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc newCliqueCfg*(db: CoreDbRef): CliqueCfg =
|
||||
result = CliqueCfg(
|
||||
db: db,
|
||||
epoch: EPOCH_LENGTH,
|
||||
period: BLOCK_PERIOD,
|
||||
ckpInterval: CHECKPOINT_INTERVAL,
|
||||
roThreshold: FULL_IMMUTABILITY_THRESHOLD,
|
||||
logInterval: SNAPS_LOG_INTERVAL_MICSECS,
|
||||
signatures: EcRecover.init(),
|
||||
prng: initRand(prngSeed))
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public helper funcion
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/clique.go(145): func ecrecover(header [..]
|
||||
proc ecRecover*(
|
||||
cfg: CliqueCfg;
|
||||
header: BlockHeader;
|
||||
): auto =
|
||||
cfg.signatures.ecRecover(header)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public setters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `epoch=`*(cfg: CliqueCfg; epoch: SomeInteger) =
|
||||
## Setter
|
||||
cfg.epoch = if 0 < epoch: epoch
|
||||
else: EPOCH_LENGTH
|
||||
|
||||
proc `period=`*(cfg: CliqueCfg; period: EthTime) =
|
||||
## Setter
|
||||
cfg.period = if period != EthTime(0): period
|
||||
else: BLOCK_PERIOD
|
||||
|
||||
proc `ckpInterval=`*(cfg: CliqueCfg; numBlocks: SomeInteger) =
|
||||
## Setter
|
||||
cfg.ckpInterval = if 0 < numBlocks: numBlocks
|
||||
else: CHECKPOINT_INTERVAL
|
||||
|
||||
proc `roThreshold=`*(cfg: CliqueCfg; numBlocks: SomeInteger) =
|
||||
## Setter
|
||||
cfg.roThreshold = if 0 < numBlocks: numBlocks
|
||||
else: FULL_IMMUTABILITY_THRESHOLD
|
||||
|
||||
proc `logInterval=`*(cfg: CliqueCfg; duration: Duration) =
|
||||
## Setter
|
||||
cfg.logInterval = if duration != Duration(): duration
|
||||
else: SNAPS_LOG_INTERVAL_MICSECS
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public PRNG, may be overloaded
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
method rand*(cfg: CliqueCfg; max: Natural): int {.gcsafe, base, raises: [].} =
|
||||
## The method returns a random number base on an internal PRNG providing a
|
||||
## reproducible stream of random data. This function is supposed to be used
|
||||
## exactly when repeatability comes in handy. Never to be used for crypto key
|
||||
## generation or like (except testing.)
|
||||
cfg.prng.rand(max)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public getter
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc epoch*(cfg: CliqueCfg): BlockNumber =
|
||||
## Getter
|
||||
cfg.epoch.u256
|
||||
|
||||
proc period*(cfg: CliqueCfg): EthTime =
|
||||
## Getter
|
||||
cfg.period
|
||||
|
||||
proc ckpInterval*(cfg: CliqueCfg): BlockNumber =
|
||||
## Getter
|
||||
cfg.ckpInterval.u256
|
||||
|
||||
proc roThreshold*(cfg: CliqueCfg): int =
|
||||
## Getter
|
||||
cfg.roThreshold
|
||||
|
||||
proc logInterval*(cfg: CliqueCfg): Duration =
|
||||
## Getter
|
||||
cfg.logInterval
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,261 +0,0 @@
|
|||
# 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>`_
|
||||
##
|
||||
|
||||
import
|
||||
std/[times],
|
||||
eth/common,
|
||||
stew/results,
|
||||
stint
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Constants copied from eip-225 specs & implementation
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
const
|
||||
# clique/clique.go(48): const ( [..]
|
||||
CHECKPOINT_INTERVAL* = ##\
|
||||
## Number of blocks after which to save the vote snapshot to the database
|
||||
4 * 1024
|
||||
|
||||
INMEMORY_SNAPSHOTS* = ##\
|
||||
## Number of recent vote snapshots to keep in memory.
|
||||
128
|
||||
|
||||
WIGGLE_TIME* = ##\
|
||||
## PoA mining only (currently unsupported).
|
||||
##
|
||||
## Random delay (per signer) to allow concurrent signers
|
||||
initDuration(seconds = 0, milliseconds = 500)
|
||||
|
||||
# clique/clique.go(57): var ( [..]
|
||||
BLOCK_PERIOD* = ##\
|
||||
## Minimum difference in seconds between two consecutive block timestamps.
|
||||
## Suggested time is 15s for the `testnet` to remain analogous to the
|
||||
## `mainnet` ethash target.
|
||||
EthTime 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# params/network_params.go(60): FullImmutabilityThreshold = 90000
|
||||
FULL_IMMUTABILITY_THRESHOLD* = ##\
|
||||
## Number of blocks after which a chain segment is considered immutable (ie.
|
||||
## 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
|
||||
|
||||
# Other
|
||||
SNAPS_LOG_INTERVAL_MICSECS* = ##\
|
||||
## Time interval after which the `snapshotApply()` function main loop
|
||||
## produces logging entries. The original value from the Go reference
|
||||
## implementation has 8 seconds (which seems a bit long.) For the first
|
||||
## 300k blocks in the Goerli chain, typical execution time in tests was
|
||||
## mostly below 300 micro secs.
|
||||
initDuration(microSeconds = 200)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Error tokens
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
type
|
||||
# clique/clique.go(76): var ( [..]
|
||||
CliqueErrorType* = enum
|
||||
resetCliqueError = ##\
|
||||
## Default/reset value (use `cliqueNoError` below rather than this valie)
|
||||
(0, "no error")
|
||||
|
||||
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"
|
||||
|
||||
errFutureBlock = ##\
|
||||
## is returned when a block's timestamp is in the future according to
|
||||
## the current node.
|
||||
"block in the future"
|
||||
|
||||
# additional/bespoke errors, manually added
|
||||
# -----------------------------------------
|
||||
|
||||
errUnknownHash = "No header found for hash value"
|
||||
errEmptyLruCache = "No snapshot available"
|
||||
|
||||
errNotInitialised = ##\
|
||||
## Initalisation value for `Result` entries
|
||||
"Not initialised"
|
||||
|
||||
errSetLruSnaps = ##\
|
||||
## Attempt to assign a value to a non-existing slot
|
||||
"Missing LRU slot for snapshot"
|
||||
|
||||
errEcRecover = ##\
|
||||
## Subsytem error"
|
||||
"ecRecover failed"
|
||||
|
||||
errSnapshotLoad ## DB subsytem error
|
||||
errSnapshotStore ## ..
|
||||
errSnapshotClone
|
||||
|
||||
errCliqueGasLimitOrBaseFee
|
||||
errCliqueExceedsGasLimit
|
||||
errCliqueGasRepriceFork
|
||||
errCliqueSealSigFn
|
||||
|
||||
errCliqueStopped = "process was interrupted"
|
||||
errCliqueUnclesNotAllowed = "uncles not allowed"
|
||||
|
||||
# not really an error
|
||||
nilCliqueSealNoBlockYet = "Sealing paused, waiting for transactions"
|
||||
nilCliqueSealSignedRecently = "Signed recently, must wait for others"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# More types and constants
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
type
|
||||
CliqueError* = ##\
|
||||
## Error message, tinned component + explanatory text (if any)
|
||||
(CliqueErrorType,string)
|
||||
|
||||
CliqueOkResult* = ##\
|
||||
## Standard ok/error result type for `Clique` functions
|
||||
Result[void,CliqueError]
|
||||
|
||||
const
|
||||
cliqueNoError* = ##\
|
||||
## No-error constant
|
||||
(resetCliqueError, "")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,186 +0,0 @@
|
|||
# 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.
|
||||
|
||||
##
|
||||
## Descriptor Objects 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>`_
|
||||
##
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/tables,
|
||||
./clique_cfg,
|
||||
./clique_defs,
|
||||
./snapshot/snapshot_desc,
|
||||
chronicles,
|
||||
eth/keys,
|
||||
stew/[keyed_queue, results]
|
||||
|
||||
type
|
||||
RawSignature* = array[RawSignatureSize, byte]
|
||||
|
||||
# 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[RawSignature, cstring]
|
||||
{.gcsafe, raises: [CatchableError].}
|
||||
|
||||
Proposals = Table[EthAddress,bool]
|
||||
|
||||
CliqueSnapKey* = ##\
|
||||
## Internal key used for the LRU cache (derived from Hash256).
|
||||
array[32,byte]
|
||||
|
||||
CliqueSnapLru = ##\
|
||||
## Snapshots cache
|
||||
KeyedQueue[CliqueSnapKey,Snapshot]
|
||||
|
||||
CliqueFailed* = ##\
|
||||
## Last failed state: block hash and error result
|
||||
(Hash256, CliqueError)
|
||||
|
||||
# clique/clique.go(172): type Clique struct { [..]
|
||||
Clique* = ref object ##\
|
||||
## Clique is the proof-of-authority consensus engine proposed to support
|
||||
## the Ethereum testnet following the Ropsten attacks.
|
||||
|
||||
signer*: EthAddress ##\
|
||||
## Ethereum address of the current signing key
|
||||
|
||||
signFn*: CliqueSignerFn ## Signer function to authorize hashes with
|
||||
|
||||
cfg: CliqueCfg ##\
|
||||
## Common engine parameters to fine tune behaviour
|
||||
|
||||
recents: CliqueSnapLru ##\
|
||||
## Snapshots cache for recent block search
|
||||
|
||||
snapshot: Snapshot ##\
|
||||
## Last successful snapshot
|
||||
|
||||
failed: CliqueFailed ##\
|
||||
## Last verification error (if any)
|
||||
|
||||
proposals: Proposals ##\
|
||||
## Cu1rrent list of proposals we are pushing
|
||||
|
||||
applySnapsMinBacklog: bool ##\
|
||||
## Epoch is a restart and sync point. Eip-225 requires that the epoch
|
||||
## header contains the full list of currently authorised signers.
|
||||
##
|
||||
## If this flag is set `true`, then the `cliqueSnapshot()` function will
|
||||
## walk back to the1 `epoch` header with at least `cfg.roThreshold` blocks
|
||||
## apart from the current header. This is how it is done in the reference
|
||||
## implementation.
|
||||
##
|
||||
## Leving the flag `false`, the assumption is that all the checkponts
|
||||
## before have been vetted already regardless of the current branch. So
|
||||
## the nearest `epoch` header is used.
|
||||
|
||||
logScope:
|
||||
topics = "clique PoA constructor"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/clique.go(191): func New(config [..]
|
||||
proc newClique*(cfg: CliqueCfg): Clique =
|
||||
## Initialiser for Clique proof-of-authority consensus engine with the
|
||||
## initial signers set to the ones provided by the user.
|
||||
result = Clique(cfg: cfg,
|
||||
snapshot: cfg.newSnapshot(BlockHeader()),
|
||||
proposals: initTable[EthAddress,bool]())
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public debug/pretty print
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `$`*(e: CliqueError): string =
|
||||
## Join text fragments
|
||||
result = $e[0]
|
||||
if e[1] != "":
|
||||
result &= ": " & e[1]
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public getters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc recents*(
|
||||
c: Clique;
|
||||
): var KeyedQueue[CliqueSnapKey,Snapshot]
|
||||
=
|
||||
## Getter
|
||||
c.recents
|
||||
|
||||
proc proposals*(c: Clique): var Proposals =
|
||||
## Getter
|
||||
c.proposals
|
||||
|
||||
proc snapshot*(c: Clique): Snapshot =
|
||||
## Getter, last successfully processed snapshot.
|
||||
c.snapshot
|
||||
|
||||
proc failed*(c: Clique): CliqueFailed =
|
||||
## Getter, last snapshot error.
|
||||
c.failed
|
||||
|
||||
proc cfg*(c: Clique): CliqueCfg =
|
||||
## Getter
|
||||
c.cfg
|
||||
|
||||
proc db*(c: Clique): CoreDbRef =
|
||||
## Getter
|
||||
c.cfg.db
|
||||
|
||||
proc applySnapsMinBacklog*(c: Clique): bool =
|
||||
## Getter.
|
||||
##
|
||||
## If this flag is set `true`, then the `cliqueSnapshot()` function will
|
||||
## walk back to the `epoch` header with at least `cfg.roThreshold` blocks
|
||||
## apart from the current header. This is how it is done in the reference
|
||||
## implementation.
|
||||
##
|
||||
## Setting the flag `false` which is the default, the assumption is that all
|
||||
## the checkponts before have been vetted already regardless of the current
|
||||
## branch. So the nearest `epoch` header is used.
|
||||
c.applySnapsMinBacklog
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public setters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `db=`*(c: Clique; db: CoreDbRef) =
|
||||
## Setter, re-set database
|
||||
c.cfg.db = db
|
||||
c.proposals = initTable[EthAddress,bool]()
|
||||
|
||||
proc `snapshot=`*(c: Clique; snaps: Snapshot) =
|
||||
## Setter
|
||||
c.snapshot = snaps
|
||||
|
||||
proc `failed=`*(c: Clique; failure: CliqueFailed) =
|
||||
## Setter
|
||||
c.failed = failure
|
||||
|
||||
proc `applySnapsMinBacklog=`*(c: Clique; value: bool) =
|
||||
## Setter
|
||||
c.applySnapsMinBacklog = value
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,208 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2024 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],
|
||||
eth/[common, keys],
|
||||
../../constants,
|
||||
./clique_cfg,
|
||||
./clique_defs,
|
||||
./clique_desc,
|
||||
./clique_helpers
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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) =
|
||||
## 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 = EthTime(0);
|
||||
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 == EthTime(0): c.cfg.period else: elapsed
|
||||
|
||||
result = BlockHeader(
|
||||
parentHash: parent.blockHash,
|
||||
ommersHash: EMPTY_UNCLE_HASH,
|
||||
stateRoot: parent.stateRoot,
|
||||
timestamp: parent.timestamp + timeElapsed,
|
||||
txRoot: EMPTY_ROOT_HASH,
|
||||
receiptRoot: EMPTY_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 = EthTime(0);
|
||||
voteInOk = false;
|
||||
outOfTurn = false;
|
||||
checkPoint: seq[EthAddress] = @[]): BlockHeader
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## Variant of `clique_genvote()` where the `parent` is the canonical head
|
||||
## on 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: CoreDbRef = ...
|
||||
## | 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.cliqueGenvote(voter, seal,
|
||||
parent = c.cfg.db.getCanonicalHead,
|
||||
elapsed = elapsed,
|
||||
voteInOk = voteInOk,
|
||||
outOfTurn = outOfTurn,
|
||||
checkPoint = checkPoint)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,111 +0,0 @@
|
|||
# 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.
|
||||
|
||||
##
|
||||
## 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>`_
|
||||
##
|
||||
|
||||
import
|
||||
std/[algorithm, times],
|
||||
../../constants,
|
||||
../../utils/utils,
|
||||
./clique_defs,
|
||||
eth/[common, rlp],
|
||||
stew/[objects, results],
|
||||
stint
|
||||
|
||||
type
|
||||
EthSortOrder* = enum
|
||||
EthDescending = SortOrder.Descending.ord
|
||||
EthAscending = SortOrder.Ascending.ord
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func zeroItem[T](t: typedesc[T]): T =
|
||||
discard
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc isZero*[T: EthAddress|Hash256|Duration](a: T): bool =
|
||||
## `true` if `a` is all zero
|
||||
a == zeroItem(T)
|
||||
|
||||
proc sorted*(e: openArray[EthAddress]; order = EthAscending): seq[EthAddress] =
|
||||
proc eCmp(x, y: EthAddress): int =
|
||||
for n in 0 ..< x.len:
|
||||
if x[n] < y[n]:
|
||||
return -1
|
||||
elif y[n] < x[n]:
|
||||
return 1
|
||||
e.sorted(cmp = eCmp, order = order.ord.SortOrder)
|
||||
|
||||
|
||||
proc cliqueResultErr*(w: CliqueError): CliqueOkResult =
|
||||
## Return error result (syntactic sugar)
|
||||
err(w)
|
||||
|
||||
|
||||
proc extraDataAddresses*(extraData: Blob): seq[EthAddress] =
|
||||
## Extract signer addresses from extraData header field
|
||||
|
||||
proc toEthAddress(a: openArray[byte]; start: int): EthAddress =
|
||||
toArray(EthAddress.len, a[start ..< start + EthAddress.len])
|
||||
|
||||
if EXTRA_VANITY + EXTRA_SEAL < extraData.len and
|
||||
((extraData.len - (EXTRA_VANITY + EXTRA_SEAL)) mod EthAddress.len) == 0:
|
||||
var addrOffset = EXTRA_VANITY
|
||||
while addrOffset + EthAddress.len <= extraData.len - EXTRA_SEAL:
|
||||
result.add extraData.toEthAddress(addrOffset)
|
||||
addrOffset += EthAddress.len
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Seal hash support
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/clique.go(730): func encodeSigHeader(w [..]
|
||||
proc encodeSealHeader*(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)
|
||||
|
||||
rlp.encode(rlpHeader)
|
||||
|
||||
# clique/clique.go(688): func SealHash(header *types.Header) common.Hash {
|
||||
proc hashSealHeader*(header: BlockHeader): Hash256 =
|
||||
## Returns the hash of a block prior to it being sealed.
|
||||
header.encodeSealHeader.keccakHash
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,261 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2023 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||
# http://opensource.org/licenses/MIT)
|
||||
# at your option. This file may not be copied, modified, or distributed except
|
||||
# according to those terms.
|
||||
|
||||
##
|
||||
## Mining Support for Clique PoA Consensus Protocol
|
||||
## ===================!=============================
|
||||
##
|
||||
## 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>`_
|
||||
##
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[sequtils],
|
||||
chronicles,
|
||||
chronos,
|
||||
eth/keys,
|
||||
"../.."/[constants, utils/ec_recover],
|
||||
../../common/common,
|
||||
./clique_cfg,
|
||||
./clique_defs,
|
||||
./clique_desc,
|
||||
./clique_helpers,
|
||||
./clique_snapshot,
|
||||
./clique_verify,
|
||||
./snapshot/[ballot, snapshot_desc]
|
||||
|
||||
logScope:
|
||||
topics = "clique PoA Mining"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc isValidVote(s: Snapshot; a: EthAddress; authorize: bool): bool {.gcsafe, raises: [].} =
|
||||
s.ballot.isValidVote(a, authorize)
|
||||
|
||||
proc isSigner*(s: Snapshot; address: EthAddress): bool {.gcsafe, raises: [].} =
|
||||
## See `clique_verify.isSigner()`
|
||||
s.ballot.isAuthSigner(address)
|
||||
|
||||
# clique/snapshot.go(319): func (s *Snapshot) inturn(number [..]
|
||||
proc inTurn*(s: Snapshot; number: BlockNumber, signer: EthAddress): bool {.gcsafe, raises: [].} =
|
||||
## See `clique_verify.inTurn()`
|
||||
let ascSignersList = s.ballot.authSigners
|
||||
for offset in 0 ..< ascSignersList.len:
|
||||
if ascSignersList[offset] == signer:
|
||||
return (number mod ascSignersList.len.u256) == offset.u256
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/clique.go(681): func calcDifficulty(snap [..]
|
||||
proc calcDifficulty(s: Snapshot; signer: EthAddress): DifficultyInt {.gcsafe, raises: [].} =
|
||||
if s.inTurn(s.blockNumber + 1, signer):
|
||||
DIFF_INTURN
|
||||
else:
|
||||
DIFF_NOTURN
|
||||
|
||||
proc recentBlockNumber*(s: Snapshot;
|
||||
a: EthAddress): Result[BlockNumber,void] {.gcsafe, raises: [].} =
|
||||
## Return `BlockNumber` for `address` argument (if any)
|
||||
for (number,recent) in s.recents.pairs:
|
||||
if recent == a:
|
||||
return ok(number)
|
||||
return err()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/clique.go(506): func (c *Clique) Prepare(chain [..]
|
||||
proc prepare*(c: Clique; parent: BlockHeader, header: var BlockHeader): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## For the Consensus Engine, `prepare()` initializes the consensus fields
|
||||
## of a block header according to the rules of a particular engine.
|
||||
##
|
||||
## This implementation prepares all the consensus fields of the header for
|
||||
## running the transactions on top.
|
||||
|
||||
# Assemble the voting snapshot to check which votes make sense
|
||||
let rc = c.cliqueSnapshot(parent.blockHash, @[])
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
|
||||
# if we are not voting, coinbase should be filled with zero
|
||||
# because other subsystem e.g txpool can produce block header
|
||||
# with non zero coinbase. if that coinbase is one of the signer
|
||||
# and the nonce is zero, that signer will be vote out from
|
||||
# signer list
|
||||
header.coinbase.reset
|
||||
|
||||
let modEpoch = (parent.blockNumber+1) mod c.cfg.epoch
|
||||
if modEpoch.isZero.not:
|
||||
# Gather all the proposals that make sense voting on
|
||||
var addresses: seq[EthAddress]
|
||||
for (address,authorize) in c.proposals.pairs:
|
||||
if c.snapshot.isValidVote(address, authorize):
|
||||
addresses.add address
|
||||
|
||||
# If there's pending proposals, cast a vote on them
|
||||
if 0 < addresses.len:
|
||||
header.coinbase = addresses[c.cfg.rand(addresses.len-1)]
|
||||
header.nonce = if header.coinbase in c.proposals: NONCE_AUTH
|
||||
else: NONCE_DROP
|
||||
|
||||
# Set the correct difficulty
|
||||
header.difficulty = c.snapshot.calcDifficulty(c.signer)
|
||||
|
||||
# Ensure the extra data has all its components
|
||||
header.extraData.setLen(EXTRA_VANITY)
|
||||
if modEpoch.isZero:
|
||||
header.extraData.add c.snapshot.ballot.authSigners.mapIt(toSeq(it)).concat
|
||||
header.extraData.add 0.byte.repeat(EXTRA_SEAL)
|
||||
|
||||
# Mix digest is reserved for now, set to empty
|
||||
header.mixDigest.reset
|
||||
|
||||
# Ensure the timestamp has the correct delay
|
||||
header.timestamp = parent.timestamp + c.cfg.period
|
||||
if header.timestamp < EthTime.now():
|
||||
header.timestamp = EthTime.now()
|
||||
|
||||
ok()
|
||||
|
||||
proc prepareForSeal*(c: Clique; prepHeader: BlockHeader; header: var BlockHeader) {.gcsafe, raises: [].} =
|
||||
# TODO: use system.move?
|
||||
header.nonce = prepHeader.nonce
|
||||
header.extraData = prepHeader.extraData
|
||||
header.mixDigest = prepHeader.mixDigest
|
||||
|
||||
# clique/clique.go(589): func (c *Clique) Authorize(signer [..]
|
||||
proc authorize*(c: Clique; signer: EthAddress; signFn: CliqueSignerFn) {.gcsafe, raises: [].} =
|
||||
## Injects private key into the consensus engine to mint new blocks with.
|
||||
c.signer = signer
|
||||
c.signFn = signFn
|
||||
|
||||
# clique/clique.go(724): func CliqueRLP(header [..]
|
||||
proc cliqueRlp*(header: BlockHeader): seq[byte] {.gcsafe, raises: [].} =
|
||||
## Returns the rlp bytes which needs to be signed for the proof-of-authority
|
||||
## sealing. The RLP to sign consists of the entire header apart from the 65
|
||||
## byte signature contained at the end of the extra data.
|
||||
##
|
||||
## Note, the method requires the extra data to be at least 65 bytes,
|
||||
## otherwise it panics. This is done to avoid accidentally using both forms
|
||||
## (signature present or not), which could be abused to produce different
|
||||
##hashes for the same header.
|
||||
header.encodeSealHeader
|
||||
|
||||
# clique/clique.go(688): func SealHash(header *types.Header) common.Hash {
|
||||
proc sealHash*(header: BlockHeader): Hash256 {.gcsafe, raises: [].} =
|
||||
## For the Consensus Engine, `sealHash()` returns the hash of a block prior
|
||||
## to it being sealed.
|
||||
##
|
||||
## This implementation returns the hash of a block prior to it being sealed.
|
||||
header.hashSealHeader
|
||||
|
||||
|
||||
# clique/clique.go(599): func (c *Clique) Seal(chain [..]
|
||||
proc seal*(c: Clique; ethBlock: var EthBlock):
|
||||
Result[void,CliqueError] {.gcsafe,
|
||||
raises: [CatchableError].} =
|
||||
## This implementation attempts to create a sealed block using the local
|
||||
## signing credentials.
|
||||
|
||||
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 == 0 and ethBlock.txs.len == 0:
|
||||
info $nilCliqueSealNoBlockYet
|
||||
return err((nilCliqueSealNoBlockYet, ""))
|
||||
|
||||
# Don't hold the signer fields for the entire sealing procedure
|
||||
let
|
||||
signer = c.signer
|
||||
signFn = c.signFn
|
||||
|
||||
# Bail out if we're unauthorized to sign a block
|
||||
let rc = c.cliqueSnapshot(header.parentHash)
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
if not c.snapshot.isSigner(signer):
|
||||
return err((errUnauthorizedSigner, ""))
|
||||
|
||||
# If we're amongst the recent signers, wait for the next block
|
||||
let seen = c.snapshot.recentBlockNumber(signer)
|
||||
if seen.isOk:
|
||||
# Signer is among recents, only wait if the current block does not
|
||||
# shift it out
|
||||
if header.blockNumber < seen.value + c.snapshot.signersThreshold.u256:
|
||||
info $nilCliqueSealSignedRecently
|
||||
return err((nilCliqueSealSignedRecently, ""))
|
||||
|
||||
when false:
|
||||
# Sweet, the protocol permits us to sign the block, wait for our time
|
||||
var delay = header.timestamp - EthTime.now()
|
||||
if header.difficulty == DIFF_NOTURN:
|
||||
# It's not our turn explicitly to sign, delay it a bit
|
||||
let wiggle = c.snapshot.signersThreshold.int64 * WIGGLE_TIME
|
||||
# Kludge for limited rand() argument range
|
||||
if wiggle.inSeconds < (int.high div 1000).int64:
|
||||
let rndWiggleMs = c.cfg.rand(wiggle.inMilliseconds.int)
|
||||
delay += initDuration(milliseconds = rndWiggleMs)
|
||||
else:
|
||||
let rndWiggleSec = c.cfg.rand((wiggle.inSeconds and int.high).int)
|
||||
delay += initDuration(seconds = rndWiggleSec)
|
||||
|
||||
trace "Out-of-turn signing requested",
|
||||
wiggle = $wiggle
|
||||
|
||||
# Sign all the things!
|
||||
try:
|
||||
let signature = signFn(signer,header.cliqueRlp)
|
||||
if signature.isErr:
|
||||
return err((errCliqueSealSigFn,$signature.error))
|
||||
let extraLen = header.extraData.len
|
||||
if EXTRA_SEAL < extraLen:
|
||||
header.extraData.setLen(extraLen - EXTRA_SEAL)
|
||||
header.extraData.add signature.value
|
||||
except CatchableError as exc:
|
||||
return err((errCliqueSealSigFn,
|
||||
"Error when signing block header: " & exc.msg))
|
||||
|
||||
ethBlock = ethBlock.withHeader(header)
|
||||
ok()
|
||||
|
||||
# clique/clique.go(673): func (c *Clique) CalcDifficulty(chain [..]
|
||||
proc calcDifficulty*(c: Clique;
|
||||
parent: BlockHeader): Result[DifficultyInt,CliqueError]
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## For the Consensus Engine, `calcDifficulty()` is the difficulty adjustment
|
||||
## algorithm. It returns the difficulty that a new block should have.
|
||||
##
|
||||
## This implementation returns the difficulty that a new block should have:
|
||||
## * DIFF_NOTURN(2) if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX
|
||||
## * DIFF_INTURN(1) if BLOCK_NUMBER % SIGNER_COUNT == SIGNER_INDEX
|
||||
let rc = c.cliqueSnapshot(parent)
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
return ok(c.snapshot.calcDifficulty(c.signer))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,442 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2024 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 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
|
||||
std/[sequtils, strutils],
|
||||
chronicles,
|
||||
eth/[keys],
|
||||
stew/[keyed_queue, results],
|
||||
../../utils/prettify,
|
||||
"."/[clique_cfg, clique_defs, clique_desc],
|
||||
./snapshot/[snapshot_apply, snapshot_desc]
|
||||
|
||||
type
|
||||
# Internal sub-descriptor for `LocalSnapsDesc`
|
||||
LocalPivot = object
|
||||
header: BlockHeader
|
||||
hash: Hash256
|
||||
|
||||
# Internal sub-descriptor for `LocalSnapsDesc`
|
||||
LocalPath = object
|
||||
snaps: Snapshot ## snapshot for given hash
|
||||
chain: seq[BlockHeader] ## header chain towards snapshot
|
||||
error: CliqueError ## error message
|
||||
|
||||
# Internal sub-descriptor for `LocalSnapsDesc`
|
||||
LocalSubChain = object
|
||||
first: int ## first chain[] element to be used
|
||||
top: int ## length of chain starting at position 0
|
||||
|
||||
LocalSnaps = object
|
||||
c: Clique
|
||||
start: LocalPivot ## start here searching for checkpoints
|
||||
trail: LocalPath ## snapshot location
|
||||
subChn: LocalSubChain ## chain[] sub-range
|
||||
parents: HeadersHolderRef ## explicit parents
|
||||
|
||||
HeadersHolderRef* = ref object
|
||||
headers*: seq[BlockHeader]
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
logScope:
|
||||
topics = "clique PoA snapshot"
|
||||
|
||||
static:
|
||||
const stopCompilerGossip {.used.} = 42.toSI
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private debugging functions, pretty printing
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
template say(d: var LocalSnaps; v: varargs[untyped]): untyped =
|
||||
discard
|
||||
# uncomment body to enable, note that say() prints on <stderr>
|
||||
# d.c.cfg.say v
|
||||
|
||||
#proc pp(a: Hash256): string =
|
||||
# if a == EMPTY_ROOT_HASH:
|
||||
# "*blank-root*"
|
||||
# elif a == EMPTY_SHA3:
|
||||
# "*empty-sha3*"
|
||||
# else:
|
||||
# a.data.mapIt(it.toHex(2)).join[56 .. 63].toLowerAscii
|
||||
|
||||
#proc pp(q: openArray[BlockHeader]; n: int): string =
|
||||
# result = "["
|
||||
# if 5 < n:
|
||||
# result &= toSeq(q[0 .. 2]).mapIt("#" & $it.blockNumber).join(", ")
|
||||
# result &= " .." & $n & ".. #" & $q[n-1].blockNumber
|
||||
# else:
|
||||
# result &= toSeq(q[0 ..< n]).mapIt("#" & $it.blockNumber).join(", ")
|
||||
# result &= "]"
|
||||
|
||||
#proc pp(b: BlockNumber, q: openArray[BlockHeader]; n: int): string =
|
||||
# "#" & $b & " + " & q.pp(n)
|
||||
|
||||
|
||||
#proc pp(q: openArray[BlockHeader]): string =
|
||||
# q.pp(q.len)
|
||||
|
||||
#proc pp(b: BlockNumber, q: openArray[BlockHeader]): string =
|
||||
# b.pp(q, q.len)
|
||||
|
||||
|
||||
#proc pp(h: BlockHeader, q: openArray[BlockHeader]; n: int): string =
|
||||
# "headers=(" & h.blockNumber.pp(q,n) & ")"
|
||||
|
||||
#proc pp(h: BlockHeader, q: openArray[BlockHeader]): string =
|
||||
# h.pp(q,q.len)
|
||||
|
||||
#proc pp(t: var LocalPath; w: var LocalSubChain): string =
|
||||
# var (a, b) = (w.first, w.top)
|
||||
# if a == 0 and b == 0: b = t.chain.len
|
||||
# "trail=(#" & $t.snaps.blockNumber & " + " & t.chain[a ..< b].pp & ")"
|
||||
|
||||
#proc pp(t: var LocalPath): string =
|
||||
# var w = LocalSubChain()
|
||||
# t.pp(w)
|
||||
|
||||
#proc pp(err: CliqueError): string =
|
||||
# "(" & $err[0] & "," & err[1] & ")"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc maxCheckPointLe(d: var LocalSnaps; number: BlockNumber): BlockNumber =
|
||||
let epc = number mod d.c.cfg.ckpInterval
|
||||
if epc < number:
|
||||
number - epc
|
||||
else:
|
||||
# epc == number => number < ckpInterval
|
||||
0.u256
|
||||
|
||||
proc isCheckPoint(d: var LocalSnaps; number: BlockNumber): bool =
|
||||
(number mod d.c.cfg.ckpInterval).isZero
|
||||
|
||||
proc isEpoch(d: var LocalSnaps; number: BlockNumber): bool =
|
||||
(number mod d.c.cfg.epoch).isZero
|
||||
|
||||
proc isSnapshotPosition(d: var LocalSnaps; number: BlockNumber): bool =
|
||||
# clique/clique.go(394): if number == 0 || (number%c.config.Epoch [..]
|
||||
if d.isEpoch(number):
|
||||
if number.isZero:
|
||||
# At the genesis => snapshot the initial state.
|
||||
return true
|
||||
if not d.c.applySnapsMinBacklog:
|
||||
return true
|
||||
if d.c.cfg.roThreshold < d.trail.chain.len:
|
||||
# We have piled up more headers than allowed to be re-orged (chain
|
||||
# reinit from a freezer), regard checkpoint trusted and snapshot it.
|
||||
return true
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
func len*(list: HeadersHolderRef): int =
|
||||
list.headers.len
|
||||
|
||||
func `[]`*(list: HeadersHolderRef, idx: int): BlockHeader =
|
||||
list.headers[idx]
|
||||
|
||||
func `[]`*(list: HeadersHolderRef, idx: BackwardsIndex): BlockHeader =
|
||||
list.headers[list.headers.len - int(idx)]
|
||||
|
||||
proc findSnapshot(d: var LocalSnaps): bool =
|
||||
## Search for a snapshot starting at current header starting at the pivot
|
||||
## value `d.start`. The snapshot returned in `trail` is a clone of the
|
||||
## cached snapshot and can be modified later.
|
||||
|
||||
var
|
||||
(header, hash) = (d.start.header, d.start.hash)
|
||||
parentsLen = d.parents.len
|
||||
|
||||
# For convenience, ignore the current header as top parents list entry
|
||||
if 0 < parentsLen and d.parents[^1] == header:
|
||||
parentsLen.dec
|
||||
|
||||
while true:
|
||||
#d.say "findSnapshot ", header.pp(d.parents, parentsLen),
|
||||
# " trail=", d.trail.chain.pp
|
||||
|
||||
let number = header.blockNumber
|
||||
|
||||
# Check whether the snapshot was recently visited and cached
|
||||
block:
|
||||
let rc = d.c.recents.lruFetch(hash.data)
|
||||
if rc.isOk:
|
||||
d.trail.snaps = rc.value.cloneSnapshot
|
||||
# d.say "findSnapshot cached ", d.trail.pp
|
||||
trace "Found recently cached voting snapshot",
|
||||
blockNumber = number,
|
||||
blockHash = hash
|
||||
return true
|
||||
|
||||
# If an on-disk checkpoint snapshot can be found, use that
|
||||
if d.isCheckPoint(number):
|
||||
let rc = d.c.cfg.loadSnapshot(hash)
|
||||
if rc.isOk:
|
||||
d.trail.snaps = rc.value.cloneSnapshot
|
||||
d.say "findSnapshot on disk ", d.trail.pp
|
||||
trace "Loaded voting snapshot from disk",
|
||||
blockNumber = number,
|
||||
blockHash = hash
|
||||
# clique/clique.go(386): snap = s
|
||||
return true
|
||||
|
||||
# Note that epoch is a restart and sync point. Eip-225 requires that the
|
||||
# epoch header contains the full list of currently authorised signers.
|
||||
if d.isSnapshotPosition(number):
|
||||
# clique/clique.go(395): checkpoint := chain.GetHeaderByNumber [..]
|
||||
d.trail.snaps = d.c.cfg.newSnapshot(header)
|
||||
let rc = d.c.cfg.storeSnapshot(d.trail.snaps)
|
||||
if rc.isOk:
|
||||
d.say "findSnapshot <epoch> ", d.trail.pp
|
||||
trace "Stored voting snapshot to disk",
|
||||
blockNumber = number,
|
||||
blockHash = hash,
|
||||
nSnaps = d.c.cfg.nSnaps,
|
||||
snapsTotal = d.c.cfg.snapsData.toSI
|
||||
return true
|
||||
|
||||
# No snapshot for this header, get the parent header and move backward
|
||||
hash = header.parentHash
|
||||
# Add to batch (reversed list order, biggest block number comes first)
|
||||
d.trail.chain.add header
|
||||
|
||||
# Assign parent header
|
||||
if 0 < parentsLen:
|
||||
# If we have explicit parents, pop it from the parents list
|
||||
parentsLen.dec
|
||||
header = d.parents[parentsLen]
|
||||
# clique/clique.go(416): if header.Hash() != hash [..]
|
||||
if header.blockHash != hash:
|
||||
d.trail.error = (errUnknownAncestor,"")
|
||||
return false
|
||||
|
||||
# No explicit parents (or no more parents left), reach out to the database
|
||||
elif not d.c.cfg.db.getBlockHeader(hash, header):
|
||||
d.trail.error = (errUnknownAncestor,"")
|
||||
return false
|
||||
|
||||
# => while loop
|
||||
|
||||
# notreached
|
||||
raiseAssert "findSnapshot(): wrong exit from forever-loop"
|
||||
|
||||
|
||||
proc applyTrail(d: var LocalSnaps): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## Apply any `trail` headers on top of the snapshot `snap`
|
||||
if d.subChn.first < d.subChn.top:
|
||||
block:
|
||||
# clique/clique.go(434): snap, err := snap.apply(headers)
|
||||
d.say "applyTrail ", d.trail.pp(d.subChn)
|
||||
let rc = d.trail.snaps.snapshotApplySeq(
|
||||
d.trail.chain, d.subChn.top-1, d.subChn.first)
|
||||
if rc.isErr:
|
||||
d.say "applyTrail snaps=#", d.trail.snaps.blockNumber,
|
||||
" err=", rc.error.pp
|
||||
return err(rc.error)
|
||||
d.say "applyTrail snaps=#", d.trail.snaps.blockNumber
|
||||
|
||||
# If we've generated a new checkpoint snapshot, save to disk
|
||||
if d.isCheckPoint(d.trail.snaps.blockNumber):
|
||||
|
||||
var rc = d.c.cfg.storeSnapshot(d.trail.snaps)
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
|
||||
d.say "applyTrail <disk> chechkpoint #", d.trail.snaps.blockNumber
|
||||
trace "Stored voting snapshot to disk",
|
||||
blockNumber = d.trail.snaps.blockNumber,
|
||||
blockHash = d.trail.snaps.blockHash,
|
||||
nSnaps = d.c.cfg.nSnaps,
|
||||
snapsTotal = d.c.cfg.snapsData.toSI
|
||||
ok()
|
||||
|
||||
|
||||
proc updateSnapshot(d: var LocalSnaps): SnapshotResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## Find snapshot for header `d.start.header` and assign it to the LRU cache.
|
||||
## This function was expects thet the LRU cache already has a slot allocated
|
||||
## for the snapshot having run `getLruSnaps()`.
|
||||
|
||||
d.say "updateSnapshot begin ", d.start.header.blockNumber.pp(d.parents)
|
||||
|
||||
# Search for previous snapshots
|
||||
if not d.findSnapshot:
|
||||
return err(d.trail.error)
|
||||
|
||||
# Initialise range for header chain[] to be applied to `d.trail.snaps`
|
||||
d.subChn.top = d.trail.chain.len
|
||||
|
||||
# Previous snapshot found, apply any pending trail headers on top of it
|
||||
if 0 < d.subChn.top:
|
||||
let
|
||||
first = d.trail.chain[^1].blockNumber
|
||||
last = d.trail.chain[0].blockNumber
|
||||
ckpt = d.maxCheckPointLe(last)
|
||||
|
||||
# If there is at least one checkpoint part of the trail sequence, make sure
|
||||
# that we can store the latest one. This will be done by the `applyTrail()`
|
||||
# handler for the largest block number in the sequence (note that the trail
|
||||
# block numbers are in reverse order.)
|
||||
if first <= ckpt and ckpt < last:
|
||||
# Split the trail sequence so that the first one has the checkpoint
|
||||
# entry with largest block number.
|
||||
let inx = (last - ckpt).truncate(int)
|
||||
|
||||
# First part (note reverse block numbers.)
|
||||
d.subChn.first = inx
|
||||
let rc = d.applyTrail
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
|
||||
# Second part (note reverse block numbers.)
|
||||
d.subChn.first = 0
|
||||
d.subChn.top = inx
|
||||
|
||||
var rc = d.applyTrail
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
|
||||
# clique/clique.go(438): c.recents.Add(snap.Hash, snap)
|
||||
discard d.c.recents.lruAppend(
|
||||
d.trail.snaps.blockHash.data, d.trail.snaps, INMEMORY_SNAPSHOTS)
|
||||
|
||||
if 1 < d.trail.chain.len:
|
||||
d.say "updateSnapshot ok #", d.trail.snaps.blockNumber,
|
||||
" trail.len=", d.trail.chain.len
|
||||
|
||||
ok(d.trail.snaps)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc cliqueSnapshotSeq*(c: Clique; header: BlockHeader;
|
||||
parents: HeadersHolderRef): SnapshotResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## Create authorisation state snapshot of a given point in the block chain
|
||||
## and store it in the `Clique` descriptor to be retrievable as `c.snapshot`
|
||||
## if successful.
|
||||
##
|
||||
## If the `parents[]` argument list top element (if any) is the same as the
|
||||
## `header` argument, this top element is silently ignored.
|
||||
##
|
||||
## If this function is successful, the compiled `Snapshot` will also be
|
||||
## stored in the `Clique` descriptor which can be retrieved later
|
||||
## via `c.snapshot`.
|
||||
block:
|
||||
let rc = c.recents.lruFetch(header.blockHash.data)
|
||||
if rc.isOk:
|
||||
c.snapshot = rc.value
|
||||
return ok(rc.value)
|
||||
|
||||
# Avoid deep copy, sequence will not be changed by `updateSnapshot()`
|
||||
|
||||
var snaps = LocalSnaps(
|
||||
c: c,
|
||||
parents: parents,
|
||||
start: LocalPivot(
|
||||
header: header,
|
||||
hash: header.blockHash))
|
||||
|
||||
let rc = snaps.updateSnapshot
|
||||
if rc.isOk:
|
||||
c.snapshot = rc.value
|
||||
|
||||
rc
|
||||
|
||||
|
||||
proc cliqueSnapshotSeq*(c: Clique; hash: Hash256;
|
||||
parents: HeadersHolderRef): SnapshotResult
|
||||
{.gcsafe,raises: [CatchableError].} =
|
||||
## Create authorisation state snapshot of a given point in the block chain
|
||||
## and store it in the `Clique` descriptor to be retrievable as `c.snapshot`
|
||||
## if successful.
|
||||
##
|
||||
## If the `parents[]` argument list top element (if any) is the same as the
|
||||
## `header` argument, this top element is silently ignored.
|
||||
##
|
||||
## If this function is successful, the compiled `Snapshot` will also be
|
||||
## stored in the `Clique` descriptor which can be retrieved later
|
||||
## via `c.snapshot`.
|
||||
block:
|
||||
let rc = c.recents.lruFetch(hash.data)
|
||||
if rc.isOk:
|
||||
c.snapshot = rc.value
|
||||
return ok(rc.value)
|
||||
|
||||
var header: BlockHeader
|
||||
if not c.cfg.db.getBlockHeader(hash, header):
|
||||
return err((errUnknownHash,""))
|
||||
|
||||
# Avoid deep copy, sequence will not be changed by `updateSnapshot()`
|
||||
|
||||
var snaps = LocalSnaps(
|
||||
c: c,
|
||||
parents: parents,
|
||||
start: LocalPivot(
|
||||
header: header,
|
||||
hash: hash))
|
||||
|
||||
let rc = snaps.updateSnapshot
|
||||
if rc.isOk:
|
||||
c.snapshot = rc.value
|
||||
|
||||
rc
|
||||
|
||||
|
||||
# clique/clique.go(369): func (c *Clique) snapshot(chain [..]
|
||||
proc cliqueSnapshot*(c: Clique; header: BlockHeader;
|
||||
parents: var seq[BlockHeader]): SnapshotResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
let list = HeadersHolderRef(
|
||||
headers: toSeq(parents)
|
||||
)
|
||||
c.cliqueSnapshotSeq(header,list)
|
||||
|
||||
proc cliqueSnapshot*(c: Clique;hash: Hash256;
|
||||
parents: openArray[BlockHeader]): SnapshotResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
let list = HeadersHolderRef(
|
||||
headers: toSeq(parents)
|
||||
)
|
||||
c.cliqueSnapshotSeq(hash,list)
|
||||
|
||||
proc cliqueSnapshot*(c: Clique; header: BlockHeader): SnapshotResult
|
||||
{.gcsafe,raises: [CatchableError].} =
|
||||
## Short for `cliqueSnapshot(c,header,@[])`
|
||||
let blind = HeadersHolderRef()
|
||||
c.cliqueSnapshotSeq(header, blind)
|
||||
|
||||
proc cliqueSnapshot*(c: Clique; hash: Hash256): SnapshotResult
|
||||
{.gcsafe,raises: [CatchableError].} =
|
||||
## Short for `cliqueSnapshot(c,hash,@[])`
|
||||
let blind = HeadersHolderRef()
|
||||
c.cliqueSnapshotSeq(hash, blind)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,426 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2024 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.
|
||||
|
||||
##
|
||||
## Verify Headers for Clique PoA Consensus Protocol
|
||||
## ================================================
|
||||
##
|
||||
## Note that mining in currently unsupported by `NIMBUS`
|
||||
##
|
||||
## For details see
|
||||
## `EIP-225 <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-225.md>`_
|
||||
## and
|
||||
## `go-ethereum <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-225.md>`_
|
||||
##
|
||||
|
||||
import
|
||||
std/[strformat, sequtils],
|
||||
../../utils/utils,
|
||||
../../common/common,
|
||||
../gaslimit,
|
||||
./clique_cfg,
|
||||
./clique_defs,
|
||||
./clique_desc,
|
||||
./clique_helpers,
|
||||
./clique_snapshot,
|
||||
./snapshot/[ballot, snapshot_desc],
|
||||
chronicles,
|
||||
stew/results
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
logScope:
|
||||
topics = "clique PoA verify header"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# consensus/misc/forks.go(30): func VerifyForkHashes(config [..]
|
||||
proc verifyForkHashes(com: CommonRef; header: BlockHeader): CliqueOkResult
|
||||
{.gcsafe, raises: [ValueError].} =
|
||||
## Verify that blocks conforming to network hard-forks do have the correct
|
||||
## hashes, to avoid clients going off on different chains.
|
||||
|
||||
if com.eip150Block.isSome and
|
||||
com.eip150Block.get == header.blockNumber:
|
||||
|
||||
# If the homestead reprice hash is set, validate it
|
||||
let
|
||||
eip150 = com.eip150Hash
|
||||
hash = header.blockHash
|
||||
|
||||
if eip150 != hash:
|
||||
return err((errCliqueGasRepriceFork,
|
||||
&"Homestead gas reprice fork: have {eip150}, want {hash}"))
|
||||
|
||||
return ok()
|
||||
|
||||
proc signersThreshold*(s: Snapshot): int =
|
||||
## Minimum number of authorised signers needed.
|
||||
s.ballot.authSignersThreshold
|
||||
|
||||
|
||||
proc recentBlockNumber*(s: Snapshot; a: EthAddress): Result[BlockNumber,void] =
|
||||
## Return `BlockNumber` for `address` argument (if any)
|
||||
for (number,recent) in s.recents.pairs:
|
||||
if recent == a:
|
||||
return ok(number)
|
||||
return err()
|
||||
|
||||
|
||||
proc isSigner*(s: Snapshot; address: EthAddress): bool =
|
||||
## Checks whether argukment ``address` is in signers list
|
||||
s.ballot.isAuthSigner(address)
|
||||
|
||||
|
||||
# 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.
|
||||
let ascSignersList = s.ballot.authSigners
|
||||
if 0 < ascSignersList.len:
|
||||
let offset = (number mod ascSignersList.len.u256).truncate(int64)
|
||||
return ascSignersList[offset] == signer
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/clique.go(463): func (c *Clique) verifySeal(chain [..]
|
||||
proc verifySeal(c: Clique; header: BlockHeader): CliqueOkResult =
|
||||
## 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,""))
|
||||
|
||||
# Get current snapshot
|
||||
let snapshot = c.snapshot
|
||||
|
||||
# Verify availability of the cached snapshot
|
||||
doAssert snapshot.blockHash == header.parentHash
|
||||
|
||||
# Resolve the authorization key and check against signers
|
||||
let signer = c.cfg.ecRecover(header)
|
||||
if signer.isErr:
|
||||
return err((errEcRecover,$signer.error))
|
||||
|
||||
if not snapshot.isSigner(signer.value):
|
||||
return err((errUnauthorizedSigner,""))
|
||||
|
||||
let seen = snapshot.recentBlockNumber(signer.value)
|
||||
if seen.isOk:
|
||||
# Signer is among recents, only fail if the current block does not
|
||||
# shift it out
|
||||
# clique/clique.go(486): if limit := uint64(len(snap.Signers)/2 + 1); [..]
|
||||
if header.blockNumber - snapshot.signersThreshold.u256 < seen.value:
|
||||
return err((errRecentlySigned,""))
|
||||
|
||||
# Ensure that the difficulty corresponds to the turn-ness of the signer
|
||||
if snapshot.inTurn(header.blockNumber, signer.value):
|
||||
if header.difficulty != DIFF_INTURN:
|
||||
return err((errWrongDifficulty,"INTURN expected"))
|
||||
else:
|
||||
if header.difficulty != DIFF_NOTURN:
|
||||
return err((errWrongDifficulty,"NOTURN expected"))
|
||||
|
||||
ok()
|
||||
|
||||
|
||||
# clique/clique.go(314): func (c *Clique) verifyCascadingFields(chain [..]
|
||||
proc verifyCascadingFields(c: Clique; com: CommonRef; header: BlockHeader;
|
||||
parents: HeadersHolderRef): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## 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 ok()
|
||||
|
||||
# Ensure that the block's timestamp isn't too close to its parent
|
||||
var parent: BlockHeader
|
||||
if 0 < parents.len:
|
||||
parent = parents[^1]
|
||||
elif not c.db.getBlockHeader(header.blockNumber-1, parent):
|
||||
return err((errUnknownAncestor,""))
|
||||
|
||||
if parent.blockNumber != header.blockNumber-1 or
|
||||
parent.blockHash != header.parentHash:
|
||||
return err((errUnknownAncestor,""))
|
||||
|
||||
# clique/clique.go(330): if parent.Time+c.config.Period > header.Time {
|
||||
if header.timestamp < parent.timestamp + c.cfg.period:
|
||||
return err((errInvalidTimestamp,""))
|
||||
|
||||
# Verify that the gasUsed is <= gasLimit
|
||||
block:
|
||||
# clique/clique.go(333): if header.GasUsed > header.GasLimit {
|
||||
let (used, limit) = (header.gasUsed, header.gasLimit)
|
||||
if limit < used:
|
||||
return err((errCliqueExceedsGasLimit,
|
||||
&"invalid gasUsed: have {used}, gasLimit {limit}"))
|
||||
|
||||
# Verify `GasLimit` or `BaseFee` depending on whether before or after
|
||||
# EIP-1559/London fork.
|
||||
block:
|
||||
# clique/clique.go(337): if !chain.Config().IsLondon(header.Number) {
|
||||
let rc = com.validateGasLimitOrBaseFee(header, parent)
|
||||
if rc.isErr:
|
||||
return err((errCliqueGasLimitOrBaseFee, rc.error))
|
||||
|
||||
# Retrieve the snapshot needed to verify this header and cache it
|
||||
block:
|
||||
# clique/clique.go(350): snap, err := c.snapshot(chain, number-1, ..
|
||||
let rc = c.cliqueSnapshotSeq(header.parentHash, parents)
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
|
||||
# If the block is a checkpoint block, verify the signer list
|
||||
if (header.blockNumber mod c.cfg.epoch.u256) == 0:
|
||||
var addrList = header.extraData.extraDataAddresses
|
||||
# not using `authSigners()` here as it is too slow
|
||||
if c.snapshot.ballot.authSignersLen != addrList.len or
|
||||
not c.snapshot.ballot.isAuthSigner(addrList):
|
||||
return err((errMismatchingCheckpointSigners,""))
|
||||
|
||||
# All basic checks passed, verify the seal and return
|
||||
return c.verifySeal(header)
|
||||
|
||||
|
||||
proc verifyHeaderFields(c: Clique; header: BlockHeader): CliqueOkResult =
|
||||
## Check header fields, the ones that do not depend on a parent block.
|
||||
# clique/clique.go(250): number := header.Number.Uint64()
|
||||
|
||||
# Don't waste time checking blocks from the future
|
||||
if EthTime.now() < header.timestamp:
|
||||
return err((errFutureBlock,""))
|
||||
|
||||
# Checkpoint blocks need to enforce zero beneficiary
|
||||
let isCheckPoint = (header.blockNumber mod c.cfg.epoch.u256).isZero
|
||||
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 a checkpoint,
|
||||
# but none otherwise
|
||||
let signersBytes = header.extraData.len - EXTRA_VANITY - EXTRA_SEAL
|
||||
if not isCheckPoint:
|
||||
if signersBytes != 0:
|
||||
return err((errExtraSigners,""))
|
||||
elif (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 != EMPTY_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:
|
||||
# Note that neither INTURN or NOTURN should be zero (but this might be
|
||||
# subject to change as it is explicitely checked for in `clique.go`)
|
||||
let diffy = header.difficulty
|
||||
# clique/clique.go(246): if header.Difficulty == nil || (header.Difficulty..
|
||||
if diffy.isZero or (diffy != DIFF_INTURN and diffy != 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"))
|
||||
ok()
|
||||
|
||||
|
||||
# clique/clique.go(246): func (c *Clique) verifyHeader(chain [..]
|
||||
proc cliqueVerifyImpl(c: Clique; com: CommonRef; header: BlockHeader;
|
||||
parents: HeadersHolderRef): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## 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.
|
||||
c.failed = (ZERO_HASH256,cliqueNoError)
|
||||
|
||||
block:
|
||||
# Check header fields independent of parent blocks
|
||||
let rc = c.verifyHeaderFields(header)
|
||||
if rc.isErr:
|
||||
c.failed = (header.blockHash, rc.error)
|
||||
return err(rc.error)
|
||||
|
||||
block:
|
||||
# If all checks passed, validate any special fields for hard forks
|
||||
let rc = com.verifyForkHashes(header)
|
||||
if rc.isErr:
|
||||
c.failed = (header.blockHash, rc.error)
|
||||
return err(rc.error)
|
||||
|
||||
# All basic checks passed, verify cascading fields
|
||||
result = c.verifyCascadingFields(com, header, parents)
|
||||
if result.isErr:
|
||||
c.failed = (header.blockHash, result.error)
|
||||
|
||||
proc cliqueVerifySeq*(c: Clique; com: CommonRef; header: BlockHeader;
|
||||
parents: HeadersHolderRef): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## 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.
|
||||
##
|
||||
## On success, the latest authorised signers list is available via the
|
||||
## fucntion `c.cliqueSigners()`. Otherwise, the latest error is also stored
|
||||
## in the `Clique` descriptor
|
||||
##
|
||||
## If there is an error, this error is also stored within the `Clique`
|
||||
## descriptor and can be retrieved via `c.failed` along with the hash/ID of
|
||||
## the failed block header.
|
||||
block:
|
||||
let rc = c.cliqueVerifyImpl(com, header, parents)
|
||||
if rc.isErr:
|
||||
return rc
|
||||
|
||||
# Adjust current shapshot (the function `cliqueVerifyImpl()` typically
|
||||
# works with the parent snapshot.
|
||||
block:
|
||||
let rc = c.cliqueSnapshotSeq(header, parents)
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
|
||||
ok()
|
||||
|
||||
proc cliqueVerifySeq(c: Clique; com: CommonRef;
|
||||
headers: HeadersHolderRef): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## This function verifies a batch of headers checking each header for
|
||||
## consensus rules conformance. The `headers` list is supposed to
|
||||
## contain a chain of headers, i e. `headers[i]` is parent to `headers[i+1]`.
|
||||
##
|
||||
## On success, the latest authorised signers list is available via the
|
||||
## fucntion `c.cliqueSigners()`. Otherwise, the latest error is also stored
|
||||
## in the `Clique` descriptor
|
||||
##
|
||||
## If there is an error, this error is also stored within the `Clique`
|
||||
## descriptor and can be retrieved via `c.failed` along with the hash/ID of
|
||||
## the failed block header.
|
||||
##
|
||||
## Note that the sequence argument must be write-accessible, even though it
|
||||
## will be left untouched by this function.
|
||||
if 0 < headers.len:
|
||||
|
||||
block:
|
||||
let blind = HeadersHolderRef()
|
||||
let rc = c.cliqueVerifyImpl(com, headers[0],blind)
|
||||
if rc.isErr:
|
||||
return rc
|
||||
|
||||
for n in 1 ..< headers.len:
|
||||
let parent = HeadersHolderRef(
|
||||
headers: headers.headers[n-1 .. n-1] # is actually a single item squence
|
||||
)
|
||||
let rc = c.cliqueVerifyImpl(com, headers[n],parent)
|
||||
if rc.isErr:
|
||||
return rc
|
||||
|
||||
# Adjust current shapshot (the function `cliqueVerifyImpl()` typically
|
||||
# works with the parent snapshot.
|
||||
block:
|
||||
let rc = c.cliqueSnapshot(headers[^1])
|
||||
if rc.isErr:
|
||||
return err(rc.error)
|
||||
|
||||
ok()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc cliqueVerify*(c: Clique; com: CommonRef; header: BlockHeader;
|
||||
parents: openArray[BlockHeader]): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## Check whether a header conforms to the consensus rules. The caller may
|
||||
## optionally pass on a batch of parents (ascending order) to avoid looking
|
||||
## those up from the database. This function updates the list of authorised
|
||||
## signers (see `cliqueSigners()` below.)
|
||||
##
|
||||
## On success, the latest authorised signers list is available via the
|
||||
## fucntion `c.cliqueSigners()`. Otherwise, the latest error is also stored
|
||||
## in the `Clique` descriptor and is accessible as `c.failed`.
|
||||
##
|
||||
## This function is not transaction-save, that is the internal state of
|
||||
## the authorised signers list has the state of the last update after a
|
||||
## successful header verification. The hash of the failing header together
|
||||
## with the error message is then accessible as `c.failed`.
|
||||
##
|
||||
## Use the directives `cliqueSave()`, `cliqueDispose()`, and/or
|
||||
## `cliqueRestore()` for transaction.
|
||||
let list = HeadersHolderRef(
|
||||
headers: toSeq(parents)
|
||||
)
|
||||
c.cliqueVerifySeq(com, header, list)
|
||||
|
||||
# clique/clique.go(217): func (c *Clique) VerifyHeader(chain [..]
|
||||
proc cliqueVerify*(c: Clique; com: CommonRef; header: BlockHeader): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## Consensus rules verifier without optional parents list.
|
||||
let blind = HeadersHolderRef()
|
||||
c.cliqueVerifySeq(com, header, blind)
|
||||
|
||||
proc cliqueVerify*(c: Clique; com: CommonRef;
|
||||
headers: openArray[BlockHeader]): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## This function verifies a batch of headers checking each header for
|
||||
## consensus rules conformance (see also the other `cliqueVerify()` function
|
||||
## instance.) The `headers` list is supposed to contain a chain of headers,
|
||||
## i.e. `headers[i]` is parent to `headers[i+1]`.
|
||||
##
|
||||
## On success, the latest authorised signers list is available via the
|
||||
## fucntion `c.cliqueSigners()`. Otherwise, the latest error is also stored
|
||||
## in the `Clique` descriptor and is accessible as `c.failed`.
|
||||
##
|
||||
## This function is not transaction-save, that is the internal state of
|
||||
## the authorised signers list has the state of the last update after a
|
||||
## successful header verification. The hash of the failing header together
|
||||
## with the error message is then accessible as `c.failed`.
|
||||
##
|
||||
## Use the directives `cliqueSave()`, `cliqueDispose()`, and/or
|
||||
## `cliqueRestore()` for transaction.
|
||||
let list = HeadersHolderRef(
|
||||
headers: toSeq(headers)
|
||||
)
|
||||
c.cliqueVerifySeq(com, list)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,213 +0,0 @@
|
|||
# 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
|
||||
std/[sequtils, tables],
|
||||
../clique_helpers,
|
||||
eth/common
|
||||
|
||||
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 = object
|
||||
authorize: bool
|
||||
signers: Table[EthAddress,Vote]
|
||||
|
||||
Ballot* = 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: [].}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public debugging/pretty-printer support
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc votesInternal*(t: var Ballot): seq[(EthAddress,EthAddress,Vote)] =
|
||||
for account,tally in t.votes.pairs:
|
||||
for signer,vote in tally.signers.pairs:
|
||||
result.add (account, signer, vote)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc initBallot*(t: var Ballot) =
|
||||
## Ininialise an empty `Ballot` descriptor.
|
||||
t.votes = initTable[EthAddress,Tally]()
|
||||
t.authSig = initTable[EthAddress,bool]()
|
||||
|
||||
proc initBallot*(t: var Ballot; signers: openArray[EthAddress]) =
|
||||
## Ininialise `Ballot` with a given authorised signers list
|
||||
t.initBallot
|
||||
for a in signers:
|
||||
t.authSig[a] = true
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public getters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc authSigners*(t: var Ballot): seq[EthAddress] =
|
||||
## Sorted ascending list of authorised signer addresses
|
||||
toSeq(t.authSig.keys).sorted(EthAscending)
|
||||
|
||||
proc authSignersLen*(t: var Ballot): int =
|
||||
## Returns the number of currently known authorised signers.
|
||||
t.authSig.len
|
||||
|
||||
proc isAuthSignersListShrunk*(t: var Ballot): bool =
|
||||
## Check whether the authorised signers list was shrunk recently after
|
||||
## appying `addVote()`
|
||||
t.authRemoved
|
||||
|
||||
proc authSignersThreshold*(t: var Ballot): 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)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc isAuthSigner*(t: var Ballot; addresses: var seq[EthAddress]): bool =
|
||||
## Check whether all `addresses` entries are authorised signers.
|
||||
##
|
||||
## Using this function should be preferable over `authSigners()` which has
|
||||
## complexity `O(log n)` while this function runs with `O(n)`.
|
||||
for a in addresses:
|
||||
if a notin t.authSig:
|
||||
return false
|
||||
true
|
||||
|
||||
proc isAuthSigner*(t: var Ballot; address: EthAddress): bool =
|
||||
## Check whether `address` is an authorised signer
|
||||
address in t.authSig
|
||||
|
||||
proc delVote*(t: var Ballot; signer, address: EthAddress) {.
|
||||
gcsafe, raises: [KeyError].} =
|
||||
## 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)
|
||||
|
||||
|
||||
proc flushVotes*(t: var Ballot) =
|
||||
## Reset/flush pending votes, authorised signers remain the same.
|
||||
t.votes.clear
|
||||
|
||||
|
||||
# clique/snapshot.go(141): func (s *Snapshot) validVote(address [..]
|
||||
proc isValidVote*(t: var Ballot; 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 Ballot; vote: Vote) {.
|
||||
gcsafe, raises: [KeyError].} =
|
||||
## 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
|
||||
var
|
||||
numVotes = 0
|
||||
authOk = vote.authorize
|
||||
|
||||
# clique/snapshot.go(147): if !s.validVote(address, [..]
|
||||
if not t.isValidVote(vote.address, vote.authorize):
|
||||
|
||||
# Corner case: touch votes for this account
|
||||
if t.votes.hasKey(vote.address):
|
||||
let refVote = t.votes[vote.address]
|
||||
numVotes = refVote.signers.len
|
||||
authOk = refVote.authorize
|
||||
|
||||
elif not t.votes.hasKey(vote.address):
|
||||
# Collect inital vote
|
||||
t.votes[vote.address] = Tally(
|
||||
authorize: vote.authorize,
|
||||
signers: {vote.signer: vote}.toTable)
|
||||
numVotes = 1
|
||||
|
||||
elif t.votes[vote.address].authorize == vote.authorize:
|
||||
# Collect additional vote
|
||||
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 authOk:
|
||||
# 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
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,182 +0,0 @@
|
|||
# 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 Processor 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
|
||||
std/times,
|
||||
chronicles,
|
||||
eth/common,
|
||||
stew/results,
|
||||
".."/[clique_cfg, clique_defs],
|
||||
"."/[ballot, snapshot_desc]
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
logScope:
|
||||
topics = "clique PoA snapshot-apply"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
template pairWalkIj(first, last: int; offTop: Positive; code: untyped) =
|
||||
if first <= last:
|
||||
for n in first .. last - offTop:
|
||||
let
|
||||
i {.inject.} = n
|
||||
j {.inject.} = n + 1
|
||||
code
|
||||
else:
|
||||
for n in first.countdown(last + offTop):
|
||||
let
|
||||
i {.inject.} = n
|
||||
j {.inject.} = n - 1
|
||||
code
|
||||
|
||||
template doWalkIt(first, last: int; code: untyped) =
|
||||
if first <= last:
|
||||
for n in first .. last:
|
||||
let it {.inject.} = n
|
||||
code
|
||||
else:
|
||||
for n in first.countdown(last):
|
||||
let it {.inject.} = n
|
||||
code
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/snapshot.go(185): func (s *Snapshot) apply(headers [..]
|
||||
proc snapshotApplySeq*(s: Snapshot; headers: var seq[BlockHeader],
|
||||
first, last: int): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
## Initialises an authorization snapshot `snap` by applying the `headers`
|
||||
## to the argument snapshot desciptor `s`.
|
||||
|
||||
# Sanity check that the headers can be applied
|
||||
if headers[first].blockNumber != s.blockNumber + 1:
|
||||
return err((errInvalidVotingChain,""))
|
||||
# clique/snapshot.go(191): for i := 0; i < len(headers)-1; i++ {
|
||||
first.pairWalkIj(last, 1):
|
||||
if headers[j].blockNumber != headers[i].blockNumber+1:
|
||||
return err((errInvalidVotingChain,""))
|
||||
|
||||
# Iterate through the headers and create a new snapshot
|
||||
let
|
||||
start = getTime()
|
||||
var
|
||||
logged = start
|
||||
|
||||
# clique/snapshot.go(206): for i, header := range headers [..]
|
||||
first.doWalkIt(last):
|
||||
let
|
||||
# headersIndex => also used for logging at the end of this loop
|
||||
headersIndex = it
|
||||
header = headers[headersIndex]
|
||||
number = header.blockNumber
|
||||
|
||||
# Remove any votes on checkpoint blocks
|
||||
if (number mod s.cfg.epoch).isZero:
|
||||
# Note that the correctness of the authorised accounts list is verified in
|
||||
# clique/clique.verifyCascadingFields(),
|
||||
# see clique/clique.go(355): if number%c.config.Epoch == 0 {
|
||||
# This means, the account list passed with the epoch header is verified
|
||||
# to be the same as the one we already have.
|
||||
#
|
||||
# clique/snapshot.go(210): snap.Votes = nil
|
||||
s.ballot.flushVotes
|
||||
|
||||
# Delete the oldest signer from the recent list to allow it signing again
|
||||
block:
|
||||
let limit = s.ballot.authSignersThreshold.u256
|
||||
if limit <= number:
|
||||
s.recents.del(number - limit)
|
||||
|
||||
# Resolve the authorization key and check against signers
|
||||
let signer = s.cfg.ecRecover(header)
|
||||
if signer.isErr:
|
||||
return err((errEcRecover,$signer.error))
|
||||
|
||||
if not s.ballot.isAuthSigner(signer.value):
|
||||
return err((errUnauthorizedSigner,""))
|
||||
|
||||
for recent in s.recents.values:
|
||||
if recent == signer.value:
|
||||
return err((errRecentlySigned,""))
|
||||
s.recents[number] = signer.value
|
||||
|
||||
# Header authorized, discard any previous vote from the signer
|
||||
# clique/snapshot.go(233): for i, vote := range snap.Votes {
|
||||
s.ballot.delVote(signer = signer.value, address = header.coinbase)
|
||||
|
||||
# Tally up the new vote from the signer
|
||||
# clique/snapshot.go(244): var authorize bool
|
||||
var authOk = false
|
||||
if header.nonce == NONCE_AUTH:
|
||||
authOk = true
|
||||
elif header.nonce != NONCE_DROP:
|
||||
return err((errInvalidVote,""))
|
||||
let vote = Vote(address: header.coinbase,
|
||||
signer: signer.value,
|
||||
blockNumber: number,
|
||||
authorize: authOk)
|
||||
# clique/snapshot.go(253): if snap.cast(header.Coinbase, authorize) {
|
||||
s.ballot.addVote(vote)
|
||||
|
||||
# clique/snapshot.go(269): if limit := uint64(len(snap.Signers)/2 [..]
|
||||
if s.ballot.isAuthSignersListShrunk:
|
||||
# Signer list shrunk, delete any leftover recent caches
|
||||
let limit = s.ballot.authSignersThreshold.u256
|
||||
if limit <= number:
|
||||
# Pop off least block number from the list
|
||||
let item = number - limit
|
||||
s.recents.del(item)
|
||||
|
||||
# If we're taking too much time (ecrecover), notify the user once a while
|
||||
if s.cfg.logInterval < getTime() - logged:
|
||||
debug "Reconstructing voting history",
|
||||
processed = headersIndex,
|
||||
total = headers.len,
|
||||
elapsed = getTime() - start
|
||||
logged = getTime()
|
||||
|
||||
let sinceStart = getTime() - start
|
||||
if s.cfg.logInterval < sinceStart:
|
||||
debug "Reconstructed voting history",
|
||||
processed = headers.len,
|
||||
elapsed = sinceStart
|
||||
|
||||
# clique/snapshot.go(303): snap.Number += uint64(len(headers))
|
||||
doAssert headers[last].blockNumber == s.blockNumber+(1+(last-first).abs).u256
|
||||
s.blockNumber = headers[last].blockNumber
|
||||
s.blockHash = headers[last].blockHash
|
||||
|
||||
ok()
|
||||
|
||||
|
||||
proc snapshotApply*(s: Snapshot; headers: var seq[BlockHeader]): CliqueOkResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
if headers.len == 0:
|
||||
return ok()
|
||||
s.snapshotApplySeq(headers, 0, headers.len - 1)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,190 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2021-2024 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>`_
|
||||
##
|
||||
|
||||
import
|
||||
std/tables,
|
||||
chronicles,
|
||||
eth/rlp,
|
||||
results,
|
||||
../../../db/[core_db, storage_types],
|
||||
../clique_cfg,
|
||||
../clique_defs,
|
||||
../clique_helpers,
|
||||
./ballot
|
||||
|
||||
export tables
|
||||
|
||||
type
|
||||
SnapshotResult* = ##\
|
||||
## Snapshot/error result type
|
||||
Result[Snapshot,CliqueError]
|
||||
|
||||
AddressHistory* = Table[BlockNumber,EthAddress]
|
||||
|
||||
SnapshotData* = object
|
||||
blockNumber: BlockNumber ## block number where snapshot was created on
|
||||
blockHash: Hash256 ## block hash where snapshot was created on
|
||||
recents: AddressHistory ## recent signers for spam protections
|
||||
|
||||
# clique/snapshot.go(58): Recents map[uint64]common.Address [..]
|
||||
ballot: Ballot ## Votes => authorised signers
|
||||
|
||||
# clique/snapshot.go(50): type Snapshot struct [..]
|
||||
Snapshot* = ref 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: [].}
|
||||
|
||||
logScope:
|
||||
topics = "clique PoA snapshot"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private functions needed to support RLP conversion
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
template logTxt(info: static[string]): static[string] =
|
||||
"Clique " & info
|
||||
|
||||
proc append[K,V](rw: var RlpWriter; tab: Table[K,V]) =
|
||||
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 {.gcsafe, raises: [RlpError].} =
|
||||
for w in rlp.items:
|
||||
let (key,value) = w.read((K,V))
|
||||
result[key] = value
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private constructor helper
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/snapshot.go(72): func newSnapshot(config [..]
|
||||
proc initSnapshot(s: Snapshot; cfg: CliqueCfg;
|
||||
number: BlockNumber; hash: Hash256; signers: openArray[EthAddress]) =
|
||||
## Initalise a new snapshot.
|
||||
s.cfg = cfg
|
||||
s.data.blockNumber = number
|
||||
s.data.blockHash = hash
|
||||
s.data.recents = initTable[BlockNumber,EthAddress]()
|
||||
s.data.ballot.initBallot(signers)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public Constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc newSnapshot*(cfg: CliqueCfg; header: BlockHeader): Snapshot =
|
||||
## Create a new snapshot for the given header. The header need not be on the
|
||||
## block chain, yet. The trusted signer list is derived from the
|
||||
## `extra data` field of the header.
|
||||
new result
|
||||
let signers = header.extraData.extraDataAddresses
|
||||
result.initSnapshot(cfg, header.blockNumber, header.blockHash, signers)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public getters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc cfg*(s: Snapshot): CliqueCfg =
|
||||
## Getter
|
||||
s.cfg
|
||||
|
||||
proc blockNumber*(s: Snapshot): BlockNumber =
|
||||
## Getter
|
||||
s.data.blockNumber
|
||||
|
||||
proc blockHash*(s: Snapshot): Hash256 =
|
||||
## Getter
|
||||
s.data.blockHash
|
||||
|
||||
proc recents*(s: Snapshot): var AddressHistory =
|
||||
## Retrieves the list of recently added addresses
|
||||
s.data.recents
|
||||
|
||||
proc ballot*(s: Snapshot): var Ballot =
|
||||
## Retrieves the ballot box descriptor with the votes
|
||||
s.data.ballot
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public setters
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `blockNumber=`*(s: Snapshot; number: BlockNumber) =
|
||||
## Getter
|
||||
s.data.blockNumber = number
|
||||
|
||||
proc `blockHash=`*(s: Snapshot; hash: Hash256) =
|
||||
## Getter
|
||||
s.data.blockHash = hash
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public load/store support
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/snapshot.go(88): func loadSnapshot(config [..]
|
||||
proc loadSnapshot*(cfg: CliqueCfg; hash: Hash256):
|
||||
Result[Snapshot,CliqueError] =
|
||||
## Load an existing snapshot from the database.
|
||||
var
|
||||
s = Snapshot(cfg: cfg)
|
||||
try:
|
||||
let rc = s.cfg.db.newKvt().get(hash.cliqueSnapshotKey.toOpenArray)
|
||||
if rc.isOk:
|
||||
s.data = rc.value.decode(SnapshotData)
|
||||
else:
|
||||
if rc.error.error != KvtNotFound:
|
||||
error logTxt "get() failed", error=($$rc.error)
|
||||
return err((errSnapshotLoad,""))
|
||||
except RlpError as e:
|
||||
return err((errSnapshotLoad, $e.name & ": " & e.msg))
|
||||
ok(s)
|
||||
|
||||
# clique/snapshot.go(104): func (s *Snapshot) store(db [..]
|
||||
proc storeSnapshot*(cfg: CliqueCfg; s: Snapshot): CliqueOkResult =
|
||||
## Insert the snapshot into the database.
|
||||
let
|
||||
key = s.data.blockHash.cliqueSnapshotKey
|
||||
val = rlp.encode(s.data)
|
||||
db = s.cfg.db.newKvt(offSite = true) # bypass block chain txs
|
||||
defer: db.forget()
|
||||
let rc = db.put(key.toOpenArray, val)
|
||||
if rc.isErr:
|
||||
error logTxt "put() failed", `error`=($$rc.error)
|
||||
db.saveOffSite()
|
||||
|
||||
cfg.nSnaps.inc
|
||||
cfg.snapsData += val.len.uint
|
||||
ok()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public deep copy
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc cloneSnapshot*(s: Snapshot): Snapshot =
|
||||
## Clone the snapshot
|
||||
Snapshot(
|
||||
cfg: s.cfg, # copy ref
|
||||
data: s.data) # copy data
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -16,7 +16,6 @@ import
|
|||
../../transaction,
|
||||
../../vm_state,
|
||||
../../vm_types,
|
||||
../clique,
|
||||
../dao,
|
||||
./calculate_reward,
|
||||
./executor_helpers,
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2018-2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||
# at your option.
|
||||
# This file may not be copied, modified, or distributed except according to
|
||||
# those terms.
|
||||
|
||||
import
|
||||
pkg/[chronos,
|
||||
stew/results,
|
||||
chronicles,
|
||||
eth/keys],
|
||||
".."/[config,
|
||||
constants],
|
||||
"."/[
|
||||
chain,
|
||||
tx_pool,
|
||||
validate],
|
||||
"."/clique/[
|
||||
clique_desc,
|
||||
clique_cfg,
|
||||
clique_sealer],
|
||||
../utils/utils,
|
||||
../common/[common, context]
|
||||
|
||||
type
|
||||
EngineState* = enum
|
||||
EngineStopped,
|
||||
EngineRunning,
|
||||
EnginePostMerge
|
||||
|
||||
SealingEngineRef* = ref SealingEngineObj
|
||||
SealingEngineObj = object of RootObj
|
||||
state: EngineState
|
||||
engineLoop: Future[void]
|
||||
chain*: ChainRef
|
||||
ctx: EthContext
|
||||
signer: EthAddress
|
||||
txPool: TxPoolRef
|
||||
|
||||
proc validateSealer*(conf: NimbusConf, ctx: EthContext, chain: ChainRef): Result[void, string] =
|
||||
if conf.engineSigner == ZERO_ADDRESS:
|
||||
return err("signer address should not zero, use --engine-signer to set signer address")
|
||||
|
||||
let res = ctx.am.getAccount(conf.engineSigner)
|
||||
if res.isErr:
|
||||
return err("signer address not in registered accounts, use --import-key/account to register the account")
|
||||
|
||||
let acc = res.get()
|
||||
if not acc.unlocked:
|
||||
return err("signer account not unlocked, please unlock it first via rpc/password file")
|
||||
|
||||
let com = chain.com
|
||||
if com.consensus != ConsensusType.POA:
|
||||
return err("currently only PoA engine is supported")
|
||||
|
||||
ok()
|
||||
|
||||
proc generateBlock(engine: SealingEngineRef,
|
||||
outBlock: var EthBlock): Result[void, string] =
|
||||
|
||||
let bundle = engine.txPool.assembleBlock().valueOr:
|
||||
return err(error)
|
||||
outBlock = bundle.blk
|
||||
|
||||
if engine.chain.com.consensus == ConsensusType.POS:
|
||||
# Stop the block generator if we reach TTD
|
||||
engine.state = EnginePostMerge
|
||||
|
||||
if engine.state != EnginePostMerge:
|
||||
# Post merge, Clique should not be executing
|
||||
let sealRes = engine.chain.clique.seal(outBlock)
|
||||
if sealRes.isErr:
|
||||
return err("error sealing block header: " & $sealRes.error)
|
||||
|
||||
debug "generated block",
|
||||
blockNumber = outBlock.header.blockNumber,
|
||||
blockHash = blockHash(outBlock.header)
|
||||
|
||||
ok()
|
||||
|
||||
proc sealingLoop(engine: SealingEngineRef): Future[void] {.async.} =
|
||||
let clique = engine.chain.clique
|
||||
|
||||
proc signerFunc(signer: EthAddress, message: openArray[byte]):
|
||||
Result[RawSignature, cstring] {.gcsafe.} =
|
||||
let
|
||||
hashData = keccakHash(message)
|
||||
ctx = engine.ctx
|
||||
acc = ctx.am.getAccount(signer).tryGet()
|
||||
rawSign = sign(acc.privateKey, SkMessage(hashData.data)).toRaw
|
||||
|
||||
ok(rawSign)
|
||||
|
||||
clique.authorize(engine.signer, signerFunc)
|
||||
|
||||
# convert times.Duration to chronos.Duration
|
||||
let period = chronos.seconds(clique.cfg.period.int64)
|
||||
|
||||
while engine.state == EngineRunning:
|
||||
# the sealing engine will tick every `cliquePeriod` seconds
|
||||
await sleepAsync(period)
|
||||
|
||||
if engine.state != EngineRunning:
|
||||
break
|
||||
|
||||
# deviation from 'correct' sealing engine:
|
||||
# - no queue for chain reorgs
|
||||
# - no async lock/guard against race with sync algo
|
||||
var blk: EthBlock
|
||||
let blkRes = engine.generateBlock(blk)
|
||||
if blkRes.isErr:
|
||||
error "sealing engine generateBlock error", msg=blkRes.error
|
||||
break
|
||||
|
||||
let res = engine.chain.persistBlocks([blk.header], [
|
||||
BlockBody(transactions: blk.txs, uncles: blk.uncles)
|
||||
])
|
||||
|
||||
if res == ValidationResult.Error:
|
||||
error "sealing engine: persistBlocks error"
|
||||
break
|
||||
|
||||
discard engine.txPool.smartHead(blk.header) # add transactions update jobs
|
||||
info "block generated", number=blk.header.blockNumber
|
||||
|
||||
proc new*(_: type SealingEngineRef,
|
||||
chain: ChainRef,
|
||||
ctx: EthContext,
|
||||
signer: EthAddress,
|
||||
txPool: TxPoolRef,
|
||||
initialState: EngineState): SealingEngineRef =
|
||||
SealingEngineRef(
|
||||
chain: chain,
|
||||
ctx: ctx,
|
||||
signer: signer,
|
||||
txPool: txPool,
|
||||
state: initialState
|
||||
)
|
||||
|
||||
proc start*(engine: SealingEngineRef) =
|
||||
## Starts sealing engine.
|
||||
if engine.state == EngineStopped:
|
||||
engine.state = EngineRunning
|
||||
engine.engineLoop = sealingLoop(engine)
|
||||
info "sealing engine started"
|
||||
|
||||
proc stop*(engine: SealingEngineRef) {.async.} =
|
||||
## Stop sealing engine from producing more blocks.
|
||||
if engine.state == EngineRunning:
|
||||
engine.state = EngineStopped
|
||||
await engine.engineLoop.cancelAndWait()
|
||||
info "sealing engine stopped"
|
|
@ -20,7 +20,6 @@ import
|
|||
../../vm_state,
|
||||
../../vm_types,
|
||||
../eip4844,
|
||||
../clique/[clique_sealer, clique_desc, clique_cfg],
|
||||
../pow/difficulty,
|
||||
../executor,
|
||||
../casper,
|
||||
|
@ -84,12 +83,6 @@ proc prepareHeader(dh: TxChainRef; parent: BlockHeader, timestamp: EthTime)
|
|||
dh.prepHeader.timestamp, parent)
|
||||
dh.prepHeader.coinbase = dh.miner
|
||||
dh.prepHeader.mixDigest.reset
|
||||
of ConsensusType.POA:
|
||||
discard dh.com.poa.prepare(parent, dh.prepHeader)
|
||||
# beware POA header.coinbase != signerAddress
|
||||
# but BaseVMState.minerAddress == signerAddress
|
||||
# - minerAddress is extracted from header.extraData
|
||||
# - header.coinbase is from clique engine
|
||||
of ConsensusType.POS:
|
||||
dh.com.pos.prepare(dh.prepHeader)
|
||||
|
||||
|
@ -98,8 +91,6 @@ proc prepareForSeal(dh: TxChainRef; header: var BlockHeader) {.gcsafe, raises: [
|
|||
of ConsensusType.POW:
|
||||
# do nothing, tx pool was designed with POW in mind
|
||||
discard
|
||||
of ConsensusType.POA:
|
||||
dh.com.poa.prepareForSeal(dh.prepHeader, header)
|
||||
of ConsensusType.POS:
|
||||
dh.com.pos.prepareForSeal(header)
|
||||
|
||||
|
@ -107,12 +98,6 @@ proc getTimestamp(dh: TxChainRef, parent: BlockHeader): EthTime =
|
|||
case dh.com.consensus
|
||||
of ConsensusType.POW:
|
||||
EthTime.now()
|
||||
of ConsensusType.POA:
|
||||
let timestamp = parent.timestamp + dh.com.poa.cfg.period
|
||||
if timestamp < EthTime.now():
|
||||
EthTime.now()
|
||||
else:
|
||||
timestamp
|
||||
of ConsensusType.POS:
|
||||
dh.com.pos.timestamp
|
||||
|
||||
|
|
|
@ -25,8 +25,6 @@ import
|
|||
./core/eip4844,
|
||||
./core/block_import,
|
||||
./db/core_db/persistent,
|
||||
./core/clique/clique_desc,
|
||||
./core/clique/clique_sealer,
|
||||
./sync/protocol,
|
||||
./sync/handlers
|
||||
|
||||
|
@ -49,7 +47,7 @@ proc importBlocks(conf: NimbusConf, com: CommonRef) =
|
|||
proc basicServices(nimbus: NimbusNode,
|
||||
conf: NimbusConf,
|
||||
com: CommonRef) =
|
||||
nimbus.txPool = TxPoolRef.new(com, conf.engineSigner)
|
||||
nimbus.txPool = TxPoolRef.new(com, ZERO_ADDRESS)
|
||||
|
||||
# txPool must be informed of active head
|
||||
# so it can know the latest account state
|
||||
|
@ -208,40 +206,6 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf,
|
|||
|
||||
nimbus.setupRpc(conf, com, protocols)
|
||||
|
||||
if conf.engineSigner != ZERO_ADDRESS and not com.forkGTE(MergeFork):
|
||||
let res = nimbus.ctx.am.getAccount(conf.engineSigner)
|
||||
if res.isErr:
|
||||
error "Failed to get account",
|
||||
msg = res.error,
|
||||
hint = "--key-store or --import-key"
|
||||
quit(QuitFailure)
|
||||
|
||||
let rs = validateSealer(conf, nimbus.ctx, nimbus.chainRef)
|
||||
if rs.isErr:
|
||||
fatal "Engine signer validation error", msg = rs.error
|
||||
quit(QuitFailure)
|
||||
|
||||
proc signFunc(signer: EthAddress, message: openArray[byte]): Result[RawSignature, cstring] {.gcsafe.} =
|
||||
let
|
||||
hashData = keccakHash(message)
|
||||
acc = nimbus.ctx.am.getAccount(signer).tryGet()
|
||||
rawSign = sign(acc.privateKey, SkMessage(hashData.data)).toRaw
|
||||
|
||||
ok(rawSign)
|
||||
|
||||
nimbus.chainRef.clique.authorize(conf.engineSigner, signFunc)
|
||||
|
||||
# disable sealing engine if beacon engine enabled
|
||||
if not com.forkGTE(MergeFork):
|
||||
nimbus.sealingEngine = SealingEngineRef.new(
|
||||
nimbus.chainRef, nimbus.ctx, conf.engineSigner,
|
||||
nimbus.txPool, EngineStopped
|
||||
)
|
||||
|
||||
# only run sealing engine if there is a signer
|
||||
if conf.engineSigner != ZERO_ADDRESS:
|
||||
nimbus.sealingEngine.start()
|
||||
|
||||
# metrics server
|
||||
if conf.metricsEnabled:
|
||||
info "Starting metrics HTTP server", address = conf.metricsAddress, port = conf.metricsPort
|
||||
|
|
|
@ -12,7 +12,6 @@ import
|
|||
eth/p2p,
|
||||
metrics/chronos_httpserver,
|
||||
./rpc/rpc_server,
|
||||
./core/sealer,
|
||||
./core/chain,
|
||||
./core/tx_pool,
|
||||
./sync/peers,
|
||||
|
@ -29,7 +28,6 @@ export
|
|||
p2p,
|
||||
chronos_httpserver,
|
||||
rpc_server,
|
||||
sealer,
|
||||
chain,
|
||||
tx_pool,
|
||||
peers,
|
||||
|
@ -50,7 +48,6 @@ type
|
|||
engineApiServer*: NimbusHttpServerRef
|
||||
ethNode*: EthereumNode
|
||||
state*: NimbusState
|
||||
sealingEngine*: SealingEngineRef
|
||||
ctx*: EthContext
|
||||
chainRef*: ChainRef
|
||||
txPool*: TxPoolRef
|
||||
|
@ -71,8 +68,6 @@ proc stop*(nimbus: NimbusNode, conf: NimbusConf) {.async, gcsafe.} =
|
|||
await nimbus.httpServer.stop()
|
||||
if nimbus.engineApiServer.isNil.not:
|
||||
await nimbus.engineApiServer.stop()
|
||||
if conf.engineSigner != ZERO_ADDRESS and nimbus.sealingEngine.isNil.not:
|
||||
await nimbus.sealingEngine.stop()
|
||||
if conf.maxPeers > 0:
|
||||
await nimbus.networkLoop.cancelAndWait()
|
||||
if nimbus.peerManager.isNil.not:
|
||||
|
|
|
@ -17,7 +17,7 @@ import
|
|||
eth/p2p/[private/p2p_types, peer_pool],
|
||||
stew/byteutils,
|
||||
"."/[protocol, types],
|
||||
../core/[chain, clique/clique_sealer, eip4844, gaslimit, withdrawals],
|
||||
../core/[chain, eip4844, gaslimit, withdrawals],
|
||||
../core/pow/difficulty,
|
||||
../constants,
|
||||
../utils/utils,
|
||||
|
@ -151,15 +151,6 @@ proc validateDifficulty(ctx: LegacySyncRef,
|
|||
let com = ctx.chain.com
|
||||
|
||||
case consensusType
|
||||
of ConsensusType.POA:
|
||||
let rc = ctx.chain.clique.calcDifficulty(parentHeader)
|
||||
if rc.isErr:
|
||||
return false
|
||||
if header.difficulty < rc.get():
|
||||
trace "provided header difficulty is too low",
|
||||
expect=rc.get(), get=header.difficulty
|
||||
return false
|
||||
|
||||
of ConsensusType.POW:
|
||||
let calcDiffc = com.calcDifficulty(header.timestamp, parentHeader)
|
||||
if header.difficulty < calcDiffc:
|
||||
|
@ -212,16 +203,6 @@ proc validateHeader(ctx: LegacySyncRef, header: BlockHeader,
|
|||
if not ctx.validateDifficulty(header, parentHeader, consensusType):
|
||||
return false
|
||||
|
||||
if consensusType == ConsensusType.POA:
|
||||
let period = com.cliquePeriod
|
||||
# Timestamp diff between blocks is lower than PERIOD (clique)
|
||||
if parentHeader.timestamp + period > header.timestamp:
|
||||
trace "invalid timestamp diff (lower than period)",
|
||||
parent=parentHeader.timestamp,
|
||||
header=header.timestamp,
|
||||
period
|
||||
return false
|
||||
|
||||
var res = com.validateGasLimitOrBaseFee(header, parentHeader)
|
||||
if res.isErr:
|
||||
trace "validate gaslimit error",
|
||||
|
|
|
@ -17,7 +17,6 @@ import
|
|||
|
||||
from ../nimbus/common/chain_config import
|
||||
MainNet,
|
||||
GoerliNet,
|
||||
SepoliaNet,
|
||||
HoleskyNet
|
||||
|
||||
|
@ -73,10 +72,9 @@ proc processU256(val: string, o: var UInt256): ConfigStatus =
|
|||
o = parse(val, UInt256)
|
||||
result = Success
|
||||
|
||||
proc processNetId(val: string, o: var NetworkId): ConfigStatus =
|
||||
func processNetId(val: string, o: var NetworkId): ConfigStatus =
|
||||
case val.toLowerAscii()
|
||||
of "main": o = MainNet
|
||||
of "goerli": o = GoerliNet
|
||||
of "sepolia": o = SepoliaNet
|
||||
of "holesky": o = HoleskyNet
|
||||
|
||||
|
|
|
@ -1,360 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2022-2024 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.
|
||||
|
||||
import
|
||||
std/[algorithm, os, sequtils, strformat, strutils, times],
|
||||
chronicles,
|
||||
eth/keys,
|
||||
stint,
|
||||
unittest2,
|
||||
../nimbus/core/[chain,
|
||||
clique,
|
||||
clique/clique_snapshot,
|
||||
clique/clique_desc,
|
||||
clique/clique_helpers
|
||||
],
|
||||
../nimbus/common/[common,context],
|
||||
../nimbus/utils/[ec_recover, utils],
|
||||
../nimbus/[config, constants],
|
||||
./test_clique/pool,
|
||||
./replay/undump_blocks_gz
|
||||
|
||||
const
|
||||
baseDir = [".", "tests", ".." / "tests", $DirSep] # path containg repo
|
||||
repoDir = ["test_clique", "replay", "status"] # alternative repos
|
||||
|
||||
goerliCapture = "goerli68161.txt.gz"
|
||||
groupReplayTransactions = 7
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc getBlockHeader(ap: TesterPool; number: BlockNumber): BlockHeader =
|
||||
## Shortcut => db/core_db.getBlockHeader()
|
||||
doAssert ap.db.getBlockHeader(number, result)
|
||||
|
||||
proc ppSecs(elapsed: Duration): string =
|
||||
result = $elapsed.inSeconds
|
||||
let ns = elapsed.inNanoseconds mod 1_000_000_000
|
||||
if ns != 0:
|
||||
# to rounded decimal seconds
|
||||
let ds = (ns + 5_000_000i64) div 10_000_000i64
|
||||
result &= &".{ds:02}"
|
||||
result &= "s"
|
||||
|
||||
proc ppRow(elapsed: Duration): string =
|
||||
let ms = elapsed.inMilliSeconds + 500
|
||||
"x".repeat(ms div 1000)
|
||||
|
||||
proc findFilePath(file: string): string =
|
||||
result = "?unknown?" / file
|
||||
for dir in baseDir:
|
||||
for repo in repoDir:
|
||||
let path = dir / repo / file
|
||||
if path.fileExists:
|
||||
return path
|
||||
|
||||
proc setTraceLevel =
|
||||
discard
|
||||
when defined(chronicles_runtime_filtering) and loggingEnabled:
|
||||
setLogLevel(LogLevel.TRACE)
|
||||
|
||||
proc setErrorLevel =
|
||||
discard
|
||||
when defined(chronicles_runtime_filtering) and loggingEnabled:
|
||||
setLogLevel(LogLevel.ERROR)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Test Runners
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/snapshot_test.go(99): func TestClique(t *testing.T) {
|
||||
proc runCliqueSnapshot(noisy = true; postProcessOk = false;
|
||||
testIds = {0'u16 .. 999'u16}; skipIds = {0'u16}-{0'u16}) =
|
||||
## Clique PoA Snapshot
|
||||
## ::
|
||||
## Tests that Clique signer voting is evaluated correctly for various
|
||||
## simple and complex scenarios, as well as that a few special corner
|
||||
## cases fail correctly.
|
||||
##
|
||||
let postProcessInfo = if postProcessOk: ", Transaction Finaliser Applied"
|
||||
else: ", Without Finaliser"
|
||||
suite &"Clique PoA Snapshot{postProcessInfo}":
|
||||
var pool = newVoterPool()
|
||||
|
||||
setErrorLevel()
|
||||
if noisy:
|
||||
pool.noisy = true
|
||||
setTraceLevel()
|
||||
|
||||
# clique/snapshot_test.go(379): for i, tt := range tests {
|
||||
for voterSample in voterSamples.filterIt(it.id.uint16 in testIds):
|
||||
let tt = voterSample
|
||||
test &"Snapshots {tt.id:2}: {tt.info.substr(0,50)}...":
|
||||
pool.say "\n"
|
||||
|
||||
# Noisily skip this test
|
||||
if tt.id.uint16 in skipIds:
|
||||
skip()
|
||||
|
||||
else:
|
||||
# Assemble a chain of headers from the cast votes
|
||||
# see clique/snapshot_test.go(407): config := *params.TestChainConfig
|
||||
pool
|
||||
.resetVoterChain(tt.signers, tt.epoch, tt.runBack)
|
||||
# see clique/snapshot_test.go(425): for j, block := range blocks {
|
||||
.appendVoter(tt.votes)
|
||||
.commitVoterChain(postProcessOk)
|
||||
|
||||
# see clique/snapshot_test.go(477): if err != nil {
|
||||
if tt.failure != cliqueNoError[0]:
|
||||
# Note that clique/snapshot_test.go does not verify _here_ against
|
||||
# the scheduled test error -- rather this voting error is supposed
|
||||
# to happen earlier (processed at clique/snapshot_test.go(467)) when
|
||||
# assembling the block chain (sounds counter intuitive to the author
|
||||
# of this source file as the scheduled errors are _clique_ related).
|
||||
check pool.failed[1][0] == tt.failure
|
||||
else:
|
||||
let
|
||||
expected = tt.results.mapIt("@" & it).sorted
|
||||
snapResult = pool.pp(pool.cliqueSigners).sorted
|
||||
pool.say "*** snap state=", pool.pp(pool.snapshot,16)
|
||||
pool.say " result=[", snapResult.join(",") & "]"
|
||||
pool.say " expected=[", expected.join(",") & "]"
|
||||
|
||||
# Verify the final list of signers against the expected ones
|
||||
check snapResult == expected
|
||||
|
||||
proc runGoerliReplay(noisy = true; showElapsed = false,
|
||||
captureFile = goerliCapture,
|
||||
startAtBlock = 0u64; stopAfterBlock = 0u64) =
|
||||
var
|
||||
pool = newVoterPool()
|
||||
cache: array[groupReplayTransactions,(seq[BlockHeader],seq[BlockBody])]
|
||||
cInx = 0
|
||||
stoppedOk = false
|
||||
|
||||
let
|
||||
fileInfo = captureFile.splitFile.name.split(".")[0]
|
||||
filePath = captureFile.findFilePath
|
||||
|
||||
pool.verifyFrom = startAtBlock
|
||||
|
||||
setErrorLevel()
|
||||
if noisy:
|
||||
pool.noisy = true
|
||||
setTraceLevel()
|
||||
|
||||
let stopThreshold = if stopAfterBlock == 0u64: uint64.high.u256
|
||||
else: stopAfterBlock.u256
|
||||
|
||||
suite &"Replay Goerli chain from {fileInfo} capture":
|
||||
|
||||
for w in filePath.undumpBlocksGz:
|
||||
|
||||
if w[0][0].blockNumber == 0.u256:
|
||||
# Verify Genesis
|
||||
doAssert w[0][0] == pool.getBlockHeader(0.u256)
|
||||
|
||||
else:
|
||||
# Condense in cache
|
||||
cache[cInx] = w
|
||||
cInx.inc
|
||||
|
||||
# Handy for partial tests
|
||||
if stopThreshold < cache[cInx-1][0][0].blockNumber:
|
||||
stoppedOk = true
|
||||
break
|
||||
|
||||
# Run from cache if complete set
|
||||
if cache.len <= cInx:
|
||||
cInx = 0
|
||||
let
|
||||
first = cache[0][0][0].blockNumber
|
||||
last = cache[^1][0][^1].blockNumber
|
||||
blkRange = &"#{first}..#{last}"
|
||||
info = if first <= startAtBlock.u256 and startAtBlock.u256 <= last:
|
||||
&", verification #{startAtBlock}.."
|
||||
else:
|
||||
""
|
||||
test &"Goerli Blocks {blkRange} ({cache.len} transactions{info})":
|
||||
let start = getTime()
|
||||
for (headers,bodies) in cache:
|
||||
let addedPersistBlocks = pool.chain.persistBlocks(headers,bodies)
|
||||
check addedPersistBlocks == ValidationResult.Ok
|
||||
if addedPersistBlocks != ValidationResult.Ok: return
|
||||
if showElapsed and startAtBlock.u256 <= last:
|
||||
let
|
||||
elpd = getTime() - start
|
||||
info = &"{elpd.ppSecs:>7} {pool.cliqueSignersLen} {elpd.ppRow}"
|
||||
echo &"\n elapsed {blkRange:<17} {info}"
|
||||
|
||||
# Rest from cache
|
||||
if 0 < cInx:
|
||||
let
|
||||
first = cache[0][0][0].blockNumber
|
||||
last = cache[cInx-1][0][^1].blockNumber
|
||||
blkRange = &"#{first}..#{last}"
|
||||
info = if first <= startAtBlock.u256 and startAtBlock.u256 <= last:
|
||||
&", Verification #{startAtBlock}.."
|
||||
else:
|
||||
""
|
||||
test &"Goerli Blocks {blkRange} ({cache.len} transactions{info})":
|
||||
let start = getTime()
|
||||
for (headers,bodies) in cache:
|
||||
let addedPersistBlocks = pool.chain.persistBlocks(headers,bodies)
|
||||
check addedPersistBlocks == ValidationResult.Ok
|
||||
if addedPersistBlocks != ValidationResult.Ok: return
|
||||
if showElapsed and startAtBlock.u256 <= last:
|
||||
let
|
||||
elpsd = getTime() - start
|
||||
info = &"{elpsd.ppSecs:>7} {pool.cliqueSignersLen} {elpsd.ppRow}"
|
||||
echo &"\n elapsed {blkRange:<17} {info}"
|
||||
|
||||
if stoppedOk:
|
||||
test &"Runner stopped after reaching #{stopThreshold}":
|
||||
discard
|
||||
|
||||
|
||||
proc runGoerliBaybySteps(noisy = true;
|
||||
captureFile = goerliCapture,
|
||||
stopAfterBlock = 0u64) =
|
||||
var
|
||||
pool = newVoterPool()
|
||||
stoppedOk = false
|
||||
|
||||
setErrorLevel()
|
||||
if noisy:
|
||||
pool.noisy = true
|
||||
setTraceLevel()
|
||||
|
||||
let
|
||||
fileInfo = captureFile.splitFile.name.split(".")[0]
|
||||
filePath = captureFile.findFilePath
|
||||
stopThreshold = if stopAfterBlock == 0u64: 20.u256
|
||||
else: stopAfterBlock.u256
|
||||
|
||||
suite &"Replay Goerli chain from {fileInfo} capture, single blockwise":
|
||||
|
||||
for w in filePath.undumpBlocksGz:
|
||||
if stoppedOk:
|
||||
break
|
||||
if w[0][0].blockNumber == 0.u256:
|
||||
# Verify Genesis
|
||||
doAssert w[0][0] == pool.getBlockHeader(0.u256)
|
||||
else:
|
||||
for n in 0 ..< w[0].len:
|
||||
let
|
||||
header = w[0][n]
|
||||
body = w[1][n]
|
||||
var
|
||||
parents = w[0][0 ..< n]
|
||||
test &"Goerli Block #{header.blockNumber} + {parents.len} parents":
|
||||
check pool.chain.clique.cliqueSnapshot(header,parents).isOk
|
||||
let addedPersistBlocks = pool.chain.persistBlocks(@[header],@[body])
|
||||
check addedPersistBlocks == ValidationResult.Ok
|
||||
if addedPersistBlocks != ValidationResult.Ok: return
|
||||
# Handy for partial tests
|
||||
if stopThreshold <= header.blockNumber:
|
||||
stoppedOk = true
|
||||
break
|
||||
|
||||
if stoppedOk:
|
||||
test &"Runner stopped after reaching #{stopThreshold}":
|
||||
discard
|
||||
|
||||
proc cliqueMiscTests() =
|
||||
let
|
||||
prvKeyFile = "private.key".findFilePath
|
||||
|
||||
suite "clique misc":
|
||||
test "signer func":
|
||||
let
|
||||
engineSigner = "658bdf435d810c91414ec09147daa6db62406379"
|
||||
privateKey = prvKeyFile
|
||||
conf = makeConfig(@["--engine-signer:" & engineSigner, "--import-key:" & privateKey])
|
||||
ctx = newEthContext()
|
||||
|
||||
check ctx.am.importPrivateKey(string conf.importKey).isOk()
|
||||
check ctx.am.getAccount(conf.engineSigner).isOk()
|
||||
|
||||
proc signFunc(signer: EthAddress, message: openArray[byte]): Result[RawSignature, cstring] {.gcsafe.} =
|
||||
let
|
||||
hashData = keccakHash(message)
|
||||
acc = ctx.am.getAccount(conf.engineSigner).tryGet()
|
||||
rawSign = sign(acc.privateKey, SkMessage(hashData.data)).toRaw
|
||||
|
||||
ok(rawSign)
|
||||
|
||||
let signerFn: CliqueSignerFn = signFunc
|
||||
var header: BlockHeader
|
||||
header.extraData.setLen(EXTRA_VANITY)
|
||||
header.extraData.add 0.byte.repeat(EXTRA_SEAL)
|
||||
|
||||
let signature = signerFn(conf.engineSigner, header.encodeSealHeader).get()
|
||||
let extraLen = header.extraData.len
|
||||
if EXTRA_SEAL < extraLen:
|
||||
header.extraData.setLen(extraLen - EXTRA_SEAL)
|
||||
header.extraData.add signature
|
||||
|
||||
let resAddr = ecRecover(header)
|
||||
check resAddr.isOk
|
||||
check resAddr.value == conf.engineSigner
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main function(s)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc cliqueMain*(noisy = defined(debug)) =
|
||||
noisy.runCliqueSnapshot(true)
|
||||
noisy.runCliqueSnapshot(false)
|
||||
noisy.runGoerliBaybySteps
|
||||
noisy.runGoerliReplay(startAtBlock = 31100u64)
|
||||
cliqueMiscTests()
|
||||
|
||||
when isMainModule:
|
||||
let
|
||||
skipIDs = {999}
|
||||
# A new capture file can be generated using
|
||||
# `test_clique/indiump.dumpGroupNl()`
|
||||
# placed at the end of
|
||||
# `p2p/chain/persist_blocks.persistBlocks()`.
|
||||
captureFile = goerliCapture
|
||||
#captureFile = "dump-stream.out.gz"
|
||||
|
||||
proc goerliReplay(noisy = true;
|
||||
showElapsed = true;
|
||||
captureFile = captureFile;
|
||||
startAtBlock = 0u64;
|
||||
stopAfterBlock = 0u64) =
|
||||
runGoerliReplay(
|
||||
noisy = noisy,
|
||||
showElapsed = showElapsed,
|
||||
captureFile = captureFile,
|
||||
startAtBlock = startAtBlock,
|
||||
stopAfterBlock = stopAfterBlock)
|
||||
|
||||
# local path is: nimbus-eth1/tests
|
||||
let noisy = defined(debug)
|
||||
|
||||
noisy.runCliqueSnapshot(true)
|
||||
noisy.runCliqueSnapshot(false)
|
||||
noisy.runGoerliBaybySteps
|
||||
false.runGoerliReplay(startAtBlock = 31100u64)
|
||||
|
||||
#noisy.goerliReplay(startAtBlock = 31100u64)
|
||||
#noisy.goerliReplay(startAtBlock = 194881u64, stopAfterBlock = 198912u64)
|
||||
|
||||
cliqueMiscTests()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1,503 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2021-2024 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.
|
||||
|
||||
import
|
||||
std/[algorithm, sequtils, strformat, strutils, tables],
|
||||
eth/keys,
|
||||
ethash,
|
||||
secp256k1/abi,
|
||||
stew/objects,
|
||||
../../nimbus/core/[chain, clique], # must be early (compilation annoyance)
|
||||
../../nimbus/common/common,
|
||||
../../nimbus/core/clique/[clique_desc, clique_genvote,
|
||||
clique_helpers, clique_snapshot],
|
||||
../../nimbus/core/clique/snapshot/[ballot, snapshot_desc],
|
||||
../../nimbus/[config, constants],
|
||||
./voter_samples as vs
|
||||
|
||||
export
|
||||
vs, snapshot_desc
|
||||
|
||||
const
|
||||
prngSeed = 42
|
||||
## The `TestSpecs` sample depends on this seed,
|
||||
|
||||
type
|
||||
XSealKey = array[EXTRA_SEAL,byte]
|
||||
XSealValue = object
|
||||
blockNumber: uint64
|
||||
account: string
|
||||
|
||||
TesterPool* = ref object ## Pool to maintain currently active tester accounts,
|
||||
## mapped from textual names used in the tests below
|
||||
## to actual Ethereum private keys capable of signing
|
||||
## transactions.
|
||||
prng: uint32 ## random state
|
||||
accounts: Table[string,PrivateKey] ## accounts table
|
||||
networkId: NetworkId
|
||||
boot: NetworkParams ## imported Genesis configuration
|
||||
batch: seq[seq[BlockHeader]] ## collect header chains
|
||||
chain: ChainRef
|
||||
|
||||
names: Table[EthAddress,string] ## reverse lookup for debugging
|
||||
xSeals: Table[XSealKey,XSealValue] ## collect signatures for debugging
|
||||
noisy*: bool
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private Prng (Clique keeps generated addresses sorted)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc posixPrngInit(state: var uint32; seed: uint32) =
|
||||
state = seed
|
||||
|
||||
proc posixPrngRand(state: var uint32): byte =
|
||||
## POSIX.1-2001 example of a rand() implementation, see manual page rand(3).
|
||||
##
|
||||
## Clique relies on the even/odd position of an address after sorting. For
|
||||
## address generation, the Nim PRNG was used which seems to have changed
|
||||
## with Nim 1.6.11 (Linux, Windoes only.)
|
||||
##
|
||||
## The `TestSpecs` sample depends on `prngSeed` and `posixPrngRand()`.
|
||||
state = state * 1103515245 + 12345;
|
||||
let val = (state shr 16) and 32767 # mod 2^31
|
||||
(val shr 8).byte # Extract second byte
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc getBlockHeader(ap: TesterPool; number: BlockNumber): BlockHeader =
|
||||
## Shortcut => db/core_db.getBlockHeader()
|
||||
doAssert ap.chain.clique.db.getBlockHeader(number, result)
|
||||
|
||||
proc getBlockHeader(ap: TesterPool; hash: Hash256): BlockHeader =
|
||||
## Shortcut => db/core_db.getBlockHeader()
|
||||
doAssert ap.chain.clique.db.getBlockHeader(hash, result)
|
||||
|
||||
proc isZero(a: openArray[byte]): bool =
|
||||
result = true
|
||||
for w in a:
|
||||
if w != 0:
|
||||
return false
|
||||
|
||||
proc rand(ap: TesterPool): byte =
|
||||
ap.prng.posixPrngRand().byte
|
||||
|
||||
proc newPrivateKey(ap: TesterPool): PrivateKey =
|
||||
## Roughly modelled after `random(PrivateKey,getRng()[])` with
|
||||
## non-secure but reproducible PRNG
|
||||
var data{.noinit.}: array[SkRawSecretKeySize,byte]
|
||||
for n in 0 ..< data.len:
|
||||
data[n] = ap.rand
|
||||
# verify generated key, see keys.random(PrivateKey) from eth/keys.nim
|
||||
var dataPtr0 = cast[ptr byte](unsafeAddr data[0])
|
||||
doAssert secp256k1_ec_seckey_verify(
|
||||
secp256k1_context_no_precomp, dataPtr0) == 1
|
||||
# Convert to PrivateKey
|
||||
PrivateKey.fromRaw(data).value
|
||||
|
||||
proc privateKey(ap: TesterPool; account: string): PrivateKey =
|
||||
## Return private key for given tester `account`
|
||||
if account != "":
|
||||
if account in ap.accounts:
|
||||
result = ap.accounts[account]
|
||||
else:
|
||||
result = ap.newPrivateKey
|
||||
ap.accounts[account] = result
|
||||
let address = result.toPublicKey.toCanonicalAddress
|
||||
ap.names[address] = account
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private pretty printer call backs
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc findName(ap: TesterPool; address: EthAddress): string =
|
||||
## Find name for a particular address
|
||||
if address notin ap.names:
|
||||
ap.names[address] = &"X{ap.names.len+1}"
|
||||
ap.names[address]
|
||||
|
||||
proc findSignature(ap: TesterPool; sig: openArray[byte]): XSealValue =
|
||||
## Find a previusly registered signature
|
||||
if sig.len == XSealKey.len:
|
||||
let key = toArray(XSealKey.len,sig)
|
||||
if key in ap.xSeals:
|
||||
result = ap.xSeals[key]
|
||||
|
||||
proc pp(ap: TesterPool; v: BlockNonce): string =
|
||||
## Pretty print nonce
|
||||
if v == NONCE_AUTH:
|
||||
"AUTH"
|
||||
elif v == NONCE_DROP:
|
||||
"DROP"
|
||||
else:
|
||||
&"0x{v.toHex}"
|
||||
|
||||
proc pp(ap: TesterPool; v: EthAddress): string =
|
||||
## Pretty print address
|
||||
if v.isZero:
|
||||
result = "@0"
|
||||
else:
|
||||
let a = ap.findName(v)
|
||||
if a == "":
|
||||
result = &"@{v}"
|
||||
else:
|
||||
result = &"@{a}"
|
||||
|
||||
proc pp*(ap: TesterPool; v: openArray[EthAddress]): seq[string] =
|
||||
## Pretty print address list
|
||||
toSeq(v).mapIt(ap.pp(it))
|
||||
|
||||
proc pp(ap: TesterPool; v: Blob): string =
|
||||
## Visualise `extraData` field
|
||||
|
||||
if v.len < EXTRA_VANITY + EXTRA_SEAL or
|
||||
((v.len - (EXTRA_VANITY + EXTRA_SEAL)) mod EthAddress.len) != 0:
|
||||
result = &"0x{v.toHex}[{v.len}]"
|
||||
else:
|
||||
var data = v
|
||||
#
|
||||
# extra vanity prefix
|
||||
let vanity = data[0 ..< EXTRA_VANITY]
|
||||
data = data[EXTRA_VANITY ..< data.len]
|
||||
result = if vanity.isZero: "0u256+" else: &"{vanity.toHex}+"
|
||||
#
|
||||
# list of addresses
|
||||
if EthAddress.len + EXTRA_SEAL <= data.len:
|
||||
var glue = "["
|
||||
while EthAddress.len + EXTRA_SEAL <= data.len:
|
||||
let address = toArray(EthAddress.len,data[0 ..< EthAddress.len])
|
||||
data = data[EthAddress.len ..< data.len]
|
||||
result &= &"{glue}{ap.pp(address)}"
|
||||
glue = ","
|
||||
result &= "]+"
|
||||
#
|
||||
# signature
|
||||
let val = ap.findSignature(data)
|
||||
if val.account != "":
|
||||
result &= &"<#{val.blockNumber},{val.account}>"
|
||||
elif data.isZero:
|
||||
result &= &"<0>"
|
||||
else:
|
||||
let sig = SkSignature.fromRaw(data)
|
||||
if sig.isOk:
|
||||
result &= &"<{sig.value.toHex}>"
|
||||
else:
|
||||
result &= &"0x{data.toHex}[{data.len}]"
|
||||
|
||||
proc pp(ap: TesterPool; v: Vote): string =
|
||||
proc authorized(b: bool): string =
|
||||
if b: "authorise" else: "de-authorise"
|
||||
"(" &
|
||||
&"address={ap.pp(v.address)}" &
|
||||
&",signer={ap.pp(v.signer)}" &
|
||||
&",blockNumber=#{v.blockNumber}" &
|
||||
&",{authorized(v.authorize)}" & ")"
|
||||
|
||||
proc pp(ap: TesterPool; h: AddressHistory): string =
|
||||
toSeq(h.keys)
|
||||
.sorted
|
||||
.mapIt("#" & $it & ":" & ap.pp(h[it.u256]))
|
||||
.join(",")
|
||||
|
||||
proc votesList(ap: TesterPool; s: Snapshot; sep: string): string =
|
||||
proc s3Cmp(a, b: (string,string,Vote)): int =
|
||||
result = cmp(a[0], b[0])
|
||||
if result == 0:
|
||||
result = cmp(a[1], b[1])
|
||||
let votes = s.ballot.votesInternal
|
||||
votes.mapIt((ap.pp(it[0]),ap.pp(it[1]),it[2]))
|
||||
.sorted(cmp = s3Cmp)
|
||||
.mapIt(ap.pp(it[2]))
|
||||
.join(sep)
|
||||
|
||||
proc signersList(ap: TesterPool; s: Snapshot): string =
|
||||
ap.pp(s.ballot.authSigners).sorted.join(",")
|
||||
|
||||
proc pp*(ap: TesterPool; s: Snapshot; delim: string): string =
|
||||
## Pretty print descriptor
|
||||
let
|
||||
p1 = if 0 < delim.len: delim else: ";"
|
||||
p2 = if 0 < delim.len and delim[0] == '\n': delim & ' '.repeat(7) else: ";"
|
||||
"(" &
|
||||
&"blockNumber=#{s.blockNumber}" &
|
||||
&"{p1}recents=" & "{" & ap.pp(s.recents) & "}" &
|
||||
&"{p1}signers=" & "{" & ap.signersList(s) & "}" &
|
||||
&"{p1}votes=[" & ap.votesList(s,p2) & "])"
|
||||
|
||||
proc pp*(ap: TesterPool; s: Snapshot; indent = 0): string =
|
||||
## Pretty print descriptor
|
||||
let delim = if 0 < indent: "\n" & ' '.repeat(indent) else: " "
|
||||
ap.pp(s, delim)
|
||||
|
||||
proc pp(ap: TesterPool; v: BlockHeader; delim: string): string =
|
||||
## Pretty print block header
|
||||
let sep = if 0 < delim.len: delim else: ";"
|
||||
&"(blockNumber=#{v.blockNumber}" &
|
||||
&"{sep}parentHash={v.parentHash}" &
|
||||
&"{sep}selfHash={v.blockHash}" &
|
||||
&"{sep}stateRoot={v.stateRoot}" &
|
||||
&"{sep}coinbase={ap.pp(v.coinbase)}" &
|
||||
&"{sep}nonce={ap.pp(v.nonce)}" &
|
||||
&"{sep}extraData={ap.pp(v.extraData)})"
|
||||
|
||||
proc pp(ap: TesterPool; v: BlockHeader; indent = 3): string =
|
||||
## Pretty print block header, NL delimited, indented fields
|
||||
let delim = if 0 < indent: "\n" & ' '.repeat(indent) else: " "
|
||||
ap.pp(v, delim)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Private: Constructor helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc resetChainDb(ap: TesterPool; extraData: Blob; debug = false) =
|
||||
## Setup new block chain with bespoke genesis
|
||||
# new genesis block
|
||||
if 0 < extraData.len:
|
||||
ap.boot.genesis.extraData = extraData
|
||||
|
||||
let com = CommonRef.new(
|
||||
newCoreDbRef DefaultDbMemory,
|
||||
networkId = ap.networkId,
|
||||
params = ap.boot)
|
||||
ap.chain = newChain(com)
|
||||
com.initializeEmptyDb()
|
||||
ap.noisy = debug
|
||||
|
||||
proc initTesterPool(ap: TesterPool): TesterPool {.discardable.} =
|
||||
result = ap
|
||||
result.prng.posixPrngInit(prngSeed)
|
||||
result.batch = @[newSeq[BlockHeader]()]
|
||||
result.accounts = initTable[string,PrivateKey]()
|
||||
result.xSeals = initTable[XSealKey,XSealValue]()
|
||||
result.names = initTable[EthAddress,string]()
|
||||
result.resetChainDb(@[])
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public: pretty printer support
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc say*(t: TesterPool; v: varargs[string,`$`]) =
|
||||
if t.noisy:
|
||||
stderr.write v.join & "\n"
|
||||
|
||||
proc sayHeaderChain*(ap: TesterPool; indent = 0): TesterPool {.discardable.} =
|
||||
result = ap
|
||||
let pfx = ' '.repeat(indent)
|
||||
var top = if 0 < ap.batch[^1].len: ap.batch[^1][^1]
|
||||
else: ap.getBlockHeader(0.u256)
|
||||
ap.say pfx, " top header: " & ap.pp(top, 16+indent)
|
||||
while not top.blockNumber.isZero:
|
||||
top = ap.getBlockHeader(top.parentHash)
|
||||
ap.say pfx, "parent header: " & ap.pp(top, 16+indent)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public: Constructor
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc newVoterPool*(networkId = GoerliNet): TesterPool =
|
||||
TesterPool(
|
||||
networkId: networkId,
|
||||
boot: networkParams(networkId)
|
||||
).initTesterPool
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public: getter
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc chain*(ap: TesterPool): ChainRef =
|
||||
## Getter
|
||||
ap.chain
|
||||
|
||||
proc clique*(ap: TesterPool): Clique =
|
||||
## Getter
|
||||
ap.chain.clique
|
||||
|
||||
proc db*(ap: TesterPool): CoreDbRef =
|
||||
## Getter
|
||||
ap.clique.db
|
||||
|
||||
proc cliqueSigners*(ap: TesterPool): seq[EthAddress] =
|
||||
## Getter
|
||||
ap.clique.cliqueSigners
|
||||
|
||||
proc cliqueSignersLen*(ap: TesterPool): int =
|
||||
## Getter
|
||||
ap.clique.cliqueSignersLen
|
||||
|
||||
proc snapshot*(ap: TesterPool): Snapshot =
|
||||
## Getter
|
||||
ap.clique.snapshot
|
||||
|
||||
proc failed*(ap: TesterPool): CliqueFailed =
|
||||
## Getter
|
||||
ap.clique.failed
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public: setter
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc `verifyFrom=`*(ap: TesterPool; verifyFrom: uint64) =
|
||||
## Setter, block number where `Clique` should start
|
||||
ap.chain.verifyFrom = verifyFrom
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# clique/snapshot_test.go(62): func (ap *testerAccountPool) address(account [..]
|
||||
proc address*(ap: TesterPool; account: string): EthAddress =
|
||||
## retrieves the Ethereum address of a tester account by label, creating
|
||||
## a new account if no previous one exists yet.
|
||||
if account != "":
|
||||
result = ap.privateKey(account).toPublicKey.toCanonicalAddress
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Public: set up & manage voter database
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
proc resetVoterChain*(ap: TesterPool; signers: openArray[string];
|
||||
epoch = 0; runBack = true): TesterPool {.discardable.} =
|
||||
## Reset the batch list for voter headers and update genesis block
|
||||
result = ap
|
||||
|
||||
ap.batch = @[newSeq[BlockHeader]()]
|
||||
|
||||
# clique/snapshot_test.go(384): signers := make([]common.Address, [..]
|
||||
let signers = signers.mapIt(ap.address(it)).sorted(EthAscending)
|
||||
|
||||
var extraData = 0.byte.repeat(EXTRA_VANITY)
|
||||
|
||||
# clique/snapshot_test.go(399): for j, signer := range signers {
|
||||
for signer in signers:
|
||||
extraData.add signer.toSeq
|
||||
|
||||
# clique/snapshot_test.go(397):
|
||||
extraData.add 0.byte.repeat(EXTRA_SEAL)
|
||||
|
||||
# store modified genesis block and epoch
|
||||
ap.resetChainDb(extraData, ap.noisy)
|
||||
ap.clique.cfg.epoch = epoch
|
||||
ap.clique.applySnapsMinBacklog = runBack
|
||||
|
||||
|
||||
# clique/snapshot_test.go(415): blocks, _ := core.GenerateChain(&config, [..]
|
||||
proc appendVoter*(ap: TesterPool;
|
||||
voter: TesterVote): TesterPool {.discardable.} =
|
||||
## Append a voter header to the block chain batch list
|
||||
result = ap
|
||||
|
||||
doAssert 0 < ap.batch.len # see initTesterPool() and resetVoterChain()
|
||||
let parent = if ap.batch[^1].len == 0:
|
||||
ap.getBlockHeader(0.u256)
|
||||
else:
|
||||
ap.batch[^1][^1]
|
||||
|
||||
let header = ap.chain.clique.cliqueGenvote(
|
||||
voter = ap.address(voter.voted),
|
||||
seal = ap.privateKey(voter.signer),
|
||||
parent = parent,
|
||||
elapsed = EthTime(100),
|
||||
voteInOk = voter.auth,
|
||||
outOfTurn = voter.noTurn,
|
||||
checkPoint = voter.checkpoint.mapIt(ap.address(it)).sorted(EthAscending))
|
||||
|
||||
if 0 < voter.checkpoint.len:
|
||||
doAssert (header.blockNumber mod ap.clique.cfg.epoch).isZero
|
||||
|
||||
# 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 @[]
|
||||
ap.batch[^1].add header
|
||||
|
||||
|
||||
proc appendVoter*(ap: TesterPool;
|
||||
voters: openArray[TesterVote]): TesterPool {.discardable.} =
|
||||
## Append a list of voter headers to the block chain batch list
|
||||
result = ap
|
||||
for voter in voters:
|
||||
ap.appendVoter(voter)
|
||||
|
||||
|
||||
proc commitVoterChain*(ap: TesterPool; postProcessOk = false;
|
||||
stopFaultyHeader = false): TesterPool {.discardable.} =
|
||||
## Write the headers from the voter header batch list to the block chain DB.
|
||||
##
|
||||
## If `postProcessOk` is set, an additional verification step is added at
|
||||
## the end of each transaction.
|
||||
##
|
||||
## if `stopFaultyHeader` is set, the function stops immediately on error.
|
||||
## Otherwise the offending block is removed, the rest of the batch is
|
||||
## adjusted and applied again repeatedly.
|
||||
result = ap
|
||||
|
||||
var reChainOk = false
|
||||
for n in 0 ..< ap.batch.len:
|
||||
block forLoop:
|
||||
|
||||
var headers = ap.batch[n]
|
||||
while true:
|
||||
if headers.len == 0:
|
||||
break forLoop # continue with for loop
|
||||
|
||||
ap.say &"*** transaction ({n}) list: [",
|
||||
headers.mapIt(&"#{it.blockNumber}").join(", "), "]"
|
||||
|
||||
# Realign rest of transaction to existing block chain
|
||||
if reChainOk:
|
||||
var parent = ap.chain.clique.db.getCanonicalHead
|
||||
for i in 0 ..< headers.len:
|
||||
headers[i].parentHash = parent.blockHash
|
||||
headers[i].blockNumber = parent.blockNumber + 1
|
||||
parent = headers[i]
|
||||
|
||||
# Perform transaction into the block chain
|
||||
let bodies = BlockBody().repeat(headers.len)
|
||||
if ap.chain.persistBlocks(headers,bodies) == ValidationResult.OK:
|
||||
break
|
||||
if stopFaultyHeader:
|
||||
return
|
||||
|
||||
# If the offending block is the last one of the last transaction,
|
||||
# then there is nothing to do.
|
||||
let culprit = headers.filterIt(ap.failed[0] == it.blockHash)
|
||||
doAssert culprit.len == 1
|
||||
let number = culprit[0].blockNumber
|
||||
if n + 1 == ap.batch.len and number == headers[^1].blockNumber:
|
||||
return
|
||||
|
||||
# Remove offending block and try again for the rest
|
||||
ap.say "*** persistBlocks failed, omitting block #", culprit
|
||||
let prevLen = headers.len
|
||||
headers = headers.filterIt(number != it.blockNumber)
|
||||
doAssert headers.len < prevLen
|
||||
reChainOk = true
|
||||
|
||||
if ap.noisy:
|
||||
ap.say "*** snapshot argument: #", headers[^1].blockNumber
|
||||
ap.sayHeaderChain(8)
|
||||
when false: # all addresses are typically pp-mappable
|
||||
ap.say " address map: ", toSeq(ap.names.pairs)
|
||||
.mapIt(&"@{it[1]}:{it[0]}")
|
||||
.sorted
|
||||
.join("\n" & ' '.repeat(23))
|
||||
if postProcessOk:
|
||||
discard ap.clique.cliqueSnapshot(headers[^1])
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# End
|
||||
# ------------------------------------------------------------------------------
|
|
@ -1 +0,0 @@
|
|||
9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c
|
|
@ -1,421 +0,0 @@
|
|||
# Nimbus
|
||||
# Copyright (c) 2021-2024 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.
|
||||
|
||||
# Test cases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-225.md
|
||||
|
||||
import
|
||||
../../nimbus/core/clique/clique_defs
|
||||
|
||||
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` 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 `noTurn` flag can be set
|
||||
## by sort of experimenting with the tests (and/or refering to earlier
|
||||
## woking test specs.)
|
||||
|
||||
newbatch*: bool
|
||||
|
||||
|
||||
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
|
||||
voterSamples* = [
|
||||
# clique/snapshot_test.go(108): {
|
||||
TestSpecs(
|
||||
id: 1,
|
||||
info: "Single signer, no votes cast",
|
||||
signers: @["A"],
|
||||
votes: @[TesterVote(signer: "A")],
|
||||
results: @["A"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 2,
|
||||
info: "Single signer, voting to add two others (only accept first, "&
|
||||
"second needs 2 votes)",
|
||||
signers: @["A"],
|
||||
votes: @[TesterVote(signer: "A", voted: "B", auth: true),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A", voted: "C", auth: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 3,
|
||||
info: "Two signers, voting to add three others (only accept first " &
|
||||
"two, third needs 3 votes already)",
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", auth: true),
|
||||
TesterVote(signer: "B", voted: "C", auth: true),
|
||||
TesterVote(signer: "A", voted: "D", auth: true, noTurn: true),
|
||||
TesterVote(signer: "B", voted: "D", auth: true),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "E", auth: true, noTurn: true),
|
||||
TesterVote(signer: "B", voted: "E", auth: true, noTurn: true)],
|
||||
results: @["A", "B", "C", "D"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 4,
|
||||
info: "Single signer, dropping itself (weird, but one less " &
|
||||
"cornercase by explicitly allowing this)",
|
||||
signers: @["A"],
|
||||
votes: @[TesterVote(signer: "A", voted: "A")]),
|
||||
|
||||
TestSpecs(
|
||||
id: 5,
|
||||
info: "Two signers, actually needing mutual consent to drop either " &
|
||||
"of them (not fulfilled)",
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A", voted: "B")],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 6,
|
||||
info: "Two signers, actually needing mutual consent to drop either " &
|
||||
"of them (fulfilled)",
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A", voted: "B"),
|
||||
TesterVote(signer: "B", voted: "B")],
|
||||
results: @["A"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 7,
|
||||
info: "Three signers, two of them deciding to drop the third",
|
||||
signers: @["A", "B", "C"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C", noTurn: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 8,
|
||||
info: "Four signers, consensus of two not being enough to drop anyone",
|
||||
signers: @["A", "B", "C", "D"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C", noTurn: true)],
|
||||
results: @["A", "B", "C", "D"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 9,
|
||||
info: "Four signers, consensus of three already being enough to " &
|
||||
"drop someone",
|
||||
signers: @["A", "B", "C", "D"],
|
||||
votes: @[TesterVote(signer: "A", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "D", noTurn: true)],
|
||||
results: @["A", "B", "C"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 10,
|
||||
info: "Authorizations are counted once per signer per target",
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", auth: true),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A", voted: "C", auth: true),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A", voted: "C", auth: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 11,
|
||||
info: "Authorizing multiple accounts concurrently is permitted",
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", auth: true),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A", voted: "D", auth: true),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A"),
|
||||
TesterVote(signer: "B", voted: "D", auth: true),
|
||||
TesterVote(signer: "A", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C", auth: true, noTurn: true)],
|
||||
results: @["A", "B", "C", "D"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 12,
|
||||
info: "Deauthorizations are counted once per signer per target",
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A", voted: "B"),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A", voted: "B"),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A", voted: "B")],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 13,
|
||||
info: "Deauthorizing multiple accounts concurrently is permitted",
|
||||
signers: @["A", "B", "C", "D"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "B", noTurn: true),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A"),
|
||||
TesterVote(signer: "B", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "A", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C", noTurn: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 14,
|
||||
info: "Votes from deauthorized signers are discarded immediately " &
|
||||
"(deauth votes)",
|
||||
signers: @["A", "B", "C"],
|
||||
votes: @[TesterVote(signer: "C", voted: "B", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "C"),
|
||||
TesterVote(signer: "B", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "B", noTurn: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 15,
|
||||
info: "Votes from deauthorized signers are discarded immediately " &
|
||||
"(auth votes)",
|
||||
signers: @["A", "B", "C"],
|
||||
votes: @[TesterVote(signer: "C", voted: "D", auth: true, noTurn: true),
|
||||
TesterVote(signer: "A", voted: "C"),
|
||||
TesterVote(signer: "B", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "D", auth: true, noTurn: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 16,
|
||||
info: "Cascading changes are not allowed, only the account being " &
|
||||
"voted on may change",
|
||||
signers: @["A", "B", "C", "D"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "B", noTurn: true),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C"),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A"),
|
||||
TesterVote(signer: "B", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "D", noTurn: true)],
|
||||
results: @["A", "B", "C"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 17,
|
||||
info: "Changes reaching consensus out of bounds (via a deauth) " &
|
||||
"execute on touch",
|
||||
signers: @["A", "B", "C", "D"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "B", noTurn: true),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C"),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A"),
|
||||
TesterVote(signer: "B", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "A", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "C", auth: true, noTurn: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 18,
|
||||
info: "Changes reaching consensus out of bounds (via a deauth) " &
|
||||
"may go out of consensus on first touch",
|
||||
signers: @["A", "B", "C", "D"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", noTurn: true),
|
||||
TesterVote(signer: "B", noTurn: true),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C"),
|
||||
TesterVote(signer: "C", noTurn: true),
|
||||
TesterVote(signer: "A"),
|
||||
TesterVote(signer: "B", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "D", noTurn: true),
|
||||
TesterVote(signer: "A", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "C", auth: true, noTurn: true)],
|
||||
results: @["A", "B", "C"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 19,
|
||||
info: "Ensure that pending votes don't survive authorization status " &
|
||||
"changes. This corner case can only appear if a signer is " &
|
||||
"quickly added, removed and then readded (or the inverse), " &
|
||||
"while one of the original voters dropped. If a past vote is " &
|
||||
"left cached in the system somewhere, this will interfere " &
|
||||
"with the final signer outcome.",
|
||||
signers: @["A", "B", "C", "D", "E"],
|
||||
votes: @[
|
||||
# Authorize F, 3 votes needed
|
||||
TesterVote(signer: "A", voted: "F", auth: true, noTurn: true),
|
||||
TesterVote(signer: "B", voted: "F", auth: true),
|
||||
TesterVote(signer: "C", voted: "F", auth: true, noTurn: true),
|
||||
|
||||
# Deauthorize F, 4 votes needed (leave A's previous vote "unchanged")
|
||||
TesterVote(signer: "D", voted: "F", noTurn: true),
|
||||
TesterVote(signer: "E", voted: "F", noTurn: true),
|
||||
TesterVote(signer: "B", voted: "F", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "F"),
|
||||
|
||||
# Almost authorize F, 2/3 votes needed
|
||||
TesterVote(signer: "D", voted: "F", auth: true),
|
||||
TesterVote(signer: "E", voted: "F", auth: true, noTurn: true),
|
||||
|
||||
# Deauthorize A, 3 votes needed
|
||||
TesterVote(signer: "B", voted: "A", noTurn: true),
|
||||
TesterVote(signer: "C", voted: "A"),
|
||||
TesterVote(signer: "D", voted: "A", noTurn: true),
|
||||
|
||||
# Finish authorizing F, 3/3 votes needed
|
||||
TesterVote(signer: "B", voted: "F", auth: true, noTurn: true)],
|
||||
results: @["B", "C", "D", "E", "F"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 20,
|
||||
info: "Epoch transitions reset all votes to allow chain checkpointing",
|
||||
epoch: 3,
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A", voted: "C", auth: true),
|
||||
TesterVote(signer: "B"),
|
||||
TesterVote(signer: "A", checkpoint: @["A", "B"]),
|
||||
TesterVote(signer: "B", voted: "C", auth: true)],
|
||||
results: @["A", "B"]),
|
||||
|
||||
TestSpecs(
|
||||
id: 21,
|
||||
info: "An unauthorized signer should not be able to sign blocks",
|
||||
signers: @["A"],
|
||||
votes: @[TesterVote(signer: "B", noTurn: true)],
|
||||
failure: errUnauthorizedSigner),
|
||||
|
||||
TestSpecs(
|
||||
id: 22,
|
||||
info: "An authorized signer that signed recenty should not be able " &
|
||||
"to sign again",
|
||||
signers: @["A", "B"],
|
||||
votes: @[TesterVote(signer: "A"),
|
||||
TesterVote(signer: "A")],
|
||||
failure: errRecentlySigned),
|
||||
|
||||
TestSpecs(
|
||||
id: 23,
|
||||
info: "Recent signatures should not reset on checkpoint blocks " &
|
||||
"imported in a batch ",
|
||||
epoch: 3,
|
||||
signers: @["A", "B", "C"],
|
||||
votes: @[TesterVote(signer: "A", noTurn: true),
|
||||
TesterVote(signer: "B", noTurn: true),
|
||||
TesterVote(signer: "A", checkpoint: @["A", "B", "C"],
|
||||
noTurn: true),
|
||||
TesterVote(signer: "A", noTurn: true)],
|
||||
|
||||
# Setting the `runBack` flag forces the shapshot handler searching for
|
||||
# a checkpoint before entry 3. So the checkpont will be ignored for
|
||||
# re-setting the system so that address `A` of block #3 is found in the
|
||||
# list of recent signers (see documentation of the flag
|
||||
# `applySnapsMinBacklog` for the `Clique` descriptor.)
|
||||
#
|
||||
# As far as I understand, there was no awareness of the tranaction batch
|
||||
# in the Go implementation -- jordan.
|
||||
runBack: true,
|
||||
failure: errRecentlySigned),
|
||||
|
||||
# The last test does not differ from the previous one with the current
|
||||
# test environment.
|
||||
TestSpecs(
|
||||
id: 24,
|
||||
info: "Recent signatures (revisted) should not reset on checkpoint " &
|
||||
"blocks imported in a batch " &
|
||||
"(https://github.com/ethereum/go-ethereum/issues/17593). "&
|
||||
"Whilst this seems overly specific and weird, it was a "&
|
||||
"Rinkeby consensus split.",
|
||||
epoch: 3,
|
||||
signers: @["A", "B", "C"],
|
||||
votes: @[TesterVote(signer: "A", noTurn: true),
|
||||
TesterVote(signer: "B", noTurn: true),
|
||||
TesterVote(signer: "A", checkpoint: @["A", "B", "C"],
|
||||
noTurn: true),
|
||||
TesterVote(signer: "A", newbatch: true, noTurn: true)],
|
||||
|
||||
# Setting the `runBack` flag forces the shapshot handler searching for
|
||||
# a checkpoint before entry 3. So the checkpont will be ignored for
|
||||
# re-setting the system so that address `A` of block #3 is found in the
|
||||
# list of recent signers (see documentation of the flag
|
||||
# `applySnapsMinBacklog` for the `Clique` descriptor.)
|
||||
#
|
||||
# As far as I understand, there was no awareness of the tranaction batch
|
||||
# in the Go implementation -- jordan.
|
||||
runBack: true,
|
||||
failure: errRecentlySigned),
|
||||
|
||||
# Not found in Go reference implementation
|
||||
TestSpecs(
|
||||
id: 25,
|
||||
info: "Test 23/24 with using the most recent <epoch> checkpoint",
|
||||
epoch: 3,
|
||||
signers: @["A", "B", "C"],
|
||||
votes: @[TesterVote(signer: "A", noTurn: true),
|
||||
TesterVote(signer: "B", noTurn: true),
|
||||
TesterVote(signer: "A", checkpoint: @["A", "B", "C"],
|
||||
noTurn: true),
|
||||
TesterVote(signer: "A", noTurn: true)],
|
||||
results: @["A", "B", "C"])]
|
||||
|
||||
static:
|
||||
# For convenience, make sure that IDs are increasing
|
||||
for n in 1 ..< voterSamples.len:
|
||||
if voterSamples[n-1].id < voterSamples[n].id:
|
||||
continue
|
||||
echo "voterSamples[", n, "] == ", voterSamples[n].id, " expected ",
|
||||
voterSamples[n-1].id + 1, " or greater"
|
||||
doAssert voterSamples[n-1].id < voterSamples[n].id
|
||||
|
||||
# End
|
|
@ -86,12 +86,12 @@ proc configurationMain*() =
|
|||
let conf = makeConfig(@["--custom-network:" & genesisFile])
|
||||
check conf.networkId == 123.NetworkId
|
||||
|
||||
test "network-id not set, goerli set":
|
||||
let conf = makeConfig(@["--network:goerli"])
|
||||
check conf.networkId == GoerliNet
|
||||
test "network-id not set, sepolia set":
|
||||
let conf = makeConfig(@["--network:sepolia"])
|
||||
check conf.networkId == SepoliaNet
|
||||
|
||||
test "network-id set, goerli set":
|
||||
let conf = makeConfig(@["--network:goerli", "--network:123"])
|
||||
test "network-id set, sepolia set":
|
||||
let conf = makeConfig(@["--network:sepolia", "--network:123"])
|
||||
check conf.networkId == 123.NetworkId
|
||||
|
||||
test "rpc-api":
|
||||
|
|
|
@ -81,21 +81,6 @@ let
|
|||
# will run over all avail files in parent folder
|
||||
files: @["00000.era1"]) # on external repo
|
||||
|
||||
# Goerli will be abondoned in future
|
||||
goerliSample = CaptureSpecs(
|
||||
builtIn: true,
|
||||
name: "goerli",
|
||||
network: GoerliNet,
|
||||
files: @["goerli68161.txt.gz"]) # on local replay folder
|
||||
|
||||
goerliSampleEx = CaptureSpecs(
|
||||
builtIn: true,
|
||||
name: "goerli",
|
||||
network: GoerliNet,
|
||||
files: @[
|
||||
"goerli482304.txt.gz", # on nimbus-eth1-blobs/replay
|
||||
"goerli482305-504192.txt.gz"])
|
||||
|
||||
# ------------------
|
||||
|
||||
# Supposed to run mostly on defaults, object name tag: m=memory, r=rocksDB
|
||||
|
@ -142,35 +127,12 @@ let
|
|||
dbType = AristoDbRocks)
|
||||
|
||||
|
||||
# Goerli will be abondoned in future
|
||||
goerliTest0m* = goerliSample
|
||||
.cloneWith(
|
||||
name = "-am-some",
|
||||
numBlocks = 1_000)
|
||||
|
||||
goerliTest1m* = goerliSample
|
||||
.cloneWith(
|
||||
name = "-am",
|
||||
numBlocks = high(int))
|
||||
|
||||
goerliTest2m* = goerliSampleEx
|
||||
.cloneWith(
|
||||
name = "-ex-am",
|
||||
numBlocks = high(int))
|
||||
|
||||
goerliTest3r* = goerliSampleEx
|
||||
.cloneWith(
|
||||
name = "-ex-ar",
|
||||
numBlocks = high(int),
|
||||
dbType = AristoDbRocks)
|
||||
|
||||
# ------------------
|
||||
|
||||
allSamples* = [
|
||||
mainTest0m, mainTest1m,
|
||||
mainTest2r, mainTest3r, mainTest4r,
|
||||
mainTest5m, mainTest6r,
|
||||
goerliTest0m, goerliTest1m, goerliTest2m, goerliTest3r
|
||||
mainTest5m, mainTest6r
|
||||
]
|
||||
|
||||
# End
|
||||
|
|
|
@ -141,7 +141,6 @@ template runGenesisTimeIdTests() =
|
|||
proc forkIdMain*() =
|
||||
suite "Fork ID tests":
|
||||
runTest(MainNet, "MainNet")
|
||||
runTest(GoerliNet, "GoerliNet")
|
||||
runTest(SepoliaNet, "SepoliaNet")
|
||||
runTest(HoleskyNet, "HoleskyNet")
|
||||
test "Genesis Time Fork ID":
|
||||
|
|
|
@ -36,10 +36,6 @@ proc genesisTest() =
|
|||
let b = makeGenesis(MainNet)
|
||||
check(b.blockHash == "D4E56740F876AEF8C010B86A40D5F56745A118D0906A34E69AEC8C0DB1CB8FA3".toDigest)
|
||||
|
||||
test "Correct goerlinet hash":
|
||||
let b = makeGenesis(GoerliNet)
|
||||
check(b.blockHash == "bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a".toDigest)
|
||||
|
||||
test "Correct sepolia hash":
|
||||
let b = makeGenesis(SepoliaNet)
|
||||
check b.blockHash == "25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9".toDigest
|
||||
|
@ -60,18 +56,6 @@ proc customGenesisTest() =
|
|||
check cgb.config.consensusType == ConsensusType.POW
|
||||
check cgc.config.consensusType == ConsensusType.POW
|
||||
|
||||
test "calaveras.json":
|
||||
var cg: NetworkParams
|
||||
check loadNetworkParams("calaveras.json".findFilePath, cg)
|
||||
let com = CommonRef.new(newCoreDbRef DefaultDbMemory, params = cg)
|
||||
let stateRoot = "664c93de37eb4a72953ea42b8c046cdb64c9f0b0bca5505ade8d970d49ebdb8c".toDigest
|
||||
let genesisHash = "eb9233d066c275efcdfed8037f4fc082770176aefdbcb7691c71da412a5670f2".toDigest
|
||||
check com.genesisHeader.stateRoot == stateRoot
|
||||
check com.genesisHeader.blockHash == genesisHash
|
||||
check com.consensus == ConsensusType.POA
|
||||
check com.cliquePeriod == 30
|
||||
check com.cliqueEpoch == 30000
|
||||
|
||||
test "Devnet4.json (aka Kintsugi in all but chainId)":
|
||||
var cg: NetworkParams
|
||||
check loadNetworkParams("devnet4.json".findFilePath, cg)
|
||||
|
|
Loading…
Reference in New Issue