mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-03-02 21:00:49 +00:00
It is common for many accounts to share the same code - at the database level, code is stored by hash meaning only one copy exists per unique program but when loaded in memory, a copy is made for each account. Further, every time we execute the code, it must be scanned for invalid jump destinations which slows down EVM exeuction. Finally, the extcodesize call causes code to be loaded even if only the size is needed. This PR improves on all these points by introducing a shared CodeBytesRef type whose code section is immutable and that can be shared between accounts. Further, a dedicated `len` API call is added so that the EXTCODESIZE opcode can operate without polluting the GC and code cache, for cases where only the size is requested - rocksdb will in this case cache the code itself in the row cache meaning that lookup of the code itself remains fast when length is asked for first. With 16k code entries, there's a 90% hit rate which goes up to 99% during the 2.3M attack - the cache significantly lowers memory consumption and execution time not only during this event but across the board.
388 lines
11 KiB
Nim
388 lines
11 KiB
Nim
# nimbus-eth1
|
|
# Copyright (c) 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.
|
|
|
|
## Stackable API for `Kvt`
|
|
## =======================
|
|
|
|
import
|
|
std/times,
|
|
eth/common,
|
|
results,
|
|
../aristo/aristo_profile,
|
|
./kvt_desc/desc_backend,
|
|
./kvt_init/memory_db,
|
|
"."/[kvt_desc, kvt_init, kvt_tx, kvt_utils]
|
|
|
|
const
|
|
AutoValidateApiHooks = defined(release).not
|
|
## No validatinon needed for production suite.
|
|
|
|
KvtPersistentBackendOk = AutoValidateApiHooks # and false
|
|
## Set true for persistent backend profiling (which needs an extra
|
|
## link library.)
|
|
|
|
when KvtPersistentBackendOk:
|
|
import ./kvt_init/rocks_db
|
|
|
|
# Annotation helper(s)
|
|
{.pragma: noRaise, gcsafe, raises: [].}
|
|
|
|
type
|
|
KvtDbProfListRef* = AristoDbProfListRef
|
|
## Borrowed from `aristo_profile`
|
|
|
|
KvtDbProfData* = AristoDbProfData
|
|
## Borrowed from `aristo_profile`
|
|
|
|
KvtApiCommitFn* = proc(tx: KvtTxRef): Result[void,KvtError] {.noRaise.}
|
|
KvtApiDelFn* = proc(db: KvtDbRef,
|
|
key: openArray[byte]): Result[void,KvtError] {.noRaise.}
|
|
KvtApiFinishFn* = proc(db: KvtDbRef, eradicate = false) {.noRaise.}
|
|
KvtApiForgetFn* = proc(db: KvtDbRef): Result[void,KvtError] {.noRaise.}
|
|
KvtApiForkTxFn* = proc(db: KvtDbRef,
|
|
backLevel: int): Result[KvtDbRef,KvtError] {.noRaise.}
|
|
KvtApiGetFn* = proc(db: KvtDbRef,
|
|
key: openArray[byte]): Result[Blob,KvtError] {.noRaise.}
|
|
KvtApiLenFn* = proc(db: KvtDbRef,
|
|
key: openArray[byte]): Result[int,KvtError] {.noRaise.}
|
|
KvtApiHasKeyFn* = proc(db: KvtDbRef,
|
|
key: openArray[byte]): Result[bool,KvtError] {.noRaise.}
|
|
KvtApiIsCentreFn* = proc(db: KvtDbRef): bool {.noRaise.}
|
|
KvtApiIsTopFn* = proc(tx: KvtTxRef): bool {.noRaise.}
|
|
KvtApiLevelFn* = proc(db: KvtDbRef): int {.noRaise.}
|
|
KvtApiNForkedFn* = proc(db: KvtDbRef): int {.noRaise.}
|
|
KvtApiPutFn* = proc(db: KvtDbRef,
|
|
key, data: openArray[byte]): Result[void,KvtError] {.noRaise.}
|
|
KvtApiReCentreFn* = proc(db: KvtDbRef): Result[void,KvtError] {.noRaise.}
|
|
KvtApiRollbackFn* = proc(tx: KvtTxRef): Result[void,KvtError] {.noRaise.}
|
|
KvtApiPersistFn* = proc(db: KvtDbRef): Result[void,KvtError] {.noRaise.}
|
|
KvtApiToKvtDbRefFn* = proc(tx: KvtTxRef): KvtDbRef {.noRaise.}
|
|
KvtApiTxBeginFn* = proc(db: KvtDbRef): Result[KvtTxRef,KvtError] {.noRaise.}
|
|
KvtApiTxTopFn* =
|
|
proc(db: KvtDbRef): Result[KvtTxRef,KvtError] {.noRaise.}
|
|
|
|
KvtApiRef* = ref KvtApiObj
|
|
KvtApiObj* = object of RootObj
|
|
## Useful set of `Kvt` fuctions that can be filtered, stacked etc. Note
|
|
## that this API is modelled after a subset of the `Aristo` API.
|
|
commit*: KvtApiCommitFn
|
|
del*: KvtApiDelFn
|
|
finish*: KvtApiFinishFn
|
|
forget*: KvtApiForgetFn
|
|
forkTx*: KvtApiForkTxFn
|
|
get*: KvtApiGetFn
|
|
len*: KvtApiLenFn
|
|
hasKey*: KvtApiHasKeyFn
|
|
isCentre*: KvtApiIsCentreFn
|
|
isTop*: KvtApiIsTopFn
|
|
level*: KvtApiLevelFn
|
|
nForked*: KvtApiNForkedFn
|
|
put*: KvtApiPutFn
|
|
reCentre*: KvtApiReCentreFn
|
|
rollback*: KvtApiRollbackFn
|
|
persist*: KvtApiPersistFn
|
|
toKvtDbRef*: KvtApiToKvtDbRefFn
|
|
txBegin*: KvtApiTxBeginFn
|
|
txTop*: KvtApiTxTopFn
|
|
|
|
|
|
KvtApiProfNames* = enum
|
|
## index/name mapping for profile slots
|
|
KvtApiProfTotal = "total"
|
|
|
|
KvtApiProfCommitFn = "commit"
|
|
KvtApiProfDelFn = "del"
|
|
KvtApiProfFinishFn = "finish"
|
|
KvtApiProfForgetFn = "forget"
|
|
KvtApiProfForkTxFn = "forkTx"
|
|
KvtApiProfGetFn = "get"
|
|
KvtApiProfLenFn = "len"
|
|
KvtApiProfHasKeyFn = "hasKey"
|
|
KvtApiProfIsCentreFn = "isCentre"
|
|
KvtApiProfIsTopFn = "isTop"
|
|
KvtApiProfLevelFn = "level"
|
|
KvtApiProfNForkedFn = "nForked"
|
|
KvtApiProfPutFn = "put"
|
|
KvtApiProfReCentreFn = "reCentre"
|
|
KvtApiProfRollbackFn = "rollback"
|
|
KvtApiProfPersistFn = "persist"
|
|
KvtApiProfToKvtDbRefFn = "toKvtDbRef"
|
|
KvtApiProfTxBeginFn = "txBegin"
|
|
KvtApiProfTxTopFn = "txTop"
|
|
|
|
KvtApiProfBeGetKvpFn = "be/getKvp"
|
|
KvtApiProfBeLenKvpFn = "be/lenKvp"
|
|
KvtApiProfBePutKvpFn = "be/putKvp"
|
|
KvtApiProfBePutEndFn = "be/putEnd"
|
|
|
|
KvtApiProfRef* = ref object of KvtApiRef
|
|
## Profiling API extension of `KvtApiObj`
|
|
data*: KvtDbProfListRef
|
|
be*: BackendRef
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
when AutoValidateApiHooks:
|
|
proc validate(api: KvtApiObj|KvtApiRef) =
|
|
doAssert not api.commit.isNil
|
|
doAssert not api.del.isNil
|
|
doAssert not api.finish.isNil
|
|
doAssert not api.forget.isNil
|
|
doAssert not api.forkTx.isNil
|
|
doAssert not api.get.isNil
|
|
doAssert not api.hasKey.isNil
|
|
doAssert not api.isCentre.isNil
|
|
doAssert not api.isTop.isNil
|
|
doAssert not api.level.isNil
|
|
doAssert not api.nForked.isNil
|
|
doAssert not api.put.isNil
|
|
doAssert not api.reCentre.isNil
|
|
doAssert not api.rollback.isNil
|
|
doAssert not api.persist.isNil
|
|
doAssert not api.toKvtDbRef.isNil
|
|
doAssert not api.txBegin.isNil
|
|
doAssert not api.txTop.isNil
|
|
|
|
proc validate(prf: KvtApiProfRef) =
|
|
prf.KvtApiRef.validate
|
|
doAssert not prf.data.isNil
|
|
|
|
proc dup(be: BackendRef): BackendRef =
|
|
case be.kind:
|
|
of BackendMemory:
|
|
return MemBackendRef(be).dup
|
|
|
|
of BackendRocksDB, BackendRdbTriggered:
|
|
when KvtPersistentBackendOk:
|
|
return RdbBackendRef(be).dup
|
|
|
|
of BackendVoid:
|
|
discard
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public API constuctors
|
|
# ------------------------------------------------------------------------------
|
|
|
|
func init*(api: var KvtApiObj) =
|
|
when AutoValidateApiHooks:
|
|
api.reset
|
|
api.commit = commit
|
|
api.del = del
|
|
api.finish = finish
|
|
api.forget = forget
|
|
api.forkTx = forkTx
|
|
api.get = get
|
|
api.len = len
|
|
api.hasKey = hasKey
|
|
api.isCentre = isCentre
|
|
api.isTop = isTop
|
|
api.level = level
|
|
api.nForked = nForked
|
|
api.put = put
|
|
api.reCentre = reCentre
|
|
api.rollback = rollback
|
|
api.persist = persist
|
|
api.toKvtDbRef = toKvtDbRef
|
|
api.txBegin = txBegin
|
|
api.txTop = txTop
|
|
when AutoValidateApiHooks:
|
|
api.validate
|
|
|
|
func init*(T: type KvtApiRef): T =
|
|
result = new T
|
|
result[].init()
|
|
|
|
func dup*(api: KvtApiRef): KvtApiRef =
|
|
result = KvtApiRef(
|
|
commit: api.commit,
|
|
del: api.del,
|
|
finish: api.finish,
|
|
forget: api.forget,
|
|
forkTx: api.forkTx,
|
|
get: api.get,
|
|
len: api.len,
|
|
hasKey: api.hasKey,
|
|
isCentre: api.isCentre,
|
|
isTop: api.isTop,
|
|
level: api.level,
|
|
nForked: api.nForked,
|
|
put: api.put,
|
|
reCentre: api.reCentre,
|
|
rollback: api.rollback,
|
|
persist: api.persist,
|
|
toKvtDbRef: api.toKvtDbRef,
|
|
txBegin: api.txBegin,
|
|
txTop: api.txTop)
|
|
when AutoValidateApiHooks:
|
|
api.validate
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public profile API constuctor
|
|
# ------------------------------------------------------------------------------
|
|
|
|
func init*(
|
|
T: type KvtApiProfRef;
|
|
api: KvtApiRef;
|
|
be = BackendRef(nil);
|
|
): T =
|
|
## This constructor creates a profiling API descriptor to be derived from
|
|
## an initialised `api` argument descriptor. For profiling the DB backend,
|
|
## the field `.be` of the result descriptor must be assigned to the
|
|
## `.backend` field of the `KvtDbRef` descriptor.
|
|
##
|
|
## The argument desctiptors `api` and `be` will not be modified and can be
|
|
## used to restore the previous set up.
|
|
##
|
|
let
|
|
data = KvtDbProfListRef(
|
|
list: newSeq[KvtDbProfData](1 + high(KvtApiProfNames).ord))
|
|
profApi = T(data: data)
|
|
|
|
template profileRunner(n: KvtApiProfNames, code: untyped): untyped =
|
|
let start = getTime()
|
|
code
|
|
data.update(n.ord, getTime() - start)
|
|
|
|
profApi.commit =
|
|
proc(a: KvtTxRef): auto =
|
|
KvtApiProfCommitFn.profileRunner:
|
|
result = api.commit(a)
|
|
|
|
profApi.del =
|
|
proc(a: KvtDbRef; b: openArray[byte]): auto =
|
|
KvtApiProfDelFn.profileRunner:
|
|
result = api.del(a, b)
|
|
|
|
profApi.finish =
|
|
proc(a: KvtDbRef; b = false) =
|
|
KvtApiProfFinishFn.profileRunner:
|
|
api.finish(a, b)
|
|
|
|
profApi.forget =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfForgetFn.profileRunner:
|
|
result = api.forget(a)
|
|
|
|
profApi.forkTx =
|
|
proc(a: KvtDbRef, b: int): auto =
|
|
KvtApiProfForkTxFn.profileRunner:
|
|
result = api.forkTx(a, b)
|
|
|
|
profApi.get =
|
|
proc(a: KvtDbRef, b: openArray[byte]): auto =
|
|
KvtApiProfGetFn.profileRunner:
|
|
result = api.get(a, b)
|
|
|
|
profApi.len =
|
|
proc(a: KvtDbRef, b: openArray[byte]): auto =
|
|
KvtApiProfLenFn.profileRunner:
|
|
result = api.len(a, b)
|
|
|
|
profApi.hasKey =
|
|
proc(a: KvtDbRef, b: openArray[byte]): auto =
|
|
KvtApiProfHasKeyFn.profileRunner:
|
|
result = api.hasKey(a, b)
|
|
|
|
profApi.isCentre =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfIsCentreFn.profileRunner:
|
|
result = api.isCentre(a)
|
|
|
|
profApi.isTop =
|
|
proc(a: KvtTxRef): auto =
|
|
KvtApiProfIsTopFn.profileRunner:
|
|
result = api.isTop(a)
|
|
|
|
profApi.level =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfLevelFn.profileRunner:
|
|
result = api.level(a)
|
|
|
|
profApi.nForked =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfNForkedFn.profileRunner:
|
|
result = api.nForked(a)
|
|
|
|
profApi.put =
|
|
proc(a: KvtDbRef; b, c: openArray[byte]): auto =
|
|
KvtApiProfPutFn.profileRunner:
|
|
result = api.put(a, b, c)
|
|
|
|
profApi.reCentre =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfReCentreFn.profileRunner:
|
|
result = api.reCentre(a)
|
|
|
|
profApi.rollback =
|
|
proc(a: KvtTxRef): auto =
|
|
KvtApiProfRollbackFn.profileRunner:
|
|
result = api.rollback(a)
|
|
|
|
profApi.persist =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfPersistFn.profileRunner:
|
|
result = api.persist(a)
|
|
|
|
profApi.toKvtDbRef =
|
|
proc(a: KvtTxRef): auto =
|
|
KvtApiProfToKvtDbRefFn.profileRunner:
|
|
result = api.toKvtDbRef(a)
|
|
|
|
profApi.txBegin =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfTxBeginFn.profileRunner:
|
|
result = api.txBegin(a)
|
|
|
|
profApi.txTop =
|
|
proc(a: KvtDbRef): auto =
|
|
KvtApiProfTxTopFn.profileRunner:
|
|
result = api.txTop(a)
|
|
|
|
let beDup = be.dup()
|
|
if beDup.isNil:
|
|
profApi.be = be
|
|
|
|
else:
|
|
beDup.getKvpFn =
|
|
proc(a: openArray[byte]): auto =
|
|
KvtApiProfBeGetKvpFn.profileRunner:
|
|
result = be.getKvpFn(a)
|
|
data.list[KvtApiProfBeGetKvpFn.ord].masked = true
|
|
|
|
beDup.lenKvpFn =
|
|
proc(a: openArray[byte]): auto =
|
|
KvtApiProfBeLenKvpFn.profileRunner:
|
|
result = be.lenKvpFn(a)
|
|
data.list[KvtApiProfBeLenKvpFn.ord].masked = true
|
|
|
|
beDup.putKvpFn =
|
|
proc(a: PutHdlRef; b: openArray[(Blob,Blob)]) =
|
|
be.putKvpFn(a,b)
|
|
data.list[KvtApiProfBePutKvpFn.ord].masked = true
|
|
|
|
beDup.putEndFn =
|
|
proc(a: PutHdlRef): auto =
|
|
KvtApiProfBePutEndFn.profileRunner:
|
|
result = be.putEndFn(a)
|
|
data.list[KvtApiProfBePutEndFn.ord].masked = true
|
|
|
|
profApi.be = beDup
|
|
|
|
when AutoValidateApiHooks:
|
|
profApi.validate
|
|
|
|
profApi
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|