Add epoch block number storage in rawdb

pull/839/head
Eugene Kim 6 years ago
parent 413e5703a0
commit abdfb5e048
  1. 42
      core/rawdb/accessors_chain.go
  2. 171
      core/rawdb/accessors_chain_test.go
  3. 2
      core/rawdb/interfaces.go
  4. 137
      core/rawdb/mock/mock.go
  5. 9
      core/rawdb/schema.go
  6. 1
      go.mod

@ -24,7 +24,10 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/ctxerror"
"github.com/harmony-one/harmony/internal/utils"
)
// ReadCanonicalHash retrieves the hash assigned to a canonical block number.
@ -337,6 +340,29 @@ func ReadBlock(db DatabaseReader, hash common.Hash, number uint64) *types.Block
func WriteBlock(db DatabaseWriter, block *types.Block) {
WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
WriteHeader(db, block.Header())
epoch := block.Header().Epoch
if epoch == nil {
// backward compatibility
return
}
epoch = new(big.Int).Set(epoch)
epochBlockNum := block.Number()
writeOne := func() {
if err := WriteEpochBlockNumber(db, epoch, epochBlockNum); err != nil {
ctxerror.Log15(utils.GetLogInstance().Error, err)
}
}
// A block may be a genesis block AND end-of-epoch block at the same time.
if epochBlockNum.Sign() == 0 {
// Genesis block; record this block's epoch and block numbers.
writeOne()
}
if len(block.Header().ShardState) > 0 {
// End-of-epoch block; record the next epoch after this block.
epoch = new(big.Int).Add(epoch, common.Big1)
epochBlockNum = new(big.Int).Add(epochBlockNum, common.Big1)
writeOne()
}
}
// DeleteBlock removes all block data associated with a hash.
@ -398,3 +424,19 @@ func WriteShardState(db DatabaseWriter, hash common.Hash, number uint64, shardSt
log.Crit("Failed to store sharding state", "err", err)
}
}
// ReadEpochBlockNumber retrieves the epoch block number for the given epoch,
// or nil if the given epoch is not found in the database.
func ReadEpochBlockNumber(db DatabaseReader, epoch *big.Int) (*big.Int, error) {
data, err := db.Get(epochBlockNumberKey(epoch))
if err != nil {
return nil, err
}
return new(big.Int).SetBytes(data), nil
}
// WriteEpochBlockNumber stores the given epoch-number-to-epoch-block-number
// in the database.
func WriteEpochBlockNumber(db DatabaseWriter, epoch, blockNum *big.Int) error {
return db.Put(epochBlockNumberKey(epoch), blockNum.Bytes())
}

@ -19,12 +19,17 @@ package rawdb
import (
"bytes"
"math/big"
"os"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/golang/mock/gomock"
mock "github.com/harmony-one/harmony/core/rawdb/mock"
"github.com/harmony-one/harmony/core/types"
"github.com/syndtr/goleveldb/leveldb"
"golang.org/x/crypto/sha3"
)
@ -108,6 +113,11 @@ func TestBlockStorage(t *testing.T) {
Extra: []byte("test block"),
TxHash: types.EmptyRootHash,
ReceiptHash: types.EmptyRootHash,
Epoch: big.NewInt(0),
Number: big.NewInt(0),
ShardState: types.ShardState{
{},
},
})
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil {
t.Fatalf("Non existent block returned: %v", entry)
@ -135,6 +145,16 @@ func TestBlockStorage(t *testing.T) {
} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(block.Transactions()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body())
}
if actual, err := ReadEpochBlockNumber(db, big.NewInt(0)); err != nil {
t.Fatalf("Genesis epoch block number not found, error=%#v", err)
} else if expected := big.NewInt(0); actual.Cmp(expected) != 0 {
t.Fatalf("Genesis epoch block number mismatch: have %v, want %v", actual, expected)
}
if actual, err := ReadEpochBlockNumber(db, big.NewInt(1)); err != nil {
t.Fatalf("Next epoch block number not found, error=%#v", err)
} else if expected := big.NewInt(1); actual.Cmp(expected) != 0 {
t.Fatalf("Next epoch block number mismatch: have %v, want %v", actual, expected)
}
// Delete the block and verify the execution
DeleteBlock(db, block.Hash(), block.NumberU64())
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil {
@ -315,3 +335,154 @@ func TestBlockReceiptStorage(t *testing.T) {
t.Fatalf("deleted receipts returned: %v", rs)
}
}
func TestReadEpochBlockNumber(t *testing.T) {
type args struct {
// db is mocked
epoch *big.Int
}
type dbCall struct {
key []byte
data []byte
error error
}
tests := []struct {
name string
args args
dbCall dbCall
want *big.Int
wantErr bool
}{
{
"0",
args{epoch: big.NewInt(0)},
dbCall{
key: []byte{},
data: []byte{},
error: nil,
},
big.NewInt(0),
false,
},
{
"1",
args{epoch: big.NewInt(1)},
dbCall{
key: []byte{0x01},
data: []byte{0x64},
error: nil,
},
big.NewInt(100),
false,
},
{
"1000",
args{epoch: big.NewInt(1000)},
dbCall{
key: []byte{0x03, 0xe8},
data: []byte{0x01, 0x86, 0xa0},
error: nil,
},
big.NewInt(100000),
false,
},
{
"error",
args{epoch: big.NewInt(0)},
dbCall{
key: []byte{},
data: []byte{},
error: leveldb.ErrNotFound,
},
nil,
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
db := mock.NewMockDatabaseReader(ctrl)
fullKey := append(epochBlockNumberPrefix, tt.dbCall.key...)
db.EXPECT().Get(fullKey).Return(tt.dbCall.data, tt.dbCall.error)
got, err := ReadEpochBlockNumber(db, tt.args.epoch)
if (err != nil) != tt.wantErr {
t.Errorf("ReadEpochBlockNumber() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ReadEpochBlockNumber() = %v, want %v", got, tt.want)
}
})
}
}
func TestWriteEpochBlockNumber(t *testing.T) {
type args struct {
epoch *big.Int
blockNum *big.Int
}
type dbCall struct {
key []byte
data []byte
error error
}
tests := []struct {
name string
args args
dbCall dbCall
wantErr bool
}{
{
"0",
args{epoch: big.NewInt(0), blockNum: big.NewInt(0)},
dbCall{
key: []byte{},
data: []byte{},
error: nil,
},
false,
},
{
"1",
args{epoch: big.NewInt(1), blockNum: big.NewInt(100)},
dbCall{
key: []byte{0x01},
data: []byte{0x64},
error: nil,
},
false,
},
{
"1000",
args{epoch: big.NewInt(1000), blockNum: big.NewInt(100000)},
dbCall{
key: []byte{0x03, 0xe8},
data: []byte{0x01, 0x86, 0xa0},
error: nil,
},
false,
},
{
"error",
args{epoch: big.NewInt(1000), blockNum: big.NewInt(100000)},
dbCall{
key: []byte{0x03, 0xe8},
data: []byte{0x01, 0x86, 0xa0},
error: os.ErrClosed,
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
db := mock.NewMockDatabaseWriter(ctrl)
fullKey := append(epochBlockNumberPrefix, tt.dbCall.key...)
db.EXPECT().Put(fullKey, tt.dbCall.data).Return(tt.dbCall.error)
if err := WriteEpochBlockNumber(db, tt.args.epoch, tt.args.blockNum); (err != nil) != tt.wantErr {
t.Errorf("WriteEpochBlockNumber() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

@ -16,6 +16,8 @@
package rawdb
//go:generate mockgen -source interfaces.go -destination mock/mock.go
// DatabaseReader wraps the Has and Get method of a backing data store.
type DatabaseReader interface {
Has(key []byte) (bool, error)

@ -0,0 +1,137 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: interfaces.go
// Package mock_rawdb is a generated GoMock package.
package mock_rawdb
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockDatabaseReader is a mock of DatabaseReader interface
type MockDatabaseReader struct {
ctrl *gomock.Controller
recorder *MockDatabaseReaderMockRecorder
}
// MockDatabaseReaderMockRecorder is the mock recorder for MockDatabaseReader
type MockDatabaseReaderMockRecorder struct {
mock *MockDatabaseReader
}
// NewMockDatabaseReader creates a new mock instance
func NewMockDatabaseReader(ctrl *gomock.Controller) *MockDatabaseReader {
mock := &MockDatabaseReader{ctrl: ctrl}
mock.recorder = &MockDatabaseReaderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDatabaseReader) EXPECT() *MockDatabaseReaderMockRecorder {
return m.recorder
}
// Has mocks base method
func (m *MockDatabaseReader) Has(key []byte) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Has", key)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Has indicates an expected call of Has
func (mr *MockDatabaseReaderMockRecorder) Has(key interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockDatabaseReader)(nil).Has), key)
}
// Get mocks base method
func (m *MockDatabaseReader) Get(key []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", key)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockDatabaseReaderMockRecorder) Get(key interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDatabaseReader)(nil).Get), key)
}
// MockDatabaseWriter is a mock of DatabaseWriter interface
type MockDatabaseWriter struct {
ctrl *gomock.Controller
recorder *MockDatabaseWriterMockRecorder
}
// MockDatabaseWriterMockRecorder is the mock recorder for MockDatabaseWriter
type MockDatabaseWriterMockRecorder struct {
mock *MockDatabaseWriter
}
// NewMockDatabaseWriter creates a new mock instance
func NewMockDatabaseWriter(ctrl *gomock.Controller) *MockDatabaseWriter {
mock := &MockDatabaseWriter{ctrl: ctrl}
mock.recorder = &MockDatabaseWriterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDatabaseWriter) EXPECT() *MockDatabaseWriterMockRecorder {
return m.recorder
}
// Put mocks base method
func (m *MockDatabaseWriter) Put(key, value []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Put", key, value)
ret0, _ := ret[0].(error)
return ret0
}
// Put indicates an expected call of Put
func (mr *MockDatabaseWriterMockRecorder) Put(key, value interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockDatabaseWriter)(nil).Put), key, value)
}
// MockDatabaseDeleter is a mock of DatabaseDeleter interface
type MockDatabaseDeleter struct {
ctrl *gomock.Controller
recorder *MockDatabaseDeleterMockRecorder
}
// MockDatabaseDeleterMockRecorder is the mock recorder for MockDatabaseDeleter
type MockDatabaseDeleterMockRecorder struct {
mock *MockDatabaseDeleter
}
// NewMockDatabaseDeleter creates a new mock instance
func NewMockDatabaseDeleter(ctrl *gomock.Controller) *MockDatabaseDeleter {
mock := &MockDatabaseDeleter{ctrl: ctrl}
mock.recorder = &MockDatabaseDeleterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDatabaseDeleter) EXPECT() *MockDatabaseDeleterMockRecorder {
return m.recorder
}
// Delete mocks base method
func (m *MockDatabaseDeleter) Delete(key []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", key)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete
func (mr *MockDatabaseDeleterMockRecorder) Delete(key interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDatabaseDeleter)(nil).Delete), key)
}

@ -19,6 +19,7 @@ package rawdb
import (
"encoding/binary"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/metrics"
@ -58,6 +59,10 @@ var (
preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db
// epochBlockNumberPrefix + epoch (big.Int.Bytes())
// -> epoch block number (big.Int.Bytes())
epochBlockNumberPrefix = []byte("harmony-epoch-block-number-")
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
@ -138,3 +143,7 @@ func configKey(hash common.Hash) []byte {
func shardStateKey(number uint64, hash common.Hash) []byte {
return append(append(shardStatePrefix, encodeBlockNumber(number)...), hash.Bytes()...)
}
func epochBlockNumberKey(epoch *big.Int) []byte {
return append(epochBlockNumberPrefix, epoch.Bytes()...)
}

@ -46,6 +46,7 @@ require (
github.com/shirou/gopsutil v2.18.12+incompatible
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
github.com/stretchr/testify v1.3.0
github.com/syndtr/goleveldb v1.0.0
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3

Loading…
Cancel
Save