Skip to content

added commitInMemory method for synching optimization#7674

Open
raduchis wants to merge 6 commits intofeat/supernova-async-execfrom
commit-in-memory
Open

added commitInMemory method for synching optimization#7674
raduchis wants to merge 6 commits intofeat/supernova-async-execfrom
commit-in-memory

Conversation

@raduchis
Copy link
Copy Markdown
Contributor

@raduchis raduchis commented Feb 5, 2026

Reasoning behind the pull request

  • synching blocks on supernova is fast -> 10 blocks per second but it is still kinda slow

Proposed changes

  • looking in the logs for a meta node, commit account state took ~50-70 ms. Created an commitInMemory every 9 out of 10 blocks and the 10th block do a normal commit.

Testing procedure

  • Remove DB with and without the current branch and see that the current branch synchronizez ~5x faster

Pre-requisites

Based on the Contributing Guidelines the PR author and the reviewers must check the following requirements are met:

  • was the PR targeted to the correct branch?
  • if this is a larger feature that probably needs more than one PR, is there a feat branch created?
  • if this is a feat branch merging, do all satellite projects have a proper tag inside go.mod?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.AccountsAdapter with CommitInMemory() and implement it across adapters/stubs.
  • Add AccountsDB.CommitInMemory() and integrate a sync-time commit interval optimization in baseProcessor.
  • 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.

return nil, err
}
}
adb.dataTries.Reset()
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
adb.dataTries.Reset()

Copilot uses AI. Check for mistakes.
Comment on lines 2137 to 2140
if headerHandler.IsStartOfEpochBlock() {
bp.resetSyncCommitCounter()
return bp.commitInLastEpoch(headerHandler.GetEpoch())
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +3527 to +3528
accnt, _ := adb.LoadAccount(make([]byte, 32))
_ = adb.SaveAccount(accnt)
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
// 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)
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
assert.True(t, result, "should return true for block %d", i+1)
assert.Truef(t, result, "should return true for block %d", i+1)

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +137
// CommitInMemory is a not permitted operation in this implementation and thus, will return an error
func (accountsDB *accountsDBApi) CommitInMemory() ([]byte, error) {
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment grammar: "CommitInMemory is a not permitted operation" should be "CommitInMemory is not a permitted operation" (or "CommitInMemory is not permitted").

Copilot uses AI. Check for mistakes.
Comment on lines +2153 to +2156
// Disabled if syncCommitInterval is 0
if bp.syncCommitInterval == 0 {
return false
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if the SetSyncCommitInterval is meant to be used only in tests, move it to the export_test file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to export_test

Comment on lines +3498 to +3499
accnt, _ := adb.LoadAccount(make([]byte, 32))
_ = adb.SaveAccount(accnt)
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +3585 to +3586
accnt, _ := adb.LoadAccount(make([]byte, 32))
_ = adb.SaveAccount(accnt)
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +78
// CommitInMemory is a not permitted operation in this implementation and thus, will return an error
func (accountsDB *accountsDBApiWithHistory) CommitInMemory() ([]byte, error) {
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment grammar: "CommitInMemory is a not permitted operation" should be "CommitInMemory is not a permitted operation" (or "CommitInMemory is not permitted").

Copilot uses AI. Check for mistakes.
Comment on lines +2153 to +2156
// Disabled if syncCommitInterval is 0
if bp.syncCommitInterval == 0 {
return false
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There a couple issues with this approach:

  1. 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
  2. If a revert occurs, a recreateFromDb will happen - and will fail

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another solution would be to commit the data tries, but into a cache.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown

codecov bot commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 79.71014% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.51%. Comparing base (862985f) to head (f090fe5).

Files with missing lines Patch % Lines
process/block/baseProcess.go 85.36% 4 Missing and 2 partials ⚠️
...ocess/transactionEvaluator/simulationAccountsDB.go 0.00% 2 Missing ⚠️
state/accountsDB.go 90.90% 1 Missing and 1 partial ⚠️
state/accountsDBApi.go 0.00% 2 Missing ⚠️
state/accountsDBApiWithHistory.go 0.00% 2 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@BeniaminDrasovean BeniaminDrasovean left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. This can be tested with an observer node started on mainnet with StartInEpoch, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants