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"
|
2020-06-15 10:20:30 -07:00
|
|
|
"github.com/ava-labs/gecko/utils/codec"
|
2020-06-18 10:34:04 -07:00
|
|
|
"github.com/ava-labs/gecko/vms/components/ava"
|
2020-03-19 15:36:10 -07:00
|
|
|
"github.com/ava-labs/gecko/vms/components/verify"
|
2020-03-10 12:20:34 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errOperationsNotSortedUnique = errors.New("operations not sorted and unique")
|
2020-03-20 17:56:43 -07:00
|
|
|
errNoOperations = errors.New("an operationTx must have at least one operation")
|
2020-03-10 12:20:34 -07:00
|
|
|
|
|
|
|
errDoubleSpend = errors.New("inputs attempt to double spend an input")
|
|
|
|
)
|
|
|
|
|
|
|
|
// OperationTx is a transaction with no credentials.
|
|
|
|
type OperationTx struct {
|
|
|
|
BaseTx `serialize:"true"`
|
2020-03-27 13:54:47 -07:00
|
|
|
Ops []*Operation `serialize:"true" json:"operations"`
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Operations track which ops this transaction is performing. The returned array
|
|
|
|
// should not be modified.
|
|
|
|
func (t *OperationTx) Operations() []*Operation { return t.Ops }
|
|
|
|
|
|
|
|
// InputUTXOs track which UTXOs this transaction is consuming.
|
2020-03-23 13:40:37 -07:00
|
|
|
func (t *OperationTx) InputUTXOs() []*ava.UTXOID {
|
2020-03-10 12:20:34 -07:00
|
|
|
utxos := t.BaseTx.InputUTXOs()
|
|
|
|
for _, op := range t.Ops {
|
2020-03-29 22:08:45 -07:00
|
|
|
utxos = append(utxos, op.UTXOIDs...)
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
return utxos
|
|
|
|
}
|
|
|
|
|
2020-06-18 10:34:04 -07:00
|
|
|
// ConsumedAssetIDs returns the IDs of the assets this transaction consumes
|
|
|
|
func (t *OperationTx) ConsumedAssetIDs() ids.Set {
|
|
|
|
assets := t.BaseTx.AssetIDs()
|
|
|
|
for _, op := range t.Ops {
|
|
|
|
if len(op.UTXOIDs) > 0 {
|
|
|
|
assets.Add(op.AssetID())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return assets
|
|
|
|
}
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
// AssetIDs returns the IDs of the assets this transaction depends on
|
|
|
|
func (t *OperationTx) AssetIDs() ids.Set {
|
|
|
|
assets := t.BaseTx.AssetIDs()
|
|
|
|
for _, op := range t.Ops {
|
|
|
|
assets.Add(op.AssetID())
|
|
|
|
}
|
|
|
|
return assets
|
|
|
|
}
|
|
|
|
|
2020-03-29 22:08:45 -07:00
|
|
|
// NumCredentials returns the number of expected credentials
|
|
|
|
func (t *OperationTx) NumCredentials() int { return t.BaseTx.NumCredentials() + len(t.Ops) }
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
// UTXOs returns the UTXOs transaction is producing.
|
2020-03-23 13:40:37 -07:00
|
|
|
func (t *OperationTx) UTXOs() []*ava.UTXO {
|
2020-03-10 12:20:34 -07:00
|
|
|
txID := t.ID()
|
|
|
|
utxos := t.BaseTx.UTXOs()
|
|
|
|
|
|
|
|
for _, op := range t.Ops {
|
|
|
|
asset := op.AssetID()
|
2020-03-29 22:08:45 -07:00
|
|
|
for _, out := range op.Op.Outs() {
|
2020-03-23 13:40:37 -07:00
|
|
|
utxos = append(utxos, &ava.UTXO{
|
|
|
|
UTXOID: ava.UTXOID{
|
2020-03-10 12:20:34 -07:00
|
|
|
TxID: txID,
|
|
|
|
OutputIndex: uint32(len(utxos)),
|
|
|
|
},
|
2020-03-23 13:40:37 -07:00
|
|
|
Asset: ava.Asset{ID: asset},
|
2020-03-20 17:56:43 -07:00
|
|
|
Out: out,
|
2020-03-10 12:20:34 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return utxos
|
|
|
|
}
|
|
|
|
|
|
|
|
// SyntacticVerify that this transaction is well-formed.
|
|
|
|
func (t *OperationTx) SyntacticVerify(ctx *snow.Context, c codec.Codec, numFxs int) error {
|
|
|
|
switch {
|
|
|
|
case t == nil:
|
|
|
|
return errNilTx
|
2020-03-20 17:56:43 -07:00
|
|
|
case len(t.Ops) == 0:
|
|
|
|
return errNoOperations
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.BaseTx.SyntacticVerify(ctx, c, numFxs); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
inputs := ids.Set{}
|
|
|
|
for _, in := range t.Ins {
|
|
|
|
inputs.Add(in.InputID())
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, op := range t.Ops {
|
|
|
|
if err := op.Verify(c); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-03-29 22:08:45 -07:00
|
|
|
for _, utxoID := range op.UTXOIDs {
|
|
|
|
inputID := utxoID.InputID()
|
2020-03-10 12:20:34 -07:00
|
|
|
if inputs.Contains(inputID) {
|
|
|
|
return errDoubleSpend
|
|
|
|
}
|
|
|
|
inputs.Add(inputID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !isSortedAndUniqueOperations(t.Ops, c) {
|
|
|
|
return errOperationsNotSortedUnique
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SemanticVerify that this transaction is well-formed.
|
2020-03-19 15:36:10 -07:00
|
|
|
func (t *OperationTx) SemanticVerify(vm *VM, uTx *UniqueTx, creds []verify.Verifiable) error {
|
2020-03-10 12:20:34 -07:00
|
|
|
if err := t.BaseTx.SemanticVerify(vm, uTx, creds); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-03-29 22:08:45 -07:00
|
|
|
|
|
|
|
offset := t.BaseTx.NumCredentials()
|
|
|
|
for i, op := range t.Ops {
|
2020-03-10 12:20:34 -07:00
|
|
|
opAssetID := op.AssetID()
|
|
|
|
|
|
|
|
utxos := []interface{}{}
|
2020-03-29 22:08:45 -07:00
|
|
|
for _, utxoID := range op.UTXOIDs {
|
|
|
|
utxo, err := vm.getUTXO(utxoID)
|
2020-03-20 17:56:43 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
utxoAssetID := utxo.AssetID()
|
|
|
|
if !utxoAssetID.Equals(opAssetID) {
|
|
|
|
return errAssetIDMismatch
|
|
|
|
}
|
|
|
|
utxos = append(utxos, utxo.Out)
|
|
|
|
}
|
|
|
|
|
2020-03-29 22:08:45 -07:00
|
|
|
fxIndex, err := vm.getFx(op.Op)
|
2020-03-10 12:20:34 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fx := vm.fxs[fxIndex].Fx
|
|
|
|
|
|
|
|
if !vm.verifyFxUsage(fxIndex, opAssetID) {
|
|
|
|
return errIncompatibleFx
|
|
|
|
}
|
|
|
|
|
2020-03-29 22:08:45 -07:00
|
|
|
if err := fx.VerifyOperation(uTx, op.Op, creds[offset+i], utxos); err != nil {
|
2020-03-10 12:20:34 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|