code.vegaprotocol.io/vega@v0.79.0/cmd/vega/commands/announce_node.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package commands
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"strconv"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/blockchain"
    27  	"code.vegaprotocol.io/vega/core/blockchain/abci"
    28  	"code.vegaprotocol.io/vega/core/config"
    29  	"code.vegaprotocol.io/vega/core/nodewallets"
    30  	"code.vegaprotocol.io/vega/core/txn"
    31  	"code.vegaprotocol.io/vega/core/validators"
    32  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    33  	vgjson "code.vegaprotocol.io/vega/libs/json"
    34  	"code.vegaprotocol.io/vega/logging"
    35  	"code.vegaprotocol.io/vega/paths"
    36  	api "code.vegaprotocol.io/vega/protos/vega/api/v1"
    37  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    38  
    39  	"github.com/jessevdk/go-flags"
    40  	"google.golang.org/grpc"
    41  	"google.golang.org/grpc/credentials/insecure"
    42  )
    43  
    44  type AnnounceNodeCmd struct {
    45  	config.VegaHomeFlag
    46  	config.OutputFlag
    47  	config.Passphrase `long:"passphrase-file"`
    48  
    49  	InfoURL          string `description:"An url to the website / information about this validator"               long:"info-url"          required:"true" short:"i"`
    50  	Country          string `description:"The country from which the validator is operating"                      long:"country"           required:"true" short:"c"`
    51  	Name             string `description:"The name of this validator"                                             long:"name"              required:"true" short:"n"`
    52  	AvatarURL        string `description:"A link to an avatar picture for this validator"                         long:"avatar-url"        required:"true" short:"a"`
    53  	FromEpoch        uint64 `description:"The epoch from which this validator should be ready to validate blocks" long:"from-epoch"        required:"true" short:"f"`
    54  	SubmitterAddress string `description:"Ethereum address to use as a submitter to contract changes"             long:"submitter-address" short:"s"`
    55  }
    56  
    57  var announceNodeCmd AnnounceNodeCmd
    58  
    59  func (opts *AnnounceNodeCmd) Execute(_ []string) error {
    60  	log := logging.NewLoggerFromConfig(logging.NewDefaultConfig())
    61  	defer log.AtExit()
    62  
    63  	output, err := opts.GetOutput()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	registryPass, err := opts.Get("node wallet", false)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	vegaPaths := paths.New(opts.VegaHome)
    74  
    75  	_, conf, err := config.EnsureNodeConfig(vegaPaths)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	if !conf.IsValidator() {
    81  		return errors.New("node is not a validator")
    82  	}
    83  
    84  	nodeWallets, err := nodewallets.GetNodeWallets(conf.NodeWallet, vegaPaths, registryPass)
    85  	if err != nil {
    86  		return fmt.Errorf("couldn't get node wallets: %w", err)
    87  	}
    88  
    89  	// ensure the nodewallet is setup properly, if not we could not complete the command
    90  	if err := nodeWallets.Verify(); err != nil {
    91  		return fmt.Errorf("node wallet misconfigured: %w", err)
    92  	}
    93  
    94  	cmd := commandspb.AnnounceNode{
    95  		Id:               nodeWallets.Vega.ID().Hex(),
    96  		VegaPubKey:       nodeWallets.Vega.PubKey().Hex(),
    97  		VegaPubKeyIndex:  nodeWallets.Vega.Index(),
    98  		ChainPubKey:      nodeWallets.Tendermint.Pubkey,
    99  		EthereumAddress:  vgcrypto.EthereumChecksumAddress(nodeWallets.Ethereum.PubKey().Hex()),
   100  		FromEpoch:        opts.FromEpoch,
   101  		InfoUrl:          opts.InfoURL,
   102  		Name:             opts.Name,
   103  		AvatarUrl:        opts.AvatarURL,
   104  		Country:          opts.Country,
   105  		SubmitterAddress: opts.SubmitterAddress,
   106  	}
   107  
   108  	if len(cmd.SubmitterAddress) != 0 {
   109  		cmd.SubmitterAddress = vgcrypto.EthereumChecksumAddress(cmd.SubmitterAddress)
   110  	}
   111  
   112  	if err := validators.SignAnnounceNode(
   113  		&cmd, nodeWallets.Vega, nodeWallets.Ethereum,
   114  	); err != nil {
   115  		return err
   116  	}
   117  
   118  	// now we are OK, send the command
   119  
   120  	commander, blockData, cfunc, err := getNodeWalletCommander(log, registryPass, vegaPaths)
   121  	if err != nil {
   122  		return fmt.Errorf("failed to get commander: %w", err)
   123  	}
   124  	defer cfunc()
   125  
   126  	tid := vgcrypto.RandomHash()
   127  	powNonce, _, err := vgcrypto.PoW(blockData.Hash, tid, uint(blockData.SpamPowDifficulty), vgcrypto.Sha3)
   128  	if err != nil {
   129  		return fmt.Errorf("failed to get commander: %w", err)
   130  	}
   131  
   132  	pow := &commandspb.ProofOfWork{
   133  		Tid:   tid,
   134  		Nonce: powNonce,
   135  	}
   136  
   137  	var txHash string
   138  	ch := make(chan struct{})
   139  	commander.CommandWithPoW(
   140  		context.Background(),
   141  		txn.AnnounceNodeCommand,
   142  		&cmd,
   143  		func(h string, e error) {
   144  			txHash, err = h, e
   145  			close(ch)
   146  		}, nil, pow)
   147  
   148  	<-ch
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	if output.IsHuman() {
   154  		fmt.Printf("node successfully announced.\ntxHash: %s\nvega signature: %v\nethereum signature: %v\n",
   155  			txHash,
   156  			cmd.VegaSignature.Value,
   157  			cmd.EthereumSignature.Value,
   158  		)
   159  	} else if output.IsJSON() {
   160  		return vgjson.Print(struct {
   161  			TxHash            string `json:"txHash"`
   162  			EthereumSignature string `json:"ethereumSignature"`
   163  			VegaSignature     string `json:"vegaSignature"`
   164  		}{
   165  			TxHash:            txHash,
   166  			EthereumSignature: cmd.EthereumSignature.Value,
   167  			VegaSignature:     cmd.VegaSignature.Value,
   168  		})
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func AnnounceNode(ctx context.Context, parser *flags.Parser) error {
   175  	announceNodeCmd = AnnounceNodeCmd{}
   176  
   177  	var (
   178  		short = "Announce a node as a potential validator to the network"
   179  		long  = "Announce a node as a potential validator to the network"
   180  	)
   181  	_, err := parser.AddCommand("announce_node", short, long, &announceNodeCmd)
   182  	return err
   183  }
   184  
   185  func getNodeWalletCommander(log *logging.Logger, registryPass string, vegaPaths paths.Paths) (*nodewallets.Commander, *api.LastBlockHeightResponse, context.CancelFunc, error) {
   186  	_, cfg, err := config.EnsureNodeConfig(vegaPaths)
   187  	if err != nil {
   188  		return nil, nil, nil, err
   189  	}
   190  
   191  	vegaWallet, err := nodewallets.GetVegaWallet(vegaPaths, registryPass)
   192  	if err != nil {
   193  		return nil, nil, nil, fmt.Errorf("couldn't get Vega node wallet: %w", err)
   194  	}
   195  
   196  	rpcAddr := cfg.Blockchain.Tendermint.RPCAddr
   197  	if len(rpcAddr) <= 0 {
   198  		rpcAddr = "tcp://127.0.0.1:26657"
   199  		log.Warn("Blockchain.Tendermint.RPCAddr is empty, using default", logging.String("address", rpcAddr))
   200  	}
   201  
   202  	abciClient, err := abci.NewClient(rpcAddr)
   203  	if err != nil {
   204  		return nil, nil, nil, fmt.Errorf("couldn't initialise ABCI client: %w", err)
   205  	}
   206  
   207  	coreClient, err := getCoreClient(net.JoinHostPort(cfg.API.IP, strconv.Itoa(cfg.API.Port)))
   208  	if err != nil {
   209  		return nil, nil, nil, fmt.Errorf("couldn't connect to node: %w", err)
   210  	}
   211  
   212  	ctx, cancel := timeoutContext()
   213  	resp, err := coreClient.LastBlockHeight(ctx, &api.LastBlockHeightRequest{})
   214  	if err != nil {
   215  		return nil, nil, nil, fmt.Errorf("couldn't get last block height: %w", err)
   216  	}
   217  
   218  	commander, err := nodewallets.NewCommander(cfg.NodeWallet, log, blockchain.NewClientWithImpl(abciClient), vegaWallet, heightProvider{height: resp.Height})
   219  	if err != nil {
   220  		return nil, nil, nil, fmt.Errorf("couldn't initialise node wallet commander: %w", err)
   221  	}
   222  
   223  	return commander, resp, cancel, nil
   224  }
   225  
   226  type heightProvider struct {
   227  	height uint64
   228  }
   229  
   230  func (h heightProvider) Height() uint64 {
   231  	return h.height
   232  }
   233  
   234  func getCoreClient(address string) (api.CoreServiceClient, error) {
   235  	tdconn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	return api.NewCoreServiceClient(tdconn), nil
   240  }
   241  
   242  func timeoutContext() (context.Context, func()) {
   243  	return context.WithTimeout(context.Background(), 5*time.Second)
   244  }