From 198accdea7004adf7cbaa593e223925cb7a6be36 Mon Sep 17 00:00:00 2001 From: Collin Montag Date: Thu, 28 May 2020 10:36:00 -0400 Subject: [PATCH 1/4] ava state prefixdb --- vms/avm/prefixed_state.go | 17 ++-------- vms/avm/state_test.go | 24 ++++++------- vms/components/ava/prefixed_state.go | 16 ++------- vms/components/ava/state.go | 51 ++++++++++++---------------- 4 files changed, 37 insertions(+), 71 deletions(-) diff --git a/vms/avm/prefixed_state.go b/vms/avm/prefixed_state.go index 8a1898d..862771a 100644 --- a/vms/avm/prefixed_state.go +++ b/vms/avm/prefixed_state.go @@ -80,11 +80,6 @@ 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) -} - // SpendUTXO consumes the provided utxo. func (s *prefixedState) SpendUTXO(utxoID ids.ID) error { utxo, err := s.UTXO(utxoID) @@ -106,11 +101,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 +126,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 } } diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index e0598cc..de5810b 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -27,13 +27,15 @@ func TestStateIDs(t *testing.T) { id1 := ids.NewID([32]byte{0xff, 0}) id2 := ids.NewID([32]byte{0xff, 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) @@ -53,8 +55,10 @@ func TestStateIDs(t *testing.T) { } expected = []ids.ID{id1, id2} - 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) @@ -115,14 +119,10 @@ 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 { + if err := state.AddID(ids.Empty, ids.ID{}); err == nil { t.Fatalf("Should have errored during serialization") } - if err := state.SetIDs(ids.Empty, []ids.ID{}); err != nil { - t.Fatal(err) - } - if _, err := state.IDs(ids.Empty); err == nil { t.Fatalf("Should have errored when reading ids") } @@ -153,7 +153,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 { + if err := state.AddID(ids.Empty, ids.Empty); err != nil { t.Fatal(err) } if _, err := state.Status(ids.Empty); err == nil { diff --git a/vms/components/ava/prefixed_state.go b/vms/components/ava/prefixed_state.go index dd7f3e8..2fcd9aa 100644 --- a/vms/components/ava/prefixed_state.go +++ b/vms/components/ava/prefixed_state.go @@ -97,11 +97,7 @@ 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 { + if err := s.RemoveID(addrID, utxoID); err != nil { return err } } @@ -111,21 +107,13 @@ 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 { + 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 { diff --git a/vms/components/ava/state.go b/vms/components/ava/state.go index 627cc56..8c8129f 100644 --- a/vms/components/ava/state.go +++ b/vms/components/ava/state.go @@ -8,6 +8,7 @@ import ( "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" @@ -116,39 +117,29 @@ 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()) - } - - bytes, err := s.Codec.Marshal(idSlice) - if err != nil { - return err - } - - s.Cache.Put(id, idSlice) - return s.DB.Put(id.Bytes(), bytes) +// AddID saves an ID to the prefixed database +func (s *State) AddID(id ids.ID, key ids.ID) error { + 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 { + db := prefixdb.New(id.Bytes(), s.DB) + return db.Delete(key.Bytes()) } From 232f962d4b2fb1e8d520d55eef5cc2e172aacb30 Mon Sep 17 00:00:00 2001 From: Collin Montag Date: Thu, 28 May 2020 23:50:33 -0400 Subject: [PATCH 2/4] updated tests --- vms/avm/prefixed_state.go | 2 +- vms/avm/prefixed_state_test.go | 9 +++-- vms/avm/state_test.go | 55 +++++++++++++++++++--------- vms/components/ava/prefixed_state.go | 2 +- vms/components/ava/state.go | 7 ++++ 5 files changed, 53 insertions(+), 22 deletions(-) diff --git a/vms/avm/prefixed_state.go b/vms/avm/prefixed_state.go index 862771a..2b19100 100644 --- a/vms/avm/prefixed_state.go +++ b/vms/avm/prefixed_state.go @@ -77,7 +77,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)) + return s.state.IDs(id) } // SpendUTXO consumes the provided utxo. diff --git a/vms/avm/prefixed_state_test.go b/vms/avm/prefixed_state_test.go index 4eea404..6ab50c3 100644 --- a/vms/avm/prefixed_state_test.go +++ b/vms/avm/prefixed_state_test.go @@ -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") } } diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index de5810b..15c8ec7 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -23,9 +23,9 @@ 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.Fatal(err) @@ -47,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) { @@ -54,6 +55,21 @@ func TestStateIDs(t *testing.T) { } } + 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 { @@ -70,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) { @@ -88,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) { @@ -99,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) @@ -119,12 +125,27 @@ func TestStateIDs(t *testing.T) { t.Fatalf("Should have returned the %s status", choices.Accepted) } + 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") + } + if err := state.AddID(ids.Empty, ids.ID{}); err == nil { t.Fatalf("Should have errored during serialization") } - if _, err := state.IDs(ids.Empty); err == nil { - t.Fatalf("Should have errored when reading ids") + if err := state.RemoveID(ids.Empty, ids.ID{}); err == nil { + t.Fatalf("Should have errored during serialization") } } diff --git a/vms/components/ava/prefixed_state.go b/vms/components/ava/prefixed_state.go index 2fcd9aa..15e931c 100644 --- a/vms/components/ava/prefixed_state.go +++ b/vms/components/ava/prefixed_state.go @@ -47,7 +47,7 @@ func (s *chainState) UTXO(id ids.ID) (*UTXO, 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 *chainState) Funds(id ids.ID) ([]ids.ID, error) { - return s.IDs(UniqueID(id, s.fundsIDPrefix, s.fundsID)) + return s.IDs(id) } // SpendUTXO consumes the provided platform utxo. diff --git a/vms/components/ava/state.go b/vms/components/ava/state.go index 8c8129f..997d0b4 100644 --- a/vms/components/ava/state.go +++ b/vms/components/ava/state.go @@ -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 @@ -134,12 +135,18 @@ func (s *State) IDs(id ids.ID) ([]ids.ID, error) { // AddID saves an ID to the prefixed database func (s *State) AddID(id ids.ID, key ids.ID) error { + if key.IsZero() { + return errZeroID + } 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()) } From 33eca06ca2ff0899935281cfd8625158b19eff1d Mon Sep 17 00:00:00 2001 From: Collin Montag Date: Fri, 29 May 2020 00:03:14 -0400 Subject: [PATCH 3/4] state test pruning --- vms/avm/state_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index 15c8ec7..1feabab 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -177,13 +177,6 @@ func TestStateStatuses(t *testing.T) { if err := state.AddID(ids.Empty, 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 { - t.Fatal(err) - } status, err = state.Status(ids.Empty) if err != nil { From 97754e2545fbfe4228c73c801fe4d8ff4468e040 Mon Sep 17 00:00:00 2001 From: StephenButtolph Date: Sat, 30 May 2020 12:44:41 -0400 Subject: [PATCH 4/4] Added tests for separation between the avm and the platformvm --- vms/avm/prefixed_state.go | 9 +-- vms/avm/prefixed_state_test.go | 4 +- vms/avm/verifiable_test.go | 14 ----- vms/avm/vm.go | 1 - vms/components/ava/prefixed_state.go | 4 +- vms/components/ava/prefixed_state_test.go | 72 +++++++++++++++++++++++ vms/components/ava/test_verifiable.go | 10 ++++ 7 files changed, 90 insertions(+), 24 deletions(-) delete mode 100644 vms/avm/verifiable_test.go create mode 100644 vms/components/ava/prefixed_state_test.go diff --git a/vms/avm/prefixed_state.go b/vms/avm/prefixed_state.go index 2b19100..5f29791 100644 --- a/vms/avm/prefixed_state.go +++ b/vms/avm/prefixed_state.go @@ -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,9 +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(id) -} +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 { diff --git a/vms/avm/prefixed_state_test.go b/vms/avm/prefixed_state_test.go index 6ab50c3..74c4b53 100644 --- a/vms/avm/prefixed_state_test.go +++ b/vms/avm/prefixed_state_test.go @@ -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}, }, diff --git a/vms/avm/verifiable_test.go b/vms/avm/verifiable_test.go deleted file mode 100644 index 6e6337a..0000000 --- a/vms/avm/verifiable_test.go +++ /dev/null @@ -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 } diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 180f4ae..de37b57 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -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}, } diff --git a/vms/components/ava/prefixed_state.go b/vms/components/ava/prefixed_state.go index 15e931c..92b3491 100644 --- a/vms/components/ava/prefixed_state.go +++ b/vms/components/ava/prefixed_state.go @@ -47,7 +47,7 @@ func (s *chainState) UTXO(id ids.ID) (*UTXO, 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 *chainState) Funds(id ids.ID) ([]ids.ID, error) { - return s.IDs(id) + return s.IDs(UniqueID(id, s.fundsIDPrefix, s.fundsID)) } // SpendUTXO consumes the provided platform utxo. @@ -97,6 +97,7 @@ 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)) + addrID = UniqueID(addrID, s.fundsIDPrefix, s.fundsID) if err := s.RemoveID(addrID, utxoID); err != nil { return err } @@ -107,6 +108,7 @@ 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)) + addrID = UniqueID(addrID, s.fundsIDPrefix, s.fundsID) if err := s.AddID(addrID, utxoID); err != nil { return err } diff --git a/vms/components/ava/prefixed_state_test.go b/vms/components/ava/prefixed_state_test.go new file mode 100644 index 0000000..06cb1df --- /dev/null +++ b/vms/components/ava/prefixed_state_test.go @@ -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) +} diff --git a/vms/components/ava/test_verifiable.go b/vms/components/ava/test_verifiable.go index 34dce1d..7282c94 100644 --- a/vms/components/ava/test_verifiable.go +++ b/vms/components/ava/test_verifiable.go @@ -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 }