Jordan/custom network (#962)

* Rearrange/rename test_kintsugu => test_custom_network

why:
  Debug, fix and test more general problems related to running
  nimbus on a custom network.

* Update UInt265/Json parser for --custom-network command line option

why:
  As found out with the Kintsugi configuration, block number and balance
  have the same Nim type which led to misunderstandings. This patch makes
  sure that UInt265 encoded string values "0x11" decodes to 17, and "b"
  and "11" to 11.

* Refactored genesis.toBlock() => genesis.toBlockHeader()

why:
  The function toBlock(g,db) may return different results depending on
  whether the db descriptor argument is nil, or initialised. This is due
  to the db.config data sub-descriptor which may give various outcomes
  for the baseFee field of the genesis header.

  Also, the version where db is non-nil initialised is used internally
  only. So the public rewrite toBlockHeader() that replaces the toBlock()
  function expects a full set of NetworkParams.

* update comments

* Rename toBlockHeader() => toGenesisHeader()

why:
  Polymorphic prototype used for BaseChainDB or NetworkParams argument.
  With a BaseChainDB descriptor argument, the name shall imply that the
  header is generated from the config fields rather than fetched from
  the database.

* Added command line option --static-peers-file

why:
  Handy feature to keep peer nodes in a file, similar to the
  --bootstrap-file option.
This commit is contained in:
Jordan Hrycaj 2022-02-11 16:28:39 +00:00 committed by GitHub
parent 0060462dc0
commit 215e9856d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 295 additions and 113 deletions

View File

@ -8,7 +8,7 @@
# those terms.
import
std/[tables, strutils, options, times],
std/[tables, strutils,sequtils, options, times],
eth/[common, rlp, p2p], stint, stew/[byteutils],
nimcrypto/hash,
json_serialization, chronicles,
@ -139,16 +139,26 @@ func decodePrealloc*(data: seq[byte]): GenesisAlloc =
for tup in rlp.decode(data, seq[AddressBalance]):
result[tup.address] = tup.account
proc readValue(reader: var JsonReader, value: var BlockNumber) =
proc readValue(reader: var JsonReader, value: var UInt256) =
## Mixin for `Json.loadFile()`. Note that this driver applies the same
## to `BlockNumber` fields as well as generic `UInt265` fields like the
## account `balance`.
let tok = reader.lexer.tok
if tok == tkInt:
value = toBlockNumber(reader.lexer.absintVal)
value = reader.lexer.absintVal.u256
reader.lexer.next()
elif tok == tkString:
value = UInt256.fromHex(reader.lexer.strVal)
# Make sure that "0x11" decodes to 17, "b" and "11" decode to 11.
if reader.lexer.strVal.filterIt(it.isDigit.not).len == 0:
try: value = reader.lexer.strVal.parse(UInt256, radix = 10)
except: reader.raiseUnexpectedValue("int string overflow")
else:
# note that radix is static, so 16 (or 10) cannot be a variable
try: value = reader.lexer.strVal.parse(UInt256, radix = 16)
except: reader.raiseUnexpectedValue("hex string parse error")
reader.lexer.next()
else:
reader.raiseUnexpectedValue("expect int or hex string")
reader.raiseUnexpectedValue("expect int or hex/int string")
proc readValue(reader: var JsonReader, value: var ChainId) =
value = reader.readValue(int).ChainId

View File

@ -268,6 +268,13 @@ type
defaultValueDesc: ""
name: "static-peers" }: seq[string]
staticPeersFile {.
desc: "Specifies a line-delimited file of trusted peers addresses(enode URL)" &
"to be added to the --staticPeers list. If the first line equals to the word `override`, "&
"the file contents will replace the --staticPeers list"
defaultValue: ""
name: "static-peers-file" }: InputFile
listenAddress* {.
desc: "Listening IP address for Ethereum P2P and Discovery traffic"
defaultValue: defaultListenAddress
@ -529,7 +536,7 @@ iterator strippedLines(filename: string): (int, string) =
yield (i, stripped)
inc i
proc loadBootstrapFile(fileName: string, output: var seq[Enode]) =
proc loadEnodeFile(fileName: string; output: var seq[Enode]; info: string) =
if fileName.len == 0:
return
@ -542,15 +549,21 @@ proc loadBootstrapFile(fileName: string, output: var seq[Enode]) =
let res = ENode.fromString(ln)
if res.isErr:
warn "Ignoring invalid bootstrap address", address=ln, line=i, file=fileName
warn "Ignoring invalid address", address=ln, line=i, file=fileName, purpose=info
continue
output.add res.get()
except IOError as e:
error "Could not read bootstrap file", msg = e.msg
error "Could not read file", msg = e.msg, purpose = info
quit 1
proc loadBootstrapFile(fileName: string, output: var seq[Enode]) =
fileName.loadEnodeFile(output, "bootstrap")
proc loadStaticPeersFile(fileName: string, output: var seq[Enode]) =
fileName.loadEnodeFile(output, "static peers")
proc getNetworkId(conf: NimbusConf): Option[NetworkId] =
if conf.network.len == 0:
return none NetworkId
@ -628,6 +641,7 @@ proc getBootNodes*(conf: NimbusConf): seq[Enode] =
proc getStaticPeers*(conf: NimbusConf): seq[Enode] =
result.append(conf.staticPeers)
loadStaticPeersFile(string conf.staticPeersFile, result)
proc makeConfig*(cmdLine = commandLineParams()): NimbusConf =
{.push warning[ProveInit]: off.}

View File

@ -5,18 +5,27 @@ import
./db/[db_chain, state_db],
"."/[constants, chain_config, forks, p2p/gaslimit]
proc toBlock*(g: Genesis, db: BaseChainDB = nil):
BlockHeader {.raises: [Defect, RlpError].} =
let (tdb, pruneTrie) = if db.isNil: (newMemoryDB(), true)
else: (db.db, db.pruneTrie)
{.push raises: [Defect].}
# For `eth/trie/db.newMemoryDB()`, the following initialiation is part of
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc initDbAccounts(db: BaseChainDB): BlockHeader
{.raises: [Defect, RlpError].} =
## Initialise block chain DB accounts derived from the `genesis.alloc` table
## of the `db` descriptor argument.
##
## The function returns the `Genesis` block header.
##
# For `eth/trie/db.newMemoryDB()`, the following initialisation is part of
# the constructor function which is missing for the permanent constructor
# function `eth/trie/db.trieDB()`.
if not db.isNil:
tdb.put(emptyRlpHash.data, emptyRlp)
db.db.put(emptyRlpHash.data, emptyRlp)
var sdb = newAccountStateDB(tdb, emptyRlpHash, pruneTrie)
var sdb = newAccountStateDB(db.db, emptyRlpHash, db.pruneTrie)
let g = db.genesis
for address, account in g.alloc:
sdb.setAccount(address, newAccount(account.nonce, account.balance))
@ -45,8 +54,8 @@ proc toBlock*(g: Genesis, db: BaseChainDB = nil):
#
# This kludge also fixes the initial crash described in
# https://github.com/status-im/nimbus-eth1/issues/932.
if not db.isNil and db.pruneTrie:
tdb.put(emptyRlpHash.data, emptyRlp) # <-- kludge
if not db.isNil and db.pruneTrie and 0 < account.storage.len:
db.db.put(emptyRlpHash.data, emptyRlp) # <-- kludge
for k, v in account.storage:
sdb.setStorage(address, k, v)
@ -68,7 +77,7 @@ proc toBlock*(g: Genesis, db: BaseChainDB = nil):
if g.baseFeePerGas.isSome:
result.baseFee = g.baseFeePerGas.get()
elif db.isNil.not and db.config.toFork(0.toBlockNumber) >= FkLondon:
elif db.config.toFork(0.toBlockNumber) >= FkLondon:
result.baseFee = EIP1559_INITIAL_BASE_FEE.u256
if g.gasLimit.isZero:
@ -77,9 +86,34 @@ proc toBlock*(g: Genesis, db: BaseChainDB = nil):
if g.difficulty.isZero:
result.difficulty = GENESIS_DIFFICULTY
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc initializeEmptyDb*(db: BaseChainDB) =
proc toGenesisHeader*(params: NetworkParams): BlockHeader
{.raises: [Defect, RlpError].} =
## Generate the genesis block header from the `params` argument value.
newBaseChainDB(
newMemoryDb(),
id = params.config.chainID.NetworkId,
params = params,
pruneTrie = true).initDbAccounts
proc toGenesisHeader*(db: BaseChainDB): BlockHeader
{.raises: [Defect, RlpError].} =
## Generate the genesis block header from the `genesis` and `config`
## fields of the argument `db` descriptor.
NetworkParams(
config: db.config,
genesis: db.genesis).toGenesisHeader
proc initializeEmptyDb*(db: BaseChainDB)
{.raises: [Defect, CatchableError].} =
trace "Writing genesis to DB"
let b = db.genesis.toBlock(db)
let b = db.initDbAccounts
doAssert(b.blockNumber.isZero, "can't commit genesis block with number > 0")
discard db.persistHeaderToDb(b)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -132,7 +132,7 @@ func calculateForkIds(c: ChainConfig,
proc setForkId(c: Chain)
{. raises: [Defect,CatchableError].} =
c.blockZeroHash = toBlock(c.db.genesis).blockHash
c.blockZeroHash = c.db.toGenesisHeader.blockHash
let genesisCRC = crc32(0, c.blockZeroHash.data)
c.forkIds = calculateForkIds(c.db.config, genesisCRC)

View File

@ -12,7 +12,7 @@ import ../test_macro
cliBuilder:
import ./test_code_stream,
./test_accounts_cache,
./test_kintsugi,
./test_custom_network,
./test_gas_meter,
./test_memory,
./test_stack,

130
tests/replay/pp.nim Normal file
View File

@ -0,0 +1,130 @@
# Nimbus
# Copyright (c) 2018-2019 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.
## Pretty printing, an alternative to `$` for debugging
## ----------------------------------------------------
import
std/[sequtils, strformat, strutils, tables, times],
../../nimbus/[chain_config, constants],
eth/[common, trie/trie_defs]
# ------------------------------------------------------------------------------
# Public functions, units pretty printer
# ------------------------------------------------------------------------------
proc ppMs*(elapsed: Duration): string =
result = $elapsed.inMilliSeconds
let ns = elapsed.inNanoSeconds mod 1_000_000
if ns != 0:
# to rounded deca milli seconds
let dm = (ns + 5_000i64) div 10_000i64
result &= &".{dm:02}"
result &= "ms"
proc ppSecs*(elapsed: Duration): string =
result = $elapsed.inSeconds
let ns = elapsed.inNanoseconds mod 1_000_000_000
if ns != 0:
# to rounded decs seconds
let ds = (ns + 5_000_000i64) div 10_000_000i64
result &= &".{ds:02}"
result &= "s"
proc toKMG*[T](s: T): string =
proc subst(s: var string; tag, new: string): bool =
if tag.len < s.len and s[s.len - tag.len ..< s.len] == tag:
s = s[0 ..< s.len - tag.len] & new
return true
result = $s
for w in [("000", "K"),("000K","M"),("000M","G"),("000G","T"),
("000T","P"),("000P","E"),("000E","Z"),("000Z","Y")]:
if not result.subst(w[0],w[1]):
return
# ------------------------------------------------------------------------------
# Public functions, pretty printer
# ------------------------------------------------------------------------------
proc pp*(s: string; hex = false): string =
if hex:
let n = (s.len + 1) div 2
(if s.len < 20: s else: s[0 .. 5] & ".." & s[s.len-8 .. s.len-1]) &
"[" & (if 0 < n: "#" & $n else: "") & "]"
elif s.len <= 30:
s
else:
(if (s.len and 1) == 0: s[0 ..< 8] else: "0" & s[0 ..< 7]) &
"..(" & $s.len & ").." & s[s.len-16 ..< s.len]
proc pp*(b: Blob): string =
b.mapIt(it.toHex(2)).join.toLowerAscii.pp(hex = true)
proc pp*(a: Hash256; collapse = true): string =
if not collapse:
a.data.mapIt(it.toHex(2)).join.toLowerAscii
elif a == emptyRlpHash:
"emptyRlpHash"
elif a == EMPTY_UNCLE_HASH:
"EMPTY_UNCLE_HASH"
elif a == EMPTY_SHA3:
"EMPTY_SHA3"
else:
a.data.mapIt(it.toHex(2)).join[56 .. 63].toLowerAscii
proc pp*(a: EthAddress): string =
a.mapIt(it.toHex(2)).join[32 .. 39].toLowerAscii
proc pp*(a: BlockNonce): string =
a.mapIt(it.toHex(2)).join.toLowerAscii
proc pp*(h: BlockHeader; sep = " "): string =
"" &
&"hash={h.blockHash.pp}{sep}" &
&"blockNumber={h.blockNumber}{sep}" &
&"parentHash={h.parentHash.pp}{sep}" &
&"coinbase={h.coinbase.pp}{sep}" &
&"gasLimit={h.gasLimit}{sep}" &
&"gasUsed={h.gasUsed}{sep}" &
&"timestamp={h.timestamp.toUnix}{sep}" &
&"extraData={h.extraData.pp}{sep}" &
&"difficulty={h.difficulty}{sep}" &
&"mixDigest={h.mixDigest.pp}{sep}" &
&"nonce={h.nonce.pp}{sep}" &
&"ommersHash={h.ommersHash.pp}{sep}" &
&"txRoot={h.txRoot.pp}{sep}" &
&"receiptRoot={h.receiptRoot.pp}{sep}" &
&"stateRoot={h.stateRoot.pp}{sep}" &
&"baseFee={h.baseFee}"
proc pp*(g: Genesis; sep = " "): string =
"" &
&"nonce={g.nonce.pp}{sep}" &
&"timestamp={g.timestamp.toUnix}{sep}" &
&"extraData={g.extraData.pp}{sep}" &
&"gasLimit={g.gasLimit}{sep}" &
&"difficulty={g.difficulty}{sep}" &
&"mixHash={g.mixHash.pp}{sep}" &
&"coinbase={g.coinbase.pp}{sep}" &
&"alloc=<{g.alloc.len} accounts>{sep}" &
&"number={g.number}{sep}" &
&"gasUser={g.gasUser}{sep}" &
&"parentHash={g.parentHash.pp}{sep}" &
&"baseFeePerGas={g.baseFeePerGas}"
proc pp*(h: BlockHeader; indent: int): string =
h.pp("\n" & " ".repeat(max(1,indent)))
proc pp*(g: Genesis; indent: int): string =
g.pp("\n" & " ".repeat(max(1,indent)))
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -64,7 +64,7 @@ proc dumpGroupBeginNl*(db: BaseChainDB;
proc dumpGroupNl*(db: BaseChainDB; headers: openArray[BlockHeader];
bodies: openArray[BlockBody]): string =
## Add this below the line `transaction.commit()` in the function
## `p2p/chain.persist_blocks.persistBlocksImpl()`:
## `p2p/chain/persist_blocks.persistBlocksImpl()`:
## ::
## dumpStream.write c.db.dumpGroupNl(headers,bodies)
##
@ -72,7 +72,8 @@ proc dumpGroupNl*(db: BaseChainDB; headers: openArray[BlockHeader];
## that could be initialised with
## ::
## var dumpStream: File
## dumpStream.open("./dump-stream.out", fmWrite)
## if dumpStream.isNil:
## doAssert dumpStream.open("./dump-stream.out", fmWrite)
##
db.dumpGroupBeginNl(headers) &
toSeq(countup(0, headers.len-1))

View File

@ -8,19 +8,33 @@
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
## This unit test was roughly inspired by repeated failings of running nimbus
## similar to
## ::
## nimbus \
## --data-dir:./kintsugi/tmp \
## --custom-network:kintsugi-network.json \
## --bootstrap-file:kintsugi-bootnodes.txt \
## --prune-mode:full ...
##
## from `issue 932` <https://github.com/status-im/nimbus-eth1/issues/932>`_.
import
std/[distros, os, strformat, strutils],
std/[distros, os, strformat, strutils, sequtils],
../nimbus/[chain_config, config, genesis],
../nimbus/db/[db_chain, select_backend],
./replay/pp,
eth/[common, p2p, trie/db],
nimcrypto/hash,
unittest2
const
baseDir = [".", "tests", ".." / "tests", $DirSep] # path containg repo
repoDir = ["status", "replay"] # alternative repo paths
jFile = "nimbus_kintsugi.json"
repoDir = ["customgenesis", "."] # alternative repo paths
jFile = "kintsugi.json"
when defined(windows):
when not defined(linux):
const isUbuntu32bit = false
else:
# The `detectOs(Ubuntu)` directive is not Windows compatible, causes an
@ -62,9 +76,14 @@ proc flushDbDir(s: string) =
# Typically under Windows: there might be stale file locks.
try: dataDir.removeDir except: discard
# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------
proc say*(noisy = false; pfx = "***"; args: varargs[string, `$`]) =
if noisy:
if args.len == 0:
echo "*** ", pfx
elif 0 < pfx.len and pfx[^1] != ' ':
echo pfx, " ", args.toSeq.join
else:
echo pfx, args.toSeq.join
# ------------------------------------------------------------------------------
# Test Runner
@ -81,15 +100,16 @@ proc runner(noisy = true; file = jFile) =
defer:
if not disablePersistentDB: tmpDir.flushDbDir
suite &"Kintsugi test scenario":
suite "Kintsugi custom network test scenario":
var
params: NetworkParams
mdb, ddb: BaseChainDB
test &"Load params from {fileInfo}":
noisy.say "***", "custom-file=", filePath
check filePath.loadNetworkParams(params)
test &"Construct in-memory BaseChainDB":
test "Construct in-memory BaseChainDB":
mdb = newBaseChainDB(
newMemoryDb(),
id = params.config.chainID.NetworkId,
@ -105,49 +125,47 @@ proc runner(noisy = true; file = jFile) =
# previous clean up.
tmpDir.flushDbDir
# The effect of this constructor is roughly equivalent to the command
# line invocation of nimbus as
#
# nimbus \
# --data-dir:$tmpDir \
# --custom-network:$filePath \
# --prune-mode:full ...
#
# as described in https://github.com/status-im/nimbus-eth1/issues/932.
# Constructor ...
ddb = newBaseChainDB(
tmpDir.newChainDb.trieDB,
id = params.config.chainID.NetworkId,
pruneTrie = true,
params = params)
test "Initialise in-memory Genesis":
mdb.initializeEmptyDb
#[
test "Initialise persistent Genesis, expect AssertionError":
if disablePersistentDB:
skip()
else:
expect AssertionError:
ddb.initializeEmptyDb
#]#
# Verify variant of `toBlockHeader()`. The function `pp()` is used
# (rather than blockHash()) for readable error report (if any).
let
storedhHeaderPP = mdb.getBlockHeader(0.u256).pp
onTheFlyHeaderPP = mdb.toGenesisHeader.pp
check storedhHeaderPP == onTheFlyHeaderPP
test "Initialise persistent Genesis (kludge)":
test "Initialise persistent Genesis":
if disablePersistentDB:
skip()
else:
ddb.initializeEmptyDb
# Must be the same as the in-memory DB value
check ddb.getBlockHash(0.u256) == mdb.getBlockHash(0.u256)
let
storedhHeaderPP = ddb.getBlockHeader(0.u256).pp
onTheFlyHeaderPP = ddb.toGenesisHeader.pp
check storedhHeaderPP == onTheFlyHeaderPP
# ------------------------------------------------------------------------------
# Main function(s)
# ------------------------------------------------------------------------------
proc kintsugiMain*(noisy = defined(debug)) =
proc customNetworkMain*(noisy = defined(debug)) =
noisy.runner
when isMainModule:
var noisy = defined(debug)
#noisy = true
noisy = true
noisy.runner
# ------------------------------------------------------------------------------

View File

@ -3,45 +3,51 @@ import
unittest2, eth/common, nimcrypto/hash,
../nimbus/[genesis, config, chain_config]
const dataFolder = "tests" / "customgenesis"
const
baseDir = [".", "tests", ".." / "tests", $DirSep] # path containg repo
repoDir = ["customgenesis", "status"] # alternative repo paths
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 genesisTest() =
suite "Genesis":
test "Correct mainnet hash":
let g = networkParams(MainNet).genesis
let b = g.toBlock
let b = networkParams(MainNet).toGenesisHeader
check(b.blockHash == "D4E56740F876AEF8C010B86A40D5F56745A118D0906A34E69AEC8C0DB1CB8FA3".toDigest)
test "Correct ropstennet hash":
let g = networkParams(RopstenNet).genesis
let b = g.toBlock
let b = networkParams(RopstenNet).toGenesisHeader
check(b.blockHash == "41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d".toDigest)
test "Correct rinkebynet hash":
let g = networkParams(RinkebyNet).genesis
let b = g.toBlock
let b = networkParams(RinkebyNet).toGenesisHeader
check(b.blockHash == "6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177".toDigest)
test "Correct goerlinet hash":
let g = networkParams(GoerliNet).genesis
let b = g.toBlock
let b = networkParams(GoerliNet).toGenesisHeader
check(b.blockHash == "bf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a".toDigest)
proc customGenesisTest() =
suite "Custom Genesis":
test "loadCustomGenesis":
var cga, cgb, cgc: NetworkParams
check loadNetworkParams(dataFolder / "berlin2000.json", cga)
check loadNetworkParams(dataFolder / "chainid7.json", cgb)
check loadNetworkParams(dataFolder / "noconfig.json", cgc)
check loadNetworkParams("berlin2000.json".findFilePath, cga)
check loadNetworkParams("chainid7.json".findFilePath, cgb)
check loadNetworkParams("noconfig.json".findFilePath, cgc)
check cga.config.poaEngine == false
check cgb.config.poaEngine == false
check cgc.config.poaEngine == false
test "calaveras.json":
var cg: NetworkParams
check loadNetworkParams(dataFolder / "calaveras.json", cg)
let h = toBlock(cg.genesis, nil)
check loadNetworkParams("calaveras.json".findFilePath, cg)
let h = cg.toGenesisHeader
let stateRoot = "664c93de37eb4a72953ea42b8c046cdb64c9f0b0bca5505ade8d970d49ebdb8c".toDigest
let genesisHash = "eb9233d066c275efcdfed8037f4fc082770176aefdbcb7691c71da412a5670f2".toDigest
check h.stateRoot == stateRoot
@ -50,6 +56,16 @@ proc customGenesisTest() =
check cg.config.cliquePeriod == 30
check cg.config.cliqueEpoch == 30000
test "kintsugi.json":
var cg: NetworkParams
check loadNetworkParams("kintsugi.json".findFilePath, cg)
let h = cg.toGenesisHeader
let stateRoot = "3b84f313bfd49c03cc94729ade2e0de220688f813c0c895a99bd46ecc9f45e1e".toDigest
let genesisHash = "a28d8d73e087a01d09d8cb806f60863652f30b6b6dfa4e0157501ff07d422399".toDigest
check h.stateRoot == stateRoot
check h.blockHash == genesisHash
check cg.config.poaEngine == false
proc genesisMain*() =
genesisTest()
customGenesisTest()

View File

@ -12,7 +12,7 @@ import
std/[strformat, sequtils, strutils, times],
../../nimbus/utils/tx_pool/[tx_chain, tx_desc, tx_gauge, tx_item, tx_tabs],
../../nimbus/utils/tx_pool/tx_tasks/[tx_packer, tx_recover],
../replay/undump,
../replay/[pp, undump],
eth/[common, keys],
stew/[keyed_queue, sorted_set],
stint
@ -20,6 +20,7 @@ import
# Make sure that the runner can stay on public view without the need
# to import `tx_pool/*` sup-modules
export
pp,
tx_chain.TxChainGasLimits,
tx_chain.`maxMode=`,
tx_chain.clearAccounts,
@ -102,52 +103,10 @@ proc toXX(h: Hash256): string =
proc toXX(v: int64; r,s: UInt256): string =
v.toXX & ":" & ($r).joinXX & ":" & ($s).joinXX
# ------------------------------------------------------------------------------
# Public functions, units pretty printer
# ------------------------------------------------------------------------------
proc ppMs*(elapsed: Duration): string =
result = $elapsed.inMilliSeconds
let ns = elapsed.inNanoSeconds mod 1_000_000
if ns != 0:
# to rounded deca milli seconds
let dm = (ns + 5_000i64) div 10_000i64
result &= &".{dm:02}"
result &= "ms"
proc ppSecs*(elapsed: Duration): string =
result = $elapsed.inSeconds
let ns = elapsed.inNanoseconds mod 1_000_000_000
if ns != 0:
# to rounded decs seconds
let ds = (ns + 5_000_000i64) div 10_000_000i64
result &= &".{ds:02}"
result &= "s"
proc toKMG*[T](s: T): string =
proc subst(s: var string; tag, new: string): bool =
if tag.len < s.len and s[s.len - tag.len ..< s.len] == tag:
s = s[0 ..< s.len - tag.len] & new
return true
result = $s
for w in [("000", "K"),("000K","M"),("000M","G"),("000G","T"),
("000T","P"),("000P","E"),("000E","Z"),("000Z","Y")]:
if not result.subst(w[0],w[1]):
return
# ------------------------------------------------------------------------------
# Public functions, pretty printer
# ------------------------------------------------------------------------------
proc pp*(a: BlockNonce): string =
a.mapIt(it.toHex(2)).join.toLowerAscii
proc pp*(a: EthAddress): string =
a.mapIt(it.toHex(2)).join[32 .. 39].toLowerAscii
proc pp*(a: Hash256): string =
a.data.mapIt(it.toHex(2)).join[56 .. 63].toLowerAscii
proc pp*(q: seq[(EthAddress,int)]): string =
"[" & q.mapIt(&"{it[0].pp}:{it[1]:03d}").join(",") & "]"