Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flag] Add Flag to switch between RLP and other future encoding #88

Merged
merged 3 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
31 changes: 14 additions & 17 deletions db/substate_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ type SubstateDB interface {

// GetLastSubstate returns last substate (block and transaction wise) inside given DB.
GetLastSubstate() (*substate.Substate, error)

// SetSubstateEncoding sets the decoder func to the provided encoding
SetSubstateEncoding(encoding string) (*substateDB, error)

// GetSubstateEncoding returns the currently configured encoding
GetSubstateEncoding() string
}

// NewDefaultSubstateDB creates new instance of SubstateDB with default options.
Expand All @@ -57,11 +63,11 @@ func NewSubstateDB(path string, o *opt.Options, wo *opt.WriteOptions, ro *opt.Re
}

func MakeDefaultSubstateDB(db *leveldb.DB) SubstateDB {
return &substateDB{&codeDB{&baseDB{backend: db}}}
return &substateDB{&codeDB{&baseDB{backend: db}}, nil}
}

func MakeDefaultSubstateDBFromBaseDB(db BaseDB) SubstateDB {
return &substateDB{&codeDB{&baseDB{backend: db.getBackend()}}}
return &substateDB{&codeDB{&baseDB{backend: db.getBackend()}}, nil}
}

// NewReadOnlySubstateDB creates a new instance of read-only SubstateDB.
Expand All @@ -70,19 +76,20 @@ func NewReadOnlySubstateDB(path string) (SubstateDB, error) {
}

func MakeSubstateDB(db *leveldb.DB, wo *opt.WriteOptions, ro *opt.ReadOptions) SubstateDB {
return &substateDB{&codeDB{&baseDB{backend: db, wo: wo, ro: ro}}}
return &substateDB{&codeDB{&baseDB{backend: db, wo: wo, ro: ro}}, nil}
}

func newSubstateDB(path string, o *opt.Options, wo *opt.WriteOptions, ro *opt.ReadOptions) (*substateDB, error) {
base, err := newCodeDB(path, o, wo, ro)
if err != nil {
return nil, err
}
return &substateDB{base}, nil
return &substateDB{base, nil}, nil
}

type substateDB struct {
*codeDB
encoding *substateEncoding
}

func (db *substateDB) GetFirstSubstate() *substate.Substate {
Expand All @@ -108,12 +115,7 @@ func (db *substateDB) GetSubstate(block uint64, tx int) (*substate.Substate, err
return nil, fmt.Errorf("cannot get substate block: %v, tx: %v from db; %w", block, tx, err)
}

rlpSubstate, err := rlp.Decode(val)
if err != nil {
return nil, fmt.Errorf("cannot decode data into rlp block: %v, tx %v; %w", block, tx, err)
}

return rlpSubstate.ToSubstate(db.GetCode, block, tx)
return db.decodeToSubstate(val, block, tx)
}

// GetBlockSubstates returns substates for given block if exists within DB.
Expand All @@ -138,14 +140,9 @@ func (db *substateDB) GetBlockSubstates(block uint64) (map[int]*substate.Substat
return nil, fmt.Errorf("record-replay: GetBlockSubstates(%v) iterated substates from block %v", block, b)
}

rlpSubstate, err := rlp.Decode(value)
if err != nil {
return nil, fmt.Errorf("cannot decode data into rlp block: %v, tx %v; %w", block, tx, err)
}

sbstt, err := rlpSubstate.ToSubstate(db.GetCode, block, tx)
sbstt, err := db.decodeToSubstate(value, block, tx)
if err != nil {
return nil, fmt.Errorf("cannot decode data into substate: %w", err)
return nil, fmt.Errorf("failed to decode substate, block %v, tx: %v; %w", block, tx, err)
}

txSubstate[tx] = sbstt
Expand Down
11 changes: 10 additions & 1 deletion db/substate_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ func TestSubstateDB_GetSubstate(t *testing.T) {
t.Fatal(err)
}

testSubstateDB_GetSubstate(db, t)
}

func testSubstateDB_GetSubstate(db *substateDB, t *testing.T) {
ss, err := db.GetSubstate(37_534_834, 1)
if err != nil {
t.Fatalf("get substate returned error; %v", err)
Expand All @@ -84,6 +88,7 @@ func TestSubstateDB_GetSubstate(t *testing.T) {
if err = ss.Equal(testSubstate); err != nil {
t.Fatalf("substates are different; %v", err)
}

}

func TestSubstateDB_DeleteSubstate(t *testing.T) {
Expand Down Expand Up @@ -200,13 +205,17 @@ func createDbAndPutSubstate(dbPath string) (*substateDB, error) {
}

func addSubstate(db *substateDB, blk uint64) error {
return addCustomSubstate(db, blk, testSubstate)
}

func addCustomSubstate(db *substateDB, blk uint64, ss *substate.Substate) error {
h1 := types.Hash{}
h1.SetBytes(nil)

h2 := types.Hash{}
h2.SetBytes(nil)

s := *testSubstate
s := *ss

s.InputSubstate[types.Address{1}] = substate.NewAccount(1, new(big.Int).SetUint64(1), h1[:])
s.OutputSubstate[types.Address{2}] = substate.NewAccount(2, new(big.Int).SetUint64(2), h2[:])
Expand Down
80 changes: 80 additions & 0 deletions db/substate_encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package db

import (
"fmt"

"github.com/Fantom-foundation/Substate/rlp"
"github.com/Fantom-foundation/Substate/substate"
"github.com/Fantom-foundation/Substate/types"
)

// SetSubstateEncoding sets the runtime encoding/decoding behavior of substateDB
// intended usage:
//
// db := &substateDB{..}
// db, err := db.SetSubstateEncoding(<schema>) // set encoding
// db.GetSubstateDecoder() // returns configured encoding
func (db *substateDB) SetSubstateEncoding(schema string) (*substateDB, error) {
encoding, err := newSubstateEncoding(schema, db.GetCode)
if err != nil {
return nil, fmt.Errorf("Failed to set decoder; %w", err)
}

db.encoding = encoding
return db, nil
}

// GetDecoder returns the encoding in use
func (db *substateDB) GetSubstateEncoding() string {
if db.encoding == nil {
return ""
}
return db.encoding.schema
}

type substateEncoding struct {
schema string
decode decoderFunc
}

// decoderFunc aliases the common function used to decode substate
type decoderFunc func([]byte, uint64, int) (*substate.Substate, error)
rpl-ffl marked this conversation as resolved.
Show resolved Hide resolved

// codeLookup aliases codehash->code lookup necessary to decode substate
type codeLookup = func(types.Hash) ([]byte, error)
rpl-ffl marked this conversation as resolved.
Show resolved Hide resolved

// newSubstateDecoder returns requested SubstateDecoder
func newSubstateEncoding(encoding string, lookup codeLookup) (*substateEncoding, error) {
switch encoding {

case "default", "rlp":
return &substateEncoding{
schema: "rlp",
decode: func(bytes []byte, block uint64, tx int) (*substate.Substate, error) {
return decodeRlp(bytes, lookup, block, tx)
},
}, nil

default:
return nil, fmt.Errorf("Encoding not supported: %s", encoding)

}
}

// decodeSubstate defensively defaults to "default" if nil
func (db *substateDB) decodeToSubstate(bytes []byte, block uint64, tx int) (*substate.Substate, error) {
if db.encoding == nil {
db.SetSubstateEncoding("default")
}
return db.encoding.decode(bytes, block, tx)
}

// decodeRlp decodes into substate the provided rlp-encoded bytecode
func decodeRlp(bytes []byte, lookup codeLookup, block uint64, tx int) (*substate.Substate, error) {
rlpSubstate, err := rlp.Decode(bytes)
if err != nil {
return nil, fmt.Errorf("cannot decode substate data from rlp block: %v, tx %v; %w", block, tx, err)
}

return rlpSubstate.ToSubstate(lookup, block, tx)
}
130 changes: 130 additions & 0 deletions db/substate_encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package db

import (
"strings"
"testing"

"github.com/Fantom-foundation/Substate/rlp"
trlp "github.com/Fantom-foundation/Substate/types/rlp"
)

var (
testRlp, _ = trlp.EncodeToBytes(rlp.NewRLP(testSubstate))
testBlk = testSubstate.Block
testTx = testSubstate.Transaction

supportedEncoding = map[string][]byte{
"rlp": testRlp,
}
)

func TestSubstateEncoding_NilEncodingDefaultsToRlp(t *testing.T) {
path := t.TempDir() + "test-db"
db, err := newSubstateDB(path, nil, nil, nil)
if err != nil {
t.Errorf("cannot open db; %v", err)
}

if got := db.GetSubstateEncoding(); got != "" {
t.Fatalf("substate encoding should be nil, got: %s", got)
}

// purposely never set encoding
_, err = db.decodeToSubstate(testRlp, testBlk, testTx)
if err != nil {
t.Fatal(err)
}

if got := db.GetSubstateEncoding(); got != "rlp" {
t.Fatalf("db should default to rlp, got: %s", got)
}
}

func TestSubstateEncoding_DefaultEncodingDefaultsToRlp(t *testing.T) {
path := t.TempDir() + "test-db"
db, err := newSubstateDB(path, nil, nil, nil)
if err != nil {
t.Errorf("cannot open db; %v", err)
}

_, err = db.SetSubstateEncoding("default")
if err != nil {
t.Fatal("default is supportet, but error")
}

_, err = db.decodeToSubstate(testRlp, testBlk, testTx)
if err != nil {
t.Fatal(err)
}

if got := db.GetSubstateEncoding(); got != "rlp" {
t.Fatalf("db should default to rlp, got: %s", got)
}
}

func TestSubstateEncoding_UnsupportedEncodingThrowsError(t *testing.T) {
path := t.TempDir() + "test-db"
db, err := newSubstateDB(path, nil, nil, nil)
if err != nil {
t.Errorf("cannot open db; %v", err)
}

_, err = db.SetSubstateEncoding("EncodingNotSupported")
if err == nil || !strings.Contains(err.Error(), "Encoding not supported") {
t.Error("Encoding not supported, but no error")
}
}

func TestSubstateEncoding_TestDb(t *testing.T) {
path := t.TempDir() + "test-db"
db, err := newSubstateDB(path, nil, nil, nil)
if err != nil {
t.Errorf("cannot open db; %v", err)
}

for encoding, bytes := range supportedEncoding {
_, err = db.SetSubstateEncoding(encoding)
if err != nil {
t.Error(err)
}

ss, err := db.decodeToSubstate(bytes, testBlk, testTx)
if err != nil {
t.Error(err)
}

err = addCustomSubstate(db, testBlk, ss)
if err != nil {
t.Error(err)
}

testSubstateDB_GetSubstate(db, t)
}
}

func TestSubstateEncoding_TestIterator(t *testing.T) {
path := t.TempDir() + "test-db"
db, err := newSubstateDB(path, nil, nil, nil)
if err != nil {
t.Errorf("cannot open db; %v", err)
}

for encoding, bytes := range supportedEncoding {
_, err = db.SetSubstateEncoding(encoding)
if err != nil {
t.Error(err)
}

ss, err := db.decodeToSubstate(bytes, testBlk, testTx)
if err != nil {
t.Error(err)
}

err = addCustomSubstate(db, testBlk, ss)
if err != nil {
t.Error(err)
}

testSubstatorIterator_Value(db, t)
}
}
11 changes: 2 additions & 9 deletions db/substate_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ package db
import (
"fmt"

"github.com/syndtr/goleveldb/leveldb/util"

"github.com/Fantom-foundation/Substate/rlp"
"github.com/Fantom-foundation/Substate/substate"
"github.com/syndtr/goleveldb/leveldb/util"
)

func newSubstateIterator(db *substateDB, start []byte) *substateIterator {
Expand All @@ -33,12 +31,7 @@ func (i *substateIterator) decode(data rawEntry) (*substate.Substate, error) {
return nil, fmt.Errorf("invalid substate key: %v; %w", key, err)
}

rlpSubstate, err := rlp.Decode(value)
if err != nil {
return nil, err
}

return rlpSubstate.ToSubstate(i.db.GetCode, block, tx)
return i.db.decodeToSubstate(value, block, tx)
}

func (i *substateIterator) start(numWorkers int) {
Expand Down
5 changes: 5 additions & 0 deletions db/substate_iterator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func TestSubstateIterator_Value(t *testing.T) {
return
}

testSubstatorIterator_Value(db, t)
}

func testSubstatorIterator_Value(db *substateDB, t *testing.T) {
iter := db.NewSubstateIterator(0, 10)

if !iter.Next() {
Expand All @@ -48,6 +52,7 @@ func TestSubstateIterator_Value(t *testing.T) {
if tx.Transaction != 1 {
t.Fatalf("iterator returned transaction with different transaction number\ngot: %v\n want: %v", tx.Transaction, 1)
}

}

func TestSubstateIterator_Release(t *testing.T) {
Expand Down