feat(sei-db): add flatkv store implementation#2793
Conversation
|
The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2793 +/- ##
===========================================
+ Coverage 48.37% 57.24% +8.87%
===========================================
Files 671 2099 +1428
Lines 50621 172410 +121789
===========================================
+ Hits 24486 98701 +74215
- Misses 23987 64851 +40864
- Partials 2148 8858 +6710
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 34a6fed4c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
34a6fed to
e3a525f
Compare
3adc396 to
eafddae
Compare
| for addrStr := range modifiedAccounts { | ||
| addr, ok := AddressFromBytes([]byte(addrStr)) | ||
| if !ok { | ||
| return fmt.Errorf("invalid address in modifiedAccounts: %x", addrStr) | ||
| } | ||
|
|
||
| // Get old AccountValue from DB (committed state) | ||
| oldAV, err := s.getAccountValueFromDB(addr) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get old account value for addr %x: %w", addr, err) | ||
| } | ||
| oldValue := oldAV.Encode() | ||
|
|
||
| // Get new AccountValue (from pending writes or DB) | ||
| var newValue []byte | ||
| var isDelete bool | ||
| if paw, ok := s.accountWrites[addrStr]; ok { | ||
| newValue = paw.value.Encode() | ||
| isDelete = paw.isDelete | ||
| } else { | ||
| // No pending write means no change (shouldn't happen, but be safe) | ||
| continue | ||
| } | ||
|
|
||
| accountPairs = append(accountPairs, lthash.KVPairWithLastValue{ | ||
| Key: AccountKey(addr), | ||
| Value: newValue, | ||
| LastValue: oldValue, | ||
| Delete: isDelete, | ||
| }) | ||
| } |
Check warning
Code scanning / CodeQL
Iteration over map Warning
| for _, paw := range s.accountWrites { | ||
| key := AccountKey(paw.addr) | ||
| if paw.isDelete { | ||
| if err := batch.Delete(key); err != nil { | ||
| return fmt.Errorf("accountDB delete: %w", err) | ||
| } | ||
| } else { | ||
| // Encode AccountValue and store with addr as key | ||
| encoded := EncodeAccountValue(paw.value) | ||
| if err := batch.Set(key, encoded); err != nil { | ||
| return fmt.Errorf("accountDB set: %w", err) | ||
| } | ||
| } | ||
| } |
Check warning
Code scanning / CodeQL
Iteration over map Warning
| for _, pw := range s.codeWrites { | ||
| if pw.isDelete { | ||
| if err := batch.Delete(pw.key); err != nil { | ||
| return fmt.Errorf("codeDB delete: %w", err) | ||
| } | ||
| } else { | ||
| if err := batch.Set(pw.key, pw.value); err != nil { | ||
| return fmt.Errorf("codeDB set: %w", err) | ||
| } | ||
| } | ||
| } |
Check warning
Code scanning / CodeQL
Iteration over map Warning
| for _, pw := range s.storageWrites { | ||
| if pw.isDelete { | ||
| if err := batch.Delete(pw.key); err != nil { | ||
| return fmt.Errorf("storageDB delete: %w", err) | ||
| } | ||
| } else { | ||
| if err := batch.Set(pw.key, pw.value); err != nil { | ||
| return fmt.Errorf("storageDB set: %w", err) | ||
| } | ||
| } | ||
| } |
Check warning
Code scanning / CodeQL
Iteration over map Warning
yzang2019
left a comment
There was a problem hiding this comment.
Overall LGTM, approved with some more minor comments
- Multi-DB storage: storageDB, accountDB, codeDB, metadataDB - LtHash-based state commitment using internal key formats - Configurable write toggles per DB type - Iterator support for storage keys
878551c to
b20956a
Compare
77a33a2 to
d7ba059
Compare
cody-littley
left a comment
There was a problem hiding this comment.
My comments are mostly nitpicks, overall LGTM. Please take my review with a grain of salt, since I'm still building context.
| func (it *dbIterator) Error() error { | ||
| if it.err != nil { | ||
| return it.err | ||
| } | ||
| return it.iter.Error() | ||
| } |
There was a problem hiding this comment.
A potential alternative to logging might be to make it so that any error returned during iterator construction can be surfaced here (e.g. by adding an err field to emptyIterator).
|
|
||
| // Get returns the value for the given memiavl key. | ||
| // Returns (value, true) if found, (nil, false) if not found. | ||
| func (s *CommitStore) Get(key []byte) ([]byte, bool) { |
There was a problem hiding this comment.
Nit, it might be nice to split out the logic in the switch statement case blocks into small helper functions.
There was a problem hiding this comment.
acknowledged, the cases are ~10-15 lines each so I lean towards keeping them inline for now to avoid function-per-case overhead. happy to revisit if the cases grow in complexity.
| default: | ||
| return nil, false | ||
| } |
There was a problem hiding this comment.
Do we expect to hit the default case during normal operation? If not, would it make sense to return an error in case we accidentally end up in that scenario?
| default: | |
| return nil, false | |
| } | |
| default: | |
| return nil, fmt.Errorf("unhandled kind: %d", kind) | |
| } |
There was a problem hiding this comment.
the Get() signature returns ([]byte, bool) per the Store interface matching the memiavl, the default case is expected to fire for some EVMKeyLegacy, but im not 100% sure during operations though. returning (nil, false) is the correct "not found" semantics here.
|
|
||
| // Route to appropriate DB based on key type | ||
| switch kind { | ||
| case evm.EVMKeyStorage: |
There was a problem hiding this comment.
Also potentially a good candidate to split switch case blocks into helper functions.
There was a problem hiding this comment.
agreed it would improve readability. I'll keep this as-is for now since the cases are tightly coupled with the local variables (modifiedAccounts, storagePairs, codePairs, s.storageWrites, etc.), happy to revisit if the function grows further.
| if kind == evm.EVMKeyUnknown { | ||
| // Skip non-EVM keys silently | ||
| continue | ||
| } |
There was a problem hiding this comment.
Do we expect to find non-EVM keys during regular operations? If not, would it make sense to log a warning?
There was a problem hiding this comment.
will add a log.Warn as good defensive measure.
| func (s *CommitStore) flushAllDBs() error { | ||
| if err := s.accountDB.Flush(); err != nil { | ||
| return fmt.Errorf("accountDB flush: %w", err) | ||
| } | ||
| if err := s.codeDB.Flush(); err != nil { | ||
| return fmt.Errorf("codeDB flush: %w", err) | ||
| } | ||
| if err := s.storageDB.Flush(); err != nil { | ||
| return fmt.Errorf("storageDB flush: %w", err) | ||
| } | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Since commit/flush is probably doing nontrivial IO work, I wonder if we could get some meaningful performance gain by committing/flushing each DB on a parallel goroutine.
| func (s *CommitStore) flushAllDBs() error { | |
| if err := s.accountDB.Flush(); err != nil { | |
| return fmt.Errorf("accountDB flush: %w", err) | |
| } | |
| if err := s.codeDB.Flush(); err != nil { | |
| return fmt.Errorf("codeDB flush: %w", err) | |
| } | |
| if err := s.storageDB.Flush(); err != nil { | |
| return fmt.Errorf("storageDB flush: %w", err) | |
| } | |
| return nil | |
| } | |
| func (s *CommitStore) flushAllDBs() error { | |
| errChan := make(chan error, 3) | |
| go func() { | |
| if err := s.accountDB.Flush(); err != nil { | |
| errChan <- fmt.Errorf("accountDB flush: %w", err) | |
| } else { | |
| errChan <- nil | |
| } | |
| }() | |
| go func() { | |
| if err := s.codeDB.Flush(); err != nil { | |
| errChan <- fmt.Errorf("codeDB flush: %w", err) | |
| } else { | |
| errChan <- nil | |
| } | |
| }() | |
| go func() { | |
| if err := s.storageDB.Flush(); err != nil { | |
| errChan <- fmt.Errorf("storageDB flush: %w", err) | |
| } else { | |
| errChan <- nil | |
| } | |
| }() | |
| for i := 0; i < 3; i++ { | |
| err := <-errChan | |
| if err != nil { | |
| return fmt.Errorf("failed to flush DB: %v", err) | |
| } | |
| } | |
| return nil | |
| } |
There was a problem hiding this comment.
good thought! i think memtable → L0 is probably fast enough, If profiling shows flush is a bottleneck we can revisit. will keep this in mind
* main: feat(sei-db): add flatkv store implementation (#2793)
| // setFullnodeTypeTendermintConfig sets common Tendermint config for fullnode-like nodes | ||
| func setFullnodeTypeTendermintConfig(config *tmcfg.Config) { | ||
| config.TxIndex.Indexer = []string{"kv"} // Full nodes need tx indexing for queries | ||
| config.RPC.ListenAddress = "tcp://0.0.0.0:26657" |
There was a problem hiding this comment.
Hey @blindchaser
These lines were added by #2903 Can you please explain why they are removed? or was it an unresolved conflict issue?
There was a problem hiding this comment.
This PR is a large one and such accident could happen. A new PR is opened to put them back #2913 would you please have a look and see if it is alright? thanks
This reverts commit 07025b9.
Describe your changes and provide context
Testing performed to validate your change