mirror of https://github.com/poanetwork/gecko.git
438 lines
11 KiB
Go
438 lines
11 KiB
Go
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package state
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/ava-labs/gecko/database/memdb"
|
|
"github.com/ava-labs/gecko/ids"
|
|
"github.com/ava-labs/gecko/utils/hashing"
|
|
"github.com/ava-labs/gecko/utils/wrappers"
|
|
)
|
|
|
|
// toy example of a block, just used for testing
|
|
type block struct {
|
|
parentID ids.ID
|
|
value uint64
|
|
}
|
|
|
|
const blockSize = 40 // hashing.HashLen (32) + length of uin64 (8)
|
|
|
|
func (b *block) Bytes() []byte {
|
|
p := wrappers.Packer{Bytes: make([]byte, blockSize)}
|
|
p.PackFixedBytes(b.parentID.Bytes())
|
|
p.PackLong(b.value)
|
|
return p.Bytes
|
|
}
|
|
|
|
func unmarshalBlock(bytes []byte) (interface{}, error) {
|
|
p := wrappers.Packer{Bytes: bytes}
|
|
|
|
parentID, err := ids.ToID(p.UnpackFixedBytes(hashing.HashLen))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
value := p.UnpackLong()
|
|
|
|
if p.Errored() {
|
|
return nil, p.Err
|
|
}
|
|
|
|
return &block{
|
|
parentID: parentID,
|
|
value: value,
|
|
}, nil
|
|
}
|
|
|
|
// toy example of an account, just used for testing
|
|
type account struct {
|
|
id ids.ID
|
|
balance uint64
|
|
nonce uint64
|
|
}
|
|
|
|
const accountSize = 32 + 8 + 8
|
|
|
|
func (acc *account) Bytes() []byte {
|
|
p := wrappers.Packer{Bytes: make([]byte, accountSize)}
|
|
p.PackFixedBytes(acc.id.Bytes())
|
|
p.PackLong(acc.balance)
|
|
p.PackLong(acc.nonce)
|
|
return p.Bytes
|
|
}
|
|
|
|
func unmarshalAccount(bytes []byte) (interface{}, error) {
|
|
p := wrappers.Packer{Bytes: bytes}
|
|
|
|
id, err := ids.ToID(p.UnpackFixedBytes(hashing.HashLen))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
balance := p.UnpackLong()
|
|
nonce := p.UnpackLong()
|
|
|
|
if p.Errored() {
|
|
return nil, p.Err
|
|
}
|
|
|
|
return &account{
|
|
id: id,
|
|
balance: balance,
|
|
nonce: nonce,
|
|
}, nil
|
|
}
|
|
|
|
// Ensure there is an error if someone tries to do a put without registering the type
|
|
func TestPutUnregistered(t *testing.T) {
|
|
// make a state and a database
|
|
state := NewState()
|
|
db := memdb.New()
|
|
defer db.Close()
|
|
|
|
// make an account
|
|
acc1 := &account{
|
|
id: ids.NewID([32]byte{1, 2, 3}),
|
|
balance: 1,
|
|
nonce: 2,
|
|
}
|
|
|
|
if err := state.Put(db, 1, ids.NewID([32]byte{1, 2, 3}), acc1); err == nil {
|
|
t.Fatal("should have failed because type ID is unregistred")
|
|
}
|
|
|
|
// register type
|
|
if err := state.RegisterType(1, unmarshalAccount); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// should not error now
|
|
if err := state.Put(db, 1, ids.NewID([32]byte{1, 2, 3}), acc1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Ensure there is an error if someone tries to get the value associated with a
|
|
// key that doesn't exist
|
|
func TestKeyDoesNotExist(t *testing.T) {
|
|
// make a state and a database
|
|
state := NewState()
|
|
db := memdb.New()
|
|
defer db.Close()
|
|
|
|
if _, err := state.Get(db, 1, ids.NewID([32]byte{1, 2, 3})); err == nil {
|
|
t.Fatal("should have failed because no such key or typeID exists")
|
|
}
|
|
|
|
// register type with ID 1
|
|
typeID := uint64(1)
|
|
if err := state.RegisterType(typeID, unmarshalAccount); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Should still fail because there is no value with this key
|
|
if _, err := state.Get(db, typeID, ids.NewID([32]byte{1, 2, 3})); err == nil {
|
|
t.Fatal("should have failed because no such key exists")
|
|
}
|
|
}
|
|
|
|
// Ensure there is an error if someone tries to register a type ID that already exists
|
|
func TestRegisterExistingTypeID(t *testing.T) {
|
|
// make a state and a database
|
|
state := NewState()
|
|
db := memdb.New()
|
|
defer db.Close()
|
|
|
|
// register type with ID 1
|
|
typeID := uint64(1)
|
|
if err := state.RegisterType(typeID, unmarshalBlock); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// try to register the same type ID
|
|
if err := state.RegisterType(typeID, unmarshalAccount); err == nil {
|
|
t.Fatal("Should have errored because typeID already registered")
|
|
}
|
|
|
|
}
|
|
|
|
// Ensure there is an error when someone tries to get a value using the wrong typeID
|
|
func TestGetWrongTypeID(t *testing.T) {
|
|
// make a state and a database
|
|
state := NewState()
|
|
db := memdb.New()
|
|
defer db.Close()
|
|
|
|
// register type with ID 1
|
|
blockTypeID := uint64(1)
|
|
if err := state.RegisterType(blockTypeID, unmarshalBlock); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make and put a block
|
|
block := &block{
|
|
parentID: ids.NewID([32]byte{4, 5, 6}),
|
|
value: 5,
|
|
}
|
|
blockID := ids.NewID([32]byte{1, 2, 3})
|
|
err := state.Put(db, blockTypeID, blockID, block)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// try to get it using the right key but wrong typeID
|
|
if _, err := state.Get(db, 2, blockID); err == nil {
|
|
t.Fatal("should have failed because type ID is wrong")
|
|
}
|
|
}
|
|
|
|
// Ensure that there is no error when someone puts two values with the same
|
|
// key but different type IDs
|
|
func TestSameKeyDifferentTypeID(t *testing.T) {
|
|
// make a state and a database
|
|
state := NewState()
|
|
db := memdb.New()
|
|
defer db.Close()
|
|
|
|
// register block type with ID 1
|
|
blockTypeID := uint64(1)
|
|
if err := state.RegisterType(blockTypeID, unmarshalBlock); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// register account type with ID 2
|
|
accountTypeID := uint64(2)
|
|
if err := state.RegisterType(accountTypeID, unmarshalAccount); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sharedKey := ids.NewID([32]byte{1, 2, 3})
|
|
|
|
// make an account
|
|
acc := &account{
|
|
id: ids.NewID([32]byte{1, 2, 3}),
|
|
balance: 1,
|
|
nonce: 2,
|
|
}
|
|
|
|
// put it using sharedKey
|
|
err := state.Put(db, accountTypeID, sharedKey, acc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make a block
|
|
block1 := &block{
|
|
parentID: ids.NewID([32]byte{4, 5, 6}),
|
|
value: 5,
|
|
}
|
|
|
|
// put it using sharedKey
|
|
err = state.Put(db, blockTypeID, sharedKey, block1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// ensure the account is still there and correct
|
|
if accInterface, err := state.Get(db, accountTypeID, sharedKey); err != nil {
|
|
t.Fatal(err)
|
|
} else if accFromState, ok := accInterface.(*account); !ok {
|
|
t.Fatal("should have been type *account")
|
|
} else if accFromState.balance != acc.balance {
|
|
t.Fatal("balances should be same")
|
|
} else if !accFromState.id.Equals(acc.id) {
|
|
t.Fatal("ids should be the same")
|
|
} else if accFromState.nonce != acc.nonce {
|
|
t.Fatal("nonces should be same")
|
|
}
|
|
|
|
// ensure the block is still there and correct
|
|
if blockInterface, err := state.Get(db, blockTypeID, sharedKey); err != nil {
|
|
t.Fatal(err)
|
|
} else if blockFromState, ok := blockInterface.(*block); !ok {
|
|
t.Fatal("should have been type *block")
|
|
} else if !blockFromState.parentID.Equals(block1.parentID) {
|
|
t.Fatal("parentIDs should be same")
|
|
} else if blockFromState.value != block1.value {
|
|
t.Fatal("values should be same")
|
|
}
|
|
}
|
|
|
|
// Ensure that overwriting a value works
|
|
func TestOverwrite(t *testing.T) {
|
|
// make a state and a database
|
|
state := NewState()
|
|
db := memdb.New()
|
|
defer db.Close()
|
|
|
|
// register block type with ID 1
|
|
blockTypeID := uint64(1)
|
|
if err := state.RegisterType(blockTypeID, unmarshalBlock); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make a block
|
|
block1 := &block{
|
|
parentID: ids.NewID([32]byte{4, 5, 6}),
|
|
value: 5,
|
|
}
|
|
|
|
key := ids.NewID([32]byte{1, 2, 3})
|
|
|
|
// put it
|
|
err := state.Put(db, blockTypeID, key, block1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make another block
|
|
block2 := &block{
|
|
parentID: ids.NewID([32]byte{100, 200, 1}),
|
|
value: 6,
|
|
}
|
|
|
|
// put it with the same key
|
|
err = state.Put(db, blockTypeID, key, block2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// ensure the first value was over-written
|
|
// get it and make sure it's right
|
|
if blockInterface, err := state.Get(db, blockTypeID, key); err != nil {
|
|
t.Fatal(err)
|
|
} else if blockFromState, ok := blockInterface.(*block); !ok {
|
|
t.Fatal("should have been type *block")
|
|
} else if !blockFromState.parentID.Equals(block2.parentID) {
|
|
t.Fatal("parentIDs should be same")
|
|
} else if blockFromState.value != block2.value {
|
|
t.Fatal("values should be same")
|
|
}
|
|
}
|
|
|
|
// Put 4 values, 2 of one type and 2 of another
|
|
func TestHappyPath(t *testing.T) {
|
|
// make a state and a database
|
|
state := NewState()
|
|
db := memdb.New()
|
|
defer db.Close()
|
|
|
|
accountTypeID := uint64(1)
|
|
|
|
// register type account
|
|
err := state.RegisterType(accountTypeID, unmarshalAccount)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make an account
|
|
acc1 := &account{
|
|
id: ids.NewID([32]byte{1, 2, 3}),
|
|
balance: 1,
|
|
nonce: 2,
|
|
}
|
|
|
|
// put it
|
|
err = state.Put(db, accountTypeID, acc1.id, acc1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get it and make sure it's right
|
|
if acc1Interface, err := state.Get(db, accountTypeID, acc1.id); err != nil {
|
|
t.Fatal(err)
|
|
} else if acc1FromState, ok := acc1Interface.(*account); !ok {
|
|
t.Fatal("should have been type *account")
|
|
} else if acc1FromState.balance != acc1.balance {
|
|
t.Fatal("balances should be same")
|
|
} else if !acc1FromState.id.Equals(acc1.id) {
|
|
t.Fatal("ids should be the same")
|
|
} else if acc1FromState.nonce != acc1.nonce {
|
|
t.Fatal("nonces should be same")
|
|
}
|
|
|
|
// make another account
|
|
acc2 := &account{
|
|
id: ids.NewID([32]byte{9, 2, 1}),
|
|
balance: 7,
|
|
nonce: 44,
|
|
}
|
|
|
|
// put it
|
|
err = state.Put(db, accountTypeID, acc2.id, acc2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get it and make sure it's right
|
|
if acc2Interface, err := state.Get(db, accountTypeID, acc2.id); err != nil {
|
|
t.Fatal(err)
|
|
} else if acc2FromState, ok := acc2Interface.(*account); !ok {
|
|
t.Fatal("should have been type *account")
|
|
} else if acc2FromState.balance != acc2.balance {
|
|
t.Fatal("balances should be same")
|
|
} else if !acc2FromState.id.Equals(acc2.id) {
|
|
t.Fatal("ids should be the same")
|
|
} else if acc2FromState.nonce != acc2.nonce {
|
|
t.Fatal("nonces should be same")
|
|
}
|
|
|
|
// register type block
|
|
blockTypeID := uint64(2)
|
|
err = state.RegisterType(blockTypeID, unmarshalBlock)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// make a block
|
|
block1ID := ids.NewID([32]byte{9, 9, 9})
|
|
block1 := &block{
|
|
parentID: ids.NewID([32]byte{4, 5, 6}),
|
|
value: 5,
|
|
}
|
|
|
|
// put it
|
|
err = state.Put(db, blockTypeID, block1ID, block1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get it and make sure it's right
|
|
if block1Interface, err := state.Get(db, blockTypeID, block1ID); err != nil {
|
|
t.Fatal(err)
|
|
} else if block1FromState, ok := block1Interface.(*block); !ok {
|
|
t.Fatal("should have been type *block")
|
|
} else if !block1FromState.parentID.Equals(block1.parentID) {
|
|
t.Fatal("parentIDs should be same")
|
|
} else if block1FromState.value != block1.value {
|
|
t.Fatal("values should be same")
|
|
}
|
|
|
|
// make another block
|
|
block2ID := ids.NewID([32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
|
block2 := &block{
|
|
parentID: ids.NewID([32]byte{10, 1, 2}),
|
|
value: 67,
|
|
}
|
|
|
|
// put it
|
|
err = state.Put(db, blockTypeID, block2ID, block2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// get it and make sure it's right
|
|
if block2Interface, err := state.Get(db, blockTypeID, block2ID); err != nil {
|
|
t.Fatal(err)
|
|
} else if block2FromState, ok := block2Interface.(*block); !ok {
|
|
t.Fatal("should have been type *block")
|
|
} else if !block2FromState.parentID.Equals(block2.parentID) {
|
|
t.Fatal("parentIDs should be same")
|
|
} else if block2FromState.value != block2.value {
|
|
t.Fatal("values should be same")
|
|
}
|
|
}
|