diff --git a/x/blockdb/memdb/database.go b/x/blockdb/memdb/database.go new file mode 100644 index 000000000000..3f4ade00d8e7 --- /dev/null +++ b/x/blockdb/memdb/database.go @@ -0,0 +1,83 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package memdb + +import ( + "sync" + + "github.com/ava-labs/avalanchego/x/blockdb" +) + +// Database is an in-memory implementation of BlockDB +type Database struct { + mu sync.RWMutex + blocks map[blockdb.BlockHeight]blockdb.BlockData + closed bool +} + +// WriteBlock stores a block in memory +func (m *Database) WriteBlock(height blockdb.BlockHeight, block blockdb.BlockData) error { + m.mu.Lock() + defer m.mu.Unlock() + + if m.closed { + return blockdb.ErrDatabaseClosed + } + + if m.blocks == nil { + m.blocks = make(map[blockdb.BlockHeight]blockdb.BlockData) + } + + if len(block) == 0 { + return blockdb.ErrBlockEmpty + } + + blockCopy := make([]byte, len(block)) + copy(blockCopy, block) + m.blocks[height] = blockCopy + + return nil +} + +// ReadBlock retrieves the full block data for the given height +func (m *Database) ReadBlock(height blockdb.BlockHeight) (blockdb.BlockData, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.closed { + return nil, blockdb.ErrDatabaseClosed + } + + block, ok := m.blocks[height] + if !ok { + return nil, blockdb.ErrBlockNotFound + } + + blockCopy := make([]byte, len(block)) + copy(blockCopy, block) + return blockCopy, nil +} + +// HasBlock checks if a block exists at the given height +func (m *Database) HasBlock(height blockdb.BlockHeight) (bool, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.closed { + return false, blockdb.ErrDatabaseClosed + } + + _, ok := m.blocks[height] + return ok, nil +} + +// Close closes the in-memory database +func (m *Database) Close() error { + m.mu.Lock() + defer m.mu.Unlock() + + m.closed = true + m.blocks = nil + return nil +} diff --git a/x/blockdb/memdb/database_test.go b/x/blockdb/memdb/database_test.go new file mode 100644 index 000000000000..1f101d4c123b --- /dev/null +++ b/x/blockdb/memdb/database_test.go @@ -0,0 +1,77 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package memdb + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/x/blockdb" +) + +func TestOperationsAfterCloseReturnError(t *testing.T) { + db := &Database{} + + // Close database + require.NoError(t, db.Close()) + + height := blockdb.BlockHeight(1) + blockData := blockdb.BlockData("test block data") + + // All operations should fail after close + err := db.WriteBlock(height, blockData) + require.ErrorIs(t, err, blockdb.ErrDatabaseClosed) + + _, err = db.ReadBlock(height) + require.ErrorIs(t, err, blockdb.ErrDatabaseClosed) + + _, err = db.HasBlock(height) + require.ErrorIs(t, err, blockdb.ErrDatabaseClosed) +} + +func TestWriteReadAndHasBlock(t *testing.T) { + db := &Database{} + + height := blockdb.BlockHeight(1) + blockData := blockdb.BlockData("test block data") + + // Write block + require.NoError(t, db.WriteBlock(height, blockData)) + + // Verify HasBlock returns true + exists, err := db.HasBlock(height) + require.NoError(t, err) + require.True(t, exists) + + // Read block back + retrievedBlock, err := db.ReadBlock(height) + require.NoError(t, err) + require.Equal(t, blockData, retrievedBlock) + + // Verify non-existent block + nonExistentHeight := blockdb.BlockHeight(999) + exists, err = db.HasBlock(nonExistentHeight) + require.NoError(t, err) + require.False(t, exists) +} + +func TestOverwritingBlockUpdatesData(t *testing.T) { + db := &Database{} + + height := blockdb.BlockHeight(1) + originalData := blockdb.BlockData("original data") + updatedData := blockdb.BlockData("updated data") + + // Write original block + require.NoError(t, db.WriteBlock(height, originalData)) + + // Overwrite with new data + require.NoError(t, db.WriteBlock(height, updatedData)) + + // Verify updated data + retrievedBlock, err := db.ReadBlock(height) + require.NoError(t, err) + require.Equal(t, updatedData, retrievedBlock) +}