github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/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  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/MetalBlockchain/metalgo/codec"
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  	"github.com/MetalBlockchain/metalgo/network/p2p/gossip"
    13  	"github.com/MetalBlockchain/metalgo/snow"
    14  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    15  	"github.com/MetalBlockchain/metalgo/utils/hashing"
    16  	"github.com/MetalBlockchain/metalgo/utils/set"
    17  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    18  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    19  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    20  )
    21  
    22  var (
    23  	_ gossip.Gossipable = (*Tx)(nil)
    24  
    25  	ErrNilSignedTx = errors.New("nil signed tx is not valid")
    26  
    27  	errSignedTxNotInitialized = errors.New("signed tx was never initialized and is not valid")
    28  )
    29  
    30  // Tx is a signed transaction
    31  type Tx struct {
    32  	// The body of this transaction
    33  	Unsigned UnsignedTx `serialize:"true" json:"unsignedTx"`
    34  
    35  	// The credentials of this transaction
    36  	Creds []verify.Verifiable `serialize:"true" json:"credentials"`
    37  
    38  	TxID  ids.ID `json:"id"`
    39  	bytes []byte
    40  }
    41  
    42  func NewSigned(
    43  	unsigned UnsignedTx,
    44  	c codec.Manager,
    45  	signers [][]*secp256k1.PrivateKey,
    46  ) (*Tx, error) {
    47  	res := &Tx{Unsigned: unsigned}
    48  	return res, res.Sign(c, signers)
    49  }
    50  
    51  func (tx *Tx) Initialize(c codec.Manager) error {
    52  	signedBytes, err := c.Marshal(CodecVersion, tx)
    53  	if err != nil {
    54  		return fmt.Errorf("couldn't marshal ProposalTx: %w", err)
    55  	}
    56  
    57  	unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned)
    58  	if err != nil {
    59  		return fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err)
    60  	}
    61  
    62  	unsignedBytes := signedBytes[:unsignedBytesLen]
    63  	tx.SetBytes(unsignedBytes, signedBytes)
    64  	return nil
    65  }
    66  
    67  func (tx *Tx) SetBytes(unsignedBytes, signedBytes []byte) {
    68  	tx.Unsigned.SetBytes(unsignedBytes)
    69  	tx.bytes = signedBytes
    70  	tx.TxID = hashing.ComputeHash256Array(signedBytes)
    71  }
    72  
    73  // Parse signed tx starting from its byte representation.
    74  // Note: We explicitly pass the codec in Parse since we may need to parse
    75  // P-Chain genesis txs whose length exceed the max length of txs.Codec.
    76  func Parse(c codec.Manager, signedBytes []byte) (*Tx, error) {
    77  	tx := &Tx{}
    78  	if _, err := c.Unmarshal(signedBytes, tx); err != nil {
    79  		return nil, fmt.Errorf("couldn't parse tx: %w", err)
    80  	}
    81  
    82  	unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err)
    85  	}
    86  
    87  	unsignedBytes := signedBytes[:unsignedBytesLen]
    88  	tx.SetBytes(unsignedBytes, signedBytes)
    89  	return tx, nil
    90  }
    91  
    92  func (tx *Tx) Bytes() []byte {
    93  	return tx.bytes
    94  }
    95  
    96  func (tx *Tx) Size() int {
    97  	return len(tx.bytes)
    98  }
    99  
   100  func (tx *Tx) ID() ids.ID {
   101  	return tx.TxID
   102  }
   103  
   104  func (tx *Tx) GossipID() ids.ID {
   105  	return tx.TxID
   106  }
   107  
   108  // UTXOs returns the UTXOs transaction is producing.
   109  func (tx *Tx) UTXOs() []*avax.UTXO {
   110  	outs := tx.Unsigned.Outputs()
   111  	utxos := make([]*avax.UTXO, len(outs))
   112  	for i, out := range outs {
   113  		utxos[i] = &avax.UTXO{
   114  			UTXOID: avax.UTXOID{
   115  				TxID:        tx.TxID,
   116  				OutputIndex: uint32(i),
   117  			},
   118  			Asset: avax.Asset{ID: out.AssetID()},
   119  			Out:   out.Out,
   120  		}
   121  	}
   122  	return utxos
   123  }
   124  
   125  // InputIDs returns the set of inputs this transaction consumes
   126  func (tx *Tx) InputIDs() set.Set[ids.ID] {
   127  	return tx.Unsigned.InputIDs()
   128  }
   129  
   130  func (tx *Tx) SyntacticVerify(ctx *snow.Context) error {
   131  	switch {
   132  	case tx == nil:
   133  		return ErrNilSignedTx
   134  	case tx.TxID == ids.Empty:
   135  		return errSignedTxNotInitialized
   136  	default:
   137  		return tx.Unsigned.SyntacticVerify(ctx)
   138  	}
   139  }
   140  
   141  // Sign this transaction with the provided signers
   142  // Note: We explicitly pass the codec in Sign since we may need to sign P-Chain
   143  // genesis txs whose length exceed the max length of txs.Codec.
   144  func (tx *Tx) Sign(c codec.Manager, signers [][]*secp256k1.PrivateKey) error {
   145  	unsignedBytes, err := c.Marshal(CodecVersion, &tx.Unsigned)
   146  	if err != nil {
   147  		return fmt.Errorf("couldn't marshal UnsignedTx: %w", err)
   148  	}
   149  
   150  	// Attach credentials
   151  	hash := hashing.ComputeHash256(unsignedBytes)
   152  	for _, keys := range signers {
   153  		cred := &secp256k1fx.Credential{
   154  			Sigs: make([][secp256k1.SignatureLen]byte, len(keys)),
   155  		}
   156  		for i, key := range keys {
   157  			sig, err := key.SignHash(hash) // Sign hash
   158  			if err != nil {
   159  				return fmt.Errorf("problem generating credential: %w", err)
   160  			}
   161  			copy(cred.Sigs[i][:], sig)
   162  		}
   163  		tx.Creds = append(tx.Creds, cred) // Attach credential
   164  	}
   165  
   166  	signedBytes, err := c.Marshal(CodecVersion, tx)
   167  	if err != nil {
   168  		return fmt.Errorf("couldn't marshal ProposalTx: %w", err)
   169  	}
   170  	tx.SetBytes(unsignedBytes, signedBytes)
   171  	return nil
   172  }