diff --git a/nimbus/constants.nim b/nimbus/constants.nim index 88e2db9d2..b7600be23 100644 --- a/nimbus/constants.nim +++ b/nimbus/constants.nim @@ -69,3 +69,11 @@ const # EIP MaxPrecompilesAddr* = 0xFFFF + + EXTRA_SEAL* = ##\ + ## Fixed number of suffix bytes reserved for signer seal of the `extraData` + ## header field. The 65 bytes constant value is for signatures based on the + ## standard secp256k1 curve. + 65 + +# End diff --git a/nimbus/p2p/clique/clique_cfg.nim b/nimbus/p2p/clique/clique_cfg.nim index e7670ac71..ada51e98a 100644 --- a/nimbus/p2p/clique/clique_cfg.nim +++ b/nimbus/p2p/clique/clique_cfg.nim @@ -21,7 +21,7 @@ import std/[random, sequtils, strutils, times], ../../db/db_chain, - ./clique_cfg/ec_recover, + ../../utils/ec_recover, ./clique_defs, eth/common, ethash, @@ -117,7 +117,7 @@ proc newCliqueCfg*(db: BaseChainDB): CliqueCfg = # clique/clique.go(145): func ecrecover(header [..] proc ecRecover*(cfg: CliqueCfg; header: BlockHeader): auto {.gcsafe, raises: [Defect,CatchableError].} = - cfg.signatures.getEcRecover(header) + cfg.signatures.ecRecover(header) # ------------------------------------------------------------------------------ # Public setters diff --git a/nimbus/p2p/clique/clique_cfg/ec_recover.nim b/nimbus/p2p/clique/clique_cfg/ec_recover.nim deleted file mode 100644 index 3221fdf94..000000000 --- a/nimbus/p2p/clique/clique_cfg/ec_recover.nim +++ /dev/null @@ -1,103 +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. - -## -## Address Cache for Clique PoA Consensus Protocol -## =============================================== -## -## For details see -## `EIP-225 `_ -## and -## `go-ethereum `_ -## - -import - ../../../utils/lru_cache, - ../clique_defs, - ../clique_helpers, - eth/[common, keys, rlp], - stint - -type - # simplify Hash256 for rlp serialisation - EcKey32 = array[32, byte] - - EcRecover* = LruCache[BlockHeader,EcKey32,EthAddress,CliqueError] - -{.push raises: [Defect].} - -# ------------------------------------------------------------------------------ -# Public functions -# ------------------------------------------------------------------------------ - -proc initEcRecover*(cache: var EcRecover) = - - var toKey: LruKey[BlockHeader,EcKey32] = - - # Use the seal hash for cache lookup - proc(header:BlockHeader): EcKey32 = - ## If the signature's already cached, return that - # clique/clique.go(148): hash := header.Hash() - header.blockHash.data - - var toValue: LruValue[BlockHeader,EthAddress,CliqueError] = - - # Retrieve signature from the header's extra data fields - proc(header: BlockHeader): Result[EthAddress,CliqueError] = - - # Extract signature from extra data field (last 65 bytes) - let msg = header.extraData - - # clique/clique.go(153): if len(header.Extra) < extraSeal { - if msg.len < EXTRA_SEAL: - return err((errMissingSignature,"")) - let sig = Signature.fromRaw( - msg.toOpenArray(msg.len - EXTRA_SEAL, msg.high)) - if sig.isErr: - return err((errSkSigResult,$sig.error)) - - # Recover the public key from signature and seal hash - # clique/clique.go(159): pubkey, err := crypto.Ecrecover( [..] - let pubKey = recover(sig.value, SKMessage(header.hashSealHeader.data)) - if pubKey.isErr: - return err((errSkPubKeyResult,$pubKey.error)) - - # Convert public key to address. - return ok(pubKey.value.toCanonicalAddress) - - cache.initCache(toKey, toValue, INMEMORY_SIGNATURES) - -proc initEcRecover*: EcRecover {.gcsafe, raises: [Defect].} = - result.initEcRecover - - -# clique/clique.go(145): func ecrecover(header [..] -proc getEcRecover*(addrCache: var EcRecover; header: BlockHeader): auto {. - gcsafe, raises: [Defect,CatchableError].} = - ## extract Ethereum account address from a signed header block, the relevant - ## signature used is appended to the re-purposed extra data field - addrCache.getItem(header) - - -proc append*(rw: var RlpWriter; ecRec: EcRecover) {. - inline, raises: [Defect,KeyError].} = - ## Generic support for `rlp.encode(ecRec)` - rw.append(ecRec.data) - -proc read*(rlp: var Rlp; Q: type EcRecover): Q {. - inline, raises: [Defect,KeyError].} = - ## Generic support for `rlp.decode(bytes)` for loading the cache from a - ## serialised data stream. - result.initEcRecover - result.data = rlp.read(type result.data) - -# ------------------------------------------------------------------------------ -# End -# ------------------------------------------------------------------------------ diff --git a/nimbus/p2p/clique/clique_defs.nim b/nimbus/p2p/clique/clique_defs.nim index 37931eeda..a5ac695f9 100644 --- a/nimbus/p2p/clique/clique_defs.nim +++ b/nimbus/p2p/clique/clique_defs.nim @@ -40,10 +40,6 @@ const ## Number of recent vote snapshots to keep in memory. 128 - INMEMORY_SIGNATURES* = ##\ - ## Number of recent block signatures to keep in memory - 4096 - WIGGLE_TIME* = ##\ ## PoA mining only (currently unsupported). ## @@ -62,11 +58,6 @@ const ## Suggested 32 bytes to retain the current extra-data allowance and/or use. 32 - EXTRA_SEAL* = ##\ - ## Fixed number of extra-data suffix bytes reserved for signer seal. - ## 65 bytes fixed as signatures are based on the standard secp256k1 curve. - 65 - NONCE_AUTH* = ##\ ## Magic nonce number 0xffffffffffffffff to vote on adding a new signer. 0xffffffffffffffffu64.toBlockNonce @@ -208,21 +199,11 @@ type ## unknown. "unknown ancestor" - # errPrunedAncestor = ##\ - # ## is returned when validating a block requires an ancestor that is - # ## known, but the state of which is not available. - # "pruned ancestor" - errFutureBlock = ##\ ## is returned when a block's timestamp is in the future according to ## the current node. "block in the future" - # errInvalidNumber = ##\ - # ## is returned if a block's number doesn't equal its parent's plus one. - # "invalid block number" - - # additional/bespoke errors, manually added # ----------------------------------------- @@ -237,8 +218,9 @@ type ## Attempt to assign a value to a non-existing slot "Missing LRU slot for snapshot" - errSkSigResult ## eth/keys subsytem error: signature - errSkPubKeyResult ## eth/keys subsytem error: public key + errEcRecover = ##\ + ## Subsytem error" + "ecRecover failed" errSnapshotLoad ## DB subsytem error errSnapshotStore ## .. diff --git a/nimbus/p2p/clique/clique_verify.nim b/nimbus/p2p/clique/clique_verify.nim index 2bb37a2f4..dadd428e7 100644 --- a/nimbus/p2p/clique/clique_verify.nim +++ b/nimbus/p2p/clique/clique_verify.nim @@ -133,7 +133,7 @@ proc verifySeal(c: Clique; header: BlockHeader): CliqueOkResult # Resolve the authorization key and check against signers let signer = c.cfg.ecRecover(header) if signer.isErr: - return err(signer.error) + return err((errEcRecover,$signer.error)) if not snapshot.isSigner(signer.value): return err((errUnauthorizedSigner,"")) diff --git a/nimbus/p2p/clique/snapshot/snapshot_apply.nim b/nimbus/p2p/clique/snapshot/snapshot_apply.nim index 9cac7621d..01712d83d 100644 --- a/nimbus/p2p/clique/snapshot/snapshot_apply.nim +++ b/nimbus/p2p/clique/snapshot/snapshot_apply.nim @@ -141,23 +141,25 @@ proc snapshotApplySeq*(s: Snapshot; headers: var seq[BlockHeader], s.recents.del(number - limit) # Resolve the authorization key and check against signers - let signer = ? s.cfg.ecRecover(header) + let signer = s.cfg.ecRecover(header) + if signer.isErr: + return err((errEcRecover,$signer.error)) #s.say "applySnapshot signer=", s.pp(signer) - if not s.ballot.isAuthSigner(signer): + if not s.ballot.isAuthSigner(signer.value): s.say "applySnapshot signer not authorised => fail ", s.pp(29) return err((errUnauthorizedSigner,"")) for recent in s.recents.values: - if recent == signer: - s.say "applySnapshot signer recently seen ", s.pp(signer) + if recent == signer.value: + s.say "applySnapshot signer recently seen ", s.pp(signer.value) echo "+++ applySnapshot #", header.blockNumber, " err=errRecentlySigned" return err((errRecentlySigned,"")) - s.recents[number] = signer + 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, address = header.coinbase) + s.ballot.delVote(signer = signer.value, address = header.coinbase) # Tally up the new vote from the signer # clique/snapshot.go(244): var authorize bool @@ -167,7 +169,7 @@ proc snapshotApplySeq*(s: Snapshot; headers: var seq[BlockHeader], elif header.nonce != NONCE_DROP: return err((errInvalidVote,"")) let vote = Vote(address: header.coinbase, - signer: signer, + signer: signer.value, blockNumber: number, authorize: authOk) #s.say "applySnapshot calling addVote ", s.pp(vote) diff --git a/nimbus/utils/ec_recover.nim b/nimbus/utils/ec_recover.nim new file mode 100644 index 000000000..7e7e09660 --- /dev/null +++ b/nimbus/utils/ec_recover.nim @@ -0,0 +1,137 @@ +# 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. + +## +## Recover Address From Signature +## ============================== +## +## This module provides caching and direct versions for recovering the +## `EthAddress` from an extended signature. The caching version reduces +## calculation time for the price of maintaing it in a LRU cache. + +import + ./utils_defs, + ./lru_cache, + ../constants, + eth/[common, keys, rlp], + nimcrypto, + stew/results, + stint + +const + INMEMORY_SIGNATURES* = ##\ + ## Number of recent block signatures to keep in memory + 4096 + +type + # simplify Hash256 for rlp serialisation + EcKey32 = array[32, byte] + + EcRecover* = LruCache[BlockHeader,EcKey32,EthAddress,UtilsError] + +{.push raises: [Defect].} + +# ------------------------------------------------------------------------------ +# Private helpers +# ------------------------------------------------------------------------------ + +proc encodePreSealed(header: BlockHeader): seq[byte] {.inline.} = + ## 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) + + +proc hashPreSealed(header: BlockHeader): Hash256 {.inline.} = + ## Returns the hash of a block prior to it being sealed. + keccak256.digest header.encodePreSealed + + +proc ecRecover*(extraData: openArray[byte]; + hash: Hash256): Result[EthAddress,UtilsError] {.inline.} = + ## Extract account address from the last 65 bytes of the `extraData` argument + ## (which is typically the bock header field with the same name.) The second + ## argument `hash` is used to extract the intermediate public key. Typically, + ## this would be the hash of the block header without the last 65 bytes of + ## the `extraData` field reserved for the signature. + if extraData.len < EXTRA_SEAL: + return err((errMissingSignature,"")) + + let sig = Signature.fromRaw( + extraData.toOpenArray(extraData.len - EXTRA_SEAL, extraData.high)) + if sig.isErr: + return err((errSkSigResult,$sig.error)) + + # Recover the public key from signature and seal hash + let pubKey = recover(sig.value, SKMessage(hash.data)) + if pubKey.isErr: + return err((errSkPubKeyResult,$pubKey.error)) + + # Convert public key to address. + return ok(pubKey.value.toCanonicalAddress) + +# ------------------------------------------------------------------------------ +# Public function: straight ecRecover version +# ------------------------------------------------------------------------------ + +proc ecRecover*(header: BlockHeader): Result[EthAddress,UtilsError] = + ## Extract account address from the `extraData` field (last 65 bytes) of the + ## argument header. + header.extraData.ecRecover(header.hashPreSealed) + +# ------------------------------------------------------------------------------ +# Public constructor for caching ecRecover version +# ------------------------------------------------------------------------------ + +proc initEcRecover*(cache: var EcRecover; cacheSize = INMEMORY_SIGNATURES) = + + var toKey: LruKey[BlockHeader,EcKey32] = + proc(header:BlockHeader): EcKey32 = + header.blockHash.data + + cache.initCache(toKey, ecRecover, cacheSize) + +proc initEcRecover*: EcRecover {.gcsafe, raises: [Defect].} = + result.initEcRecover + +# ------------------------------------------------------------------------------ +# Public function: caching ecRecover version +# ------------------------------------------------------------------------------ + +proc ecRecover*(addrCache: var EcRecover; + header: BlockHeader): Result[EthAddress,UtilsError] + {.gcsafe, raises: [Defect,CatchableError].} = + ## Extract account address from `extraData` field (last 65 bytes) of the + ## argument header. The result is kept in a LRU cache to re-purposed for + ## improved result delivery avoiding calculations. + addrCache.getItem(header) + +# ------------------------------------------------------------------------------ +# Public PLP mixin functions for caching version +# ------------------------------------------------------------------------------ + +proc append*(rw: var RlpWriter; ecRec: EcRecover) {. + inline, raises: [Defect,KeyError].} = + ## Generic support for `rlp.encode(ecRec)` + rw.append(ecRec.data) + +proc read*(rlp: var Rlp; Q: type EcRecover): Q {. + inline, raises: [Defect,KeyError].} = + ## Generic support for `rlp.decode(bytes)` for loading the cache from a + ## serialised data stream. + result.initEcRecover + result.data = rlp.read(type result.data) + +# ------------------------------------------------------------------------------ +# End +# ------------------------------------------------------------------------------ diff --git a/nimbus/utils/utils_defs.nim b/nimbus/utils/utils_defs.nim new file mode 100644 index 000000000..9234e2b59 --- /dev/null +++ b/nimbus/utils/utils_defs.nim @@ -0,0 +1,52 @@ +# 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. + +## +## Definitions, Error Constants, etc. +## =================================== +## + +type + UtilsErrorType* = enum + resetUtilsError = ##\ + ## Default/reset value (use `utilsNoError` below rather than this valie) + (0, "no error") + + errMissingSignature = ##\ + ## is returned if the `extraData` header field does not seem to contain + ## a 65 byte secp256k1 signature. + "extraData 65 byte signature suffix missing" + + errSkSigResult = ##\ + ## eth/keys subsytem error: signature + "signature error" + + errSkPubKeyResult = ##\ + ## eth/keys subsytem error: public key + "public key error" + + UtilsError* = ##\ + ## Error message, tinned component + explanatory text (if any) + (UtilsErrorType,string) + + +const + utilsNoError* = ##\ + ## No-error constant + (resetUtilsError, "") + + +proc `$`*(e: UtilsError): string {.inline.} = + ## Join text fragments + result = $e[0] + if e[1] != "": + result &= ": " & e[1] + +# End diff --git a/nimbus/vm/state.nim b/nimbus/vm/state.nim index 6d6f7a7e7..eb9fd92a2 100644 --- a/nimbus/vm/state.nim +++ b/nimbus/vm/state.nim @@ -9,12 +9,32 @@ # according to those terms. import - macros, strformat, tables, sets, options, - eth/[common, keys, rlp], nimcrypto/keccak, - ./interpreter/gas_costs, ../errors, ../forks, - ../constants, ../db/[db_chain, accounts_cache], - ../utils, json, ./transaction_tracer, ./types, - ../config, ../../stateless/[witness_from_tree, witness_types] + std/[json, macros, options, sets, strformat, tables], + ../../stateless/[witness_from_tree, witness_types], + ../config, + ../constants, + ../db/[db_chain, accounts_cache], + ../errors, + ../forks, + ../utils, + ../utils/ec_recover, + ./interpreter/gas_costs, + ./transaction_tracer, + ./types, + eth/[common, keys] + + +proc getMinerAddress(vmState: BaseVMState): EthAddress = + if not vmState.consensusEnginePoA: + return vmState.blockHeader.coinbase + + let account = vmState.blockHeader.ecRecover + if account.isErr: + let msg = "Could not recover account address: " & $account.error + raise newException(ValidationError, msg) + + account.value + proc newAccessLogs*: AccessLogs = AccessLogs(reads: initTable[string, string](), writes: initTable[string, string]()) @@ -29,8 +49,6 @@ proc `$`*(vmState: BaseVMState): string = else: result = &"VMState {vmState.name}:\n header: {vmState.blockHeader}\n chaindb: {vmState.chaindb}" -proc getMinerAddress(vmState: BaseVMState): EthAddress - proc init*(self: BaseVMState, prevStateRoot: Hash256, header: BlockHeader, chainDB: BaseChainDB, tracerFlags: set[TracerFlags] = {}) = self.prevHeaders = @[] @@ -74,43 +92,6 @@ proc consensusEnginePoA*(vmState: BaseVMState): bool = # using `real` engine configuration vmState.chainDB.config.poaEngine -proc getSignature(bytes: openArray[byte], output: var Signature): bool = - let sig = Signature.fromRaw(bytes) - if sig.isOk: - output = sig[] - return true - return false - -proc headerHashOriExtraData(vmState: BaseVMState): Hash256 = - var tmp = vmState.blockHeader - tmp.extraData.setLen(tmp.extraData.len-65) - result = keccak256.digest(rlp.encode(tmp)) - -proc calcMinerAddress(sigRaw: openArray[byte], vmState: BaseVMState, output: var EthAddress): bool = - var sig: Signature - if sigRaw.getSignature(sig): - let headerHash = headerHashOriExtraData(vmState) - let pubkey = recover(sig, SKMessage(headerHash.data)) - if pubkey.isOk: - output = pubkey[].toCanonicalAddress() - result = true - -proc getMinerAddress(vmState: BaseVMState): EthAddress = - if not vmState.consensusEnginePoA: - return vmState.blockHeader.coinbase - - template data: untyped = - vmState.blockHeader.extraData - - let len = data.len - doAssert(len >= 65) - - var miner: EthAddress - if calcMinerAddress(data.toOpenArray(len - 65, len-1), vmState, miner): - result = miner - else: - raise newException(ValidationError, "Could not derive miner address from header extradata") - proc updateBlockHeader*(vmState: BaseVMState, header: BlockHeader) = vmState.blockHeader = header vmState.touchedAccounts.clear() @@ -141,7 +122,7 @@ method difficulty*(vmState: BaseVMState): UInt256 {.base, gcsafe.} = method gasLimit*(vmState: BaseVMState): GasInt {.base, gcsafe.} = vmState.blockHeader.gasLimit -method baseFee*(vmState: BaseVMState): Uint256 {.base, gcsafe.} = +method baseFee*(vmState: BaseVMState): UInt256 {.base, gcsafe.} = vmState.blockHeader.baseFee when defined(geth): diff --git a/nimbus/vm2/state.nim b/nimbus/vm2/state.nim index 8a9351161..9c2e3630f 100644 --- a/nimbus/vm2/state.nim +++ b/nimbus/vm2/state.nim @@ -9,12 +9,31 @@ # according to those terms. import - macros, strformat, tables, sets, options, - eth/[common, keys, rlp], nimcrypto/keccak, - ../errors, ../forks, - ../constants, ../db/[db_chain, accounts_cache], - ../utils, json, ./transaction_tracer, ./types, - ../config, ../../stateless/[witness_from_tree, witness_types] + std/[json, macros, options, sets, strformat, tables], + ../../stateless/[witness_from_tree, witness_types], + ../config, + ../constants, + ../db/[db_chain, accounts_cache], + ../errors, + ../forks, + ../utils, + ../utils/ec_recover, + ./transaction_tracer, + ./types, + eth/[common, keys] + + +proc getMinerAddress(vmState: BaseVMState): EthAddress = + if not vmState.consensusEnginePoA: + return vmState.blockHeader.coinbase + + let account = vmState.blockHeader.ecRecover + if account.isErr: + let msg = "Could not recover account address: " & $account.error + raise newException(ValidationError, msg) + + account.value + proc newAccessLogs*: AccessLogs = AccessLogs(reads: initTable[string, string](), writes: initTable[string, string]()) @@ -29,8 +48,6 @@ proc `$`*(vmState: BaseVMState): string = else: result = &"VMState {vmState.name}:\n header: {vmState.blockHeader}\n chaindb: {vmState.chaindb}" -proc getMinerAddress(vmState: BaseVMState): EthAddress - proc init*(self: BaseVMState, prevStateRoot: Hash256, header: BlockHeader, chainDB: BaseChainDB, tracerFlags: set[TracerFlags] = {}) = self.prevHeaders = @[] @@ -62,43 +79,6 @@ proc consensusEnginePoA*(vmState: BaseVMState): bool = # using `real` engine configuration vmState.chainDB.config.poaEngine -proc getSignature(bytes: openArray[byte], output: var Signature): bool = - let sig = Signature.fromRaw(bytes) - if sig.isOk: - output = sig[] - return true - return false - -proc headerHashOriExtraData(vmState: BaseVMState): Hash256 = - var tmp = vmState.blockHeader - tmp.extraData.setLen(tmp.extraData.len-65) - result = keccak256.digest(rlp.encode(tmp)) - -proc calcMinerAddress(sigRaw: openArray[byte], vmState: BaseVMState, output: var EthAddress): bool = - var sig: Signature - if sigRaw.getSignature(sig): - let headerHash = headerHashOriExtraData(vmState) - let pubkey = recover(sig, SKMessage(headerHash.data)) - if pubkey.isOk: - output = pubkey[].toCanonicalAddress() - result = true - -proc getMinerAddress(vmState: BaseVMState): EthAddress = - if not vmState.consensusEnginePoA: - return vmState.blockHeader.coinbase - - template data: untyped = - vmState.blockHeader.extraData - - let len = data.len - doAssert(len >= 65) - - var miner: EthAddress - if calcMinerAddress(data.toOpenArray(len - 65, len-1), vmState, miner): - result = miner - else: - raise newException(ValidationError, "Could not derive miner address from header extradata") - proc updateBlockHeader*(vmState: BaseVMState, header: BlockHeader) = vmState.blockHeader = header vmState.touchedAccounts.clear() diff --git a/tests/test_clique.nim b/tests/test_clique.nim index a12c38bf0..fead5a7e7 100644 --- a/tests/test_clique.nim +++ b/tests/test_clique.nim @@ -246,11 +246,11 @@ when isMainModule: # `test_clique/indiump.dumpGroupNl()` # placed at the end of # `p2p/chain/persist_blocks.persistBlocks()`. - captureFile = "test_clique" / "goerli504192.txt.gz" - #captureFile = "test_clique" / "dump-stream.out.gz" + captureFile = "goerli504192.txt.gz" + #captureFile = "dump-stream.out.gz" proc goerliReplay(noisy = true; showElapsed = true; - dir = "."; captureFile = captureFile; + dir = "/status"; captureFile = captureFile; startAtBlock = 0u64; stopAfterBlock = 0u64) = runGoerliReplay( noisy = noisy, showElapsed = showElapsed, diff --git a/tests/test_clique/pool.nim b/tests/test_clique/pool.nim index 0afc04587..ff825dd09 100644 --- a/tests/test_clique/pool.nim +++ b/tests/test_clique/pool.nim @@ -386,8 +386,8 @@ proc commitVoterChain*(ap: TesterPool; postProcessOk = false; ## If `postProcessOk` is set, an additional verification step is added at ## the end of each transaction. ## - ## if `stopFaultyHeader` is set, the function stopps immediately on error. - ## Otherwise the offending bloch is removed, the rest of the batch is + ## 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