gecko/vms/components/state/state_test.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")
}
}