222 lines
7.4 KiB
Nim
222 lines
7.4 KiB
Nim
# 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.
|
|
|
|
## Block PoW Support (Verifying & Mining)
|
|
## ======================================
|
|
##
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/[options, strutils],
|
|
../utils/utils,
|
|
./pow/pow_cache,
|
|
eth/[common, keys, p2p, rlp],
|
|
stew/endians2,
|
|
ethash,
|
|
stint
|
|
|
|
type
|
|
PowDigest = tuple ##\
|
|
## Return value from the `hashimotoLight()` function
|
|
mixDigest: Hash256
|
|
value: Hash256
|
|
|
|
PowSpecs* = object ##\
|
|
## Relevant block header parts for PoW mining & verifying. This object
|
|
## might be more useful for testing and debugging than for production.
|
|
blockNumber*: BlockNumber
|
|
miningHash*: Hash256
|
|
nonce*: BlockNonce
|
|
mixDigest*: Hash256
|
|
difficulty*: DifficultyInt
|
|
|
|
PowHeader = object ##\
|
|
## Stolen from `p2p/validate.MiningHeader`
|
|
parentHash : Hash256
|
|
ommersHash : Hash256
|
|
coinbase : EthAddress
|
|
stateRoot : Hash256
|
|
txRoot : Hash256
|
|
receiptRoot : Hash256
|
|
bloom : common.BloomFilter
|
|
difficulty : DifficultyInt
|
|
blockNumber : BlockNumber
|
|
gasLimit : GasInt
|
|
gasUsed : GasInt
|
|
timestamp : EthTime
|
|
extraData : Blob
|
|
|
|
PowRef* = ref object of RootObj ##\
|
|
## PoW context descriptor
|
|
lightByEpoch: PowCacheRef ## PoW cache indexed by epoch
|
|
nonceAttempts: uint64 ## Unsuccessful tests in last mining process
|
|
|
|
# You should only create one instance of the RNG per application / library
|
|
# Ref is used so that it can be shared between components
|
|
rng: ref HmacDrbgContext
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions: RLP support
|
|
# ------------------------------------------------------------------------------
|
|
|
|
func append(w: var RlpWriter; specs: PowSpecs) =
|
|
## RLP support
|
|
w.startList(5)
|
|
w.append(HashOrNum(isHash: false, number: specs.blockNumber))
|
|
w.append(HashOrNum(isHash: true, hash: specs.miningHash))
|
|
w.append(specs.nonce.toUint)
|
|
w.append(HashOrNum(isHash: true, hash: specs.mixDigest))
|
|
w.append(specs.difficulty)
|
|
|
|
func read(rlp: var Rlp; Q: type PowSpecs): Q
|
|
{.raises: [RlpError].} =
|
|
## RLP support
|
|
rlp.tryEnterList()
|
|
result.blockNumber = rlp.read(HashOrNum).number
|
|
result.miningHash = rlp.read(HashOrNum).hash
|
|
result.nonce = rlp.read(uint64).toBlockNonce
|
|
result.mixDigest = rlp.read(HashOrNum).hash
|
|
result.difficulty = rlp.read(DifficultyInt)
|
|
|
|
func rlpTextEncode(specs: PowSpecs): string =
|
|
"specs #" & $specs.blockNumber & " " & rlp.encode(specs).toHex
|
|
|
|
func decodeRlpText(data: string): PowSpecs
|
|
{.raises: [CatchableError].} =
|
|
if 180 < data.len and data[0 .. 6] == "specs #":
|
|
let hexData = data.split
|
|
if hexData.len == 3:
|
|
var rlpData = hexData[2].rlpFromHex
|
|
result = rlpData.read(PowSpecs)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
func miningHash(header: BlockHeader): Hash256 =
|
|
## Calculate hash from mining relevant fields of the argument `header`
|
|
let miningHeader = PowHeader(
|
|
parentHash: header.parentHash,
|
|
ommersHash: header.ommersHash,
|
|
coinbase: header.coinbase,
|
|
stateRoot: header.stateRoot,
|
|
txRoot: header.txRoot,
|
|
receiptRoot: header.receiptRoot,
|
|
bloom: header.bloom,
|
|
difficulty: header.difficulty,
|
|
blockNumber: header.blockNumber,
|
|
gasLimit: header.gasLimit,
|
|
gasUsed: header.gasUsed,
|
|
timestamp: header.timestamp,
|
|
extraData: header.extraData)
|
|
|
|
rlp.encode(miningHeader).keccakHash
|
|
|
|
# ---------------
|
|
|
|
proc init(tm: PowRef;
|
|
rng: Option[ref HmacDrbgContext];
|
|
light: Option[PowCacheRef]) =
|
|
## Constructor
|
|
if rng.isSome:
|
|
tm.rng = rng.get
|
|
else:
|
|
tm.rng = newRng()
|
|
|
|
if light.isSome:
|
|
tm.lightByEpoch = light.get
|
|
else:
|
|
tm.lightByEpoch = PowCacheRef.new
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions, Constructor
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc new*(T: type PowRef; cache: PowCacheRef): T =
|
|
## Constructor
|
|
new result
|
|
result.init(none(ref HmacDrbgContext), some(cache))
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
func getPowSpecs*(header: BlockHeader): PowSpecs =
|
|
## Extracts relevant parts from the `header` argument that are needed
|
|
## for mining or pow verification. This function might be more useful for
|
|
## testing and debugging than for production.
|
|
PowSpecs(
|
|
blockNumber: header.blockNumber,
|
|
miningHash: header.miningHash,
|
|
nonce: header.nonce,
|
|
mixDigest: header.mixDigest,
|
|
difficulty: header.difficulty)
|
|
|
|
func getPowCacheLookup*(tm: PowRef;
|
|
blockNumber: BlockNumber): (uint64, Hash256)
|
|
{.gcsafe, raises: [KeyError].} =
|
|
## Returns the pair `(size,digest)` derived from the lookup cache for the
|
|
## `hashimotoLight()` function for the given block number. The `size` is the
|
|
## full size of the dataset (the cache represents) as passed on to the
|
|
## `hashimotoLight()` function. The `digest` is a hash derived from the
|
|
## cache that would be passed on to `hashimotoLight()`.
|
|
##
|
|
## This function is intended for error reporting and might also be useful
|
|
## for testing and debugging.
|
|
let ds = tm.lightByEpoch.get(blockNumber)
|
|
if ds == nil:
|
|
raise newException(KeyError, "block not found")
|
|
|
|
result[0] = ds.size
|
|
result[1] = withKeccakHash:
|
|
for a in ds.data:
|
|
h.update(a.data)
|
|
|
|
# ------------------------
|
|
|
|
func getPowDigest(tm: PowRef; blockNumber: BlockNumber;
|
|
powHeaderDigest: Hash256; nonce: BlockNonce): PowDigest =
|
|
## Calculate the expected value of `header.mixDigest` using the
|
|
## `hashimotoLight()` library method.
|
|
let
|
|
ds = tm.lightByEpoch.get(blockNumber)
|
|
u64Nonce = uint64.fromBytesBE(nonce)
|
|
hashimotoLight(ds.size, ds.data, powHeaderDigest, u64Nonce)
|
|
|
|
func getPowDigest*(tm: PowRef; header: BlockHeader): PowDigest =
|
|
## Variant of `getPowDigest()`
|
|
tm.getPowDigest(header.blockNumber, header.miningHash, header.nonce)
|
|
|
|
func getPowDigest*(tm: PowRef; specs: PowSpecs): PowDigest =
|
|
## Variant of `getPowDigest()`
|
|
tm.getPowDigest(specs.blockNumber, specs.miningHash, specs.nonce)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions, debugging & testing
|
|
# ------------------------------------------------------------------------------
|
|
|
|
func dumpPowSpecs*(specs: PowSpecs): string =
|
|
## Text representation of `PowSpecs` argument object
|
|
specs.rlpTextEncode
|
|
|
|
func dumpPowSpecs*(header: BlockHeader): string =
|
|
## Variant of `dumpPowSpecs()`
|
|
header.getPowSpecs.dumpPowSpecs
|
|
|
|
func undumpPowSpecs*(data: string): PowSpecs
|
|
{.raises: [CatchableError].} =
|
|
## Recover `PowSpecs` object from text representation
|
|
data.decodeRlpText
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|