2020-03-10 12:20:34 -07:00
|
|
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
|
|
|
|
package avm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/ava-labs/gecko/ids"
|
|
|
|
"github.com/ava-labs/gecko/snow/choices"
|
|
|
|
"github.com/ava-labs/gecko/snow/consensus/snowstorm"
|
2020-03-23 13:40:37 -07:00
|
|
|
"github.com/ava-labs/gecko/vms/components/ava"
|
2020-03-10 12:20:34 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo")
|
|
|
|
errMissingUTXO = errors.New("missing utxo")
|
|
|
|
errUnknownTx = errors.New("transaction is unknown")
|
|
|
|
errRejectedTx = errors.New("transaction is rejected")
|
|
|
|
)
|
|
|
|
|
|
|
|
// UniqueTx provides a de-duplication service for txs. This only provides a
|
|
|
|
// performance boost
|
|
|
|
type UniqueTx struct {
|
2020-03-19 15:36:10 -07:00
|
|
|
*TxState
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
vm *VM
|
|
|
|
txID ids.ID
|
|
|
|
}
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
// TxState ...
|
|
|
|
type TxState struct {
|
|
|
|
*Tx
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
unique, verifiedTx, verifiedState bool
|
|
|
|
validity error
|
|
|
|
|
|
|
|
inputs ids.Set
|
2020-03-23 13:40:37 -07:00
|
|
|
inputUTXOs []*ava.UTXOID
|
|
|
|
utxos []*ava.UTXO
|
2020-03-10 12:20:34 -07:00
|
|
|
deps []snowstorm.Tx
|
|
|
|
|
|
|
|
status choices.Status
|
2020-03-13 14:31:23 -07:00
|
|
|
|
|
|
|
onDecide func(choices.Status)
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (tx *UniqueTx) refresh() {
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.TxState == nil {
|
|
|
|
tx.TxState = &TxState{}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.unique {
|
2020-03-10 12:20:34 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
unique := tx.vm.state.UniqueTx(tx)
|
2020-03-19 15:36:10 -07:00
|
|
|
prevTx := tx.Tx
|
2020-03-10 12:20:34 -07:00
|
|
|
if unique == tx {
|
|
|
|
// If no one was in the cache, make sure that there wasn't an
|
|
|
|
// intermediate object whose state I must reflect
|
|
|
|
if status, err := tx.vm.state.Status(tx.ID()); err == nil {
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.status = status
|
|
|
|
tx.unique = true
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If someone is in the cache, they must be up to date
|
|
|
|
|
|
|
|
// This ensures that every unique tx object points to the same tx state
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.TxState = unique.TxState
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.Tx != nil {
|
2020-03-10 12:20:34 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if prevTx == nil {
|
|
|
|
if innerTx, err := tx.vm.state.Tx(tx.ID()); err == nil {
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.Tx = innerTx
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
} else {
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.Tx = prevTx
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Evict is called when this UniqueTx will no longer be returned from a cache
|
|
|
|
// lookup
|
2020-03-19 15:36:10 -07:00
|
|
|
func (tx *UniqueTx) Evict() { tx.unique = false } // Lock is already held here
|
2020-03-10 12:20:34 -07:00
|
|
|
|
|
|
|
func (tx *UniqueTx) setStatus(status choices.Status) error {
|
|
|
|
tx.refresh()
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.status == status {
|
2020-03-10 12:20:34 -07:00
|
|
|
return nil
|
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.status = status
|
2020-03-10 12:20:34 -07:00
|
|
|
return tx.vm.state.SetStatus(tx.ID(), status)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID returns the wrapped txID
|
|
|
|
func (tx *UniqueTx) ID() ids.ID { return tx.txID }
|
|
|
|
|
|
|
|
// Accept is called when the transaction was finalized as accepted by consensus
|
|
|
|
func (tx *UniqueTx) Accept() {
|
2020-03-20 17:56:43 -07:00
|
|
|
defer tx.vm.db.Abort()
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
if err := tx.setStatus(choices.Accepted); err != nil {
|
|
|
|
tx.vm.ctx.Log.Error("Failed to accept tx %s due to %s", tx.txID, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove spent utxos
|
2020-03-20 17:56:43 -07:00
|
|
|
for _, utxo := range tx.InputUTXOs() {
|
|
|
|
if utxo.Symbolic() {
|
|
|
|
// If the UTXO is symbolic, it can't be spent
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
utxoID := utxo.InputID()
|
2020-03-10 12:20:34 -07:00
|
|
|
if err := tx.vm.state.SpendUTXO(utxoID); err != nil {
|
|
|
|
tx.vm.ctx.Log.Error("Failed to spend utxo %s due to %s", utxoID, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add new utxos
|
|
|
|
for _, utxo := range tx.UTXOs() {
|
|
|
|
if err := tx.vm.state.FundUTXO(utxo); err != nil {
|
|
|
|
tx.vm.ctx.Log.Error("Failed to fund utxo %s due to %s", utxoID, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txID := tx.ID()
|
2020-03-20 17:56:43 -07:00
|
|
|
commitBatch, err := tx.vm.db.CommitBatch()
|
|
|
|
if err != nil {
|
|
|
|
tx.vm.ctx.Log.Error("Failed to calculate CommitBatch for %s due to %s", txID, err)
|
2020-03-23 13:40:37 -07:00
|
|
|
return
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
2020-03-23 13:40:37 -07:00
|
|
|
if err := tx.ExecuteWithSideEffects(tx.vm, commitBatch); err != nil {
|
2020-03-20 17:56:43 -07:00
|
|
|
tx.vm.ctx.Log.Error("Failed to commit accept %s due to %s", txID, err)
|
2020-03-23 13:40:37 -07:00
|
|
|
return
|
2020-03-20 17:56:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
tx.vm.ctx.Log.Verbo("Accepted Tx: %s", txID)
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
tx.vm.pubsub.Publish("accepted", txID)
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.deps = nil // Needed to prevent a memory leak
|
2020-03-13 14:31:23 -07:00
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.onDecide != nil {
|
|
|
|
tx.onDecide(choices.Accepted)
|
2020-03-13 14:31:23 -07:00
|
|
|
}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reject is called when the transaction was finalized as rejected by consensus
|
|
|
|
func (tx *UniqueTx) Reject() {
|
2020-03-20 17:56:43 -07:00
|
|
|
defer tx.vm.db.Abort()
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
if err := tx.setStatus(choices.Rejected); err != nil {
|
|
|
|
tx.vm.ctx.Log.Error("Failed to reject tx %s due to %s", tx.txID, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
txID := tx.ID()
|
|
|
|
tx.vm.ctx.Log.Debug("Rejecting Tx: %s", txID)
|
|
|
|
|
|
|
|
if err := tx.vm.db.Commit(); err != nil {
|
|
|
|
tx.vm.ctx.Log.Error("Failed to commit reject %s due to %s", tx.txID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.vm.pubsub.Publish("rejected", txID)
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.deps = nil // Needed to prevent a memory leak
|
2020-03-13 14:31:23 -07:00
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.onDecide != nil {
|
|
|
|
tx.onDecide(choices.Rejected)
|
2020-03-13 14:31:23 -07:00
|
|
|
}
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Status returns the current status of this transaction
|
|
|
|
func (tx *UniqueTx) Status() choices.Status {
|
|
|
|
tx.refresh()
|
2020-03-19 15:36:10 -07:00
|
|
|
return tx.status
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Dependencies returns the set of transactions this transaction builds on
|
|
|
|
func (tx *UniqueTx) Dependencies() []snowstorm.Tx {
|
|
|
|
tx.refresh()
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.Tx == nil || len(tx.deps) != 0 {
|
|
|
|
return tx.deps
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
txIDs := ids.Set{}
|
|
|
|
for _, in := range tx.InputUTXOs() {
|
|
|
|
txID, _ := in.InputSource()
|
|
|
|
if !txIDs.Contains(txID) {
|
|
|
|
txIDs.Add(txID)
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.deps = append(tx.deps, &UniqueTx{
|
2020-03-10 12:20:34 -07:00
|
|
|
vm: tx.vm,
|
|
|
|
txID: txID,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
for _, assetID := range tx.Tx.AssetIDs().List() {
|
2020-03-10 12:20:34 -07:00
|
|
|
if !txIDs.Contains(assetID) {
|
|
|
|
txIDs.Add(assetID)
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.deps = append(tx.deps, &UniqueTx{
|
2020-03-10 12:20:34 -07:00
|
|
|
vm: tx.vm,
|
|
|
|
txID: assetID,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
return tx.deps
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// InputIDs returns the set of utxoIDs this transaction consumes
|
|
|
|
func (tx *UniqueTx) InputIDs() ids.Set {
|
|
|
|
tx.refresh()
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.Tx == nil || tx.inputs.Len() != 0 {
|
|
|
|
return tx.inputs
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, utxo := range tx.InputUTXOs() {
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.inputs.Add(utxo.InputID())
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
return tx.inputs
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// InputUTXOs returns the utxos that will be consumed on tx acceptance
|
2020-03-23 13:40:37 -07:00
|
|
|
func (tx *UniqueTx) InputUTXOs() []*ava.UTXOID {
|
2020-03-10 12:20:34 -07:00
|
|
|
tx.refresh()
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.Tx == nil || len(tx.inputUTXOs) != 0 {
|
|
|
|
return tx.inputUTXOs
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.inputUTXOs = tx.Tx.InputUTXOs()
|
|
|
|
return tx.inputUTXOs
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// UTXOs returns the utxos that will be added to the UTXO set on tx acceptance
|
2020-03-23 13:40:37 -07:00
|
|
|
func (tx *UniqueTx) UTXOs() []*ava.UTXO {
|
2020-03-10 12:20:34 -07:00
|
|
|
tx.refresh()
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.Tx == nil || len(tx.utxos) != 0 {
|
|
|
|
return tx.utxos
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.utxos = tx.Tx.UTXOs()
|
|
|
|
return tx.utxos
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Bytes returns the binary representation of this transaction
|
|
|
|
func (tx *UniqueTx) Bytes() []byte {
|
|
|
|
tx.refresh()
|
2020-03-19 15:36:10 -07:00
|
|
|
return tx.Tx.Bytes()
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the validity of this transaction
|
|
|
|
func (tx *UniqueTx) Verify() error {
|
|
|
|
switch status := tx.Status(); status {
|
|
|
|
case choices.Unknown:
|
|
|
|
return errUnknownTx
|
|
|
|
case choices.Accepted:
|
|
|
|
return nil
|
|
|
|
case choices.Rejected:
|
|
|
|
return errRejectedTx
|
|
|
|
default:
|
|
|
|
return tx.SemanticVerify()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SyntacticVerify verifies that this transaction is well formed
|
|
|
|
func (tx *UniqueTx) SyntacticVerify() error {
|
|
|
|
tx.refresh()
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.Tx == nil {
|
2020-03-10 12:20:34 -07:00
|
|
|
return errUnknownTx
|
|
|
|
}
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.verifiedTx {
|
|
|
|
return tx.validity
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.verifiedTx = true
|
|
|
|
tx.validity = tx.Tx.SyntacticVerify(tx.vm.ctx, tx.vm.codec, len(tx.vm.fxs))
|
|
|
|
return tx.validity
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// SemanticVerify the validity of this transaction
|
|
|
|
func (tx *UniqueTx) SemanticVerify() error {
|
|
|
|
tx.SyntacticVerify()
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.validity != nil || tx.verifiedState {
|
|
|
|
return tx.validity
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
tx.verifiedState = true
|
|
|
|
tx.validity = tx.Tx.SemanticVerify(tx.vm, tx)
|
2020-03-10 12:20:34 -07:00
|
|
|
|
2020-03-19 15:36:10 -07:00
|
|
|
if tx.validity == nil {
|
2020-03-10 12:20:34 -07:00
|
|
|
tx.vm.pubsub.Publish("verified", tx.ID())
|
|
|
|
}
|
2020-03-19 15:36:10 -07:00
|
|
|
return tx.validity
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnsignedBytes returns the unsigned bytes of the transaction
|
|
|
|
func (tx *UniqueTx) UnsignedBytes() []byte {
|
2020-03-19 15:36:10 -07:00
|
|
|
b, err := tx.vm.codec.Marshal(&tx.UnsignedTx)
|
2020-03-10 12:20:34 -07:00
|
|
|
tx.vm.ctx.Log.AssertNoError(err)
|
|
|
|
return b
|
|
|
|
}
|