Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a3c00cd
txFrame serialization
advaita-saha Apr 24, 2026
896c245
Merge branch 'master' into txFrame-persist
advaita-saha May 7, 2026
93e8357
add realistic tests
advaita-saha May 7, 2026
c7fe704
use txFrame persist into the fc module
advaita-saha May 7, 2026
83dfbb9
some cleanups
advaita-saha May 7, 2026
dcc2892
Merge branch 'master' into txFrame-persist
advaita-saha May 7, 2026
1f96b12
fix: crash
advaita-saha May 7, 2026
143aab0
update comments
advaita-saha May 7, 2026
2120417
remove code redundancy
advaita-saha May 7, 2026
14f26be
Merge branch 'master' into txFrame-persist
advaita-saha May 10, 2026
8fc2c62
fix copyright year
advaita-saha May 11, 2026
b8547de
fix: suggestions
advaita-saha May 12, 2026
d8214bf
Merge branch 'master' into txFrame-persist
advaita-saha May 12, 2026
86c38fd
fix: isNil overload
advaita-saha May 12, 2026
20f3be1
fix
advaita-saha May 12, 2026
2a3fdf5
Lengths read as uint32 then widened to uint64 immediately (no signed-…
advaita-saha May 12, 2026
ac2d309
cap u32 in initTable
advaita-saha May 12, 2026
db827d8
delete frames after loading them
advaita-saha May 12, 2026
0f1a5e6
Merge branch 'master' into txFrame-persist
advaita-saha May 14, 2026
2b92240
Merge branch 'master' into txFrame-persist
advaita-saha May 20, 2026
1e976b6
include tests in the test suite
advaita-saha May 20, 2026
83288f9
more tests and cleanup
advaita-saha May 20, 2026
e8418ab
update comments
advaita-saha May 21, 2026
6a6ca40
Merge branch 'master' into txFrame-persist
advaita-saha May 21, 2026
ce94dca
Merge branch 'master' into txFrame-persist
advaita-saha May 21, 2026
0633246
fix copyright year
advaita-saha May 21, 2026
8e2fc45
reduce code duplication
advaita-saha May 21, 2026
8306b66
code cleanups
advaita-saha May 21, 2026
f05bfbb
Merge branch 'master' into txFrame-persist
advaita-saha May 21, 2026
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
118 changes: 37 additions & 81 deletions execution_chain/core/chain/forked_chain/chain_serialize.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import
eth/common/blocks_rlp,
./chain_desc,
./chain_branch,
./chain_private,
../../../db/core_db,
../../../db/fcu_db,
../../../db/storage_types,
../../../db/tx_frame_db,
../../../utils/utils

logScope:
Expand Down Expand Up @@ -46,9 +46,12 @@ type
# ------------------------------------------------------------------------------

proc append(w: var RlpWriter, b: BlockRef) =
let fullBlk = b.txFrame.getEthBlock(b.hash).expect("block body must be in txFrame during serialize")
# Only the header is persisted in the block-index entry. The full block
# body lives in the per-block txFrame blob (written separately under
# txFrameKey(b.hash)) and is no longer needed at deserialize time since
# we restore the txFrame directly instead of re-executing the block.
w.startList(3)
w.append(fullBlk)
w.append(b.header)
w.append(b.hash)
let parentIndex = if b.parent.isNil: 0'u
else: b.parent.index + 1'u
Expand Down Expand Up @@ -80,9 +83,7 @@ proc append(w: var RlpWriter, fc: ForkedChainRef) =
proc read(rlp: var Rlp, T: type BlockRef): T {.raises: [RlpError].} =
rlp.tryEnterList()
result = T()
var blk: Block
rlp.read(blk) # Parse full block from RLP (old format)
result.header = blk.header # Store only header in BlockRef
rlp.read(result.header)
rlp.read(result.hash)
rlp.read(result.index)

Expand Down Expand Up @@ -122,72 +123,40 @@ proc getState(db: CoreDbTxRef): Opt[FcState] =

err()

proc replayBlock(fc: ForkedChainRef;
parent: BlockRef,
blk: BlockRef,
fullBlk: Block): Result[void, string] =
let
parentFrame = parent.txFrame
txFrame = parentFrame.txFrameBegin()
blockAccessList = ?fc.baseTxFrame.getBlockAccessList(blk.hash)

# Set finalized to true in order to skip the stateroot check when replaying the
# block because the blocks should have already been checked previously during
# the initial block execution.
fc.processBlock(
parent,
txFrame,
fullBlk,
blockAccessList,
blk.hash,
finalized = true
).isOkOr:
txFrame.dispose()
return err(error)

# After processing the block the BAL should now be stored in the txFrame in
# memory so we can delete the copy on disk
if blockAccessList.isSome():
fc.baseTxFrame.deleteBlockAccessList(blk.hash)

# Checkpoint creates a snapshot of ancestor changes in txFrame - it is an
# expensive operation, specially when creating a new branch (ie when blk
# is being applied to a block that is currently not a head).
txFrame.checkpoint(blk.header.number, skipSnapshot = false)

blk.txFrame = txFrame

ok()

proc replayBranch(fc: ForkedChainRef;
parent: BlockRef;
head: BlockRef;
bodies: Table[Hash32, Block];
): Result[void, string] =

proc loadBranchTxFrames(parent: BlockRef;
head: BlockRef;
srcBase: CoreDbTxRef): Result[void, string] =
## Walk the branch from `parent` (exclusive, txFrame already set) up to
## `head` (inclusive), materialising each block's txFrame from its
## persisted blob as a child of the previous frame.
var blocks = newSeqOfCap[BlockRef](head.number - parent.number)
for it in ancestors(head):
if it.number > parent.number:
blocks.add it
else:
break

var parent = parent
var p = parent
for i in countdown(blocks.len-1, 0):
bodies.withValue(blocks[i].hash, fullBlk):
?fc.replayBlock(parent, blocks[i], fullBlk)
do:
return err("block body not found for hash: " & $blocks[i].hash)
parent = blocks[i]
let b = blocks[i]
let frame = srcBase.loadTxFrameAsChild(p.txFrame, b.hash).valueOr:
return err($error)
b.txFrame = frame
# The blob has been materialised into memory; drop the on-disk copy so
# it doesn't accumulate across restart/prune cycles. The delete sits in
# srcBase's in-memory delta and commits on the next baseTxFrame persist
# during normal chain operation.
srcBase.deleteTxFrame(b.hash).isOkOr:
return err($error)
p = b

ok()

proc replay(fc: ForkedChainRef; bodies: Table[Hash32, Block]): Result[void, string] =
proc loadAllTxFrames(fc: ForkedChainRef): Result[void, string] =
# Should have no parent
doAssert fc.base.parent.isNil

# Receipts for base block are loaded from database
# see `receiptsByBlockHash`
# Base block shares its txFrame with the on-disk base
fc.base.txFrame = fc.baseTxFrame

# Base block always have finalized marker
Expand All @@ -196,7 +165,7 @@ proc replay(fc: ForkedChainRef; bodies: Table[Hash32, Block]): Result[void, stri
for head in fc.heads:
for it in ancestors(head):
if it.txFrame.isNil.not:
?fc.replayBranch(it, head, bodies)
?loadBranchTxFrames(it, head, fc.baseTxFrame)
break

ok()
Expand Down Expand Up @@ -234,11 +203,11 @@ proc serialize*(fc: ForkedChainRef, txFrame: CoreDbTxRef): Result[void, CoreDbEr

for b in fc.hashToBlock.values:
?txFrame.put(blockIndexKey(b.index), rlp.encode(b))
# Move the BAL from the block txFrame into the target (base) txFrame
let bal = b.txFrame.getBlockAccessList(b.hash).valueOr:
Opt.none(BlockAccessListRef)
if bal.isSome():
txFrame.persistBlockAccessList(b.hash, bal.get())
# Persist the per-block txFrame delta (Aristo + KVT) so deserialize can
# restore the in-memory frame without re-executing the block. The base
# block shares its frame with the on-disk base and needs no blob.
if b != fc.base:
?txFrame.storeTxFrame(b.txFrame, b.hash)

info "Blocks DAG written to database",
base=fc.base.number,
Expand All @@ -259,9 +228,7 @@ proc deserialize*(fc: ForkedChainRef): Result[void, string] =
return err("Cannot find previous FC state in database")

let prevBase = fc.base
var
bodies: Table[Hash32, Block]
blocks = newSeq[BlockRef](state.numBlocks)
var blocks = newSeq[BlockRef](state.numBlocks)

# Sanity Checks for the FC state
if state.latest > state.numBlocks or
Expand All @@ -281,15 +248,7 @@ proc deserialize*(fc: ForkedChainRef): Result[void, string] =
for i in 0..<state.numBlocks:
let data = fc.baseTxFrame.get(blockIndexKey(i)).valueOr:
return err("Cannot find branch data")
# Single pass: parse full block for replay and keep only header in BlockRef
var r = rlpFromBytes(data)
r.tryEnterList()
var fullBlk: Block
r.read(fullBlk)
blocks[i] = BlockRef(header: fullBlk.header)
r.read(blocks[i].hash)
r.read(blocks[i].index)
bodies[blocks[i].hash] = move(fullBlk)
blocks[i] = rlp.decode(data, BlockRef)
except RlpError as exc:
return err(exc.msg)

Expand All @@ -315,9 +274,6 @@ proc deserialize*(fc: ForkedChainRef): Result[void, string] =
numBlocks=state.numBlocks,
heads=fc.heads.toString

if state.numBlocks > 64:
info "Please wait until DAG finish loading..."

if fc.base.hash != prevBase.hash:
fc.reset(prevBase)
return err("loaded baseHash != baseHash")
Expand All @@ -336,11 +292,11 @@ proc deserialize*(fc: ForkedChainRef): Result[void, string] =
b.parent = parentCandidate
fc.hashToBlock[b.hash] = b

fc.replay(bodies).isOkOr:
fc.loadAllTxFrames().isOkOr:
fc.reset(prevBase)
return err(error)

# All blocks should have replayed
# All blocks should have their txFrame loaded
for b in blocks:
if b.txFrame.isNil:
fc.reset(prevBase)
Expand Down
4 changes: 4 additions & 0 deletions execution_chain/db/aristo/aristo_desc/desc_error.nim
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ type
PartTrkLinkExpected
PartTrkRlpError

# TxFrame blobify/deblobify
DeblobTxFrameVersion
DeblobTxFrameTruncated

# RocksDB backend
RdbBeCantCreateTmpDir
RdbBeDriverDelAdmError
Expand Down
Loading
Loading