github.com/MetalBlockchain/metalgo@v1.11.9/vms/example/xsvm/cmd/issue/importtx/cmd.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package importtx
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"log"
    10  	"time"
    11  
    12  	"github.com/spf13/cobra"
    13  
    14  	"github.com/MetalBlockchain/metalgo/api/info"
    15  	"github.com/MetalBlockchain/metalgo/utils/crypto/bls"
    16  	"github.com/MetalBlockchain/metalgo/utils/set"
    17  	"github.com/MetalBlockchain/metalgo/vms/example/xsvm/api"
    18  	"github.com/MetalBlockchain/metalgo/vms/example/xsvm/cmd/issue/status"
    19  	"github.com/MetalBlockchain/metalgo/vms/example/xsvm/tx"
    20  	"github.com/MetalBlockchain/metalgo/vms/platformvm/warp"
    21  )
    22  
    23  func Command() *cobra.Command {
    24  	c := &cobra.Command{
    25  		Use:   "import",
    26  		Short: "Issues an import transaction",
    27  		RunE:  importFunc,
    28  	}
    29  	flags := c.Flags()
    30  	AddFlags(flags)
    31  	return c
    32  }
    33  
    34  func importFunc(c *cobra.Command, args []string) error {
    35  	flags := c.Flags()
    36  	config, err := ParseFlags(flags, args)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	txStatus, err := Import(c.Context(), config)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	log.Print(txStatus)
    46  
    47  	return nil
    48  }
    49  
    50  func Import(ctx context.Context, config *Config) (*status.TxIssuance, error) {
    51  	var (
    52  		// Note: here we assume the unsigned message is correct from the last
    53  		//       URI in sourceURIs. In practice this shouldn't be done.
    54  		unsignedMessage *warp.UnsignedMessage
    55  		// Note: assumes that sourceURIs are all of the validators of the subnet
    56  		//       and that they do not share public keys.
    57  		signatures = make([]*bls.Signature, len(config.SourceURIs))
    58  	)
    59  	for i, uri := range config.SourceURIs {
    60  		xsClient := api.NewClient(uri, config.SourceChainID)
    61  
    62  		fetchStartTime := time.Now()
    63  		var (
    64  			rawSignature []byte
    65  			err          error
    66  		)
    67  		unsignedMessage, rawSignature, err = xsClient.Message(ctx, config.TxID)
    68  		if err != nil {
    69  			return nil, fmt.Errorf("failed to fetch BLS signature from %s with: %w", uri, err)
    70  		}
    71  
    72  		sig, err := bls.SignatureFromBytes(rawSignature)
    73  		if err != nil {
    74  			return nil, fmt.Errorf("failed to parse BLS signature from %s with: %w", uri, err)
    75  		}
    76  
    77  		// Note: the public key should not be fetched from the node in practice.
    78  		//       The public key should be fetched from the P-chain directly.
    79  		infoClient := info.NewClient(uri)
    80  		_, nodePOP, err := infoClient.GetNodeID(ctx)
    81  		if err != nil {
    82  			return nil, fmt.Errorf("failed to fetch BLS public key from %s with: %w", uri, err)
    83  		}
    84  
    85  		pk := nodePOP.Key()
    86  		if !bls.Verify(pk, sig, unsignedMessage.Bytes()) {
    87  			return nil, fmt.Errorf("failed to verify BLS signature against public key from %s", uri)
    88  		}
    89  
    90  		log.Printf("fetched BLS signature from %s in %s\n", uri, time.Since(fetchStartTime))
    91  		signatures[i] = sig
    92  	}
    93  
    94  	signers := set.NewBits()
    95  	for i := range signatures {
    96  		signers.Add(i)
    97  	}
    98  	signature := &warp.BitSetSignature{
    99  		Signers: signers.Bytes(),
   100  	}
   101  
   102  	aggSignature, err := bls.AggregateSignatures(signatures)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	aggSignatureBytes := bls.SignatureToBytes(aggSignature)
   108  	copy(signature.Signature[:], aggSignatureBytes)
   109  
   110  	message, err := warp.NewMessage(
   111  		unsignedMessage,
   112  		signature,
   113  	)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	client := api.NewClient(config.URI, config.DestinationChainID)
   119  
   120  	address := config.PrivateKey.Address()
   121  	nonce, err := client.Nonce(ctx, address)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	utx := &tx.Import{
   127  		Nonce:   nonce,
   128  		MaxFee:  config.MaxFee,
   129  		Message: message.Bytes(),
   130  	}
   131  	stx, err := tx.Sign(utx, config.PrivateKey)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	issueTxStartTime := time.Now()
   137  	txID, err := client.IssueTx(ctx, stx)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	if err := api.AwaitTxAccepted(ctx, client, address, nonce, api.DefaultPollingInterval); err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	return &status.TxIssuance{
   147  		Tx:        stx,
   148  		TxID:      txID,
   149  		Nonce:     nonce,
   150  		StartTime: issueTxStartTime,
   151  	}, nil
   152  }