Don't write slot hashes on import (#2564)

The reverse slot hash mechanism causes quite a bit of database traffic
but is broadly not useful except for iterating the storage of an
account, something that a validator never does (it's used by the
tracers).

This flag adds one more thing that is not stored in the database, to be
explored more comprehensively when designing full, validator and archive
modes with different pruning options in the future.

`ldb` says this is 60gb of data (!):
```
ldb --db=. --ignore_unknown_options --column_family=KvtGen approxsize
--hex --from=0x05
--to=0x05ffffffffffffffffffffffffffffffffffffffffffffff
66488353954
```
This commit is contained in:
Jacek Sieka 2024-08-16 08:22:51 +02:00 committed by GitHub
parent 4dbc1653ea
commit 43d93bcdab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 52 additions and 32 deletions

View File

@ -81,7 +81,7 @@ proc getVmState(c: ChainRef, header: BlockHeader):
return ok(c.vmState) return ok(c.vmState)
let vmState = BaseVMState() let vmState = BaseVMState()
if not vmState.init(header, c.com): if not vmState.init(header, c.com, storeSlotHash = storeSlotHash):
debug "Cannot initialise VmState", debug "Cannot initialise VmState",
number = header.number number = header.number
return err() return err()

View File

@ -550,6 +550,12 @@ type
defaultValue: false defaultValue: false
name: "debug-store-receipts".}: bool name: "debug-store-receipts".}: bool
storeSlotHashes* {.
hidden
desc: "Store reverse slot hashes in database"
defaultValue: false
name: "debug-store-slot-hashes".}: bool
func parseCmdArg(T: type NetworkId, p: string): T func parseCmdArg(T: type NetworkId, p: string): T
{.gcsafe, raises: [ValueError].} = {.gcsafe, raises: [ValueError].} =
parseInt(p).T parseInt(p).T

View File

@ -37,6 +37,7 @@ type
NoPersistUncles NoPersistUncles
NoPersistWithdrawals NoPersistWithdrawals
NoPersistReceipts NoPersistReceipts
NoPersistSlotHashes
PersistBlockFlags* = set[PersistBlockFlag] PersistBlockFlags* = set[PersistBlockFlag]
@ -54,12 +55,14 @@ const
# Private # Private
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc getVmState(c: ChainRef, header: BlockHeader): Result[BaseVMState, string] = proc getVmState(
c: ChainRef, header: BlockHeader, storeSlotHash = false
): Result[BaseVMState, string] =
if not c.vmState.isNil: if not c.vmState.isNil:
return ok(c.vmState) return ok(c.vmState)
let vmState = BaseVMState() let vmState = BaseVMState()
if not vmState.init(header, c.com): if not vmState.init(header, c.com, storeSlotHash = storeSlotHash):
return err("Could not initialise VMState") return err("Could not initialise VMState")
ok(vmState) ok(vmState)
@ -86,7 +89,8 @@ proc persistBlocksImpl(
# Note that `0 < headers.len`, assured when called from `persistBlocks()` # Note that `0 < headers.len`, assured when called from `persistBlocks()`
let let
vmState = ?c.getVmState(blocks[0].header) vmState =
?c.getVmState(blocks[0].header, storeSlotHash = NoPersistSlotHashes notin flags)
fromBlock = blocks[0].header.number fromBlock = blocks[0].header.number
toBlock = blocks[blocks.high()].header.number toBlock = blocks[blocks.high()].header.number
trace "Persisting blocks", fromBlock, toBlock trace "Persisting blocks", fromBlock, toBlock

View File

@ -22,18 +22,16 @@ import
./ledger/base/[base_config, base_desc, base_helpers], ./ledger/base/[base_config, base_desc, base_helpers],
./ledger/[base, base_iterators] ./ledger/[base, base_iterators]
export export AccountsLedgerRef, base, base_config, base_iterators
AccountsLedgerRef,
base,
base_config,
base_iterators
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Public constructor # Public constructor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
proc init*(_: type LedgerRef, db: CoreDbRef; root: Hash256): LedgerRef = proc init*(
LedgerRef(ac: AccountsLedgerRef.init(db, root)).bless(db) _: type LedgerRef, db: CoreDbRef, root: Hash256, storeSlotHash: bool = false
): LedgerRef =
LedgerRef(ac: AccountsLedgerRef.init(db, root, storeSlotHash)).bless(db)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# End # End

View File

@ -65,6 +65,7 @@ type
witnessCache: Table[EthAddress, WitnessData] witnessCache: Table[EthAddress, WitnessData]
isDirty: bool isDirty: bool
ripemdSpecial: bool ripemdSpecial: bool
storeSlotHash*: bool
cache: Table[EthAddress, AccountRef] cache: Table[EthAddress, AccountRef]
# Second-level cache for the ledger save point, which is cleared on every # Second-level cache for the ledger save point, which is cleared on every
# persist # persist
@ -149,11 +150,12 @@ proc resetCoreDbAccount(ac: AccountsLedgerRef, acc: AccountRef) =
# The AccountsLedgerRef is modeled after TrieDatabase for it's transaction style # The AccountsLedgerRef is modeled after TrieDatabase for it's transaction style
proc init*(x: typedesc[AccountsLedgerRef], db: CoreDbRef, proc init*(x: typedesc[AccountsLedgerRef], db: CoreDbRef,
root: KeccakHash): AccountsLedgerRef = root: KeccakHash, storeSlotHash: bool): AccountsLedgerRef =
new result new result
result.ledger = db.ctx.getAccounts() result.ledger = db.ctx.getAccounts()
result.kvt = db.ctx.getKvt() result.kvt = db.ctx.getKvt()
result.witnessCache = Table[EthAddress, WitnessData]() result.witnessCache = Table[EthAddress, WitnessData]()
result.storeSlotHash = storeSlotHash
discard result.beginSavepoint discard result.beginSavepoint
proc init*(x: typedesc[AccountsLedgerRef], db: CoreDbRef): AccountsLedgerRef = proc init*(x: typedesc[AccountsLedgerRef], db: CoreDbRef): AccountsLedgerRef =
@ -400,7 +402,7 @@ proc persistStorage(acc: AccountRef, ac: AccountsLedgerRef) =
discard discard
acc.originalStorage.del(slot) acc.originalStorage.del(slot)
if not cached: if ac.storeSlotHash and not cached:
# Write only if it was not cached to avoid writing the same data over and # Write only if it was not cached to avoid writing the same data over and
# over.. # over..
let let

View File

@ -73,7 +73,8 @@ proc new*(
parent: BlockHeader; ## parent header, account sync position parent: BlockHeader; ## parent header, account sync position
blockCtx: BlockContext; blockCtx: BlockContext;
com: CommonRef; ## block chain config com: CommonRef; ## block chain config
tracer: TracerRef = nil): T = tracer: TracerRef = nil,
storeSlotHash = false): T =
## Create a new `BaseVMState` descriptor from a parent block header. This ## Create a new `BaseVMState` descriptor from a parent block header. This
## function internally constructs a new account state cache rooted at ## function internally constructs a new account state cache rooted at
## `parent.stateRoot` ## `parent.stateRoot`
@ -83,7 +84,7 @@ proc new*(
## with the `parent` block header. ## with the `parent` block header.
new result new result
result.init( result.init(
ac = LedgerRef.init(com.db, parent.stateRoot), ac = LedgerRef.init(com.db, parent.stateRoot, storeSlotHash),
parent = parent, parent = parent,
blockCtx = blockCtx, blockCtx = blockCtx,
com = com, com = com,
@ -109,7 +110,7 @@ proc reinit*(self: BaseVMState; ## Object descriptor
com = self.com com = self.com
db = com.db db = com.db
ac = if linear or self.stateDB.rootHash == parent.stateRoot: self.stateDB ac = if linear or self.stateDB.rootHash == parent.stateRoot: self.stateDB
else: LedgerRef.init(db, parent.stateRoot) else: LedgerRef.init(db, parent.stateRoot, self.stateDB.ac.storeSlotHash)
flags = self.flags flags = self.flags
self[].reset self[].reset
self.init( self.init(
@ -157,7 +158,8 @@ proc init*(
parent: BlockHeader; ## parent header, account sync position parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## header with tx environment data fields header: BlockHeader; ## header with tx environment data fields
com: CommonRef; ## block chain config com: CommonRef; ## block chain config
tracer: TracerRef = nil) = tracer: TracerRef = nil,
storeSlotHash = false) =
## Variant of `new()` constructor above for in-place initalisation. The ## Variant of `new()` constructor above for in-place initalisation. The
## `parent` argument is used to sync the accounts cache and the `header` ## `parent` argument is used to sync the accounts cache and the `header`
## is used as a container to pass the `timestamp`, `gasLimit`, and `fee` ## is used as a container to pass the `timestamp`, `gasLimit`, and `fee`
@ -166,7 +168,7 @@ proc init*(
## It requires the `header` argument properly initalised so that for PoA ## It requires the `header` argument properly initalised so that for PoA
## networks, the miner address is retrievable via `ecRecover()`. ## networks, the miner address is retrievable via `ecRecover()`.
self.init( self.init(
ac = LedgerRef.init(com.db, parent.stateRoot), ac = LedgerRef.init(com.db, parent.stateRoot, storeSlotHash),
parent = parent, parent = parent,
blockCtx = com.blockCtx(header), blockCtx = com.blockCtx(header),
com = com, com = com,
@ -177,7 +179,8 @@ proc new*(
parent: BlockHeader; ## parent header, account sync position parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## header with tx environment data fields header: BlockHeader; ## header with tx environment data fields
com: CommonRef; ## block chain config com: CommonRef; ## block chain config
tracer: TracerRef = nil): T = tracer: TracerRef = nil,
storeSlotHash = false): T =
## This is a variant of the `new()` constructor above where the `parent` ## This is a variant of the `new()` constructor above where the `parent`
## argument is used to sync the accounts cache and the `header` is used ## argument is used to sync the accounts cache and the `header` is used
## as a container to pass the `timestamp`, `gasLimit`, and `fee` values. ## as a container to pass the `timestamp`, `gasLimit`, and `fee` values.
@ -189,13 +192,15 @@ proc new*(
parent = parent, parent = parent,
header = header, header = header,
com = com, com = com,
tracer = tracer) tracer = tracer,
storeSlotHash = storeSlotHash)
proc new*( proc new*(
T: type BaseVMState; T: type BaseVMState;
header: BlockHeader; ## header with tx environment data fields header: BlockHeader; ## header with tx environment data fields
com: CommonRef; ## block chain config com: CommonRef; ## block chain config
tracer: TracerRef = nil): EvmResult[T] = tracer: TracerRef = nil,
storeSlotHash = false): EvmResult[T] =
## This is a variant of the `new()` constructor above where the field ## This is a variant of the `new()` constructor above where the field
## `header.parentHash`, is used to fetch the `parent` BlockHeader to be ## `header.parentHash`, is used to fetch the `parent` BlockHeader to be
## used in the `new()` variant, above. ## used in the `new()` variant, above.
@ -205,7 +210,8 @@ proc new*(
parent = parent, parent = parent,
header = header, header = header,
com = com, com = com,
tracer = tracer)) tracer = tracer,
storeSlotHash = storeSlotHash))
else: else:
err(evmErr(EvmHeaderNotFound)) err(evmErr(EvmHeaderNotFound))
@ -213,7 +219,8 @@ proc init*(
vmState: BaseVMState; vmState: BaseVMState;
header: BlockHeader; ## header with tx environment data fields header: BlockHeader; ## header with tx environment data fields
com: CommonRef; ## block chain config com: CommonRef; ## block chain config
tracer: TracerRef = nil): bool = tracer: TracerRef = nil,
storeSlotHash = false): bool =
## Variant of `new()` which does not throw an exception on a dangling ## Variant of `new()` which does not throw an exception on a dangling
## `BlockHeader` parent hash reference. ## `BlockHeader` parent hash reference.
var parent: BlockHeader var parent: BlockHeader
@ -222,7 +229,8 @@ proc init*(
parent = parent, parent = parent,
header = header, header = header,
com = com, com = com,
tracer = tracer) tracer = tracer,
storeSlotHash = storeSlotHash)
return true return true
func coinbase*(vmState: BaseVMState): EthAddress = func coinbase*(vmState: BaseVMState): EthAddress =

View File

@ -103,7 +103,8 @@ proc importBlocks*(conf: NimbusConf, com: CommonRef) =
boolFlag({PersistBlockFlag.NoValidation}, conf.noValidation) + boolFlag({PersistBlockFlag.NoValidation}, conf.noValidation) +
boolFlag({PersistBlockFlag.NoFullValidation}, not conf.fullValidation) + boolFlag({PersistBlockFlag.NoFullValidation}, not conf.fullValidation) +
boolFlag(NoPersistBodies, not conf.storeBodies) + boolFlag(NoPersistBodies, not conf.storeBodies) +
boolFlag({PersistBlockFlag.NoPersistReceipts}, not conf.storeReceipts) boolFlag({PersistBlockFlag.NoPersistReceipts}, not conf.storeReceipts) +
boolFlag({PersistBlockFlag.NoPersistSlotHashes}, not conf.storeSlotHashes)
blocks: seq[EthBlock] blocks: seq[EthBlock]
clConfig: Eth2NetworkMetadata clConfig: Eth2NetworkMetadata
genesis_validators_root: Eth2Digest genesis_validators_root: Eth2Digest

View File

@ -169,7 +169,7 @@ proc traceTransactionImpl(
let let
tracerInst = newLegacyTracer(tracerFlags) tracerInst = newLegacyTracer(tracerFlags)
cc = activate CaptCtxRef.init(com, header) cc = activate CaptCtxRef.init(com, header)
vmState = BaseVMState.new(header, com).valueOr: return newJNull() vmState = BaseVMState.new(header, com, storeSlotHash = true).valueOr: return newJNull()
stateDb = vmState.stateDB stateDb = vmState.stateDB
defer: cc.release() defer: cc.release()
@ -217,7 +217,7 @@ proc traceTransactionImpl(
# internal transactions: # internal transactions:
let let
cx = activate stateCtx cx = activate stateCtx
ldgBefore = LedgerRef.init(com.db, cx.root) ldgBefore = LedgerRef.init(com.db, cx.root, storeSlotHash = true)
defer: cx.release() defer: cx.release()
for idx, acc in tracedAccountsPairs(tracerInst): for idx, acc in tracedAccountsPairs(tracerInst):
@ -252,7 +252,7 @@ proc dumpBlockStateImpl(
# only need a stack dump when scanning for internal transaction address # only need a stack dump when scanning for internal transaction address
captureFlags = {DisableMemory, DisableStorage, EnableAccount} captureFlags = {DisableMemory, DisableStorage, EnableAccount}
tracerInst = newLegacyTracer(captureFlags) tracerInst = newLegacyTracer(captureFlags)
vmState = BaseVMState.new(header, com, tracerInst).valueOr: vmState = BaseVMState.new(header, com, tracerInst, storeSlotHash = true).valueOr:
return newJNull() return newJNull()
miner = vmState.coinbase() miner = vmState.coinbase()
@ -261,7 +261,7 @@ proc dumpBlockStateImpl(
var var
before = newJArray() before = newJArray()
after = newJArray() after = newJArray()
stateBefore = LedgerRef.init(com.db, parent.stateRoot) stateBefore = LedgerRef.init(com.db, parent.stateRoot, storeSlotHash = true)
for idx, tx in blk.transactions: for idx, tx in blk.transactions:
let sender = tx.getSender let sender = tx.getSender
@ -316,7 +316,8 @@ proc traceBlockImpl(
let let
cc = activate CaptCtxRef.init(com, header) cc = activate CaptCtxRef.init(com, header)
tracerInst = newLegacyTracer(tracerFlags) tracerInst = newLegacyTracer(tracerFlags)
vmState = BaseVMState.new(header, com, tracerInst).valueOr: # Tracer needs a database where the reverse slot hash table has been set up
vmState = BaseVMState.new(header, com, tracerInst, storeSlotHash = true).valueOr:
return newJNull() return newJNull()
defer: cc.release() defer: cc.release()

View File

@ -675,7 +675,7 @@ proc runLedgerBasicOperationsTests() =
check ac.contractCollision(addr4) == true check ac.contractCollision(addr4) == true
test "Ledger storage iterator": test "Ledger storage iterator":
var ac = LedgerRef.init(memDB, EMPTY_ROOT_HASH) var ac = LedgerRef.init(memDB, EMPTY_ROOT_HASH, storeSlotHash = true)
let addr2 = initAddr(2) let addr2 = initAddr(2)
ac.setStorage(addr2, 1.u256, 2.u256) ac.setStorage(addr2, 1.u256, 2.u256)
ac.setStorage(addr2, 2.u256, 3.u256) ac.setStorage(addr2, 2.u256, 3.u256)