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 }