mirror of https://github.com/poanetwork/gecko.git
merged utxo indexing upgrade
This commit is contained in:
commit
3d5a838ce6
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
|
@ -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},
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue