2021-08-24 07:34:58 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2021 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
|
2023-01-30 22:10:23 +00:00
|
|
|
std/[times, typetraits],
|
2022-03-11 03:48:34 +00:00
|
|
|
pkg/[chronos,
|
|
|
|
stew/results,
|
|
|
|
chronicles,
|
|
|
|
eth/keys,
|
|
|
|
eth/rlp],
|
2022-12-02 04:35:41 +00:00
|
|
|
".."/[config,
|
|
|
|
constants],
|
|
|
|
"."/[
|
|
|
|
chain,
|
|
|
|
tx_pool,
|
2022-12-06 05:55:40 +00:00
|
|
|
casper,
|
2022-12-02 04:35:41 +00:00
|
|
|
validate],
|
2023-01-30 22:10:23 +00:00
|
|
|
"."/clique/[
|
2021-08-24 07:34:58 +00:00
|
|
|
clique_desc,
|
|
|
|
clique_cfg,
|
2021-09-11 14:58:01 +00:00
|
|
|
clique_sealer],
|
2022-12-02 04:35:41 +00:00
|
|
|
../utils/utils,
|
|
|
|
../common/[common, context]
|
|
|
|
|
2021-08-24 07:34:58 +00:00
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
from web3/ethtypes as web3types import nil
|
2022-02-22 08:55:04 +00:00
|
|
|
from web3/engine_api_types import PayloadAttributesV1, ExecutionPayloadV1
|
2021-10-05 23:31:35 +00:00
|
|
|
|
2021-08-24 07:34:58 +00:00
|
|
|
type
|
2021-10-05 23:31:35 +00:00
|
|
|
EngineState* = enum
|
|
|
|
EngineStopped,
|
|
|
|
EngineRunning,
|
|
|
|
EnginePostMerge
|
|
|
|
|
|
|
|
Web3BlockHash = web3types.BlockHash
|
|
|
|
Web3Address = web3types.Address
|
|
|
|
Web3Bloom = web3types.FixedBytes[256]
|
|
|
|
Web3Quantity = web3types.Quantity
|
2021-08-24 07:34:58 +00:00
|
|
|
|
|
|
|
SealingEngineRef* = ref SealingEngineObj
|
|
|
|
SealingEngineObj = object of RootObj
|
|
|
|
state: EngineState
|
|
|
|
engineLoop: Future[void]
|
2022-12-02 04:35:41 +00:00
|
|
|
chain*: ChainRef
|
2021-09-07 13:45:01 +00:00
|
|
|
ctx: EthContext
|
|
|
|
signer: EthAddress
|
2022-01-23 11:39:43 +00:00
|
|
|
txPool: TxPoolRef
|
2021-08-24 07:34:58 +00:00
|
|
|
|
2022-12-02 04:35:41 +00:00
|
|
|
proc validateSealer*(conf: NimbusConf, ctx: EthContext, chain: ChainRef): Result[void, string] =
|
2021-08-24 07:34:58 +00:00
|
|
|
if conf.engineSigner == ZERO_ADDRESS:
|
|
|
|
return err("signer address should not zero, use --engine-signer to set signer address")
|
|
|
|
|
2021-09-07 13:45:01 +00:00
|
|
|
let res = ctx.am.getAccount(conf.engineSigner)
|
|
|
|
if res.isErr:
|
2021-08-24 07:34:58 +00:00
|
|
|
return err("signer address not in registered accounts, use --import-key/account to register the account")
|
|
|
|
|
2021-09-07 13:45:01 +00:00
|
|
|
let acc = res.get()
|
2021-08-24 07:34:58 +00:00
|
|
|
if not acc.unlocked:
|
|
|
|
return err("signer account not unlocked, please unlock it first via rpc/password file")
|
|
|
|
|
2022-12-02 04:35:41 +00:00
|
|
|
let com = chain.com
|
|
|
|
if com.consensus != ConsensusType.POA:
|
2021-08-24 07:34:58 +00:00
|
|
|
return err("currently only PoA engine is supported")
|
|
|
|
|
|
|
|
ok()
|
|
|
|
|
2022-12-06 05:55:40 +00:00
|
|
|
proc generateBlock(engine: SealingEngineRef,
|
|
|
|
outBlock: var EthBlock): Result[void, string] =
|
2022-04-03 09:38:32 +00:00
|
|
|
|
2022-12-06 05:55:40 +00:00
|
|
|
outBlock = engine.txPool.ethBlock()
|
|
|
|
if engine.chain.com.consensus == ConsensusType.POS:
|
2022-03-21 09:15:52 +00:00
|
|
|
# Stop the block generator if we reach TTD
|
|
|
|
engine.state = EnginePostMerge
|
2021-08-24 07:34:58 +00:00
|
|
|
|
2022-03-21 09:15:52 +00:00
|
|
|
if engine.state != EnginePostMerge:
|
2022-02-22 08:55:04 +00:00
|
|
|
# Post merge, Clique should not be executing
|
2022-01-24 13:08:33 +00:00
|
|
|
let sealRes = engine.chain.clique.seal(outBlock)
|
|
|
|
if sealRes.isErr:
|
|
|
|
return err("error sealing block header: " & $sealRes.error)
|
2021-08-24 07:34:58 +00:00
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
debug "generated block",
|
|
|
|
blockNumber = outBlock.header.blockNumber,
|
|
|
|
blockHash = blockHash(outBlock.header)
|
|
|
|
|
2021-08-24 07:34:58 +00:00
|
|
|
ok()
|
|
|
|
|
|
|
|
proc sealingLoop(engine: SealingEngineRef): Future[void] {.async.} =
|
|
|
|
let clique = engine.chain.clique
|
|
|
|
|
2021-09-07 13:45:01 +00:00
|
|
|
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)
|
2021-08-24 07:34:58 +00:00
|
|
|
|
|
|
|
# convert times.Duration to chronos.Duration
|
|
|
|
let period = chronos.seconds(clique.cfg.period.inSeconds)
|
|
|
|
|
|
|
|
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
|
2022-05-29 03:29:56 +00:00
|
|
|
let blkRes = engine.generateBlock(blk)
|
2021-08-24 07:34:58 +00:00
|
|
|
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
|
|
|
|
|
2022-04-08 08:38:47 +00:00
|
|
|
discard engine.txPool.smartHead(blk.header) # add transactions update jobs
|
2022-04-03 09:38:32 +00:00
|
|
|
info "block generated", number=blk.header.blockNumber
|
2022-03-23 14:06:26 +00:00
|
|
|
|
2022-01-24 13:08:33 +00:00
|
|
|
template unsafeQuantityToInt64(q: web3types.Quantity): int64 =
|
|
|
|
int64 q
|
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
proc generateExecutionPayload*(engine: SealingEngineRef,
|
2022-01-24 13:08:33 +00:00
|
|
|
payloadAttrs: PayloadAttributesV1,
|
|
|
|
payloadRes: var ExecutionPayloadV1): Result[void, string] =
|
2022-04-03 09:38:32 +00:00
|
|
|
let
|
2022-04-11 09:36:22 +00:00
|
|
|
headBlock = try: engine.chain.db.getCanonicalHead()
|
|
|
|
except CatchableError: return err "No head block in database"
|
2022-12-06 05:55:40 +00:00
|
|
|
pos = engine.chain.com.pos
|
|
|
|
|
|
|
|
pos.prevRandao = Hash256(data: distinctBase payloadAttrs.prevRandao)
|
|
|
|
pos.timestamp = fromUnix(payloadAttrs.timestamp.unsafeQuantityToInt64)
|
|
|
|
pos.feeRecipient = EthAddress payloadAttrs.suggestedFeeRecipient
|
2022-04-03 09:38:32 +00:00
|
|
|
|
2022-04-11 09:36:22 +00:00
|
|
|
if headBlock.blockHash != engine.txPool.head.blockHash:
|
|
|
|
# reorg
|
|
|
|
discard engine.txPool.smartHead(headBlock)
|
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
var blk: EthBlock
|
2022-12-06 05:55:40 +00:00
|
|
|
let res = engine.generateBlock(blk)
|
|
|
|
if res.isErr:
|
|
|
|
error "sealing engine generateBlock error", msg = res.error
|
|
|
|
return res
|
2021-10-05 23:31:35 +00:00
|
|
|
|
2022-03-11 03:48:34 +00:00
|
|
|
# make sure both generated block header and payloadRes(ExecutionPayloadV1)
|
|
|
|
# produce the same blockHash
|
2022-03-21 09:15:52 +00:00
|
|
|
blk.header.fee = some(blk.header.fee.get(UInt256.zero)) # force it with some(UInt256)
|
2022-03-11 03:48:34 +00:00
|
|
|
|
|
|
|
let blockHash = rlpHash(blk.header)
|
2022-02-22 08:55:04 +00:00
|
|
|
if blk.header.extraData.len > 32:
|
|
|
|
return err "extraData length should not exceed 32 bytes"
|
|
|
|
|
2021-10-05 23:31:35 +00:00
|
|
|
payloadRes.parentHash = Web3BlockHash blk.header.parentHash.data
|
2022-01-24 13:08:33 +00:00
|
|
|
payloadRes.feeRecipient = Web3Address blk.header.coinbase
|
2021-10-05 23:31:35 +00:00
|
|
|
payloadRes.stateRoot = Web3BlockHash blk.header.stateRoot.data
|
2022-01-24 13:08:33 +00:00
|
|
|
payloadRes.receiptsRoot = Web3BlockHash blk.header.receiptRoot.data
|
2021-10-05 23:31:35 +00:00
|
|
|
payloadRes.logsBloom = Web3Bloom blk.header.bloom
|
2022-04-04 02:01:39 +00:00
|
|
|
payloadRes.prevRandao = payloadAttrs.prevRandao
|
2021-10-05 23:31:35 +00:00
|
|
|
payloadRes.blockNumber = Web3Quantity blk.header.blockNumber.truncate(uint64)
|
|
|
|
payloadRes.gasLimit = Web3Quantity blk.header.gasLimit
|
|
|
|
payloadRes.gasUsed = Web3Quantity blk.header.gasUsed
|
|
|
|
payloadRes.timestamp = payloadAttrs.timestamp
|
2022-04-08 04:54:11 +00:00
|
|
|
payloadRes.extraData = web3types.DynamicBytes[0, 32] blk.header.extraData
|
2021-10-05 23:31:35 +00:00
|
|
|
payloadRes.baseFeePerGas = blk.header.fee.get(UInt256.zero)
|
2022-03-11 03:48:34 +00:00
|
|
|
payloadRes.blockHash = Web3BlockHash blockHash.data
|
2022-02-22 08:55:04 +00:00
|
|
|
|
|
|
|
for tx in blk.txs:
|
|
|
|
let txData = rlp.encode(tx)
|
|
|
|
payloadRes.transactions.add web3types.TypedTransaction(txData)
|
2021-10-05 23:31:35 +00:00
|
|
|
|
|
|
|
return ok()
|
|
|
|
|
2021-09-07 13:45:01 +00:00
|
|
|
proc new*(_: type SealingEngineRef,
|
2022-12-02 04:35:41 +00:00
|
|
|
chain: ChainRef,
|
2021-09-07 13:45:01 +00:00
|
|
|
ctx: EthContext,
|
2021-10-05 23:31:35 +00:00
|
|
|
signer: EthAddress,
|
2022-01-23 11:39:43 +00:00
|
|
|
txPool: TxPoolRef,
|
2021-10-05 23:31:35 +00:00
|
|
|
initialState: EngineState): SealingEngineRef =
|
2021-08-24 07:34:58 +00:00
|
|
|
SealingEngineRef(
|
2021-09-07 13:45:01 +00:00
|
|
|
chain: chain,
|
|
|
|
ctx: ctx,
|
2021-10-05 23:31:35 +00:00
|
|
|
signer: signer,
|
2022-01-23 11:39:43 +00:00
|
|
|
txPool: txPool,
|
2021-10-05 23:31:35 +00:00
|
|
|
state: initialState
|
2021-08-24 07:34:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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"
|