Skip to content

balances and bools - #4093

Draft
cody-littley wants to merge 2 commits into
mainfrom
cjl/balances-and-bools
Draft

balances and bools#4093
cody-littley wants to merge 2 commits into
mainfrom
cjl/balances-and-bools

Conversation

@cody-littley

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

This PR updates FlatKV in two important ways:

  • balance EVM keys are now supported
  • read API now has an ok bool return parameter

@cursor

cursor Bot commented Sep 3, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Changes EVM account/balance persistence, merged-account merge/delete semantics, and the core state read API—mistakes could corrupt balances or mis-report account existence across FlatKV, SS, and execution.

Overview
EVM state reads now return a (value, ok bool) pair on EVMStateView / giga.StateView, so callers can tell missing entries from zero values. FlatKV and the in-memory overlays stop substituting EVM semantics (e.g. EmptyCodeHash or implicit zero balances); adapters like gigaSnapshotStateReader still map missing reads to zeros for legacy StateReader consumers.

Balance storage is wired end-to-end: new 0x21 balance keys in x/evm/types and sei-db/common/keys, merged into FlatKV account rows with nonce and code hash on commit/import/export/iterate, plus a dedicated balance iterator lane. Snapshot import splits merged account rows into separate balance nodes only when the field is non-zero.

Tests and random/composite SS paths are updated for balance-only accounts, coordinated deletes of merged account fields, and the new presence semantics (cleared/deleted slots and code read as absent, not empty).

Reviewed by Cursor Bugbot for commit 792b4af. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 3, 2026, 6:11 PM

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.41958% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.64%. Comparing base (c9f9922) to head (792b4af).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
giga/evmonly/memory_store.go 62.50% 10 Missing and 2 partials ⚠️
sei-db/state_db/sc/flatkv/store_iteration.go 64.70% 3 Missing and 3 partials ⚠️
...b/tools/cmd/seidb/operations/evm_logical_digest.go 37.50% 3 Missing and 2 partials ⚠️
sei-db/state_db/sc/flatkv/store_read.go 90.47% 2 Missing ⚠️
x/evm/types/keys.go 0.00% 2 Missing ⚠️
...ei-tendermint/internal/p2p/evmonly_inmemory_app.go 66.66% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4093      +/-   ##
==========================================
- Coverage   61.31%   60.64%   -0.67%     
==========================================
  Files        2183     2111      -72     
  Lines      191331   184181    -7150     
==========================================
- Hits       117310   111694    -5616     
+ Misses      62942    61924    -1018     
+ Partials    11079    10563     -516     
Flag Coverage Δ
sei-chain-pr 61.98% <66.66%> (?)
sei-db 69.80% <ø> (ø)
sei-db-state-db ?
sei-db-state-db-pr 73.68% <90.36%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
giga/evmonly/giga_store.go 91.30% <100.00%> (+0.60%) ⬆️
sei-db/common/keys/evm.go 80.35% <100.00%> (+2.35%) ⬆️
sei-db/state_db/sc/flatkv/import_translator.go 84.84% <100.00%> (+3.03%) ⬆️
sei-db/state_db/sc/flatkv/ktype/ktype.go 87.23% <100.00%> (ø)
sei-db/state_db/sc/flatkv/state_view.go 76.14% <100.00%> (-0.22%) ⬇️
sei-db/state_db/sc/flatkv/store.go 78.75% <100.00%> (ø)
sei-db/state_db/sc/flatkv/store_apply.go 88.51% <100.00%> (+3.22%) ⬆️
sei-db/state_db/ss/composite/store.go 70.99% <100.00%> (+0.40%) ⬆️
sei-db/state_db/ss/evm/types.go 100.00% <100.00%> (ø)
...db/tools/cmd/seidb/operations/flatkv_state_size.go 63.86% <100.00%> (ø)
... and 6 more

... and 73 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 792b4af. Configure here.

s.store.mu.RUnlock()
if ok {
return value.value
return value.value, true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Zero overlay balance reported present

Medium Severity

The memory-store overlay reports a balance as present for any latest write, including a zeroed or deleted value. FlatKV and the new read contract treat a zero balance as not stored, so GetBalance's ok disagrees across implementations when an account is drained or a balance is removed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 792b4af. Configure here.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid, well-tested groundwork: the new 0x21 balance key kind is wired consistently through FlatKV's account row (apply/merge, read, six-lane iterator, import translator, SS snapshot split, digest tool), and the (value, ok) state-view API change is mechanical with no production consumer of the bool yet. No blockers found; four non-blocking issues, mostly around contract consistency and the newly-created SS balance sub-DB on existing evm-ss-separate-dbs stores.

Findings: 0 blocking | 6 non-blocking | 4 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • [suggestion] The "a zero account field means the field is absent" rule is now restated at six independent sites: accountFieldValue (store_read.go), flatKVStateView.GetBalance and .GetCodeHash (state_view.go), buildAccountCodehashLane and buildAccountBalanceLane (store_iteration.go), and convertFlatKVNodes (ss/composite/store.go). Adding balance required touching five of them; a seventh account field would require all six. Per AGENTS.md ("Guard at the choke point, never at each caller"), consider a single vtype-level field-presence accessor (e.g. AccountData.Field(kind) ([]byte, bool)) that every projection path routes through, so the rule cannot be forgotten at a new call site.
  • 4 suggestion(s)/nit(s) flagged inline on specific lines.
  • 1 non-blocking pre-existing issue(s) listed below under pre-existing issues.

Pre-existing issues

  • [suggestion] CommitStore.GetBlockHeightModified (sei-db/state_db/sc/flatkv/store_read.go:113) already had this shape for EVMKeyCodeHash before this PR: an account row with a zero code hash reports found=true (while Get reports the same key absent), and the height returned is the row's shared height, so a nonce-only write moves the code hash's reported "last modified" height. The PR extends the same behaviour to balance.

return sd.GetBlockHeight(), true, nil

case keys.EVMKeyNonce, keys.EVMKeyCodeHash:
case keys.EVMKeyNonce, keys.EVMKeyCodeHash, keys.EVMKeyBalance:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Adding EVMKeyBalance here inherits two behaviours that disagree with Get for the same key:

  • Presence: Get routes through accountFieldValue, which reports a zero balance as absent. GetBlockHeightModified reports found=true for a balance key on any live account row, so a nonce-only account (or one whose balance was just zeroed, i.e. deleted) answers (rowHeight, true, nil) for a balance that Get/GetBalance both call missing.
  • Height: the height is the account row's, shared by all three merged fields, so a later nonce or code-hash write silently changes the balance's reported last-modified height.

There are no production callers of GetBlockHeightModified today (only the giga.LiveStateStore interface declaration), so this is latent rather than live. Routing the account case through the same field-presence check Get uses — returning (-1, false, nil) when the named field is zero — would keep the two reads consistent. Note the code-hash half of this predates the PR; balance is the new instance.

StoreCode,
StoreStorage,
StoreMisc,
StoreBalance,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Adding StoreBalance to AllEVMStoreTypes() makes NewEVMStateStore open a sixth sub-DB directory (<dir>/balance) when evm-ss-separate-dbs = true. On an existing store that directory is created fresh, so its GetLatestVersion() is 0 — and EVMStateStore.GetLatestVersion() takes the min across managedDBs (store.go:174-185). RecoverCompositeStateStore (ss/composite/store.go:686-694) derives startVersion from that min, so the first startup after this change replays the whole available changelog into the EVM store, re-applying already-present versions and walking SetLatestVersion back up from the WAL's first entry.

The flag is non-default and documented as experimental (docs/migration/giga_store_migration.md), so this is a slow/noisy upgrade rather than data loss. Still worth handling explicitly — e.g. seed a newly created sub-DB's latest-version marker from the store's existing max on first open, or have recovery ignore sub-DBs that are empty.

baseNonce := s.store.base.GetNonce(address)
// The base reader reports no presence of its own, so a missing account is indistinguishable from
// one whose nonce is zero.
return baseNonce, baseNonce != 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] This contradicts the contract the PR just wrote for EVMStateView: "an account that exists with a nonce of 0 reads as (0, true)" (sei-db/state_db/giga/state_view.go:50-51), and the method doc "whether addr has an account". An account present only in the base reader with nonce 0 (e.g. funded but never having sent a tx) reads as (0, false) here.

AccountExists on this same type (line 387) already solves the base-presence problem by type-asserting the base reader for an AccountExists(common.Address) bool method; GetNonce could reuse it instead of inferring presence from baseNonce != 0.

The mirror-image case is GetBalance (line 422): an explicitly written zero balance returns (0, true), whereas flatKVStateView.GetBalance returns (0, false) for a stored zero. So the two implementations of one interface disagree on the zero cases in opposite directions. No caller branches on the bool yet, so nothing is broken today — but the first one that does will get implementation-dependent behaviour. Either align the implementations or state in the interface doc that a zero value may be reported as present or absent for fields whose zero encodes absence.

Comment thread sei-db/common/keys/evm.go
codeKeyPrefix = []byte{0x07}
codeHashKeyPrefix = []byte{0x08}
nonceKeyPrefix = []byte{0x0a}
balanceKeyPrefix = []byte{0x21}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] This is the second declaration of 0x21; x/evm/types.BalanceKeyPrefix is the first, and the block comment above ("mirrored from x/evm/types.go") is the only thing tying them together. TestEVMKeyPrefixesAreDistinct checks the mirrored prefixes are distinct from each other but not that they still match their originals, so a future prefix change in x/evm/types reroutes balance keys to the misc lane with a green test suite.

sei-db test packages can import x/evm/types (see sei-db/state_db/ss/composite/recovery_test.go:374 using evmtypes.NonceKeyPrefix), so a drift test asserting each EVMKeyPrefixByte(kind) equals the corresponding evmtypes.*KeyPrefix[0] is available and would cover the whole mirrored set, not just balance.

return value, value != nil

case keys.EVMKeyNonce, keys.EVMKeyCodeHash:
case keys.EVMKeyNonce, keys.EVMKeyCodeHash, keys.EVMKeyBalance:

@yzang2019 yzang2019 Sep 3, 2026

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.

GetBlockHeightModified and Get disagree about whether a balance key exists.

Get on a balance key for an account whose balance is zero returns (nil, false), but GetBlockHeightModified on the same key returns (height, true).

Is that an issue?

changesByType[keys.EVMKeyNonce],
changesByType[keys.EVMKeyCodeHash],
nil, // TODO: update this when we add a balance key!
changesByType[keys.EVMKeyBalance],

@yzang2019 yzang2019 Sep 3, 2026

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.

Before this PR the balance bytes were always zero, because we passed nil for the balance argument so "a row exists" implied "nonce ≠ 0 or codeHash ≠ 0".

This PR replaces that nil with changesByType[keys.EVMKeyBalance] (store_apply.go:129), so a balance write alone now creates a row, and "a row exists" no longer implies anything about the nonce.

Question is will this behavior change cause any issues?

@cody-littley

Copy link
Copy Markdown
Contributor Author

Marking this PR as a draft until we figure out proper semantics

@cody-littley
cody-littley marked this pull request as draft September 4, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants