Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1c9f2b5
Add wrapped_database.go
JonathanOppenheimer Sep 24, 2025
1082510
lint
JonathanOppenheimer Sep 24, 2025
b4cf02f
Apply suggestions from code review
JonathanOppenheimer Sep 24, 2025
04075c3
Rodrigo suggestions
JonathanOppenheimer Sep 24, 2025
76d4295
cleanup comments
JonathanOppenheimer Sep 24, 2025
7af3440
Merge branch 'master' into JonathanOppenheimer/uplift-database
JonathanOppenheimer Sep 25, 2025
13955ae
Merge branch 'master' into JonathanOppenheimer/uplift-database
JonathanOppenheimer Oct 1, 2025
5258cd6
Josh review
JonathanOppenheimer Oct 1, 2025
1c8cf95
dedouble implementation
JonathanOppenheimer Oct 1, 2025
c005a86
Merge branch 'master' into JonathanOppenheimer/uplift-database
JonathanOppenheimer Oct 10, 2025
1bda77f
rename type
JonathanOppenheimer Oct 10, 2025
99e6e6f
remove embed
JonathanOppenheimer Oct 10, 2025
2ba7b0e
Merge branch 'master' into JonathanOppenheimer/uplift-database
JonathanOppenheimer Oct 23, 2025
c47e2b7
remove unneeded functions
JonathanOppenheimer Oct 24, 2025
d9237b4
add basic test suite
JonathanOppenheimer Oct 24, 2025
0152bc1
rename to database
JonathanOppenheimer Oct 24, 2025
6fe651f
add test to ensure prod returns errors
JonathanOppenheimer Oct 24, 2025
daad661
use correct testdb
JonathanOppenheimer Oct 24, 2025
3ae6c29
lint
JonathanOppenheimer Oct 24, 2025
42e8b9a
Josh nits
JonathanOppenheimer Oct 24, 2025
dd3f00f
Update vms/evm/database/database_test.go
JonathanOppenheimer Oct 24, 2025
18038b5
nits
JonathanOppenheimer Oct 24, 2025
cb00b75
Merge branch 'master' into JonathanOppenheimer/uplift-database
JonathanOppenheimer Oct 24, 2025
dfd33c7
remove copy command
JonathanOppenheimer Oct 24, 2025
46fc003
Apply suggestions from code review
JonathanOppenheimer Oct 24, 2025
903aa1f
remove comment
JonathanOppenheimer Oct 24, 2025
7cde922
Update vms/evm/database/database_test.go
JonathanOppenheimer Oct 24, 2025
072975b
remove comment
JonathanOppenheimer Oct 24, 2025
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
73 changes: 73 additions & 0 deletions vms/evm/database/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package database

import (
"errors"

"github.com/ava-labs/libevm/ethdb"

avalanchegodb "github.com/ava-labs/avalanchego/database"
)

var (
errSnapshotNotSupported = errors.New("snapshot is not supported")
errStatNotSupported = errors.New("stat is not supported")

_ ethdb.Batch = (*batch)(nil)
_ ethdb.KeyValueStore = (*database)(nil)
)

type database struct {
db avalanchegodb.Database
}

func New(db avalanchegodb.Database) ethdb.KeyValueStore { return database{db} }

func (database) Stat(string) (string, error) { return "", errStatNotSupported }

func (db database) NewBatch() ethdb.Batch { return batch{batch: db.db.NewBatch()} }

func (db database) Has(key []byte) (bool, error) { return db.db.Has(key) }

func (db database) Get(key []byte) ([]byte, error) { return db.db.Get(key) }

func (db database) Put(key, value []byte) error { return db.db.Put(key, value) }

func (db database) Delete(key []byte) error { return db.db.Delete(key) }

func (db database) Compact(start, limit []byte) error { return db.db.Compact(start, limit) }

func (db database) Close() error { return db.db.Close() }

func (db database) NewBatchWithSize(int) ethdb.Batch { return db.NewBatch() }

func (database) NewSnapshot() (ethdb.Snapshot, error) {
return nil, errSnapshotNotSupported
}

func (db database) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
newStart := make([]byte, len(prefix)+len(start))
copy(newStart, prefix)
copy(newStart[len(prefix):], start)
start = newStart

return db.db.NewIteratorWithStartAndPrefix(start, prefix)
}

type batch struct {
batch avalanchegodb.Batch
}

func (b batch) Put(key, value []byte) error { return b.batch.Put(key, value) }

func (b batch) Delete(key []byte) error { return b.batch.Delete(key) }

func (b batch) ValueSize() int { return b.batch.Size() }

func (b batch) Write() error { return b.batch.Write() }

func (b batch) Reset() { b.batch.Reset() }

func (b batch) Replay(w ethdb.KeyValueWriter) error { return b.batch.Replay(w) }
148 changes: 148 additions & 0 deletions vms/evm/database/database_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package database

import (
"bytes"
"errors"
"slices"
"testing"

"github.com/ava-labs/libevm/ethdb"
"github.com/ava-labs/libevm/ethdb/dbtest"
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/database/memdb"
)

// testDatabase wraps the production database with test-only snapshot functionality
type testDatabase struct {
ethdb.KeyValueStore
}

// Creates a snapshot by iterating over the entire database and copying key-value pairs.
func (db testDatabase) NewSnapshot() (ethdb.Snapshot, error) {
snapshotData := make(map[string][]byte)

iter := db.NewIterator(nil, nil)
defer iter.Release()

for iter.Next() {
key := iter.Key()
value := iter.Value()
valueCopy := make([]byte, len(value))
copy(valueCopy, value)
snapshotData[string(key)] = valueCopy
}

if err := iter.Error(); err != nil {
return nil, err
}

return &testSnapshot{data: snapshotData}, nil
}

// testSnapshot implements [ethdb.Snapshot] by storing a copy of the database state.
type testSnapshot struct {
data map[string][]byte
}

func (t *testSnapshot) Get(key []byte) ([]byte, error) {
value, ok := t.data[string(key)]
if !ok {
return nil, errors.New("not found")
}
return value, nil
}

func (t *testSnapshot) Has(key []byte) (bool, error) {
_, ok := t.data[string(key)]
return ok, nil
}

func (*testSnapshot) Release() {}

func (t *testSnapshot) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
// Create a slice of key-value pairs that match the prefix and start criteria
pairs := make([]kvPair, 0, len(t.data))

for keyStr, value := range t.data {
key := []byte(keyStr)

if prefix != nil && len(key) < len(prefix) {
continue
}
if prefix != nil && !bytes.HasPrefix(key, prefix) {
continue
}

if start != nil && bytes.Compare(key, start) < 0 {
continue
}

pairs = append(pairs, kvPair{key: key, value: value})
}

// Sort by key for consistent iteration
slices.SortFunc(pairs, func(a, b kvPair) int {
return bytes.Compare(a.key, b.key)
})

return &testSnapshotIterator{pairs: pairs, index: -1}
}

type kvPair struct {
key []byte
value []byte
}

type testSnapshotIterator struct {
pairs []kvPair
index int
}

func (it *testSnapshotIterator) Next() bool {
it.index++
return it.index < len(it.pairs)
}

func (it *testSnapshotIterator) Key() []byte {
if it.index < 0 || it.index >= len(it.pairs) {
return nil
}
return it.pairs[it.index].key
}

func (it *testSnapshotIterator) Value() []byte {
if it.index < 0 || it.index >= len(it.pairs) {
return nil
}
return it.pairs[it.index].value
}

func (*testSnapshotIterator) Release() {}

func (*testSnapshotIterator) Error() error {
return nil
}

func TestInterface(t *testing.T) {
dbtest.TestDatabaseSuite(t, func() ethdb.KeyValueStore {
return &testDatabase{KeyValueStore: New(memdb.New())}
})
}

func TestUnimplemented(t *testing.T) {
t.Run("NewSnapshot_ReturnsError", func(t *testing.T) {
db := New(memdb.New())
_, err := db.NewSnapshot()
require.ErrorIs(t, err, errSnapshotNotSupported)
})

t.Run("Stat_ReturnsError", func(t *testing.T) {
db := New(memdb.New())
_, err := db.Stat("test")
require.ErrorIs(t, err, errStatNotSupported)
})
}
Loading