Skip to content

Commit a81cc87

Browse files
db/state, db/integrity: FilesOnlyStateReader for commitment rebuild and CommitmentRoot (#21026)
## LimitedHistoryStateReader -> FilesOnlyStateReader `CommitmentRoot` has a subcheck `checkCommitmentRootViaRecompute`: it touches/replays content of the final block in commitment.kv file and does `ComputeCommitment`. The resulting root hash should remain unchanged. `RebuildCommitment`: it creates shard of size 16 and ultimately merged. Both need contrainted/limited query of state data (i.e. only return value for key K from before step X) both suffer from 2 problems today: - `getLatestFromFiles(maxTxNum)`: it effectively searches only a single kv file (the one that contains maxTxNum). The files "left and right" of it are ignored. - `LimitedHistoryStateReader`: if `getLatestFromFiles(maxTxNum)` returns nil, it fallbacks to GetLatest. This creates problem like: - commitment prefix is queried, it's not there in current step range. It's there in some previous file, so it'd return nil (because of `getLatestFromFiles` logic) - say we're building 128-192 range, we look for a storage slot key which became nil in this range; `LimitedHistoryStateReader` would fallback on GetLatest in this case. FilesOnlyStateReader: it's essentially LimitedHistoryStateReader, without the fallback on `GetLatest`. ## getLatestFromFiles walkback the problem is mentioned above. The fix simply walks back from "maxTxNum containing snapshot" to the first snapshot. The change only impacts when `maxTxNum != uint64.max` which is used in `FilesOnlyStateReader`, `checkCommitmentRootViaFileData` and `commitment print` command. ## checkCommitmentRootViaRecompute doing TouchKey only on accounts domain. - `checkCommitmentRootViaRecompute` — switch from account-domain-only `touchHistoricalKeys` to `sd.TouchChangedKeysFromHistory` (accounts + storage; code is covered transitively via `account.codeHash`). This is the same helper used by `rpc/rpchelper/commitment.go` and receipts generation.
1 parent fa2ab7f commit a81cc87

5 files changed

Lines changed: 36 additions & 59 deletions

File tree

db/integrity/commitment_integrity.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ func checkCommitmentRootViaSd(ctx context.Context, tx kv.TemporalTx, f state.Vis
207207
return nil, err
208208
}
209209
sd.GetCommitmentCtx().SetTrace(logger.Enabled(ctx, log.LvlTrace))
210-
sd.GetCommitmentCtx().SetLimitedHistoryStateReader(tx, maxTxNum) // to use tx.Debug().GetLatestFromFiles with maxTxNum
211-
latestTxNum, _, err := sd.SeekCommitment(ctx, tx) // seek commitment again to use the new state reader instead
210+
sd.GetCommitmentCtx().SetStateReader(commitmentdb.NewFilesOnlyStateReader(tx, maxTxNum))
211+
latestTxNum, _, err := sd.SeekCommitment(ctx, tx) // seek commitment again to use the new state reader instead
212212
if err != nil {
213213
return nil, err
214214
}
@@ -240,17 +240,13 @@ func checkCommitmentRootViaSd(ctx context.Context, tx kv.TemporalTx, f state.Vis
240240
}
241241

242242
func checkCommitmentRootViaRecompute(ctx context.Context, tx kv.TemporalTx, sd *execctx.SharedDomains, info commitmentRootInfo, f state.VisibleFile, logger log.Logger) error {
243-
trace := logger.Enabled(ctx, log.LvlTrace)
244-
touchLoggingVisitor := func(k []byte) {
245-
if trace {
246-
logger.Trace("[integrity] CommitmentRoot", "key", common.Address(k), "blockNum", info.blockNum, "file", filepath.Base(f.Fullpath()))
247-
}
248-
}
249-
touches, err := touchHistoricalKeys(sd, tx, kv.AccountsDomain, info.blockMinTxNum, info.txNum+1, 0, nil /* no pre-built index */, touchLoggingVisitor)
243+
accountTouches, storageTouches, err := sd.TouchChangedKeysFromHistory(tx, info.blockMinTxNum, info.txNum+1)
250244
if err != nil {
251245
return err
252246
}
253-
logger.Info("[integrity] CommitmentRoot recomputing", "touches", touches, "file", filepath.Base(f.Fullpath()))
247+
logger.Info("[integrity] CommitmentRoot recomputing",
248+
"accountTouches", accountTouches, "storageTouches", storageTouches,
249+
"file", filepath.Base(f.Fullpath()))
254250
recomputedBytes, err := sd.ComputeCommitment(ctx, tx, false /* saveStateAfter */, info.blockNum, info.txNum, "", nil /* commitProgress */)
255251
if err != nil {
256252
return err
@@ -259,7 +255,10 @@ func checkCommitmentRootViaRecompute(ctx context.Context, tx kv.TemporalTx, sd *
259255
if recomputed != info.rootHash {
260256
return fmt.Errorf("%w: recomputed root does not match verified root: %s != %s", ErrIntegrity, recomputed, info.rootHash)
261257
}
262-
logger.Info("[integrity] CommitmentRoot recomputed matches", "root", recomputed, "touches", touches, "file", filepath.Base(f.Fullpath()))
258+
logger.Info("[integrity] CommitmentRoot recomputed matches",
259+
"root", recomputed,
260+
"accountTouches", accountTouches, "storageTouches", storageTouches,
261+
"file", filepath.Base(f.Fullpath()))
263262
return nil
264263
}
265264

db/state/domain.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,9 +1333,10 @@ func (dt *DomainRoTx) getLatestFromFiles(k []byte, maxTxNum uint64) (v []byte, f
13331333
}
13341334
}
13351335

1336+
// Walk newest→oldest; skip files starting strictly after maxTxNum so a key's
1337+
// last write in an older .kv is still found (walkback bounded by maxTxNum).
13361338
for i := len(dt.files) - 1; i >= 0; i-- {
1337-
if maxTxNum != math.MaxUint64 && (dt.files[i].startTxNum > maxTxNum || maxTxNum > dt.files[i].endTxNum) { // (maxTxNum > dt.files[i].endTxNum || dt.files[i].startTxNum > maxTxNum) { // skip partially matched files
1338-
//fmt.Printf("getLatestFromFiles: skipping file %d %s, maxTxNum=%d, startTxNum=%d, endTxNum=%d\n", i, dt.files[i].src.decompressor.FileName(), maxTxNum, dt.files[i].startTxNum, dt.files[i].endTxNum)
1339+
if maxTxNum != math.MaxUint64 && dt.files[i].startTxNum > maxTxNum {
13391340
continue
13401341
}
13411342
// fmt.Printf("getLatestFromFiles: lim=%d %d %d %d %d\n", maxTxNum, dt.files[i].startTxNum, dt.files[i].endTxNum, dt.files[i].startTxNum/dt.stepSize, dt.files[i].endTxNum/dt.stepSize)

db/state/squeeze.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ func RebuildCommitmentFiles(ctx context.Context, rwDb kv.TemporalRwDB, txNumsRea
987987

988988
domains.SetTxNum(lastTxnumInShard - 1)
989989
currentTxNum := lastTxnumInShard - 1
990-
domains.GetCommitmentCtx().SetLimitedHistoryStateReader(rwTx, lastTxnumInShard) // this helps to read state from correct file during commitment
990+
domains.GetCommitmentCtx().SetStateReader(commitmentdb.NewFilesOnlyStateReader(rwTx, lastTxnumInShard-1))
991991
if concurrent {
992992
domains.EnableParaTrieDB(rwDb)
993993
}

execution/commitment/commitmentdb/commitment_context.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,6 @@ func (sdc *SharedDomainsCommitmentContext) SetCustomHistoryStateReader(stateRead
141141
sdc.SetStateReader(stateReader)
142142
}
143143

144-
// SetLimitedHistoryStateReader sets the state reader to read *limited* (i.e. *without-recent-files*) historical state at specified txNum.
145-
func (sdc *SharedDomainsCommitmentContext) SetLimitedHistoryStateReader(roTx kv.TemporalTx, limitReadAsOfTxNum uint64) {
146-
sdc.SetStateReader(NewLimitedHistoryStateReader(roTx, sdc.sharedDomains, limitReadAsOfTxNum))
147-
}
148-
149144
func (sdc *SharedDomainsCommitmentContext) SetTrace(trace bool) {
150145
sdc.trace = trace
151146
sdc.patriciaTrie.SetTrace(trace)

execution/commitment/commitmentdb/reader.go

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -80,57 +80,39 @@ func (r *HistoryStateReader) Clone(tx kv.TemporalTx) StateReader {
8080
return NewHistoryStateReader(tx, r.limitReadAsOfTxNum)
8181
}
8282

83-
// LimitedHistoryStateReader reads from *limited* (i.e. *without-recent-files*) state at specified txNum, otherwise from *latest*.
84-
// `limitReadAsOfTxNum` here is used for unusual operation: "hide recent .kv files and read the latest state from files".
85-
type LimitedHistoryStateReader struct {
86-
HistoryStateReader
87-
sharedDomains sd
88-
getter kv.TemporalGetter
83+
// FilesOnlyStateReader reads from .kv files only, capped at limitTxNum.
84+
// On miss (key not present in any frozen .kv file ≤ limitTxNum), returns nil
85+
// without any fallback. This is the right semantic for integrity checks and
86+
// commitment rebuild that validate "what does the .kv snapshot at this
87+
// boundary actually contain?": no consultation of history index, no
88+
// consultation of current DB state, no consultation of .kv files past the
89+
// boundary.
90+
type FilesOnlyStateReader struct {
91+
roTx kv.TemporalTx
92+
limitTxNum uint64
8993
}
9094

91-
func NewLimitedHistoryStateReader(roTx kv.TemporalTx, sd sd, limitReadAsOfTxNum uint64) *LimitedHistoryStateReader {
92-
return &LimitedHistoryStateReader{
93-
HistoryStateReader: HistoryStateReader{
94-
roTx: roTx,
95-
limitReadAsOfTxNum: limitReadAsOfTxNum,
96-
},
97-
sharedDomains: sd,
98-
getter: sd.AsGetter(roTx),
99-
}
95+
func NewFilesOnlyStateReader(roTx kv.TemporalTx, limitTxNum uint64) *FilesOnlyStateReader {
96+
return &FilesOnlyStateReader{roTx: roTx, limitTxNum: limitTxNum}
10097
}
10198

102-
func (r *LimitedHistoryStateReader) WithHistory() bool {
103-
return false
104-
}
99+
func (r *FilesOnlyStateReader) WithHistory() bool { return false }
100+
101+
func (r *FilesOnlyStateReader) CheckDataAvailable(kv.Domain, kv.Step) error { return nil }
105102

106-
// Reason why we have `kv.TemporalDebugTx.GetLatestFromFiles' call here: `state.RebuildCommitmentFiles` can build commitment.kv from account.kv.
107-
// Example: we have account.0-16.kv and account.16-18.kv, let's generate commitment.0-16.kv => it means we need to make account.16-18.kv invisible
108-
// and then read "latest state" like there is no account.16-18.kv
109-
func (r *LimitedHistoryStateReader) Read(d kv.Domain, plainKey []byte, stepSize uint64) (enc []byte, step kv.Step, err error) {
110-
var ok bool
111-
var endTxNum uint64
112-
// reading from domain files this way will dereference domain key correctly,
113-
// GetAsOf itself does not dereference keys in commitment domain values
114-
enc, ok, _, endTxNum, err = r.roTx.Debug().GetLatestFromFiles(d, plainKey, r.limitReadAsOfTxNum)
103+
func (r *FilesOnlyStateReader) Read(d kv.Domain, plainKey []byte, stepSize uint64) (enc []byte, step kv.Step, err error) {
104+
enc, ok, _, endTxNum, err := r.roTx.Debug().GetLatestFromFiles(d, plainKey, r.limitTxNum)
115105
if err != nil {
116-
return nil, 0, fmt.Errorf("LimitedHistoryStateReader(GetLatestFromFiles) %q: (limitTxNum=%d): %w", d, r.limitReadAsOfTxNum, err)
106+
return nil, 0, fmt.Errorf("FilesOnlyStateReader %q (limitTxNum=%d): %w", d, r.limitTxNum, err)
117107
}
118108
if !ok {
119-
enc = nil
120-
} else {
121-
step = kv.Step(endTxNum / stepSize)
109+
return nil, 0, nil
122110
}
123-
if enc == nil {
124-
enc, step, err = r.getter.GetLatest(d, plainKey)
125-
if err != nil {
126-
return nil, 0, fmt.Errorf("LimitedHistoryStateReader(GetLatest) %q: %w", d, err)
127-
}
128-
}
129-
return enc, step, nil
111+
return enc, kv.Step(endTxNum / stepSize), nil
130112
}
131113

132-
func (r *LimitedHistoryStateReader) Clone(tx kv.TemporalTx) StateReader {
133-
return NewLimitedHistoryStateReader(tx, r.sharedDomains, r.limitReadAsOfTxNum)
114+
func (r *FilesOnlyStateReader) Clone(tx kv.TemporalTx) StateReader {
115+
return NewFilesOnlyStateReader(tx, r.limitTxNum)
134116
}
135117

136118
// SplitStateReader implements commitmentdb.StateReader using (potentially) different state readers for commitment

0 commit comments

Comments
 (0)