mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-24 19:19:21 +00:00
768307d91d
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.
187 lines
5.1 KiB
Nim
187 lines
5.1 KiB
Nim
# nimbus-eth1
|
|
# Copyright (c) 2023-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.
|
|
|
|
## In-memory backend for Kvt DB
|
|
## ============================
|
|
##
|
|
## The iterators provided here are currently available only by direct
|
|
## backend access
|
|
## ::
|
|
## import
|
|
## kvt/kvt_init,
|
|
## kvt/kvt_init/kvt_memory
|
|
##
|
|
## let rc = newKvtDbRef(BackendMemory)
|
|
## if rc.isOk:
|
|
## let be = rc.value.to(MemBackendRef)
|
|
## for (n, key, vtx) in be.walkVtx:
|
|
## ...
|
|
##
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/tables,
|
|
chronicles,
|
|
eth/common,
|
|
results,
|
|
stew/byteutils,
|
|
../kvt_desc,
|
|
../kvt_desc/desc_backend,
|
|
./init_common
|
|
|
|
type
|
|
MemDbRef = ref object
|
|
## Database
|
|
tab: Table[Blob,Blob] ## Structural key-value table
|
|
|
|
MemBackendRef* = ref object of TypedBackendRef
|
|
## Inheriting table so access can be extended for debugging purposes
|
|
mdb: MemDbRef ## Database
|
|
|
|
MemPutHdlRef = ref object of TypedPutHdlRef
|
|
tab: Table[Blob,Blob]
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
template logTxt(info: static[string]): static[string] =
|
|
"MemoryDB " & info
|
|
|
|
|
|
proc newSession(db: MemBackendRef): MemPutHdlRef =
|
|
new result
|
|
result.TypedPutHdlRef.beginSession db
|
|
|
|
proc getSession(hdl: PutHdlRef; db: MemBackendRef): MemPutHdlRef =
|
|
hdl.TypedPutHdlRef.verifySession db
|
|
hdl.MemPutHdlRef
|
|
|
|
proc endSession(hdl: PutHdlRef; db: MemBackendRef): MemPutHdlRef =
|
|
hdl.TypedPutHdlRef.finishSession db
|
|
hdl.MemPutHdlRef
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private functions: interface
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc getKvpFn(db: MemBackendRef): GetKvpFn =
|
|
result =
|
|
proc(key: openArray[byte]): Result[Blob,KvtError] =
|
|
if key.len == 0:
|
|
return err(KeyInvalid)
|
|
var data = db.mdb.tab.getOrVoid @key
|
|
if data.isValid:
|
|
return ok(move(data))
|
|
err(GetNotFound)
|
|
|
|
proc lenKvpFn(db: MemBackendRef): LenKvpFn =
|
|
result =
|
|
proc(key: openArray[byte]): Result[int,KvtError] =
|
|
if key.len == 0:
|
|
return err(KeyInvalid)
|
|
var data = db.mdb.tab.getOrVoid @key
|
|
if data.isValid:
|
|
return ok(data.len)
|
|
err(GetNotFound)
|
|
|
|
# -------------
|
|
|
|
proc putBegFn(db: MemBackendRef): PutBegFn =
|
|
result =
|
|
proc(): Result[PutHdlRef,KvtError] =
|
|
ok db.newSession()
|
|
|
|
proc putKvpFn(db: MemBackendRef): PutKvpFn =
|
|
result =
|
|
proc(hdl: PutHdlRef; kvps: openArray[(Blob,Blob)]) =
|
|
let hdl = hdl.getSession db
|
|
if hdl.error == KvtError(0):
|
|
for (k,v) in kvps:
|
|
if k.isValid:
|
|
hdl.tab[k] = v
|
|
else:
|
|
hdl.error = KeyInvalid
|
|
|
|
proc putEndFn(db: MemBackendRef): PutEndFn =
|
|
result =
|
|
proc(hdl: PutHdlRef): Result[void,KvtError] =
|
|
let hdl = hdl.endSession db
|
|
if hdl.error != KvtError(0):
|
|
debug logTxt "putEndFn: key/value failed", error=hdl.error
|
|
return err(hdl.error)
|
|
|
|
for (k,v) in hdl.tab.pairs:
|
|
db.mdb.tab[k] = v
|
|
|
|
ok()
|
|
|
|
# -------------
|
|
|
|
proc closeFn(db: MemBackendRef): CloseFn =
|
|
result =
|
|
proc(ignore: bool) =
|
|
discard
|
|
|
|
proc canModFn(db: MemBackendRef): CanModFn =
|
|
result =
|
|
proc(): Result[void,KvtError] =
|
|
ok()
|
|
|
|
proc setWrReqFn(db: MemBackendRef): SetWrReqFn =
|
|
result =
|
|
proc(kvt: RootRef): Result[void,KvtError] =
|
|
err(RdbBeHostNotApplicable)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc memoryBackend*: BackendRef =
|
|
let db = MemBackendRef(
|
|
beKind: BackendMemory,
|
|
mdb: MemDbRef())
|
|
|
|
db.getKvpFn = getKvpFn db
|
|
db.lenKvpFn = lenKvpFn db
|
|
|
|
db.putBegFn = putBegFn db
|
|
db.putKvpFn = putKvpFn db
|
|
db.putEndFn = putEndFn db
|
|
|
|
db.closeFn = closeFn db
|
|
db.canModFn = canModFn db
|
|
db.setWrReqFn = setWrReqFn db
|
|
db
|
|
|
|
proc dup*(db: MemBackendRef): MemBackendRef =
|
|
## Duplicate descriptor shell as needed for API debugging
|
|
new result
|
|
init_common.init(result[], db[])
|
|
result.mdb = db.mdb
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public iterators (needs direct backend access)
|
|
# ------------------------------------------------------------------------------
|
|
|
|
iterator walk*(
|
|
be: MemBackendRef;
|
|
): tuple[key: Blob, data: Blob] =
|
|
## Walk over all key-value pairs of the database.
|
|
for (key,data) in be.mdb.tab.pairs:
|
|
if data.isValid:
|
|
yield (key, data)
|
|
else:
|
|
debug logTxt "walk() skip empty", key
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|