github.com/MetalBlockchain/metalgo@v1.11.9/vms/components/avax/transferables.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package avax
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"sort"
    10  
    11  	"github.com/MetalBlockchain/metalgo/codec"
    12  	"github.com/MetalBlockchain/metalgo/ids"
    13  	"github.com/MetalBlockchain/metalgo/snow"
    14  	"github.com/MetalBlockchain/metalgo/utils"
    15  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    16  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    17  )
    18  
    19  var (
    20  	ErrNilTransferableOutput   = errors.New("nil transferable output is not valid")
    21  	ErrNilTransferableFxOutput = errors.New("nil transferable feature extension output is not valid")
    22  	ErrOutputsNotSorted        = errors.New("outputs not sorted")
    23  
    24  	ErrNilTransferableInput   = errors.New("nil transferable input is not valid")
    25  	ErrNilTransferableFxInput = errors.New("nil transferable feature extension input is not valid")
    26  	ErrInputsNotSortedUnique  = errors.New("inputs not sorted and unique")
    27  
    28  	_ verify.Verifiable                  = (*TransferableOutput)(nil)
    29  	_ verify.Verifiable                  = (*TransferableInput)(nil)
    30  	_ utils.Sortable[*TransferableInput] = (*TransferableInput)(nil)
    31  )
    32  
    33  // Amounter is a data structure that has an amount of something associated with it
    34  type Amounter interface {
    35  	snow.ContextInitializable
    36  	// Amount returns how much value this element represents of the asset in its
    37  	// transaction.
    38  	Amount() uint64
    39  }
    40  
    41  // Coster is a data structure that has a cost associated with it
    42  type Coster interface {
    43  	// Cost returns how much this element costs to be included in its
    44  	// transaction.
    45  	Cost() (uint64, error)
    46  }
    47  
    48  // TransferableIn is the interface a feature extension must provide to transfer
    49  // value between features extensions.
    50  type TransferableIn interface {
    51  	verify.Verifiable
    52  	Amounter
    53  	Coster
    54  }
    55  
    56  // TransferableOut is the interface a feature extension must provide to transfer
    57  // value between features extensions.
    58  type TransferableOut interface {
    59  	snow.ContextInitializable
    60  	verify.State
    61  	Amounter
    62  }
    63  
    64  type TransferableOutput struct {
    65  	Asset `serialize:"true"`
    66  	// FxID has serialize false because we don't want this to be encoded in bytes
    67  	FxID ids.ID          `serialize:"false" json:"fxID"`
    68  	Out  TransferableOut `serialize:"true"  json:"output"`
    69  }
    70  
    71  func (out *TransferableOutput) InitCtx(ctx *snow.Context) {
    72  	out.Out.InitCtx(ctx)
    73  }
    74  
    75  // Output returns the feature extension output that this Output is using.
    76  func (out *TransferableOutput) Output() TransferableOut {
    77  	return out.Out
    78  }
    79  
    80  func (out *TransferableOutput) Verify() error {
    81  	switch {
    82  	case out == nil:
    83  		return ErrNilTransferableOutput
    84  	case out.Out == nil:
    85  		return ErrNilTransferableFxOutput
    86  	default:
    87  		return verify.All(&out.Asset, out.Out)
    88  	}
    89  }
    90  
    91  type innerSortTransferableOutputs struct {
    92  	outs  []*TransferableOutput
    93  	codec codec.Manager
    94  }
    95  
    96  func (outs *innerSortTransferableOutputs) Less(i, j int) bool {
    97  	iOut := outs.outs[i]
    98  	jOut := outs.outs[j]
    99  
   100  	iAssetID := iOut.AssetID()
   101  	jAssetID := jOut.AssetID()
   102  
   103  	switch bytes.Compare(iAssetID[:], jAssetID[:]) {
   104  	case -1:
   105  		return true
   106  	case 1:
   107  		return false
   108  	}
   109  
   110  	iBytes, err := outs.codec.Marshal(codecVersion, &iOut.Out)
   111  	if err != nil {
   112  		return false
   113  	}
   114  	jBytes, err := outs.codec.Marshal(codecVersion, &jOut.Out)
   115  	if err != nil {
   116  		return false
   117  	}
   118  	return bytes.Compare(iBytes, jBytes) == -1
   119  }
   120  
   121  func (outs *innerSortTransferableOutputs) Len() int {
   122  	return len(outs.outs)
   123  }
   124  
   125  func (outs *innerSortTransferableOutputs) Swap(i, j int) {
   126  	o := outs.outs
   127  	o[j], o[i] = o[i], o[j]
   128  }
   129  
   130  // SortTransferableOutputs sorts output objects
   131  func SortTransferableOutputs(outs []*TransferableOutput, c codec.Manager) {
   132  	sort.Sort(&innerSortTransferableOutputs{outs: outs, codec: c})
   133  }
   134  
   135  // IsSortedTransferableOutputs returns true if output objects are sorted
   136  func IsSortedTransferableOutputs(outs []*TransferableOutput, c codec.Manager) bool {
   137  	return sort.IsSorted(&innerSortTransferableOutputs{outs: outs, codec: c})
   138  }
   139  
   140  type TransferableInput struct {
   141  	UTXOID `serialize:"true"`
   142  	Asset  `serialize:"true"`
   143  	// FxID has serialize false because we don't want this to be encoded in bytes
   144  	FxID ids.ID         `serialize:"false" json:"fxID"`
   145  	In   TransferableIn `serialize:"true"  json:"input"`
   146  }
   147  
   148  // Input returns the feature extension input that this Input is using.
   149  func (in *TransferableInput) Input() TransferableIn {
   150  	return in.In
   151  }
   152  
   153  func (in *TransferableInput) Verify() error {
   154  	switch {
   155  	case in == nil:
   156  		return ErrNilTransferableInput
   157  	case in.In == nil:
   158  		return ErrNilTransferableFxInput
   159  	default:
   160  		return verify.All(&in.UTXOID, &in.Asset, in.In)
   161  	}
   162  }
   163  
   164  func (in *TransferableInput) Compare(other *TransferableInput) int {
   165  	return in.UTXOID.Compare(&other.UTXOID)
   166  }
   167  
   168  type innerSortTransferableInputsWithSigners struct {
   169  	ins     []*TransferableInput
   170  	signers [][]*secp256k1.PrivateKey
   171  }
   172  
   173  func (ins *innerSortTransferableInputsWithSigners) Less(i, j int) bool {
   174  	iID, iIndex := ins.ins[i].InputSource()
   175  	jID, jIndex := ins.ins[j].InputSource()
   176  
   177  	switch bytes.Compare(iID[:], jID[:]) {
   178  	case -1:
   179  		return true
   180  	case 0:
   181  		return iIndex < jIndex
   182  	default:
   183  		return false
   184  	}
   185  }
   186  
   187  func (ins *innerSortTransferableInputsWithSigners) Len() int {
   188  	return len(ins.ins)
   189  }
   190  
   191  func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) {
   192  	ins.ins[j], ins.ins[i] = ins.ins[i], ins.ins[j]
   193  	ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j]
   194  }
   195  
   196  // SortTransferableInputsWithSigners sorts the inputs and signers based on the
   197  // input's utxo ID
   198  func SortTransferableInputsWithSigners(ins []*TransferableInput, signers [][]*secp256k1.PrivateKey) {
   199  	sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers})
   200  }
   201  
   202  // VerifyTx verifies that the inputs and outputs flowcheck, including a fee.
   203  // Additionally, this verifies that the inputs and outputs are sorted.
   204  func VerifyTx(
   205  	feeAmount uint64,
   206  	feeAssetID ids.ID,
   207  	allIns [][]*TransferableInput,
   208  	allOuts [][]*TransferableOutput,
   209  	c codec.Manager,
   210  ) error {
   211  	fc := NewFlowChecker()
   212  
   213  	fc.Produce(feeAssetID, feeAmount) // The txFee must be burned
   214  
   215  	// Add all the outputs to the flow checker and make sure they are sorted
   216  	for _, outs := range allOuts {
   217  		for _, out := range outs {
   218  			if err := out.Verify(); err != nil {
   219  				return err
   220  			}
   221  			fc.Produce(out.AssetID(), out.Output().Amount())
   222  		}
   223  		if !IsSortedTransferableOutputs(outs, c) {
   224  			return ErrOutputsNotSorted
   225  		}
   226  	}
   227  
   228  	// Add all the inputs to the flow checker and make sure they are sorted
   229  	for _, ins := range allIns {
   230  		for _, in := range ins {
   231  			if err := in.Verify(); err != nil {
   232  				return err
   233  			}
   234  			fc.Consume(in.AssetID(), in.Input().Amount())
   235  		}
   236  		if !utils.IsSortedAndUnique(ins) {
   237  			return ErrInputsNotSortedUnique
   238  		}
   239  	}
   240  
   241  	return fc.Verify()
   242  }