Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions sei-db/state_db/sc/flatkv/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ func (e *KVExporter) Next() (interface{}, error) {
continue
}

if isMetaKey(e.currentIter.Key()) {
e.currentIter.Next()
continue
}
key := bytes.Clone(e.currentIter.Key())
value := bytes.Clone(e.currentIter.Value())
e.currentIter.Next()
Expand Down Expand Up @@ -123,10 +127,8 @@ func (e *KVExporter) Close() error {
return nil
}

// openIterForDB returns an iterator over all user data in the given DB,
// excluding internal metadata. metaKeyLowerBound() returns {0x00, 0x00} which
// skips the single-byte DBLocalMetaKey ({0x00}) while including all user keys
// (minimum 20 bytes for an EVM address).
// openIterForDB returns an iterator over all user data in the given DB.
// Metadata keys are filtered out by isMetaKey() in the iteration loop.
func (e *KVExporter) openIterForDB(db exportDBKind) (dbtypes.KeyValueDBIterator, error) {
var kvDB dbtypes.KeyValueDB
switch db {
Expand All @@ -144,9 +146,7 @@ func (e *KVExporter) openIterForDB(db exportDBKind) (dbtypes.KeyValueDBIterator,
if kvDB == nil {
return nil, nil
}
return kvDB.NewIter(&dbtypes.IterOptions{
LowerBound: metaKeyLowerBound(),
})
return kvDB.NewIter(&dbtypes.IterOptions{})
}

func (e *KVExporter) convertToNodes(db exportDBKind, key, value []byte) ([]*types.SnapshotNode, error) {
Expand Down
64 changes: 47 additions & 17 deletions sei-db/state_db/sc/flatkv/iterator.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package flatkv

import (
"bytes"
"fmt"

"github.com/sei-protocol/sei-chain/sei-db/common/evm"
Expand Down Expand Up @@ -54,11 +53,6 @@ func newDBIterator(db types.KeyValueDB, kind evm.EVMKeyKind, start, end []byte)
return &emptyIterator{}
}

// Exclude metadata key (0x00)
if internalStart == nil {
internalStart = metaKeyLowerBound()
}

iter, err := db.NewIter(&types.IterOptions{
LowerBound: internalStart,
UpperBound: internalEnd,
Expand All @@ -79,11 +73,6 @@ func newDBIterator(db types.KeyValueDB, kind evm.EVMKeyKind, start, end []byte)
func newDBPrefixIterator(db types.KeyValueDB, kind evm.EVMKeyKind, internalPrefix []byte, externalPrefix []byte) Iterator {
internalEnd := PrefixEnd(internalPrefix)

// Exclude metadata key (0x00)
if internalPrefix == nil || bytes.Compare(internalPrefix, metaKeyLowerBound()) < 0 {
internalPrefix = metaKeyLowerBound()
}

iter, err := db.NewIter(&types.IterOptions{
LowerBound: internalPrefix,
UpperBound: internalEnd,
Expand Down Expand Up @@ -132,14 +121,22 @@ func (it *dbIterator) First() bool {
if it.closed {
return false
}
return it.iter.First()
if !it.iter.First() {
return false
}
it.skipMetaForward()
return it.iter.Valid()
}

func (it *dbIterator) Last() bool {
if it.closed {
return false
}
return it.iter.Last()
if !it.iter.Last() {
return false
}
it.skipMetaBackward()
return it.iter.Valid()
}

func (it *dbIterator) SeekGE(key []byte) bool {
Expand All @@ -153,7 +150,11 @@ func (it *dbIterator) SeekGE(key []byte) bool {
return false
}

return it.iter.SeekGE(internalKey)
if !it.iter.SeekGE(internalKey) {
return false
}
it.skipMetaForward()
return it.iter.Valid()
}

func (it *dbIterator) SeekLT(key []byte) bool {
Expand All @@ -167,21 +168,50 @@ func (it *dbIterator) SeekLT(key []byte) bool {
return false
}

return it.iter.SeekLT(internalKey)
if !it.iter.SeekLT(internalKey) {
return false
}
it.skipMetaBackward()
return it.iter.Valid()
}

func (it *dbIterator) Next() bool {
if it.closed {
return false
}
return it.iter.Next()
if !it.iter.Next() {
return false
}
it.skipMetaForward()
return it.iter.Valid()
}

func (it *dbIterator) Prev() bool {
if it.closed {
return false
}
return it.iter.Prev()
if !it.iter.Prev() {
return false
}
it.skipMetaBackward()
return it.iter.Valid()
}

// skipMetaForward advances past any _meta/ keys.
// On I/O error Valid() becomes false and the loop exits;
// the caller surfaces the error via Error().
func (it *dbIterator) skipMetaForward() {
for it.iter.Valid() && isMetaKey(it.iter.Key()) {
it.iter.Next()
}
}

// skipMetaBackward retreats past any _meta/ keys.
// Error handling mirrors skipMetaForward.
func (it *dbIterator) skipMetaBackward() {
for it.iter.Valid() && isMetaKey(it.iter.Key()) {
it.iter.Prev()
}
}

func (it *dbIterator) Key() []byte {
Expand Down
60 changes: 26 additions & 34 deletions sei-db/state_db/sc/flatkv/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,32 @@ import (
"bytes"
"encoding/binary"
"fmt"

"github.com/sei-protocol/sei-chain/sei-db/state_db/sc/flatkv/lthash"
)

// DBLocalMetaKey is the key for per-DB local metadata.
// It is a single-byte key (0x00), which cannot collide with any valid user key
// because all user keys have minimum length of 20 bytes (EVM address).
//
// Invariant: All user keys are >= 20 bytes (address=20, storage=52).
var DBLocalMetaKey = []byte{0x00}
const metaKeyPrefix = "_meta/"

const (
metaVersion = metaKeyPrefix + "version"
metaLtHash = metaKeyPrefix + "hash"
)

var (
metaKeyPrefixBytes = []byte(metaKeyPrefix)
metaVersionKey = []byte(metaVersion)
metaLtHashKey = []byte(metaLtHash)
)

// metaKeyLowerBound returns the iterator lower bound that excludes DBLocalMetaKey.
// Lexicographically: 0x00 (1 byte) < 0x00,0x00 (2 bytes) < any user key (>=20 bytes).
// This ensures metadata key is excluded while all user keys (even those starting
// with 0x00) are included.
func metaKeyLowerBound() []byte {
return []byte{0x00, 0x00}
// isMetaKey reports whether key is a per-DB internal metadata key (not user data).
//
// Safety: _meta/ keys are 10–13 bytes; the shortest user key is 20 bytes
// (an EVM address). Prefix collision would require an address starting with
// 0x5F6D657461 ("_meta") — probability ~2^-48 for random addresses and
// negligible even under CREATE2 brute-force. Legacy DB keys must not use
// the _meta/ prefix.
func isMetaKey(key []byte) bool {
return bytes.HasPrefix(key, metaKeyPrefixBytes)
}

const (
Expand All @@ -27,32 +38,13 @@ const (
SlotLen = 32
BalanceLen = 32
NonceLen = 8

// localMetaSize is the serialized size of LocalMeta (version = 8 bytes)
localMetaSize = 8
)

// LocalMeta stores per-DB version tracking metadata.
// Stored inside each DB at DBLocalMetaKey (0x00).
// Version is stored at _meta/version, LtHash at _meta/hash.
type LocalMeta struct {
CommittedVersion int64 // Current committed version in this DB
}

// MarshalLocalMeta encodes LocalMeta as fixed 8 bytes (big-endian).
func MarshalLocalMeta(m *LocalMeta) []byte {
buf := make([]byte, localMetaSize)
binary.BigEndian.PutUint64(buf, uint64(m.CommittedVersion)) //nolint:gosec // version is always non-negative
return buf
}

// UnmarshalLocalMeta decodes LocalMeta from bytes.
func UnmarshalLocalMeta(data []byte) (*LocalMeta, error) {
if len(data) != localMetaSize {
return nil, fmt.Errorf("invalid LocalMeta size: got %d, want %d", len(data), localMetaSize)
}
return &LocalMeta{
CommittedVersion: int64(binary.BigEndian.Uint64(data)), //nolint:gosec // version won't exceed int64 max
}, nil
CommittedVersion int64 // Current committed version in this DB
LtHash *lthash.LtHash // nil for old format (version-only)
}

// Address is an EVM address (20 bytes).
Expand Down
60 changes: 7 additions & 53 deletions sei-db/state_db/sc/flatkv/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,57 +237,11 @@ func TestFlatKVTypeConversions(t *testing.T) {
})
}

func TestLocalMetaSerialization(t *testing.T) {
t.Run("RoundTripZero", func(t *testing.T) {
original := &LocalMeta{CommittedVersion: 0}
encoded := MarshalLocalMeta(original)
require.Equal(t, localMetaSize, len(encoded))

decoded, err := UnmarshalLocalMeta(encoded)
require.NoError(t, err)
require.Equal(t, original.CommittedVersion, decoded.CommittedVersion)
})

t.Run("RoundTripPositive", func(t *testing.T) {
original := &LocalMeta{CommittedVersion: 12345}
encoded := MarshalLocalMeta(original)
require.Equal(t, localMetaSize, len(encoded))

decoded, err := UnmarshalLocalMeta(encoded)
require.NoError(t, err)
require.Equal(t, original.CommittedVersion, decoded.CommittedVersion)
})

t.Run("RoundTripMaxInt64", func(t *testing.T) {
original := &LocalMeta{CommittedVersion: math.MaxInt64}
encoded := MarshalLocalMeta(original)
require.Equal(t, localMetaSize, len(encoded))

decoded, err := UnmarshalLocalMeta(encoded)
require.NoError(t, err)
require.Equal(t, original.CommittedVersion, decoded.CommittedVersion)
})

t.Run("InvalidLength", func(t *testing.T) {
// Too short
_, err := UnmarshalLocalMeta([]byte{0x00})
require.Error(t, err)
require.Contains(t, err.Error(), "invalid LocalMeta size")

// Too long
_, err = UnmarshalLocalMeta(make([]byte, localMetaSize+1))
require.Error(t, err)
require.Contains(t, err.Error(), "invalid LocalMeta size")
})

t.Run("BigEndianEncoding", func(t *testing.T) {
// Verify big-endian encoding: version 0x0102030405060708
meta := &LocalMeta{CommittedVersion: 0x0102030405060708}
encoded := MarshalLocalMeta(meta)

// Big-endian: most significant byte first
require.Equal(t, byte(0x01), encoded[0])
require.Equal(t, byte(0x02), encoded[1])
require.Equal(t, byte(0x08), encoded[7])
})
func TestIsMetaKey(t *testing.T) {
require.True(t, isMetaKey(metaVersionKey))
require.True(t, isMetaKey(metaLtHashKey))
require.True(t, isMetaKey([]byte("_meta/future")))
require.False(t, isMetaKey([]byte{0x00}))
require.False(t, isMetaKey(AccountKey(Address{0x01})))
require.False(t, isMetaKey(StorageKey(Address{0x01}, Slot{0x02})))
}
7 changes: 4 additions & 3 deletions sei-db/state_db/sc/flatkv/lthash_correctness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ func fullScanLtHash(t *testing.T, s *CommitStore) *lthash.LtHash {
var pairs []lthash.KVPairWithLastValue

scanDB := func(db types.KeyValueDB) {
iter, err := db.NewIter(&types.IterOptions{
LowerBound: metaKeyLowerBound(),
})
iter, err := db.NewIter(&types.IterOptions{})
require.NoError(t, err)
defer iter.Close()
for iter.First(); iter.Valid(); iter.Next() {
if isMetaKey(iter.Key()) {
continue
}
key := bytes.Clone(iter.Key())
value := bytes.Clone(iter.Value())
pairs = append(pairs, lthash.KVPairWithLastValue{
Expand Down
Loading
Loading