gecko/vms/spdagvm/prefixed_state.go

180 lines
5.3 KiB
Go

// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spdagvm
import (
"github.com/ava-labs/gecko/cache"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/snow/choices"
"github.com/ava-labs/gecko/utils/wrappers"
)
const (
txID uint64 = iota
utxoID
txStatusID
fundsID
dbInitializedID
)
var (
dbInitialized = ids.Empty.Prefix(dbInitializedID)
)
// 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 *state
tx, utxo, txStatus, funds cache.Cacher
uniqueTx cache.Deduplicator
generatedStatus ids.ID
}
// UniqueTx de-duplicates the transaction.
func (s *prefixedState) UniqueTx(tx *UniqueTx) *UniqueTx {
return s.uniqueTx.Deduplicate(tx).(*UniqueTx)
}
// Tx loads the transaction whose ID is [id] from storage.
func (s *prefixedState) Tx(id ids.ID) (*Tx, error) { return s.state.Tx(s.uniqueID(id, txID, s.tx)) }
// SetTx saves transaction [tx], whose ID is [id], to storage.
func (s *prefixedState) SetTx(id ids.ID, tx *Tx) error {
return s.state.SetTx(s.uniqueID(id, txID, s.tx), tx)
}
// UTXO loads a UTXO from storage.
func (s *prefixedState) UTXO(id ids.ID) (*UTXO, error) {
return s.state.UTXO(s.uniqueID(id, utxoID, s.utxo))
}
// SetUTXO saves the provided utxo to storage.
func (s *prefixedState) SetUTXO(id ids.ID, utxo *UTXO) error {
return s.state.SetUTXO(s.uniqueID(id, utxoID, s.utxo), utxo)
}
// Status returns the status of the transaction whose ID is [id] from storage.
func (s *prefixedState) Status(id ids.ID) (choices.Status, error) {
return s.state.Status(s.uniqueID(id, txStatusID, s.txStatus))
}
// SetStatus saves the status of [id] as [status]
func (s *prefixedState) SetStatus(id ids.ID, status choices.Status) error {
return s.state.SetStatus(s.uniqueID(id, txStatusID, s.txStatus), status)
}
// DBInitialized returns the status of this database. If the database is
// uninitialized, the status will be unknown.
func (s *prefixedState) DBInitialized() (choices.Status, error) { return s.state.Status(dbInitialized) }
// SetDBInitialized saves the provided status of the database.
func (s *prefixedState) SetDBInitialized(status choices.Status) error {
return s.state.SetStatus(dbInitialized, status)
}
// Funds returns the IDs of unspent UTXOs that reference address [addr]
func (s *prefixedState) Funds(addr ids.ID) ([]ids.ID, error) {
return s.state.IDs(s.uniqueID(addr, fundsID, s.funds))
}
// SetFunds saves the mapping from address [addr] to the IDs of unspent UTXOs
// that reference [addr]
func (s *prefixedState) SetFunds(addr ids.ID, idSlice []ids.ID) error {
return s.state.SetIDs(s.uniqueID(addr, fundsID, s.funds), idSlice)
}
// Make [id] unique by prefixing [prefix] to it
func (s *prefixedState) uniqueID(id ids.ID, prefix uint64, cacher cache.Cacher) ids.ID {
if cachedIDIntf, found := cacher.Get(id); found {
return cachedIDIntf.(ids.ID)
}
uID := id.Prefix(prefix)
cacher.Put(id, uID)
return uID
}
// SpendUTXO consumes the utxo whose ID is [utxoID]
func (s *prefixedState) SpendUTXO(utxoID ids.ID) error {
utxo, err := s.UTXO(utxoID)
if err != nil {
return err
}
if err := s.SetUTXO(utxoID, nil); err != nil {
return err
}
// Update funds
// TODO: Clean this up. More into the output object?
switch out := utxo.Out().(type) {
case *OutputPayment:
return s.removeUTXO(out.Addresses(), utxoID)
case *OutputTakeOrLeave:
errs := wrappers.Errs{}
errs.Add(s.removeUTXO(out.Addresses1(), utxoID))
errs.Add(s.removeUTXO(out.Addresses2(), utxoID))
return errs.Err
default:
return errOutputType
}
}
// For each address in [addrs], persist that the UTXO whose ID is [utxoID]
// has been spent and can no longer be spent by the address
func (s *prefixedState) removeUTXO(addrs []ids.ShortID, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := addr.LongID()
utxos := ids.Set{} // IDs of unspent UTXOs referencing [addr]
if funds, err := s.Funds(addrID); err == nil {
utxos.Add(funds...)
}
utxos.Remove(utxoID) // Remove [utxoID] from this set
if err := s.SetFunds(addrID, utxos.List()); err != nil { // Persist
return err
}
}
return nil
}
// FundUTXO persists [utxo].
// For each address referenced in [utxo]'s output, persists
// that the address is referenced by [utxo]
func (s *prefixedState) FundUTXO(utxo *UTXO) error {
utxoID := utxo.ID()
s.SetUTXO(utxoID, utxo) // Save [utxo]
switch out := utxo.Out().(type) {
case *OutputPayment:
return s.addUTXO(out.Addresses(), utxoID)
case *OutputTakeOrLeave:
errs := wrappers.Errs{}
errs.Add(s.addUTXO(out.Addresses1(), utxoID))
errs.Add(s.addUTXO(out.Addresses2(), utxoID))
return errs.Err
default:
return errOutputType
}
}
// Persist that each address in [addrs] is referenced in the UTXO whose ID is [utxoID]
func (s *prefixedState) addUTXO(addrs []ids.ShortID, utxoID ids.ID) error {
for _, addr := range addrs {
addrID := addr.LongID()
utxos := ids.Set{}
// Get the set of UTXO IDs such that [addr] is referenced in
// the UTXO
if funds, err := s.Funds(addrID); err == nil {
utxos.Add(funds...)
}
// Add [utxoID] to that set
utxos.Add(utxoID)
// Persist the new set
if err := s.SetFunds(addrID, utxos.List()); err != nil {
return err
}
}
return nil
}