added commitInMemory method for synching optimization#7674
added commitInMemory method for synching optimization#7674raduchis wants to merge 6 commits intofeat/supernova-async-execfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an “in-memory commit” path to speed up node syncing by avoiding disk persistence on most blocks, while still computing root hashes and periodically performing full commits.
Changes:
- Extend
state.AccountsAdapterwithCommitInMemory()and implement it across adapters/stubs. - Add
AccountsDB.CommitInMemory()and integrate a sync-time commit interval optimization inbaseProcessor. - Add unit tests for
CommitInMemory()and for the sync commit interval decision logic.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| testscommon/state/accountsAdapterStub.go | Extends accounts adapter stub with CommitInMemory() for tests. |
| state/interface.go | Adds CommitInMemory() to AccountsAdapter interface. |
| state/accountsDB.go | Implements CommitInMemory() and internal commitInMemory() flow. |
| state/accountsDB_test.go | Adds tests for AccountsDB.CommitInMemory(). |
| state/accountsDBApi.go | Implements CommitInMemory() as not permitted for API adapter. |
| state/accountsDBApiWithHistory.go | Implements CommitInMemory() as not permitted for API-with-history adapter. |
| process/transactionEvaluator/simulationAccountsDB.go | Adds no-op CommitInMemory() for simulation DB. |
| epochStart/bootstrap/disabled/disabledAccountsAdapter.go | Adds no-op CommitInMemory() for disabled adapter. |
| process/block/baseProcess.go | Adds sync commit interval optimization + in-memory commit path during sync. |
| process/block/export_test.go | Exposes constants/helpers for testing the new optimization logic. |
| process/block/baseProcess_test.go | Adds tests for sync commit optimization and in-memory commit invocation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
state/accountsDB.go
Outdated
| return nil, err | ||
| } | ||
| } | ||
| adb.dataTries.Reset() |
There was a problem hiding this comment.
commitInMemory() calls adb.dataTries.Reset(), which clears the holder map (see state/dataTriesHolder.go:70-82). This drops references to dirty data tries without committing them, so a subsequent full Commit() may no longer flush those data tries to disk, risking missing persistence / corrupted state. Consider not resetting dataTries in CommitInMemory (or tracking/retaining dirty tries until the next full commit).
| adb.dataTries.Reset() |
| if headerHandler.IsStartOfEpochBlock() { | ||
| bp.resetSyncCommitCounter() | ||
| return bp.commitInLastEpoch(headerHandler.GetEpoch()) | ||
| } |
There was a problem hiding this comment.
In the start-of-epoch path, the deferred log will still print in memory=true because inMemory is initialized to true and never set to false before returning commitInLastEpoch(). This makes the log line misleading (epoch commits are persisted). Set inMemory=false before the early return, or initialize it to false and only set true on the in-memory path.
state/accountsDB_test.go
Outdated
| accnt, _ := adb.LoadAccount(make([]byte, 32)) | ||
| _ = adb.SaveAccount(accnt) |
There was a problem hiding this comment.
This test ignores the errors from LoadAccount and SaveAccount, which can mask a broken setup (e.g., TrieStub.UpdateCalled is unset so SaveAccount will error). Prefer asserting require.NoError and stubbing the trie to allow SaveAccount to succeed, so the test validates behavior under realistic conditions.
| // Call 5 times - first 4 should return true (in-memory), 5th should return false (full commit) | ||
| for i := 0; i < 4; i++ { | ||
| result := sp.ShouldUseSyncCommitOptimization(header) | ||
| assert.True(t, result, "should return true for block %d", i+1) |
There was a problem hiding this comment.
assert.True(t, result, "should return true for block %d", i+1) will not format the message (testify doesn't treat this as printf-style). Use assert.Truef/require.Truef, or pre-format the string, to keep failure output correct.
| assert.True(t, result, "should return true for block %d", i+1) | |
| assert.Truef(t, result, "should return true for block %d", i+1) |
state/accountsDBApi.go
Outdated
| // CommitInMemory is a not permitted operation in this implementation and thus, will return an error | ||
| func (accountsDB *accountsDBApi) CommitInMemory() ([]byte, error) { |
There was a problem hiding this comment.
Comment grammar: "CommitInMemory is a not permitted operation" should be "CommitInMemory is not a permitted operation" (or "CommitInMemory is not permitted").
| // Disabled if syncCommitInterval is 0 | ||
| if bp.syncCommitInterval == 0 { | ||
| return false | ||
| } |
There was a problem hiding this comment.
shouldUseSyncCommitOptimization() reads bp.syncCommitInterval without holding mutSyncCommit, but SetSyncCommitInterval() writes it under the mutex. This is an actual data race if the interval is ever updated at runtime (and also leaves blocksSinceLastCommit stale when interval is set to 0). Read syncCommitInterval under the same lock (or use atomics), and consider resetting the counter when disabling/changing the interval.
There was a problem hiding this comment.
Or if the SetSyncCommitInterval is meant to be used only in tests, move it to the export_test file.
There was a problem hiding this comment.
moved to export_test
| accnt, _ := adb.LoadAccount(make([]byte, 32)) | ||
| _ = adb.SaveAccount(accnt) |
There was a problem hiding this comment.
This test ignores the errors from LoadAccount and SaveAccount, which can mask a broken setup (e.g., TrieStub.UpdateCalled is unset so SaveAccount will error). Prefer asserting require.NoError and stubbing the trie to allow SaveAccount to succeed, so the test validates behavior under realistic conditions.
state/accountsDB_test.go
Outdated
| accnt, _ := adb.LoadAccount(make([]byte, 32)) | ||
| _ = adb.SaveAccount(accnt) |
There was a problem hiding this comment.
This test ignores the errors from LoadAccount and SaveAccount, which can mask a broken setup (e.g., TrieStub.UpdateCalled is unset so SaveAccount will error). Prefer asserting require.NoError and stubbing the trie to allow SaveAccount to succeed, so the test validates behavior under realistic conditions.
state/accountsDBApiWithHistory.go
Outdated
| // CommitInMemory is a not permitted operation in this implementation and thus, will return an error | ||
| func (accountsDB *accountsDBApiWithHistory) CommitInMemory() ([]byte, error) { |
There was a problem hiding this comment.
Comment grammar: "CommitInMemory is a not permitted operation" should be "CommitInMemory is not a permitted operation" (or "CommitInMemory is not permitted").
| // Disabled if syncCommitInterval is 0 | ||
| if bp.syncCommitInterval == 0 { | ||
| return false | ||
| } |
There was a problem hiding this comment.
Or if the SetSyncCommitInterval is meant to be used only in tests, move it to the export_test file.
| return adb.commitInMemory() | ||
| } | ||
|
|
||
| func (adb *AccountsDB) commitInMemory() ([]byte, error) { |
There was a problem hiding this comment.
There a couple issues with this approach:
- The dataTries holder reset drops the reference to all of the changed data tries. When an account is loaded, it will have the new root hash, so it will try to load the data trie with that root hash from the db, but since the dataTrie was not commited, the recreate from db will fail. To fix this, you can remove the reset, but
- If a revert occurs, a recreateFromDb will happen - and will fail
There was a problem hiding this comment.
Another solution would be to commit the data tries, but into a cache.
There was a problem hiding this comment.
Removed the reset from the commitInMemory. It should only happen on the normal Commit. Reverts should not happen because we are on sync and we do not do the commitInMemory if we are closer than 50 nonces from the consensus.
There was a problem hiding this comment.
Ok. This can be tested with an observer node started on mainnet with StartInEpoch, right?
# Conflicts: # process/block/baseProcess.go # process/block/baseProcess_test.go
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## feat/supernova-async-exec #7674 +/- ##
=============================================================
- Coverage 77.51% 77.51% -0.01%
=============================================================
Files 882 882
Lines 122760 122829 +69
=============================================================
+ Hits 95157 95207 +50
- Misses 21259 21274 +15
- Partials 6344 6348 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
BeniaminDrasovean
left a comment
There was a problem hiding this comment.
Another issue came to mind: The in-epoch pruning will not work with this approach. Also, state snapshot might be incompatible, if a snapshot is triggered while the tries are not yet committed to db.
| return adb.commitInMemory() | ||
| } | ||
|
|
||
| func (adb *AccountsDB) commitInMemory() ([]byte, error) { |
There was a problem hiding this comment.
Ok. This can be tested with an observer node started on mainnet with StartInEpoch, right?
Reasoning behind the pull request
Proposed changes
Testing procedure
Pre-requisites
Based on the Contributing Guidelines the PR author and the reviewers must check the following requirements are met:
featbranch created?featbranch merging, do all satellite projects have a proper tag insidego.mod?