Skip to content
This repository was archived by the owner on Jan 20, 2026. It is now read-only.

Commit 7c736a5

Browse files
authored
Merge pull request #87 from sei-protocol/ReverseIteratorFix
Add Upper Bound ReverseIterator
2 parents 40e9138 + 95912d8 commit 7c736a5

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

ss/pebbledb/db.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,20 @@ func (db *Database) Iterator(storeKey string, version int64, start, end []byte)
662662
return newPebbleDBIterator(itr, storePrefix(storeKey), start, end, version, db.earliestVersion, false), nil
663663
}
664664

665+
// Taken from pebbledb prefix upper bound
666+
// Returns smallest key strictly greater than the prefix
667+
func prefixEnd(b []byte) []byte {
668+
end := make([]byte, len(b))
669+
copy(end, b)
670+
for i := len(end) - 1; i >= 0; i-- {
671+
if end[i] != 0xFF {
672+
end[i]++
673+
return end[:i+1]
674+
}
675+
}
676+
return nil
677+
}
678+
665679
func (db *Database) ReverseIterator(storeKey string, version int64, start, end []byte) (types.DBIterator, error) {
666680
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
667681
return nil, errorutils.ErrKeyEmpty
@@ -676,6 +690,8 @@ func (db *Database) ReverseIterator(storeKey string, version int64, start, end [
676690
var upperBound []byte
677691
if end != nil {
678692
upperBound = MVCCEncode(prependStoreKey(storeKey, end), 0)
693+
} else {
694+
upperBound = MVCCEncode(prefixEnd(storePrefix(storeKey)), 0)
679695
}
680696

681697
itr, err := db.storage.NewIter(&pebble.IterOptions{LowerBound: lowerBound, UpperBound: upperBound})

ss/test/storage_test_suite.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
const (
1515
storeKey1 = "store1"
16+
storeKey2 = "store2"
1617
)
1718

1819
// StorageTestSuite defines a reusable test suite for all storage backends.
@@ -1254,3 +1255,65 @@ func (s *StorageTestSuite) TestDatabaseRawImport() {
12541255
s.Require().Equal([]byte(fmt.Sprintf("value%03d", i)), val)
12551256
}
12561257
}
1258+
1259+
// Verifies that ReverseIterator(nil, nil) is clamped to the caller's prefix
1260+
// via prefixEnd()/UpperBound and does **not** spill into the next module.
1261+
func (s *StorageTestSuite) TestDatabaseReverseIteratorPrefixIsolation() {
1262+
1263+
db, err := s.NewDB(s.T().TempDir(), s.Config)
1264+
s.Require().NoError(err)
1265+
defer db.Close()
1266+
1267+
// store1 : key000-key009
1268+
// store2 : key000-key009 (different prefix, same suffixes)
1269+
var (
1270+
keys1, vals1 [][]byte
1271+
keys2, vals2 [][]byte
1272+
)
1273+
for i := 0; i < 10; i++ {
1274+
k := []byte(fmt.Sprintf("key%03d", i))
1275+
v1 := []byte(fmt.Sprintf("val1-%03d", i))
1276+
v2 := []byte(fmt.Sprintf("val2-%03d", i))
1277+
1278+
keys1 = append(keys1, k)
1279+
vals1 = append(vals1, v1)
1280+
1281+
keys2 = append(keys2, k)
1282+
vals2 = append(vals2, v2)
1283+
}
1284+
1285+
s.Require().NoError(DBApplyChangeset(db, 1, storeKey1, keys1, vals1))
1286+
s.Require().NoError(DBApplyChangeset(db, 1, storeKey2, keys2, vals2))
1287+
1288+
// ---------- nil / nil reverse scan on store1 ----------
1289+
itr, err := db.ReverseIterator(storeKey1, 1, nil, nil)
1290+
s.Require().NoError(err)
1291+
defer itr.Close()
1292+
1293+
// We should see exactly the 10 keys from store1, in reverse order,
1294+
// and we should *never* see a key that belongs to store2.
1295+
expectedI := 9
1296+
count := 0
1297+
for ; itr.Valid(); itr.Next() {
1298+
wantKey := []byte(fmt.Sprintf("key%03d", expectedI))
1299+
s.Require().Equal(wantKey, itr.Key(), "unexpected key from iterator")
1300+
s.Require().Equal([]byte(fmt.Sprintf("val1-%03d", expectedI)), itr.Value())
1301+
1302+
s.Require().NotContains(string(itr.Key()), "val2-")
1303+
1304+
expectedI--
1305+
count++
1306+
}
1307+
s.Require().Equal(10, count, "iterator should yield exactly the 10 keys in store1")
1308+
s.Require().NoError(itr.Error())
1309+
1310+
itr2, err := db.ReverseIterator(storeKey2, 1, nil, nil)
1311+
s.Require().NoError(err)
1312+
defer itr2.Close()
1313+
1314+
count = 0
1315+
for ; itr2.Valid(); itr2.Next() {
1316+
count++
1317+
}
1318+
s.Require().Equal(10, count, "store2 should have its own 10 keys")
1319+
}

0 commit comments

Comments
 (0)