Add address tracking to atomic utxos

This commit is contained in:
StephenButtolph 2020-03-26 23:11:23 -04:00
parent a7f2a887ca
commit 9da52e70c0
14 changed files with 378 additions and 134 deletions

View File

@ -8,7 +8,6 @@ import (
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
@ -131,13 +130,7 @@ func (t *ExportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error {
Asset: ava.Asset{ID: out.AssetID()},
Out: out.Out,
}
utxoID := utxo.InputID()
if _, err := state.AVMStatus(utxoID); err == nil {
if err := state.SetAVMStatus(utxoID, choices.Unknown); err != nil {
return err
}
} else if err := state.SetAVMUTXO(utxoID, utxo); err != nil {
if err := state.FundAVMUTXO(utxo); err != nil {
return err
}
}

View File

@ -11,7 +11,6 @@ import (
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/logging"
@ -243,9 +242,6 @@ func TestIssueExportTx(t *testing.T) {
if _, err := state.AVMUTXO(utxoID); err != nil {
t.Fatal(err)
}
if _, err := state.AVMStatus(utxoID); err == nil {
t.Fatalf("should have failed to read the status")
}
}
// Test force accepting an import transaction.
@ -374,7 +370,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
OutputIndex: 0,
}
utxoID := utxo.InputID()
if err := state.SetAVMStatus(utxoID, choices.Accepted); err != nil {
if err := state.SpendAVMUTXO(utxoID); err != nil {
t.Fatal(err)
}
@ -390,7 +386,4 @@ func TestClearForceAcceptedExportTx(t *testing.T) {
if _, err := state.AVMUTXO(utxoID); err == nil {
t.Fatalf("should have failed to read the utxo")
}
if _, err := state.AVMStatus(utxoID); err == nil {
t.Fatalf("should have failed to read the status")
}
}

View File

@ -30,9 +30,3 @@ type Fx interface {
// credential, a non-nil error should be returned.
VerifyOperation(tx interface{}, utxos, ins, creds, outs []interface{}) error
}
// FxAddressable is the interface a feature extension must provide to be able to
// be tracked as a part of the utxo set for a set of addresses
type FxAddressable interface {
Addresses() [][]byte
}

View File

@ -11,7 +11,6 @@ import (
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/vms/components/ava"
"github.com/ava-labs/gecko/vms/components/codec"
"github.com/ava-labs/gecko/vms/components/verify"
@ -162,11 +161,7 @@ func (t *ImportTx) ExecuteWithSideEffects(vm *VM, batch database.Batch) error {
state := ava.NewPrefixedState(vsmDB, vm.codec)
for _, in := range t.Ins {
utxoID := in.UTXOID.InputID()
if _, err := state.PlatformUTXO(utxoID); err == nil {
if err := state.SetPlatformUTXO(utxoID, nil); err != nil {
return err
}
} else if err := state.SetPlatformStatus(utxoID, choices.Accepted); err != nil {
if err := state.SpendPlatformUTXO(utxoID); err != nil {
return err
}
}

View File

@ -11,7 +11,6 @@ import (
"github.com/ava-labs/gecko/database/memdb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/snow/engine/common"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/logging"
@ -226,7 +225,7 @@ func TestIssueImportTx(t *testing.T) {
}
state := ava.NewPrefixedState(smDB, vm.codec)
if err := state.SetPlatformUTXO(utxoID.InputID(), utxo); err != nil {
if err := state.FundPlatformUTXO(utxo); err != nil {
t.Fatal(err)
}
@ -365,9 +364,5 @@ func TestForceAcceptImportTx(t *testing.T) {
utxoSource := utxoID.InputID()
if _, err := state.PlatformUTXO(utxoSource); err == nil {
t.Fatalf("shouldn't have been able to read the utxo")
} else if status, err := state.PlatformStatus(utxoSource); err != nil {
t.Fatal(err)
} else if status != choices.Accepted {
t.Fatalf("should have marked the utxo as consumed")
}
}

View File

@ -95,7 +95,7 @@ func (s *prefixedState) SpendUTXO(utxoID ids.ID) error {
return err
}
addressable, ok := utxo.Out.(FxAddressable)
addressable, ok := utxo.Out.(ava.Addressable)
if !ok {
return nil
}
@ -124,7 +124,7 @@ func (s *prefixedState) FundUTXO(utxo *ava.UTXO) error {
return err
}
addressable, ok := utxo.Out.(FxAddressable)
addressable, ok := utxo.Out.(ava.Addressable)
if !ok {
return nil
}

View File

@ -947,3 +947,176 @@ func (service *Service) SignMintTx(r *http.Request, args *SignMintTxArgs, reply
reply.Tx.Bytes = txBytes
return nil
}
// SendExportArgs are arguments for passing into SendExport requests
type SendExportArgs struct {
Username string `json:"username"`
Password string `json:"password"`
Amount json.Uint64 `json:"amount"`
To string `json:"to"`
}
// SendExportReply defines the Send replies returned from the API
type SendExportReply struct {
TxID ids.ID `json:"txID"`
}
// SendExport returns the ID of the newly created atomic transaction
func (service *Service) SendExport(_ *http.Request, args *SendExportArgs, reply *SendExportReply) error {
service.vm.ctx.Log.Verbo("SendExport called with username: %s", args.Username)
if args.Amount == 0 {
return errInvalidAmount
}
toBytes, err := service.vm.Parse(args.To)
if err != nil {
return fmt.Errorf("problem parsing to address: %w", err)
}
to, err := ids.ToShortID(toBytes)
if err != nil {
return fmt.Errorf("problem parsing to address: %w", err)
}
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
return fmt.Errorf("problem retrieving user: %w", err)
}
user := userState{vm: service.vm}
addresses, _ := user.Addresses(db)
addrs := ids.Set{}
addrs.Add(addresses...)
utxos, err := service.vm.GetUTXOs(addrs)
if err != nil {
return fmt.Errorf("problem retrieving user's UTXOs: %w", err)
}
kc := secp256k1fx.NewKeychain()
for _, addr := range addresses {
sk, err := user.Key(db, addr)
if err != nil {
return fmt.Errorf("problem retrieving private key: %w", err)
}
kc.Add(sk)
}
amountSpent := uint64(0)
time := service.vm.clock.Unix()
ins := []*ava.TransferableInput{}
keys := [][]*crypto.PrivateKeySECP256K1R{}
for _, utxo := range utxos {
if !utxo.AssetID().Equals(service.vm.ava) {
continue
}
inputIntf, signers, err := kc.Spend(utxo.Out, time)
if err != nil {
continue
}
input, ok := inputIntf.(ava.Transferable)
if !ok {
continue
}
spent, err := math.Add64(amountSpent, input.Amount())
if err != nil {
return errSpendOverflow
}
amountSpent = spent
in := &ava.TransferableInput{
UTXOID: utxo.UTXOID,
Asset: ava.Asset{ID: service.vm.ava},
In: input,
}
ins = append(ins, in)
keys = append(keys, signers)
if amountSpent >= uint64(args.Amount) {
break
}
}
if amountSpent < uint64(args.Amount) {
return errInsufficientFunds
}
ava.SortTransferableInputsWithSigners(ins, keys)
exportOuts := []*ava.TransferableOutput{&ava.TransferableOutput{
Asset: ava.Asset{ID: service.vm.ava},
Out: &secp256k1fx.TransferOutput{
Amt: uint64(args.Amount),
Locktime: 0,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{to},
},
},
}}
outs := []*ava.TransferableOutput{}
if amountSpent > uint64(args.Amount) {
changeAddr := kc.Keys[0].PublicKey().Address()
outs = append(outs, &ava.TransferableOutput{
Asset: ava.Asset{ID: service.vm.ava},
Out: &secp256k1fx.TransferOutput{
Amt: amountSpent - uint64(args.Amount),
Locktime: 0,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{changeAddr},
},
},
})
}
ava.SortTransferableOutputs(outs, service.vm.codec)
tx := Tx{UnsignedTx: &ExportTx{
BaseTx: BaseTx{
NetID: service.vm.ctx.NetworkID,
BCID: service.vm.ctx.ChainID,
Outs: outs,
Ins: ins,
},
ExportOuts: exportOuts,
}}
unsignedBytes, err := service.vm.codec.Marshal(&tx.UnsignedTx)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", err)
}
hash := hashing.ComputeHash256(unsignedBytes)
for _, credKeys := range keys {
cred := &secp256k1fx.Credential{}
for _, key := range credKeys {
sig, err := key.SignHash(hash)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", err)
}
fixedSig := [crypto.SECP256K1RSigLen]byte{}
copy(fixedSig[:], sig)
cred.Sigs = append(cred.Sigs, fixedSig)
}
tx.Creds = append(tx.Creds, cred)
}
b, err := service.vm.codec.Marshal(tx)
if err != nil {
return fmt.Errorf("problem creating transaction: %w", err)
}
txID, err := service.vm.IssueTx(b, nil)
if err != nil {
return fmt.Errorf("problem issuing transaction: %w", err)
}
reply.TxID = txID
return nil
}

View File

@ -63,43 +63,3 @@ func (s *state) SetTx(id ids.ID, tx *Tx) error {
s.Cache.Put(id, tx)
return s.DB.Put(id.Bytes(), tx.Bytes())
}
// 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
}
s.Cache.Put(id, idSlice)
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())
}
s.Cache.Put(id, idSlice)
bytes, err := s.Codec.Marshal(idSlice)
if err != nil {
return err
}
return s.DB.Put(id.Bytes(), bytes)
}

View File

@ -8,14 +8,23 @@ import (
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/vms/components/codec"
)
// Addressable is the interface a feature extension must provide to be able to
// be tracked as a part of the utxo set for a set of addresses
type Addressable interface {
Addresses() [][]byte
}
const (
platformUTXOID uint64 = iota
platformStatusID
platformFundsID
avmUTXOID
avmStatusID
avmFundsID
)
const (
@ -23,65 +32,177 @@ const (
idCacheSize = 10000
)
type chainState struct {
*State
utxoIDPrefix, statusIDPrefix, fundsIDPrefix uint64
utxoID, statusID, fundsID cache.Cacher
}
// UTXO attempts to load a utxo from platform's storage.
func (s *chainState) UTXO(id ids.ID) (*UTXO, error) {
return s.State.UTXO(UniqueID(id, s.utxoIDPrefix, s.utxoID))
}
// 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))
}
// SpendUTXO consumes the provided platform utxo.
func (s *chainState) SpendUTXO(utxoID ids.ID) error {
utxo, err := s.UTXO(utxoID)
if err != nil {
return s.setStatus(utxoID, choices.Accepted)
} else if err := s.setUTXO(utxoID, nil); err != nil {
return err
}
if addressable, ok := utxo.Out.(Addressable); ok {
return s.removeUTXO(addressable.Addresses(), utxoID)
}
return nil
}
// FundUTXO adds the provided utxo to the database
func (s *chainState) FundUTXO(utxo *UTXO) error {
utxoID := utxo.InputID()
if _, err := s.status(utxoID); err == nil {
return s.setStatus(utxoID, choices.Unknown)
} else if err := s.setUTXO(utxoID, utxo); err != nil {
return err
}
if addressable, ok := utxo.Out.(Addressable); ok {
return s.addUTXO(addressable.Addresses(), utxoID)
}
return nil
}
// setUTXO saves the provided utxo to platform's storage.
func (s *chainState) setUTXO(id ids.ID, utxo *UTXO) error {
return s.SetUTXO(UniqueID(id, s.utxoIDPrefix, s.utxoID), utxo)
}
func (s *chainState) status(id ids.ID) (choices.Status, error) {
return s.Status(UniqueID(id, s.statusIDPrefix, s.statusID))
}
// setStatus saves the provided platform status to storage.
func (s *chainState) setStatus(id ids.ID, status choices.Status) error {
return s.State.SetStatus(UniqueID(id, s.statusIDPrefix, s.statusID), status)
}
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 {
return err
}
}
return nil
}
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 {
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 {
State
platformUTXO, platformStatus, avmUTXO, avmStatus cache.Cacher
platform, avm chainState
}
// NewPrefixedState ...
func NewPrefixedState(db database.Database, codec codec.Codec) *PrefixedState {
state := &State{
Cache: &cache.LRU{Size: stateCacheSize},
DB: db,
Codec: codec,
}
return &PrefixedState{
State: State{
Cache: &cache.LRU{Size: stateCacheSize},
DB: db,
Codec: codec,
platform: chainState{
State: state,
utxoIDPrefix: platformUTXOID,
statusIDPrefix: platformStatusID,
fundsIDPrefix: platformFundsID,
utxoID: &cache.LRU{Size: idCacheSize},
statusID: &cache.LRU{Size: idCacheSize},
fundsID: &cache.LRU{Size: idCacheSize},
},
avm: chainState{
State: state,
utxoIDPrefix: avmUTXOID,
statusIDPrefix: avmStatusID,
fundsIDPrefix: avmFundsID,
utxoID: &cache.LRU{Size: idCacheSize},
statusID: &cache.LRU{Size: idCacheSize},
fundsID: &cache.LRU{Size: idCacheSize},
},
platformUTXO: &cache.LRU{Size: idCacheSize},
platformStatus: &cache.LRU{Size: idCacheSize},
avmUTXO: &cache.LRU{Size: idCacheSize},
avmStatus: &cache.LRU{Size: idCacheSize},
}
}
// PlatformUTXO attempts to load a utxo from platform's storage.
func (s *PrefixedState) PlatformUTXO(id ids.ID) (*UTXO, error) {
return s.UTXO(UniqueID(id, platformUTXOID, s.platformUTXO))
return s.platform.UTXO(id)
}
// SetPlatformUTXO saves the provided utxo to platform's storage.
func (s *PrefixedState) SetPlatformUTXO(id ids.ID, utxo *UTXO) error {
return s.SetUTXO(UniqueID(id, platformUTXOID, s.platformUTXO), utxo)
// PlatformFunds returns the mapping from the 32 byte representation of an
// address to a list of utxo IDs that reference the address.
func (s *PrefixedState) PlatformFunds(id ids.ID) ([]ids.ID, error) {
return s.platform.Funds(id)
}
// PlatformStatus returns the platform status from storage.
func (s *PrefixedState) PlatformStatus(id ids.ID) (choices.Status, error) {
return s.Status(UniqueID(id, platformStatusID, s.platformStatus))
// SpendPlatformUTXO consumes the provided platform utxo.
func (s *PrefixedState) SpendPlatformUTXO(utxoID ids.ID) error {
return s.platform.SpendUTXO(utxoID)
}
// SetPlatformStatus saves the provided platform status to storage.
func (s *PrefixedState) SetPlatformStatus(id ids.ID, status choices.Status) error {
return s.SetStatus(UniqueID(id, platformStatusID, s.platformStatus), status)
// FundPlatformUTXO adds the provided utxo to the database
func (s *PrefixedState) FundPlatformUTXO(utxo *UTXO) error {
return s.platform.FundUTXO(utxo)
}
// AVMUTXO attempts to load a utxo from AVM's storage.
// AVMUTXO attempts to load a utxo from avm's storage.
func (s *PrefixedState) AVMUTXO(id ids.ID) (*UTXO, error) {
return s.UTXO(UniqueID(id, avmUTXOID, s.platformUTXO))
return s.avm.UTXO(id)
}
// SetAVMUTXO saves the provided utxo to AVM's storage.
func (s *PrefixedState) SetAVMUTXO(id ids.ID, utxo *UTXO) error {
return s.SetUTXO(UniqueID(id, avmUTXOID, s.platformUTXO), utxo)
// AVMFunds returns the mapping from the 32 byte representation of an
// address to a list of utxo IDs that reference the address.
func (s *PrefixedState) AVMFunds(id ids.ID) ([]ids.ID, error) {
return s.avm.Funds(id)
}
// AVMStatus returns the AVM status from storage.
func (s *PrefixedState) AVMStatus(id ids.ID) (choices.Status, error) {
return s.Status(UniqueID(id, avmStatusID, s.platformStatus))
// SpendAVMUTXO consumes the provided platform utxo.
func (s *PrefixedState) SpendAVMUTXO(utxoID ids.ID) error {
return s.avm.SpendUTXO(utxoID)
}
// SetAVMStatus saves the provided platform status to storage.
func (s *PrefixedState) SetAVMStatus(id ids.ID, status choices.Status) error {
return s.SetStatus(UniqueID(id, avmStatusID, s.platformStatus), status)
// FundAVMUTXO adds the provided utxo to the database
func (s *PrefixedState) FundAVMUTXO(utxo *UTXO) error {
return s.avm.FundUTXO(utxo)
}

View File

@ -111,3 +111,43 @@ func (s *State) SetStatus(id ids.ID, status choices.Status) error {
}
return s.DB.Put(id.Bytes(), bytes)
}
// 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
}
s.Cache.Put(id, idSlice)
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())
}
s.Cache.Put(id, idSlice)
bytes, err := s.Codec.Marshal(idSlice)
if err != nil {
return err
}
return s.DB.Put(id.Bytes(), bytes)
}

View File

@ -325,9 +325,9 @@ func TestAddDefaultSubnetDelegatorTxSemanticVerify(t *testing.T) {
}
tx, err = vm.newAddDefaultSubnetDelegatorTx(
defaultNonce+1, // nonce
defaultStakeAmount, // weight
uint64(newTimestamp.Unix()), // start time
defaultNonce+1, // nonce
defaultStakeAmount, // weight
uint64(newTimestamp.Unix()), // start time
uint64(newTimestamp.Add(MinimumStakingDuration).Unix()), // end time
defaultKey.PublicKey().Address(), // node ID
defaultKey.PublicKey().Address(), // destination

View File

@ -10,7 +10,6 @@ import (
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/math"
@ -156,13 +155,7 @@ func (tx *ExportTx) Accept(batch database.Batch) error {
Asset: ava.Asset{ID: out.AssetID()},
Out: out.Out,
}
utxoID := utxo.InputID()
if _, err := state.PlatformStatus(utxoID); err == nil {
if err := state.SetPlatformStatus(utxoID, choices.Unknown); err != nil {
return err
}
} else if err := state.SetPlatformUTXO(utxoID, utxo); err != nil {
if err := state.FundPlatformUTXO(utxo); err != nil {
return err
}
}

View File

@ -11,7 +11,6 @@ import (
"github.com/ava-labs/gecko/database"
"github.com/ava-labs/gecko/database/versiondb"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/math"
@ -218,11 +217,7 @@ func (tx *ImportTx) Accept(batch database.Batch) error {
state := ava.NewPrefixedState(vsmDB, Codec)
for _, in := range tx.Ins {
utxoID := in.UTXOID.InputID()
if _, err := state.AVMUTXO(utxoID); err == nil {
if err := state.SetAVMUTXO(utxoID, nil); err != nil {
return err
}
} else if err := state.SetAVMStatus(utxoID, choices.Accepted); err != nil {
if err := state.SpendAVMUTXO(utxoID); err != nil {
return err
}
}

View File

@ -1128,7 +1128,7 @@ func TestAtomicImport(t *testing.T) {
}
state := ava.NewPrefixedState(smDB, Codec)
if err := state.SetAVMUTXO(utxoID.InputID(), utxo); err != nil {
if err := state.FundAVMUTXO(utxo); err != nil {
t.Fatal(err)
}
@ -1221,12 +1221,4 @@ func TestOptimisticAtomicImport(t *testing.T) {
if newAccount.Balance != previousAccount.Balance+amount {
t.Fatalf("failed to provide funds")
}
smDB := vm.Ctx.SharedMemory.GetDatabase(avmID)
defer vm.Ctx.SharedMemory.ReleaseDatabase(avmID)
state := ava.NewPrefixedState(smDB, Codec)
if _, err := state.AVMStatus(utxoID.InputID()); err != nil {
t.Fatal(err)
}
}