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 }