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  }