Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jordan/transaction pool study #799

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3375364
Sorted list framework based on red-black tree
mjfh Aug 5, 2021
8fc3749
Data queue with keyed random access
mjfh Aug 11, 2021
a2ecc76
Activate SLst and RndQu tests
mjfh Aug 13, 2021
74b5d4b
complete rb-tree verification:
mjfh Aug 23, 2021
65ff9d4
Update queue interface
mjfh Aug 23, 2021
89afb61
Update list interface
mjfh Aug 23, 2021
9e0554e
tx-pool test framework & play ground
mjfh Aug 13, 2021
e34ada2
Jazzed up API
mjfh Aug 24, 2021
07c0005
Make queue iterators robust against deletion
mjfh Aug 24, 2021
e08f9ea
Make delete() non-discardable
mjfh Aug 25, 2021
a11a7cd
Update preliminary API and unit tests
mjfh Aug 25, 2021
b0253d7
Re-implemented tx-pool basic queue with local/remote sub-queues
mjfh Aug 26, 2021
b30b506
Renamed queue/list `value` fields => `data`
mjfh Aug 26, 2021
0526631
Fix deletion functions
mjfh Aug 27, 2021
b8a6a79
Test deletion of group of transactions based on gas price
mjfh Aug 27, 2021
41e1f37
Replaced ref objects (aka pointers) by var objects
mjfh Aug 27, 2021
92a5cc3
Typo in object initialiser
mjfh Aug 31, 2021
74f5b54
Renamed rnd_qu/RndQueue => keequ/KeeQu
mjfh Aug 31, 2021
f998909
Fix more typos
mjfh Sep 1, 2021
df6edaf
Added LRU functionality to keequ
mjfh Sep 6, 2021
5d18678
Update ecRecover for Transaction types and replace lru_cache by keequ
mjfh Sep 6, 2021
caf0a7b
Added by-sender address grouping
mjfh Sep 7, 2021
18819b2
Make local/remote queue re-assignment work: tx_pool.reassign()
mjfh Sep 7, 2021
8c86a1f
Reassign current tx-class/functions as base class
mjfh Sep 7, 2021
48e2c9b
Added job/commit system
mjfh Sep 8, 2021
f02f466
Using refs as tx list representing tx groups (e.g. same sender or gas…
mjfh Sep 9, 2021
1616fdc
Added go API functions: lookup sub-system
mjfh Sep 10, 2021
b3cb7be
Re-arranged/split address groups into local/remote transactions
mjfh Sep 10, 2021
0c8f06d
Replaced byGasPrice list by two lists indexed
mjfh Sep 12, 2021
1df3fd2
Reorganised byGasPrice list with cascaded index
mjfh Sep 14, 2021
afc3491
Renamed TxGroup => TxSender
mjfh Sep 16, 2021
6d5fb38
Fixes and code improvements (thanks to Zah)
mjfh Sep 16, 2021
02bc5ac
Shuffled source files a bit
mjfh Sep 17, 2021
de6d4e0
Cleaned up list interfaces
mjfh Sep 19, 2021
a5f13ba
Renamed tx_tab list sources tx_queue => tx_itemid, tx_gas => tx_tipcap
mjfh Sep 20, 2021
a8eabfe
Added status management as sum-lists in sender table
mjfh Sep 20, 2021
e846cf9
Serialise jobs
mjfh Sep 23, 2021
6a49122
Simplify datatabases
mjfh Sep 24, 2021
7214b92
Added block chain from main-net for testing/TDD
mjfh Sep 25, 2021
8c1deb3
Always delete via rejection queue (aka waste basket)
mjfh Sep 27, 2021
65b9621
Replace transaction queue iterator as call-back service
mjfh Sep 27, 2021
4e62dec
Implemented pending queue/bucket handling
mjfh Sep 28, 2021
606ae5c
Explict u64 baseFee type in unit tests
mjfh Sep 30, 2021
f66c2e6
Move block chain dumps to separate directory
mjfh Oct 1, 2021
e0b1234
Can re-calculate queued/pending txs on-the-fly
mjfh Oct 1, 2021
22f2179
Re-orgaising some source files
mjfh Oct 1, 2021
e649e08
backup
mjfh Oct 2, 2021
9d573e4
Moved simple jobs out of the batch queue to be executed immediately
mjfh Oct 2, 2021
92489ff
Using async paradigm from chronos for processing batch queue
mjfh Oct 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nimbus/p2p/clique/clique_cfg.nim
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ proc newCliqueCfg*(db: BaseChainDB): CliqueCfg =
ckpInterval: CHECKPOINT_INTERVAL,
roThreshold: FULL_IMMUTABILITY_THRESHOLD,
logInterval: SNAPS_LOG_INTERVAL_MICSECS,
signatures: initEcRecover(),
signatures: init(type EcRecover),
prng: initRand(prngSeed),
prettyPrint: PrettyPrinters(
nonce: proc(v:BlockNonce): string = $v,
Expand Down
187 changes: 141 additions & 46 deletions nimbus/utils/ec_recover.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,128 +9,223 @@
# according to those terms.

##
## Recover Address From Signature
## ==============================
## Recover Address From Signature (Version 2)
## ==========================================
##
## Intended to replace utils/ec_recover eventually.
##
## 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],
./keequ,
./utils_defs,
eth/[common, common/transaction, keys, rlp],
nimcrypto,
stew/results,
stint

const
INMEMORY_SIGNATURES* = ##\
## Number of recent block signatures to keep in memory
## Default number of recent block signatures to keep in memory
4096

type
# simplify Hash256 for rlp serialisation
EcKey32 = array[32, byte]
EcKey* = ##\
## Internal key used for the LRU cache (derived from Hash256).
array[32,byte]

EcAddrResult* = ##\
## Typical `EthAddress` result as returned bu `ecRecover()` functions.
Result[EthAddress,UtilsError]

EcRecover* = LruCache[BlockHeader,EcKey32,EthAddress,UtilsError]
EcRecover* = object
size: uint
q: KeeQu[EcKey,EthAddress]

{.push raises: [Defect].}

# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------

proc vrsSerialised(tx: Transaction): Result[array[65,byte],UtilsError]
{.inline.} =
## Parts copied from `transaction.getSignature`.
var data: array[65,byte]
data[0..31] = tx.R.toByteArrayBE
data[32..63] = tx.S.toByteArrayBE

if tx.txType != TxLegacy:
data[64] = tx.V.byte
elif tx.V >= EIP155_CHAIN_ID_OFFSET:
data[64] = byte(1 - (tx.V and 1))
elif tx.V == 27 or tx.V == 28:
data[64] = byte(tx.V - 27)
else:
return err((errSigPrefixError,"")) # legacy error

ok(data)

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
## Cut sigature off `extraData` header field.
if header.extraData.len < EXTRA_SEAL:
return rlp.encode(header)

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.} =
proc recoverImpl(rawSig: openArray[byte]; msg: Hash256): EcAddrResult =
## 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:
if rawSig.len < EXTRA_SEAL:
return err((errMissingSignature,""))

let sig = Signature.fromRaw(
extraData.toOpenArray(extraData.len - EXTRA_SEAL, extraData.high))
rawSig.toOpenArray(rawSig.len - EXTRA_SEAL, rawSig.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))
let pubKey = recover(sig.value, SKMessage(msg.data))
if pubKey.isErr:
return err((errSkPubKeyResult,$pubKey.error))

# Convert public key to address.
return ok(pubKey.value.toCanonicalAddress)
ok(pubKey.value.toCanonicalAddress)

# ------------------------------------------------------------------------------
# Public function: straight ecRecover version
# Public function: straight ecRecover versions
# ------------------------------------------------------------------------------

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)
proc ecRecover*(header: BlockHeader): EcAddrResult =
## Extracts account address from the `extraData` field (last 65 bytes) of
## the argument header.
header.extraData.recoverImpl(header.hashPreSealed)

proc ecRecover*(tx: var Transaction): EcAddrResult =
## Extracts sender address from transaction. This function has similar
## functionality as `transaction.getSender()`.
let txSig = tx.vrsSerialised
if txSig.isErr:
return err(txSig.error)
txSig.value.recoverImpl(tx.txHashNoSignature)

proc ecRecover*(tx: Transaction): EcAddrResult {.inline.} =
## Variant of `ecRecover()` for call-by-value header.
var ty = tx
ty.ecRecover

# ------------------------------------------------------------------------------
# Public constructor for caching ecRecover version
# ------------------------------------------------------------------------------

proc initEcRecover*(cache: var EcRecover; cacheSize = INMEMORY_SIGNATURES) =
proc init*(er: var EcRecover; cacheSize = INMEMORY_SIGNATURES; initSize = 10) =
## Inialise recover cache
er.size = cacheSize.uint
er.q.init(initSize)

var toKey: LruKey[BlockHeader,EcKey32] =
proc(header:BlockHeader): EcKey32 =
header.blockHash.data
proc init*(T: type EcRecover;
cacheSize = INMEMORY_SIGNATURES; initSize = 10): T =
## Inialise recover cache
result.init(cacheSize, initSize)

cache.initCache(toKey, ecRecover, cacheSize)
# ------------------------------------------------------------------------------
# Public functions: miscellaneous
# ------------------------------------------------------------------------------

proc initEcRecover*: EcRecover {.gcsafe, raises: [Defect].} =
result.initEcRecover
proc len*(er: var EcRecover): int =
## Returns the current number of entries in the LRU cache.
er.q.len

# ------------------------------------------------------------------------------
# Public function: caching ecRecover version
# Public functions: caching ecRecover version
# ------------------------------------------------------------------------------

proc ecRecover*(addrCache: var EcRecover;
header: BlockHeader): Result[EthAddress,UtilsError]
{.gcsafe, raises: [Defect,CatchableError].} =
proc ecRecover*(er: var EcRecover; header: var BlockHeader): EcAddrResult
{.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)
let key = header.blockHash.data
block:
let rc = er.q.lruFetch(key)
if rc.isOK:
return ok(rc.value)
block:
let rc = header.extraData.recoverImpl(header.hashPreSealed)
if rc.isOK:
return ok(er.q.lruAppend(key, rc.value, er.size.int))
err(rc.error)

proc ecRecover*(er: var EcRecover; header: BlockHeader): EcAddrResult
{.inline, gcsafe, raises: [Defect,CatchableError].} =
## Variant of `ecRecover()` for call-by-value header
var hdr = header
er.ecRecover(hdr)

proc ecRecover*(er: var EcRecover; hash: Hash256): EcAddrResult
{.inline, gcsafe, raises: [Defect,CatchableError].} =
## Variant of `ecRecover()` for hash only. Will only succeed it the
## argument hash is uk the LRU queue.
let rc = er.q.lruFetch(hash.data)
if rc.isOK:
return ok(rc.value)
err((errItemNotFound,""))

proc ecRecover*(er: var EcRecover; tx: var Transaction): EcAddrResult
{.gcsafe, raises: [Defect,CatchableError].} =
## Variant of `recover()` managing the sender addresses for transactions.
let txSig = tx.vrsSerialised
if txSig.isErr:
return err(txSig.error)
let key = (keccak256.digest tx.rlpEncode).data
block:
let rc = er.q.lruFetch(key)
if rc.isOK:
return ok(rc.value)
block:
let rc = txSig.value.recoverImpl(tx.txHashNoSignature)
if rc.isOK:
return ok(er.q.lruAppend(key, rc.value, er.size.int))
err(rc.error)

# ------------------------------------------------------------------------------
# Public PLP mixin functions for caching version
# Public RLP 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 append*(rw: var RlpWriter; data: EcRecover)
{.inline, raises: [Defect,KeyError].} =
## Generic support for `rlp.encode()`
rw.append((data.size,data.q))

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
proc read*(rlp: var Rlp; Q: type EcRecover): Q
{.inline, raises: [Defect,KeyError].} =
## Generic support for `rlp.decode()` for loading the cache from a
## serialised data stream.
result.initEcRecover
result.data = rlp.read(type result.data)
(result.size, result.q) = rlp.read((type result.size, type result.q))

# ------------------------------------------------------------------------------
# Debugging
# ------------------------------------------------------------------------------

iterator keyItemPairs*(er: var EcRecover): (EcKey,EthAddress)
{.gcsafe, raises: [Defect,CatchableError].} =
var rc = er.q.first
while rc.isOK:
yield (rc.value.key, rc.value.data)
rc = er.q.next(rc.value.key)

# ------------------------------------------------------------------------------
# End
Expand Down
Loading