Skip to content

Commit d80f530

Browse files
authored
Clean lastSnapshots when finalizing (#3161)
When cleaning snapshots, it may happen that frames have already been written (via base updates) leading to a crash when trying to clean up their snapshot - `deques` doesn't readily support removing items in the middle so use a circular buffer instead. Also fixes an issue where a partial database write may lead to a "future" block header may turn up in the database - when this happens, we don't have an in-memory state that can be advanced and the next block will fail to apply. Most likely, this happens because of how the syncer shortcuts txframe updates when writing headers, which in and of itself is a problem that needs a proper solution - in the meantime however, checking that the state exists allows recovering from such an inconsistent database.
1 parent e2d1dfa commit d80f530

File tree

3 files changed

+14
-6
lines changed

3 files changed

+14
-6
lines changed

execution_chain/beacon/api_handler/api_newpayload.nim

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ proc newPayload*(ben: BeaconEngineRef,
161161

162162
# If we already have the block locally, ignore the entire execution and just
163163
# return a fake success.
164-
if ben.chain.haveBlockLocally(blockHash):
164+
if ben.chain.haveBlockAndState(blockHash):
165165
warn "Ignoring already known beacon payload",
166166
number = header.number, hash = blockHash.short
167167
return validStatus(blockHash)

execution_chain/core/chain/forked_chain.nim

+11-4
Original file line numberDiff line numberDiff line change
@@ -142,20 +142,22 @@ proc validateBlock(c: ForkedChainRef,
142142

143143
c.writeBaggage(blk, blkHash, txFrame, receipts)
144144

145-
while c.lastSnapshots.len() >= 10:
145+
let pos = c.lastSnapshotPos
146+
c.lastSnapshotPos = (c.lastSnapshotPos + 1) mod c.lastSnapshots.len
147+
if not isNil(c.lastSnapshots[pos]):
146148
# Put a cap on frame memory usage by clearing out the oldest snapshots -
147149
# this works at the expense of making building on said branches slower.
148150
# 10 is quite arbitrary.
149-
let oldFrame = c.lastSnapshots.popFirst()
150-
oldFrame.clearSnapshot()
151+
c.lastSnapshots[pos].clearSnapshot()
152+
c.lastSnapshots[pos] = nil
151153

152154
# Block fully written to txFrame, mark it as such
153155
# Checkpoint creates a snapshot of ancestor changes in txFrame - it is an
154156
# expensive operation, specially when creating a new branch (ie when blk
155157
# is being applied to a block that is currently not a head)
156158
txFrame.checkpoint(blk.header.number)
157159

158-
c.lastSnapshots.addLast(txFrame)
160+
c.lastSnapshots[pos] = txFrame
159161

160162
c.updateBranch(parent, blk, blkHash, txFrame, move(receipts))
161163

@@ -281,6 +283,11 @@ proc removeBlockFromCache(c: ForkedChainRef, bd: BlockDesc) =
281283
c.hashToBlock.del(bd.hash)
282284
for tx in bd.blk.transactions:
283285
c.txRecords.del(rlpHash(tx))
286+
287+
for v in c.lastSnapshots.mitems():
288+
if v == bd.txFrame:
289+
v = nil
290+
284291
bd.txFrame.dispose()
285292

286293
proc updateHead(c: ForkedChainRef, head: BlockPos) =

execution_chain/core/chain/forked_chain/chain_desc.nim

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ type
3434
extraValidation*: bool
3535
baseDistance*: uint64
3636

37-
lastSnapshots*: Deque[CoreDbTxRef]
37+
lastSnapshots*: array[10, CoreDbTxRef]
38+
lastSnapshotPos*: int
3839
# ----------------
3940

4041
func txRecords*(c: ForkedChainRef): var Table[Hash32, (Hash32, uint64)] =

0 commit comments

Comments
 (0)