merged utxo indexing upgrade

This commit is contained in:
StephenButtolph 2020-05-30 12:47:23 -04:00
commit 3d5a838ce6
9 changed files with 174 additions and 119 deletions

View File

@ -15,7 +15,6 @@ const (
txID uint64 = iota
utxoID
txStatusID
fundsID
dbInitializedID
)
@ -28,8 +27,8 @@ var (
type prefixedState struct {
state *state
tx, utxo, txStatus, funds cache.Cacher
uniqueTx cache.Deduplicator
tx, utxo, txStatus cache.Cacher
uniqueTx cache.Deduplicator
}
// UniqueTx de-duplicates the transaction.
@ -76,14 +75,7 @@ func (s *prefixedState) SetDBInitialized(status choices.Status) error {
// Funds returns the mapping from the 32 byte representation of an address to a
// list of utxo IDs that reference the address.
func (s *prefixedState) Funds(id ids.ID) ([]ids.ID, error) {
return s.state.IDs(uniqueID(id, fundsID, s.funds))
}
// SetFunds saves the mapping from address to utxo IDs to storage.
func (s *prefixedState) SetFunds(id ids.ID, idSlice []ids.ID) error {
return s.state.SetIDs(uniqueID(id, fundsID, s.funds), idSlice)
}
func (s *prefixedState) Funds(id ids.ID) ([]ids.ID, error) { return s.state.IDs(id) }
// SpendUTXO consumes the provided utxo.
func (s *prefixedState) SpendUTXO(utxoID ids.ID) error {
@ -106,11 +98,7 @@ func (s *prefixedState) SpendUTXO(utxoID ids.ID) error {
func (s *prefixedState) removeUTXO(addrs [][]byte, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := ids.NewID(hashing.ComputeHash256Array(addr))
utxos := ids.Set{}
funds, _ := s.Funds(addrID)
utxos.Add(funds...)
utxos.Remove(utxoID)
if err := s.SetFunds(addrID, utxos.List()); err != nil {
if err := s.state.RemoveID(addrID, utxoID); err != nil {
return err
}
}
@ -135,11 +123,7 @@ func (s *prefixedState) FundUTXO(utxo *ava.UTXO) error {
func (s *prefixedState) addUTXO(addrs [][]byte, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := ids.NewID(hashing.ComputeHash256Array(addr))
utxos := ids.Set{}
funds, _ := s.Funds(addrID)
utxos.Add(funds...)
utxos.Add(utxoID)
if err := s.SetFunds(addrID, utxos.List()); err != nil {
if err := s.state.AddID(addrID, utxoID); err != nil {
return err
}
}

View File

@ -151,7 +151,7 @@ func TestPrefixedFundingAddresses(t *testing.T) {
state := vm.state
vm.codec.RegisterType(&testAddressable{})
vm.codec.RegisterType(&ava.TestAddressable{})
utxo := &ava.UTXO{
UTXOID: ava.UTXOID{
@ -159,7 +159,7 @@ func TestPrefixedFundingAddresses(t *testing.T) {
OutputIndex: 1,
},
Asset: ava.Asset{ID: ids.Empty},
Out: &testAddressable{
Out: &ava.TestAddressable{
Addrs: [][]byte{
[]byte{0},
},
@ -182,8 +182,11 @@ func TestPrefixedFundingAddresses(t *testing.T) {
if err := state.SpendUTXO(utxo.InputID()); err != nil {
t.Fatal(err)
}
_, err = state.Funds(ids.NewID(hashing.ComputeHash256Array([]byte{0})))
if err == nil {
t.Fatalf("Should have returned no utxoIDs")
funds, err = state.Funds(ids.NewID(hashing.ComputeHash256Array([]byte{0})))
if err != nil {
t.Fatal(err)
}
if len(funds) != 0 {
t.Fatalf("Should have returned 0 utxoIDs")
}
}

View File

@ -23,17 +23,19 @@ func TestStateIDs(t *testing.T) {
state := vm.state.state
id0 := ids.NewID([32]byte{0xff, 0})
id1 := ids.NewID([32]byte{0xff, 0})
id2 := ids.NewID([32]byte{0xff, 0})
id0 := ids.NewID([32]byte{0x00, 0})
id1 := ids.NewID([32]byte{0x01, 0})
id2 := ids.NewID([32]byte{0x02, 0})
if _, err := state.IDs(ids.Empty); err == nil {
t.Fatalf("Should have errored when reading ids")
if _, err := state.IDs(ids.Empty); err != nil {
t.Fatal(err)
}
expected := []ids.ID{id0, id1}
if err := state.SetIDs(ids.Empty, expected); err != nil {
t.Fatal(err)
for _, id := range expected {
if err := state.AddID(ids.Empty, id); err != nil {
t.Fatal(err)
}
}
result, err := state.IDs(ids.Empty)
@ -45,6 +47,7 @@ func TestStateIDs(t *testing.T) {
t.Fatalf("Returned the wrong number of ids")
}
ids.SortIDs(result)
for i, resultID := range result {
expectedID := expected[i]
if !expectedID.Equals(resultID) {
@ -52,11 +55,28 @@ func TestStateIDs(t *testing.T) {
}
}
expected = []ids.ID{id1, id2}
if err := state.SetIDs(ids.Empty, expected); err != nil {
for _, id := range expected {
if err := state.RemoveID(ids.Empty, id); err != nil {
t.Fatal(err)
}
}
result, err = state.IDs(ids.Empty)
if err != nil {
t.Fatal(err)
}
if len(result) != 0 {
t.Fatalf("Should have returned 0 IDs")
}
expected = []ids.ID{id1, id2}
for _, id := range expected {
if err := state.AddID(ids.Empty, id); err != nil {
t.Fatal(err)
}
}
result, err = state.IDs(ids.Empty)
if err != nil {
t.Fatal(err)
@ -66,6 +86,7 @@ func TestStateIDs(t *testing.T) {
t.Fatalf("Returned the wrong number of ids")
}
ids.SortIDs(result)
for i, resultID := range result {
expectedID := expected[i]
if !expectedID.Equals(resultID) {
@ -84,6 +105,7 @@ func TestStateIDs(t *testing.T) {
t.Fatalf("Returned the wrong number of ids")
}
ids.SortIDs(result)
for i, resultID := range result {
expectedID := expected[i]
if !expectedID.Equals(resultID) {
@ -95,18 +117,6 @@ func TestStateIDs(t *testing.T) {
t.Fatal(err)
}
result, err = state.IDs(ids.Empty)
if err == nil {
t.Fatalf("Should have errored during cache lookup")
}
state.Cache.Flush()
result, err = state.IDs(ids.Empty)
if err == nil {
t.Fatalf("Should have errored during parsing")
}
statusResult, err := state.Status(ids.Empty)
if err != nil {
t.Fatal(err)
@ -115,16 +125,27 @@ func TestStateIDs(t *testing.T) {
t.Fatalf("Should have returned the %s status", choices.Accepted)
}
if err := state.SetIDs(ids.Empty, []ids.ID{ids.ID{}}); err == nil {
t.Fatalf("Should have errored during serialization")
for _, id := range expected {
if err := state.RemoveID(ids.Empty, id); err != nil {
t.Fatal(err)
}
}
if err := state.SetIDs(ids.Empty, []ids.ID{}); err != nil {
result, err = state.IDs(ids.Empty)
if err != nil {
t.Fatal(err)
}
if _, err := state.IDs(ids.Empty); err == nil {
t.Fatalf("Should have errored when reading ids")
if len(result) != 0 {
t.Fatalf("Should have returned 0 IDs")
}
if err := state.AddID(ids.Empty, ids.ID{}); err == nil {
t.Fatalf("Should have errored during serialization")
}
if err := state.RemoveID(ids.Empty, ids.ID{}); err == nil {
t.Fatalf("Should have errored during serialization")
}
}
@ -153,14 +174,7 @@ func TestStateStatuses(t *testing.T) {
t.Fatalf("Should have returned the %s status", choices.Accepted)
}
if err := state.SetIDs(ids.Empty, []ids.ID{ids.Empty}); err != nil {
t.Fatal(err)
}
if _, err := state.Status(ids.Empty); err == nil {
t.Fatalf("Should have errored when reading ids")
}
if err := state.SetStatus(ids.Empty, choices.Accepted); err != nil {
if err := state.AddID(ids.Empty, ids.Empty); err != nil {
t.Fatal(err)
}

View File

@ -1,14 +0,0 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package avm
import "github.com/ava-labs/gecko/vms/components/ava"
type testAddressable struct {
ava.TestTransferable `serialize:"true"`
Addrs [][]byte `serialize:"true"`
}
func (a *testAddressable) Addresses() [][]byte { return a.Addrs }

View File

@ -171,7 +171,6 @@ func (vm *VM) Initialize(
tx: &cache.LRU{Size: idCacheSize},
utxo: &cache.LRU{Size: idCacheSize},
txStatus: &cache.LRU{Size: idCacheSize},
funds: &cache.LRU{Size: idCacheSize},
uniqueTx: &cache.EvictableLRU{Size: txCacheSize},
}

View File

@ -97,11 +97,8 @@ func (s *chainState) setStatus(id ids.ID, status choices.Status) error {
func (s *chainState) removeUTXO(addrs [][]byte, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := ids.NewID(hashing.ComputeHash256Array(addr))
utxos := ids.Set{}
funds, _ := s.Funds(addrID)
utxos.Add(funds...)
utxos.Remove(utxoID)
if err := s.setFunds(addrID, utxos.List()); err != nil {
addrID = UniqueID(addrID, s.fundsIDPrefix, s.fundsID)
if err := s.RemoveID(addrID, utxoID); err != nil {
return err
}
}
@ -111,21 +108,14 @@ func (s *chainState) removeUTXO(addrs [][]byte, utxoID ids.ID) error {
func (s *chainState) addUTXO(addrs [][]byte, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := ids.NewID(hashing.ComputeHash256Array(addr))
utxos := ids.Set{}
funds, _ := s.Funds(addrID)
utxos.Add(funds...)
utxos.Add(utxoID)
if err := s.setFunds(addrID, utxos.List()); err != nil {
addrID = UniqueID(addrID, s.fundsIDPrefix, s.fundsID)
if err := s.AddID(addrID, utxoID); err != nil {
return err
}
}
return nil
}
func (s *chainState) setFunds(id ids.ID, idSlice []ids.ID) error {
return s.SetIDs(UniqueID(id, s.fundsIDPrefix, s.fundsID), idSlice)
}
// PrefixedState wraps a state object. By prefixing the state, there will
// be no collisions between different types of objects that have the same hash.
type PrefixedState struct {

View File

@ -0,0 +1,72 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ava
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/vms/components/codec"
"github.com/stretchr/testify/assert"
)
func TestPrefixedFunds(t *testing.T) {
db := memdb.New()
cc := codec.NewDefault()
cc.RegisterType(&TestAddressable{})
st := NewPrefixedState(db, cc)
avmUTXO := &UTXO{
UTXOID: UTXOID{
TxID: ids.Empty,
OutputIndex: 0,
},
Asset: Asset{
ID: ids.Empty,
},
Out: &TestAddressable{
Addrs: [][]byte{
[]byte{0},
},
},
}
platformUTXO := &UTXO{
UTXOID: UTXOID{
TxID: ids.Empty,
OutputIndex: 1,
},
Asset: Asset{
ID: ids.Empty,
},
Out: &TestAddressable{
Addrs: [][]byte{
[]byte{0},
},
},
}
assert.NoError(t, st.FundAVMUTXO(avmUTXO))
assert.NoError(t, st.FundPlatformUTXO(platformUTXO))
addrID := ids.NewID(hashing.ComputeHash256Array([]byte{0}))
avmUTXOIDs, err := st.AVMFunds(addrID)
assert.NoError(t, err)
assert.Equal(t, []ids.ID{avmUTXO.InputID()}, avmUTXOIDs)
platformUTXOIDs, err := st.PlatformFunds(addrID)
assert.NoError(t, err)
assert.Equal(t, []ids.ID{platformUTXO.InputID()}, platformUTXOIDs)
assert.NoError(t, st.SpendAVMUTXO(avmUTXO.InputID()))
avmUTXOIDs, err = st.AVMFunds(addrID)
assert.NoError(t, err)
assert.Len(t, avmUTXOIDs, 0)
}

View File

@ -5,10 +5,10 @@ package ava
import (
"errors"
"fmt"
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/prefixdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/vms/components/codec"
@ -16,6 +16,7 @@ import (
var (
errCacheTypeMismatch = errors.New("type returned from cache doesn't match the expected type")
errZeroID = errors.New("database key ID value not initialized")
)
// UniqueID returns a unique identifier
@ -117,39 +118,35 @@ func (s *State) SetStatus(id ids.ID, status choices.Status) error {
// IDs returns a slice of IDs from storage
func (s *State) IDs(id ids.ID) ([]ids.ID, error) {
if idsIntf, found := s.Cache.Get(id); found {
if idSlice, ok := idsIntf.([]ids.ID); ok {
return idSlice, nil
}
return nil, errCacheTypeMismatch
}
bytes, err := s.DB.Get(id.Bytes())
if err != nil {
return nil, err
}
idSlice := []ids.ID(nil)
if err := s.Codec.Unmarshal(bytes, &idSlice); err != nil {
return nil, err
}
iter := prefixdb.New(id.Bytes(), s.DB).NewIterator()
defer iter.Release()
s.Cache.Put(id, idSlice)
for iter.Next() {
keyID, err := ids.ToID(iter.Key())
if err != nil {
return nil, err
}
idSlice = append(idSlice, keyID)
}
return idSlice, nil
}
// SetIDs saves a slice of IDs to the database.
func (s *State) SetIDs(id ids.ID, idSlice []ids.ID) error {
if len(idSlice) == 0 {
s.Cache.Evict(id)
return s.DB.Delete(id.Bytes())
// AddID saves an ID to the prefixed database
func (s *State) AddID(id ids.ID, key ids.ID) error {
if key.IsZero() {
return errZeroID
}
bytes, err := s.Codec.Marshal(idSlice)
if err != nil {
return fmt.Errorf("failed to marshal an ID array due to %w", err)
}
s.Cache.Put(id, idSlice)
return s.DB.Put(id.Bytes(), bytes)
db := prefixdb.New(id.Bytes(), s.DB)
return db.Put(key.Bytes(), nil)
}
// RemoveID removes an ID from the prefixed database
func (s *State) RemoveID(id ids.ID, key ids.ID) error {
if key.IsZero() {
return errZeroID
}
db := prefixdb.New(id.Bytes(), s.DB)
return db.Delete(key.Bytes())
}

View File

@ -18,3 +18,13 @@ type TestTransferable struct {
// Amount ...
func (t *TestTransferable) Amount() uint64 { return t.Val }
// TestAddressable ...
type TestAddressable struct {
TestTransferable `serialize:"true"`
Addrs [][]byte `serialize:"true"`
}
// Addresses ...
func (a *TestAddressable) Addresses() [][]byte { return a.Addrs }