Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 39 additions & 16 deletions execution/commitment/preload_parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ func estimatedEntryCost(key, value []byte) int {

// minEntryBytes: true lower bound on estimatedEntryCost for a storage-trunk
// branch (33 = shortest HexToCompact key at depth >= 64; value may be empty).
// Bounds a wave's file fetch so the budget is guaranteed exhausted inside it.
// Bounds a wave's file fetch to what the remaining budget could still pin; a
// wave that pins nothing anyway is ended by the no-progress check in Run.
const minEntryBytes = estimatedEntryOverheadBytes + 33

// maxStorageTrunkDepth: 64 (account path) + 64 (keccak256(slot)) = 128.
Expand Down Expand Up @@ -164,19 +165,19 @@ func (p *ContractTrunkPreloadParallel) Run(
return 0, false, fmt.Errorf("ContractTrunkPreloadParallel.Run: resolver is nil")
}
if stepBudgetBytes <= 0 {
return 0, len(p.frontier) == 0, nil
return 0, p.queueEmpty(), nil
}
defer p.releaseScratch()

stepCap := p.usedBytes + stepBudgetBytes
chunkPinned := 0
budgetHit := false
endStep := false

// pin records the entry and queues its children. Returns false on budget hit.
pin := func(pk pathKey, v []byte, depth int, next *[]pathKey) bool {
cost := estimatedEntryCost(pk.key, v)
if p.usedBytes+cost > stepCap {
budgetHit = true
endStep = true
return false
}
// step=0: a storage-trunk branch resolved across merged files has no single
Expand Down Expand Up @@ -210,13 +211,15 @@ func (p *ContractTrunkPreloadParallel) Run(
return true
}

for !budgetHit && p.nextDepth <= maxStorageTrunkDepth && len(p.frontier) > 0 {
for !endStep && p.nextDepth <= maxStorageTrunkDepth && len(p.frontier) > 0 {
depth := p.nextDepth
wavePinnedBefore := chunkPinned
dbHits, dbVals, fileMiss, dbHitsBytes := p.sortAndPartitionFrontier(dbBranches)

// Cap the file fetch by what the budget can absorb after dbHits.
// Cap the file fetch by what the budget can absorb after dbHits. Below
// minEntryBytes no file entry can be pinned, so fetching any is waste.
var fileMissDeferred []pathKey
if fileBudget := stepCap - p.usedBytes - dbHitsBytes; fileBudget <= 0 {
if fileBudget := stepCap - p.usedBytes - dbHitsBytes; fileBudget < minEntryBytes {
fileMissDeferred = fileMiss
fileMiss = nil
} else if maxFileFetch := fileBudget/minEntryBytes + 1; maxFileFetch < len(fileMiss) {
Expand Down Expand Up @@ -247,8 +250,9 @@ func (p *ContractTrunkPreloadParallel) Run(
}
p.dbHitsPinned++
}
fileMissStop := len(fileMiss)
if !budgetHit {
fileMissStop := 0
if !endStep {
fileMissStop = len(fileMiss)
for i, pk := range fileMiss {
v := fileVals[i]
if v == nil {
Expand All @@ -261,7 +265,16 @@ func (p *ContractTrunkPreloadParallel) Run(
}
}

if budgetHit {
// Re-entering only helps if the next iteration sees a different budget.
// It does not when the whole miss set was deferred (dbHits consume
// exactly the bytes the deferral already accounted for), nor when a
// capped fetch pinned nothing because its keys were absent from the file
// layer. Either way, end the step; the tail resumes on the next Run.
if len(fileMissDeferred) > 0 && (len(fileMiss) == 0 || chunkPinned == wavePinnedBefore) {
endStep = true
}

if endStep {
// Preserve un-pinned items at current depth; pendingChildren stays
// at depth+1 for when this depth is drained on a future Run.
rest := make([]pathKey, 0, len(dbHits)-dbHitStop+len(fileMiss)-fileMissStop+len(fileMissDeferred))
Expand All @@ -273,8 +286,9 @@ func (p *ContractTrunkPreloadParallel) Run(
}

if len(fileMissDeferred) > 0 {
// Defensive: !budgetHit should mean no truncation. Clone out of the
// scratch-aliased slice so the next wave's partition can't overwrite it.
// Capped fetch that still pinned something: stay at this depth and
// resume with the tail. Clone out of the scratch-aliased slice so the
// next wave's partition can't overwrite it.
p.frontier = slices.Clone(fileMissDeferred)
} else {
p.frontier = p.pendingChildren
Expand All @@ -283,7 +297,7 @@ func (p *ContractTrunkPreloadParallel) Run(
}
}

queueEmpty = (len(p.frontier) == 0 && len(p.pendingChildren) == 0) || p.nextDepth > maxStorageTrunkDepth
queueEmpty = p.queueEmpty()
if logger != nil && (chunkPinned > 0 || queueEmpty) {
logger.Info("[trunk-preload-parallel] step",
"step_budget_mb", stepBudgetBytes/(1<<20),
Expand All @@ -310,6 +324,12 @@ func (p *ContractTrunkPreloadParallel) QueueRemaining() int {
return len(p.frontier) + len(p.pendingChildren)
}

// queueEmpty reports that no further Run can pin anything: the walk drained, or
// it reached the depth ceiling with entries still queued below it.
func (p *ContractTrunkPreloadParallel) queueEmpty() bool {
return p.QueueRemaining() == 0 || p.nextDepth > maxStorageTrunkDepth
}

// PinnedPrefixes returns slices aliasing internal storage — do not mutate.
func (p *ContractTrunkPreloadParallel) PinnedPrefixes() [][]byte { return p.pinnedPrefixes }

Expand All @@ -326,13 +346,16 @@ func PreloadContractTrunkParallel(
if ramBudgetBytes <= 0 {
return 0, fmt.Errorf("PreloadContractTrunkParallel: ramBudgetBytes must be positive, got %d", ramBudgetBytes)
}
p, err := NewContractTrunkPreloadParallel(contractHash)
if err != nil {
return 0, err
if cache == nil {
return 0, fmt.Errorf("PreloadContractTrunkParallel: cache is nil")
}
if resolve == nil {
return 0, fmt.Errorf("PreloadContractTrunkParallel: resolver is nil")
}
p, err := NewContractTrunkPreloadParallel(contractHash)
if err != nil {
return 0, err
}
pinned, queueEmpty, err := p.Run(ramBudgetBytes, dbBranches, resolve, cache, logger)
if logger != nil {
logger.Info("[trunk-preload-parallel] complete",
Expand Down
Loading
Loading