github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/txs/tx.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package txs 5 6 import ( 7 "fmt" 8 9 "github.com/MetalBlockchain/metalgo/codec" 10 "github.com/MetalBlockchain/metalgo/ids" 11 "github.com/MetalBlockchain/metalgo/network/p2p/gossip" 12 "github.com/MetalBlockchain/metalgo/snow" 13 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 14 "github.com/MetalBlockchain/metalgo/utils/hashing" 15 "github.com/MetalBlockchain/metalgo/utils/set" 16 "github.com/MetalBlockchain/metalgo/vms/avm/fxs" 17 "github.com/MetalBlockchain/metalgo/vms/components/avax" 18 "github.com/MetalBlockchain/metalgo/vms/nftfx" 19 "github.com/MetalBlockchain/metalgo/vms/propertyfx" 20 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 21 ) 22 23 var _ gossip.Gossipable = (*Tx)(nil) 24 25 type UnsignedTx interface { 26 snow.ContextInitializable 27 28 SetBytes(unsignedBytes []byte) 29 Bytes() []byte 30 31 InputIDs() set.Set[ids.ID] 32 33 NumCredentials() int 34 // TODO: deprecate after x-chain linearization 35 InputUTXOs() []*avax.UTXOID 36 37 // Visit calls [visitor] with this transaction's concrete type 38 Visit(visitor Visitor) error 39 } 40 41 // Tx is the core operation that can be performed. The tx uses the UTXO model. 42 // Specifically, a txs inputs will consume previous txs outputs. A tx will be 43 // valid if the inputs have the authority to consume the outputs they are 44 // attempting to consume and the inputs consume sufficient state to produce the 45 // outputs. 46 type Tx struct { 47 Unsigned UnsignedTx `serialize:"true" json:"unsignedTx"` 48 Creds []*fxs.FxCredential `serialize:"true" json:"credentials"` // The credentials of this transaction 49 50 TxID ids.ID `json:"id"` 51 bytes []byte 52 } 53 54 func (t *Tx) Initialize(c codec.Manager) error { 55 signedBytes, err := c.Marshal(CodecVersion, t) 56 if err != nil { 57 return fmt.Errorf("problem creating transaction: %w", err) 58 } 59 60 unsignedBytesLen, err := c.Size(CodecVersion, &t.Unsigned) 61 if err != nil { 62 return fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err) 63 } 64 65 unsignedBytes := signedBytes[:unsignedBytesLen] 66 t.SetBytes(unsignedBytes, signedBytes) 67 return nil 68 } 69 70 func (t *Tx) SetBytes(unsignedBytes, signedBytes []byte) { 71 t.TxID = hashing.ComputeHash256Array(signedBytes) 72 t.bytes = signedBytes 73 t.Unsigned.SetBytes(unsignedBytes) 74 } 75 76 // ID returns the unique ID of this tx 77 func (t *Tx) ID() ids.ID { 78 return t.TxID 79 } 80 81 // GossipID returns the unique ID that this tx should use for mempool gossip 82 func (t *Tx) GossipID() ids.ID { 83 return t.TxID 84 } 85 86 // Bytes returns the binary representation of this tx 87 func (t *Tx) Bytes() []byte { 88 return t.bytes 89 } 90 91 func (t *Tx) Size() int { 92 return len(t.bytes) 93 } 94 95 // UTXOs returns the UTXOs transaction is producing. 96 func (t *Tx) UTXOs() []*avax.UTXO { 97 u := utxoGetter{tx: t} 98 // The visit error is explicitly dropped here because no error is ever 99 // returned from the utxoGetter. 100 _ = t.Unsigned.Visit(&u) 101 return u.utxos 102 } 103 104 func (t *Tx) InputIDs() set.Set[ids.ID] { 105 return t.Unsigned.InputIDs() 106 } 107 108 func (t *Tx) SignSECP256K1Fx(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { 109 unsignedBytes, err := c.Marshal(CodecVersion, &t.Unsigned) 110 if err != nil { 111 return fmt.Errorf("problem creating transaction: %w", err) 112 } 113 114 hash := hashing.ComputeHash256(unsignedBytes) 115 for _, keys := range signers { 116 cred := &secp256k1fx.Credential{ 117 Sigs: make([][secp256k1.SignatureLen]byte, len(keys)), 118 } 119 for i, key := range keys { 120 sig, err := key.SignHash(hash) 121 if err != nil { 122 return fmt.Errorf("problem creating transaction: %w", err) 123 } 124 copy(cred.Sigs[i][:], sig) 125 } 126 t.Creds = append(t.Creds, &fxs.FxCredential{Credential: cred}) 127 } 128 129 signedBytes, err := c.Marshal(CodecVersion, t) 130 if err != nil { 131 return fmt.Errorf("problem creating transaction: %w", err) 132 } 133 t.SetBytes(unsignedBytes, signedBytes) 134 return nil 135 } 136 137 func (t *Tx) SignPropertyFx(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { 138 unsignedBytes, err := c.Marshal(CodecVersion, &t.Unsigned) 139 if err != nil { 140 return fmt.Errorf("problem creating transaction: %w", err) 141 } 142 143 hash := hashing.ComputeHash256(unsignedBytes) 144 for _, keys := range signers { 145 cred := &propertyfx.Credential{Credential: secp256k1fx.Credential{ 146 Sigs: make([][secp256k1.SignatureLen]byte, len(keys)), 147 }} 148 for i, key := range keys { 149 sig, err := key.SignHash(hash) 150 if err != nil { 151 return fmt.Errorf("problem creating transaction: %w", err) 152 } 153 copy(cred.Sigs[i][:], sig) 154 } 155 t.Creds = append(t.Creds, &fxs.FxCredential{Credential: cred}) 156 } 157 158 signedBytes, err := c.Marshal(CodecVersion, t) 159 if err != nil { 160 return fmt.Errorf("problem creating transaction: %w", err) 161 } 162 t.SetBytes(unsignedBytes, signedBytes) 163 return nil 164 } 165 166 func (t *Tx) SignNFTFx(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { 167 unsignedBytes, err := c.Marshal(CodecVersion, &t.Unsigned) 168 if err != nil { 169 return fmt.Errorf("problem creating transaction: %w", err) 170 } 171 172 hash := hashing.ComputeHash256(unsignedBytes) 173 for _, keys := range signers { 174 cred := &nftfx.Credential{Credential: secp256k1fx.Credential{ 175 Sigs: make([][secp256k1.SignatureLen]byte, len(keys)), 176 }} 177 for i, key := range keys { 178 sig, err := key.SignHash(hash) 179 if err != nil { 180 return fmt.Errorf("problem creating transaction: %w", err) 181 } 182 copy(cred.Sigs[i][:], sig) 183 } 184 t.Creds = append(t.Creds, &fxs.FxCredential{Credential: cred}) 185 } 186 187 signedBytes, err := c.Marshal(CodecVersion, t) 188 if err != nil { 189 return fmt.Errorf("problem creating transaction: %w", err) 190 } 191 t.SetBytes(unsignedBytes, signedBytes) 192 return nil 193 }