diff --git a/db/hash_provider.go b/db/hash_provider.go
new file mode 100644
index 0000000..ef8353a
--- /dev/null
+++ b/db/hash_provider.go
@@ -0,0 +1,216 @@
+// Copyright 2025 Sonic Labs
+// This file is part of Aida Testing Infrastructure for Sonic
+//
+// Aida is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Aida is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Aida. If not, see .
+
+package db
+
+//go:generate mockgen -source hash_provider.go -destination hash_provider_mock.go -package db
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/0xsoniclabs/substate/types"
+ "github.com/status-im/keycard-go/hexutils"
+)
+
+const (
+ StateRootHashPrefix = "dbh"
+ BlockHashPrefix = "bh"
+)
+
+// ClientInterface defines the methods that an RPC client must implement.
+type IRpcClient interface {
+ Call(result interface{}, method string, args ...interface{}) error
+}
+
+type HashProvider interface {
+ GetStateRootHash(blockNumber int) (types.Hash, error)
+ GetBlockHash(blockNumber int) (types.Hash, error)
+}
+
+func MakeHashProvider(db BaseDB) HashProvider {
+ return &hashProvider{db}
+}
+
+type hashProvider struct {
+ db BaseDB
+}
+
+func (p *hashProvider) GetBlockHash(number int) (types.Hash, error) {
+ blockHash, err := p.db.Get(BlockHashDBKey(uint64(number)))
+ if err != nil {
+ return types.Hash{}, err
+ }
+
+ if blockHash == nil {
+ return types.Hash{}, nil
+ }
+
+ if len(blockHash) != 32 {
+ return types.Hash{}, fmt.Errorf("invalid block hash length for block %d: expected 32 bytes, got %d bytes", number, len(blockHash))
+ }
+
+ return types.Hash(blockHash), nil
+}
+
+func (p *hashProvider) GetStateRootHash(number int) (types.Hash, error) {
+ hex := strconv.FormatUint(uint64(number), 16)
+ stateRoot, err := p.db.Get([]byte(StateRootHashPrefix + "0x" + hex))
+ if err != nil {
+ return types.Hash{}, err
+ }
+
+ if stateRoot == nil {
+ return types.Hash{}, nil
+ }
+
+ if len(stateRoot) != 32 {
+ return types.Hash{}, fmt.Errorf("invalid state root length for block %d: expected 32 bytes, got %d bytes", number, len(stateRoot))
+ }
+
+ return types.BytesToHash(stateRoot), nil
+}
+
+// SaveStateRoot saves the state root hash to the database
+func SaveStateRoot(db BaseDB, blockNumber string, stateRoot string) error {
+ fullPrefix := StateRootHashPrefix + blockNumber
+ err := db.Put([]byte(fullPrefix), hexutils.HexToBytes(strings.TrimPrefix(stateRoot, "0x")))
+ if err != nil {
+ return fmt.Errorf("unable to put state hash for block %s: %v", blockNumber, err)
+ }
+ return nil
+}
+
+// SaveBlockHash saves the block hash to the database
+func SaveBlockHash(db BaseDB, blockNumber string, hash string) error {
+ bn, err := strconv.ParseUint(strings.TrimPrefix(blockNumber, "0x"), 16, 64)
+ if err != nil {
+ return fmt.Errorf("invalid block number %s: %v", blockNumber, err)
+ }
+ fullPrefix := BlockHashDBKey(bn)
+ err = db.Put(fullPrefix, hexutils.HexToBytes(strings.TrimPrefix(hash, "0x")))
+ if err != nil {
+ return fmt.Errorf("unable to put state hash for block %s: %v", blockNumber, err)
+ }
+ return nil
+}
+
+// getBlockByNumber get block from the rpc node
+func GetBlockByNumber(client IRpcClient, blockNumber string) (map[string]interface{}, error) {
+ var block map[string]interface{}
+ err := client.Call(&block, "eth_getBlockByNumber", blockNumber, false)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get block %s: %v", blockNumber, err)
+ }
+ return block, nil
+}
+
+// StateHashKeyToUint64 converts a state hash key to a uint64
+func StateHashKeyToUint64(hexBytes []byte) (uint64, error) {
+ prefix := []byte(StateRootHashPrefix)
+
+ if len(hexBytes) >= len(prefix) && bytes.HasPrefix(hexBytes, prefix) {
+ hexBytes = hexBytes[len(prefix):]
+ }
+
+ res, err := strconv.ParseUint(string(hexBytes), 0, 64)
+
+ if err != nil {
+ return 0, fmt.Errorf("cannot parse uint %v; %v", string(hexBytes), err)
+ }
+ return res, nil
+}
+
+// GetFirstStateHash returns the first block number for which we have a state hash
+func GetFirstStateHash(db BaseDB) (uint64, error) {
+ // TODO MATEJ will be fixed in future commit
+ //iter := db.NewIterator([]byte(StateRootHashPrefix), []byte("0x"))
+ //
+ //defer iter.Release()
+ //
+ //// start with writing first block
+ //if !iter.Next() {
+ // return 0, fmt.Errorf("no state hash found")
+ //}
+ //
+ //firstStateHashBlock, err := StateHashKeyToUint64(iter.Key())
+ //if err != nil {
+ // return 0, err
+ //}
+ //return firstStateHashBlock, nil
+ return 0, fmt.Errorf("not implemented")
+}
+
+// GetLastStateHash returns the last block number for which we have a state hash
+func GetLastStateHash(db BaseDB) (uint64, error) {
+ // TODO MATEJ will be fixed in future commit
+ //return GetLastKey(db, StateRootHashPrefix)
+ return 0, fmt.Errorf("not implemented")
+}
+
+// GetFirstBlockHash returns the first block number for which we have a block hash
+func GetFirstBlockHash(db BaseDB) (uint64, error) {
+ iter := db.NewIterator([]byte(BlockHashPrefix), nil)
+ defer iter.Release()
+
+ if !iter.Next() {
+ return 0, fmt.Errorf("no block hash found")
+ }
+
+ firstBlock, err := DecodeBlockHashDBKey(iter.Key())
+ if err != nil {
+ return 0, err
+ }
+ return firstBlock, nil
+}
+
+// GetLastBlockHash returns the last block number for which we have a block hash
+func GetLastBlockHash(db BaseDB) (uint64, error) {
+ iter := db.NewIterator([]byte(BlockHashPrefix), nil)
+ defer iter.Release()
+
+ if !iter.Last() {
+ return 0, fmt.Errorf("no block hash found")
+ }
+
+ lastBlock, err := DecodeBlockHashDBKey(iter.Key())
+ if err != nil {
+ return 0, err
+ }
+ return lastBlock, nil
+}
+
+func BlockHashDBKey(block uint64) []byte {
+ prefix := []byte(BlockHashPrefix)
+ blockByte := make([]byte, 8)
+ binary.BigEndian.PutUint64(blockByte[0:8], block)
+ return append(prefix, blockByte...)
+}
+
+// DecodeBlockHashDBKey decodes a block hash key into a block number
+func DecodeBlockHashDBKey(data []byte) (uint64, error) {
+ if len(data) < len(BlockHashPrefix)+8 {
+ return 0, fmt.Errorf("invalid length of block hash key, expected at least %d, got %d", len(BlockHashPrefix)+8, len(data))
+ }
+ if !bytes.HasPrefix(data, []byte(BlockHashPrefix)) {
+ return 0, fmt.Errorf("invalid prefix of block hash key")
+ }
+ block := binary.BigEndian.Uint64(data[len(BlockHashPrefix):])
+ return block, nil
+}
diff --git a/db/hash_provider_mock.go b/db/hash_provider_mock.go
new file mode 100644
index 0000000..8f6fe63
--- /dev/null
+++ b/db/hash_provider_mock.go
@@ -0,0 +1,112 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: hash_provider.go
+//
+// Generated by this command:
+//
+// mockgen -source hash_provider.go -destination hash_provider_mock.go -package db
+//
+
+// Package db is a generated GoMock package.
+package db
+
+import (
+ reflect "reflect"
+
+ types "github.com/0xsoniclabs/substate/types"
+ gomock "go.uber.org/mock/gomock"
+)
+
+// MockIRpcClient is a mock of IRpcClient interface.
+type MockIRpcClient struct {
+ ctrl *gomock.Controller
+ recorder *MockIRpcClientMockRecorder
+}
+
+// MockIRpcClientMockRecorder is the mock recorder for MockIRpcClient.
+type MockIRpcClientMockRecorder struct {
+ mock *MockIRpcClient
+}
+
+// NewMockIRpcClient creates a new mock instance.
+func NewMockIRpcClient(ctrl *gomock.Controller) *MockIRpcClient {
+ mock := &MockIRpcClient{ctrl: ctrl}
+ mock.recorder = &MockIRpcClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockIRpcClient) EXPECT() *MockIRpcClientMockRecorder {
+ return m.recorder
+}
+
+// Call mocks base method.
+func (m *MockIRpcClient) Call(result any, method string, args ...any) error {
+ m.ctrl.T.Helper()
+ varargs := []any{result, method}
+ for _, a := range args {
+ varargs = append(varargs, a)
+ }
+ ret := m.ctrl.Call(m, "Call", varargs...)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Call indicates an expected call of Call.
+func (mr *MockIRpcClientMockRecorder) Call(result, method any, args ...any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ varargs := append([]any{result, method}, args...)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockIRpcClient)(nil).Call), varargs...)
+}
+
+// MockHashProvider is a mock of HashProvider interface.
+type MockHashProvider struct {
+ ctrl *gomock.Controller
+ recorder *MockHashProviderMockRecorder
+}
+
+// MockHashProviderMockRecorder is the mock recorder for MockHashProvider.
+type MockHashProviderMockRecorder struct {
+ mock *MockHashProvider
+}
+
+// NewMockHashProvider creates a new mock instance.
+func NewMockHashProvider(ctrl *gomock.Controller) *MockHashProvider {
+ mock := &MockHashProvider{ctrl: ctrl}
+ mock.recorder = &MockHashProviderMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockHashProvider) EXPECT() *MockHashProviderMockRecorder {
+ return m.recorder
+}
+
+// GetBlockHash mocks base method.
+func (m *MockHashProvider) GetBlockHash(blockNumber int) (types.Hash, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetBlockHash", blockNumber)
+ ret0, _ := ret[0].(types.Hash)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetBlockHash indicates an expected call of GetBlockHash.
+func (mr *MockHashProviderMockRecorder) GetBlockHash(blockNumber any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockHash", reflect.TypeOf((*MockHashProvider)(nil).GetBlockHash), blockNumber)
+}
+
+// GetStateRootHash mocks base method.
+func (m *MockHashProvider) GetStateRootHash(blockNumber int) (types.Hash, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetStateRootHash", blockNumber)
+ ret0, _ := ret[0].(types.Hash)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetStateRootHash indicates an expected call of GetStateRootHash.
+func (mr *MockHashProviderMockRecorder) GetStateRootHash(blockNumber any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateRootHash", reflect.TypeOf((*MockHashProvider)(nil).GetStateRootHash), blockNumber)
+}
diff --git a/db/hash_provider_test.go b/db/hash_provider_test.go
new file mode 100644
index 0000000..7960fb6
--- /dev/null
+++ b/db/hash_provider_test.go
@@ -0,0 +1,377 @@
+// Copyright 2025 Sonic Labs
+// This file is part of Aida Testing Infrastructure for Sonic
+//
+// Aida is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Aida is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Aida. If not, see .
+
+package db
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "math/rand"
+ "strconv"
+ "testing"
+
+ "github.com/0xsoniclabs/substate/types"
+ "github.com/stretchr/testify/assert"
+ "github.com/syndtr/goleveldb/leveldb"
+ "go.uber.org/mock/gomock"
+)
+
+func TestStateHash_KeyToUint64(t *testing.T) {
+ type args struct {
+ hexBytes []byte
+ }
+ tests := []struct {
+ name string
+ args args
+ want uint64
+ wantErr bool
+ }{
+ {"testZeroConvert", args{[]byte(StateRootHashPrefix + "0x0")}, 0, false},
+ {"testOneConvert", args{[]byte(StateRootHashPrefix + "0x1")}, 1, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := StateHashKeyToUint64(tt.args.hexBytes)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("StateHashKeyToUint64() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("StateHashKeyToUint64() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestStateHash_GetStateRootHash(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ // case success
+ mockDb := NewMockBaseDB(ctrl)
+ mockDb.EXPECT().Get(gomock.Any()).Return([]byte("abcdefghijabcdefghijabcdefghij32"), nil)
+ stateHash := MakeHashProvider(mockDb)
+ hash, err := stateHash.GetStateRootHash(1234)
+ assert.NoError(t, err)
+ assert.Equal(t, "0x6162636465666768696a6162636465666768696a6162636465666768696a3332", hash.String())
+
+ // case error
+ mockDb = NewMockBaseDB(ctrl)
+ mockDb.EXPECT().Get(gomock.Any()).Return(nil, leveldb.ErrNotFound)
+ stateHash = MakeHashProvider(mockDb)
+ hash, err = stateHash.GetStateRootHash(1234)
+ assert.Equal(t, leveldb.ErrNotFound, err)
+ assert.Equal(t, types.Hash{}, hash)
+
+ // case empty
+ mockDb = NewMockBaseDB(ctrl)
+ mockDb.EXPECT().Get(gomock.Any()).Return(nil, nil)
+ stateHash = MakeHashProvider(mockDb)
+ hash, err = stateHash.GetStateRootHash(1234)
+ assert.NoError(t, err)
+ assert.Equal(t, types.Hash{}, hash)
+}
+
+func TestStateHash_SaveStateRoot(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ // case success
+ mockDb := NewMockBaseDB(ctrl)
+ mockDb.EXPECT().Put(gomock.Any(), gomock.Any()).Return(nil)
+ err := SaveStateRoot(mockDb, "0x1234", "0x5678")
+ assert.NoError(t, err)
+
+ // case error
+ mockDb = NewMockBaseDB(ctrl)
+ mockDb.EXPECT().Put(gomock.Any(), gomock.Any()).Return(leveldb.ErrNotFound)
+ err = SaveStateRoot(mockDb, "0x1234", "0x5678")
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "leveldb: not found")
+}
+
+func TestStateHash_StateHashKeyToUint64(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ // case success
+ output, err := StateHashKeyToUint64([]byte("dbh0x1234"))
+ assert.NoError(t, err)
+ assert.Equal(t, uint64(0x1234), output)
+
+ // case error
+ output, err = StateHashKeyToUint64([]byte("ggggggg"))
+ assert.Equal(t, uint64(0), output)
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "invalid syntax")
+}
+
+func TestStateHash_retrieveStateRoot(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ // case success
+ client := NewMockIRpcClient(ctrl)
+ client.EXPECT().Call(gomock.Any(), "eth_getBlockByNumber", "0x1234", false).Return(nil)
+ output, err := GetBlockByNumber(client, "0x1234")
+ assert.NoError(t, err)
+ assert.Equal(t, map[string]interface{}(nil), output)
+
+ // case error
+ mockErr := errors.New("error")
+ client = NewMockIRpcClient(ctrl)
+ client.EXPECT().Call(gomock.Any(), "eth_getBlockByNumber", "0x1234", false).Return(mockErr)
+ output, err = GetBlockByNumber(client, "0x1234")
+ assert.Error(t, err)
+ assert.Nil(t, output)
+}
+
+func TestStateHash_GetFirstStateHash(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ mockDb := NewMockBaseDB(ctrl)
+ output, err := GetFirstStateHash(mockDb)
+ assert.Equal(t, uint64(0x0), output)
+ assert.Error(t, err)
+
+}
+
+func TestStateHash_GetLastStateHash(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ mockDb := NewMockBaseDB(ctrl)
+ output, err := GetLastStateHash(mockDb)
+ assert.Equal(t, uint64(0x0), output)
+ assert.Error(t, err)
+}
+
+func TestStateHashProvider_GetStateRootHash(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ blk := 10
+ tests := []struct {
+ name string
+ expect func(mockAidaDb *MockBaseDB)
+ want types.Hash
+ }{
+ {
+ name: "GetStatRootHash_OK",
+ expect: func(mockAidaDb *MockBaseDB) {
+ hex := strconv.FormatUint(uint64(blk), 16)
+ mockAidaDb.EXPECT().Get([]byte(StateRootHashPrefix+"0x"+hex)).Return(types.Hash{0x11}.Bytes(), nil)
+ },
+ want: types.Hash{0x11},
+ },
+ {
+ name: "GetStatRootHash_NilHash",
+ expect: func(mockAidaDb *MockBaseDB) {
+ hex := strconv.FormatUint(uint64(blk), 16)
+ mockAidaDb.EXPECT().Get([]byte(StateRootHashPrefix+"0x"+hex)).Return(nil, nil)
+ },
+ want: types.Hash{},
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ mockAidaDb := NewMockBaseDB(ctrl)
+ hp := MakeHashProvider(mockAidaDb)
+ test.expect(mockAidaDb)
+ got, err := hp.GetStateRootHash(blk)
+ assert.NoError(t, err)
+ assert.Equal(t, got, test.want)
+ })
+ }
+}
+
+func TestStateHashProvider_GetBlockHash(t *testing.T) {
+ ctrl := gomock.NewController(t)
+
+ blk := 10
+ tests := []struct {
+ name string
+ expect func(mockAidaDb *MockBaseDB)
+ wantHash types.Hash
+ wantError bool
+ }{
+ {
+ name: "GetBlockHash_OK",
+ expect: func(mockAidaDb *MockBaseDB) {
+ mockAidaDb.EXPECT().Get(BlockHashDBKey(uint64(blk))).Return(types.Hash{0x11}.Bytes(), nil)
+ },
+ wantHash: types.Hash{0x11},
+ wantError: false,
+ },
+ {
+ name: "GetBlockHash_NilHash",
+ expect: func(mockAidaDb *MockBaseDB) {
+ mockAidaDb.EXPECT().Get(BlockHashDBKey(uint64(blk))).Return(nil, nil)
+ },
+ wantHash: types.Hash{},
+ wantError: false,
+ },
+ {
+ name: "GetBlockHash_DBError",
+ expect: func(mockAidaDb *MockBaseDB) {
+ mockAidaDb.EXPECT().Get(BlockHashDBKey(uint64(blk))).Return(nil, errors.New("db error"))
+ },
+ wantHash: types.Hash{},
+ wantError: true,
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ mockAidaDb := NewMockBaseDB(ctrl)
+ hp := MakeHashProvider(mockAidaDb)
+ test.expect(mockAidaDb)
+ got, err := hp.GetBlockHash(blk)
+ if test.wantError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ }
+ assert.Equal(t, got, test.wantHash)
+ })
+ }
+}
+
+func TestStateHash_GetFirstBlockHash(t *testing.T) {
+ testDb := generateTestBlockHashDb(t)
+ defer func() {
+ err := testDb.Close()
+ assert.NoError(t, err, "error closing test database")
+ }()
+
+ keyFirst := fmt.Sprintf("0x%x", 1+rand.Intn(1000))
+ err := SaveBlockHash(testDb, keyFirst, "0x1234")
+ assert.NoError(t, err, "error saving state root "+keyFirst)
+ keyLast := fmt.Sprintf("0x%x", 1000+rand.Intn(1000))
+ err = SaveBlockHash(testDb, keyLast, "0x1234")
+ assert.NoError(t, err, "error saving state root "+keyLast)
+
+ output, err := GetFirstBlockHash(testDb)
+ assert.NoError(t, err)
+
+ assert.Equal(t, keyFirst, "0x"+strconv.FormatUint(output, 16))
+}
+
+func TestStateHash_GetLastBlockHash(t *testing.T) {
+ testDb := generateTestBlockHashDb(t)
+ defer func() {
+ err := testDb.Close()
+ assert.NoError(t, err, "error closing test database")
+ }()
+
+ keyFirst := fmt.Sprintf("0x%x", 1+rand.Intn(1000))
+ err := SaveBlockHash(testDb, keyFirst, "0x1234")
+ assert.NoError(t, err, "error saving state root "+keyFirst)
+ keyLast := fmt.Sprintf("0x%x", 1000+rand.Intn(1000))
+ err = SaveBlockHash(testDb, keyLast, "0x1234")
+ assert.NoError(t, err, "error saving state root "+keyLast)
+
+ output, err := GetLastBlockHash(testDb)
+ assert.NoError(t, err)
+ assert.Equal(t, keyLast, "0x"+strconv.FormatUint(output, 16))
+}
+
+func generateTestBlockHashDb(t *testing.T) BaseDB {
+ tmpDir := t.TempDir() + "/blockHashDb"
+ database, err := NewCodeDB(tmpDir, nil, nil, nil)
+ if err != nil {
+ t.Fatalf("error opening stateHash leveldb %s: %v", tmpDir, err)
+ }
+
+ return database
+}
+
+func TestDecodeBlockHashDBKey_Errors(t *testing.T) {
+ tests := []struct {
+ name string
+ key []byte
+ want uint64
+ wantErr string
+ }{
+ {"valid key", append([]byte(BlockHashPrefix), binary.BigEndian.AppendUint64(nil, uint64(2))...), 2, ""},
+ {"invalid key", []byte("invalidkey"), 0, "invalid prefix of block hash key"},
+ {"invalid key", []byte("shrt"), 0, "invalid length of block hash key, expected at least 10, got 4"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := DecodeBlockHashDBKey(tt.key)
+
+ if err != nil {
+ if err.Error() != tt.wantErr {
+ t.Errorf("DecodeBlockHashDBKey() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ } else if tt.wantErr != "" {
+ t.Errorf("DecodeBlockHashDBKey() expected error %v, got nil", tt.wantErr)
+ return
+ }
+
+ if got != tt.want {
+ t.Errorf("DecodeBlockHashDBKey() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestGetLastBlockHash_EmptyDb(t *testing.T) {
+ tmpDir := t.TempDir() + "/blockHashDb"
+ database, err := NewCodeDB(tmpDir, nil, nil, nil)
+ if err != nil {
+ t.Fatalf("error opening stateHash leveldb %s: %v", tmpDir, err)
+ }
+ defer func() {
+ err := database.Close()
+ assert.NoError(t, err, "error closing test database")
+ }()
+
+ output, err := GetLastBlockHash(database)
+ if err == nil {
+ t.Fatalf("expected error when getting last block hash from empty db, but got nil")
+ }
+ assert.Equal(t, "no block hash found", err.Error())
+ assert.Equal(t, uint64(0), output)
+}
+
+func TestGetLastBlockHash_InvalidKey(t *testing.T) {
+ tmpDir := t.TempDir() + "/blockHashDb"
+ database, err := NewCodeDB(tmpDir, nil, nil, nil)
+ if err != nil {
+ t.Fatalf("error opening stateHash leveldb %s: %v", tmpDir, err)
+ }
+
+ // Save an invalid block hash key
+ err = database.Put([]byte(BlockHashPrefix+"inv"), []byte("someValue"))
+ if err != nil {
+ t.Fatalf("error saving invalid block hash key: %v", err)
+ }
+
+ defer func() {
+ err := database.Close()
+ assert.NoError(t, err, "error closing test database")
+ }()
+
+ output, err := GetLastBlockHash(database)
+ if err == nil {
+ t.Fatalf("expected error when getting last block hash with invalid key, but got nil")
+ }
+ assert.Equal(t, "invalid length of block hash key, expected at least 10, got 5", err.Error())
+ assert.Equal(t, uint64(0), output)
+}
diff --git a/go.mod b/go.mod
index 51881f5..54d10d1 100644
--- a/go.mod
+++ b/go.mod
@@ -1,33 +1,34 @@
module github.com/0xsoniclabs/substate
-go 1.24
+go 1.24.0
require (
github.com/holiman/uint256 v1.3.2
- github.com/stretchr/testify v1.9.0
+ github.com/status-im/keycard-go v0.3.3
+ github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
- github.com/urfave/cli/v2 v2.25.7
+ github.com/urfave/cli/v2 v2.27.5
go.uber.org/mock v0.5.0
golang.org/x/crypto v0.36.0
google.golang.org/protobuf v1.34.2
)
require (
- github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/fsnotify/fsnotify v1.4.9 // indirect
- github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/golang/snappy v1.0.0 // indirect
github.com/nxadm/tail v1.4.4 // indirect
github.com/onsi/ginkgo v1.14.0 // indirect
github.com/onsi/gomega v1.10.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
- github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/net v0.38.0 // indirect
- golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
- gopkg.in/yaml.v2 v2.3.0 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 893467f..64df172 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,11 @@
-github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
+github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -15,8 +16,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
-github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
+github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -38,14 +39,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/status-im/keycard-go v0.3.3 h1:qk/JHSkT9sMka+lVXrTOIVSgHIY7lDm46wrUqTsNa4s=
+github.com/status-im/keycard-go v0.3.3/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
-github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
-github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
-github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
-github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
+github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
+github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
+github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -68,8 +71,9 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
+golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -93,7 +97,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=