code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_send_transaction.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 api
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/commands"
    26  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    27  	"code.vegaprotocol.io/vega/libs/jsonrpc"
    28  	apipb "code.vegaprotocol.io/vega/protos/vega/api/v1"
    29  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    30  	walletpb "code.vegaprotocol.io/vega/protos/vega/wallet/v1"
    31  	"code.vegaprotocol.io/vega/wallet/api/node"
    32  	wcommands "code.vegaprotocol.io/vega/wallet/commands"
    33  
    34  	"github.com/golang/protobuf/jsonpb"
    35  	"github.com/mitchellh/mapstructure"
    36  )
    37  
    38  type AdminSendTransactionParams struct {
    39  	Wallet                 string        `json:"wallet"`
    40  	PublicKey              string        `json:"publicKey"`
    41  	Network                string        `json:"network"`
    42  	NodeAddress            string        `json:"nodeAddress"`
    43  	Retries                uint64        `json:"retries"`
    44  	MaximumRequestDuration time.Duration `json:"maximumRequestDuration"`
    45  	SendingMode            string        `json:"sendingMode"`
    46  	Transaction            interface{}   `json:"transaction"`
    47  }
    48  
    49  type ParsedAdminSendTransactionParams struct {
    50  	Wallet                 string
    51  	PublicKey              string
    52  	Network                string
    53  	NodeAddress            string
    54  	Retries                uint64
    55  	SendingMode            apipb.SubmitTransactionRequest_Type
    56  	RawTransaction         string
    57  	MaximumRequestDuration time.Duration
    58  }
    59  
    60  type AdminSendTransactionResult struct {
    61  	ReceivedAt      time.Time               `json:"receivedAt"`
    62  	SentAt          time.Time               `json:"sentAt"`
    63  	TransactionHash string                  `json:"transactionHash"`
    64  	Transaction     *commandspb.Transaction `json:"transaction"`
    65  	Node            AdminNodeInfoResult     `json:"node"`
    66  }
    67  
    68  type AdminNodeInfoResult struct {
    69  	Host string `json:"host"`
    70  }
    71  
    72  type AdminSendTransaction struct {
    73  	walletStore         WalletStore
    74  	networkStore        NetworkStore
    75  	nodeSelectorBuilder NodeSelectorBuilder
    76  }
    77  
    78  func (h *AdminSendTransaction) Handle(ctx context.Context, rawParams jsonrpc.Params) (jsonrpc.Result, *jsonrpc.ErrorDetails) {
    79  	receivedAt := time.Now()
    80  
    81  	params, err := validateAdminSendTransactionParams(rawParams)
    82  	if err != nil {
    83  		return nil, InvalidParams(err)
    84  	}
    85  
    86  	if exist, err := h.walletStore.WalletExists(ctx, params.Wallet); err != nil {
    87  		return nil, InternalError(fmt.Errorf("could not verify the wallet exists: %w", err))
    88  	} else if !exist {
    89  		return nil, InvalidParams(ErrWalletDoesNotExist)
    90  	}
    91  
    92  	alreadyUnlocked, err := h.walletStore.IsWalletAlreadyUnlocked(ctx, params.Wallet)
    93  	if err != nil {
    94  		return nil, InternalError(fmt.Errorf("could not verify whether the wallet is already unlock or not: %w", err))
    95  	}
    96  	if !alreadyUnlocked {
    97  		return nil, RequestNotPermittedError(ErrWalletIsLocked)
    98  	}
    99  
   100  	w, err := h.walletStore.GetWallet(ctx, params.Wallet)
   101  	if err != nil {
   102  		return nil, InternalError(fmt.Errorf("could not retrieve the wallet: %w", err))
   103  	}
   104  	request := &walletpb.SubmitTransactionRequest{}
   105  	if err := jsonpb.Unmarshal(strings.NewReader(params.RawTransaction), request); err != nil {
   106  		return nil, InvalidParams(fmt.Errorf("the transaction does not use a valid Vega command: %w", err))
   107  	}
   108  
   109  	request.PubKey = params.PublicKey
   110  	request.Propagate = true
   111  	if errs := wcommands.CheckSubmitTransactionRequest(request); !errs.Empty() {
   112  		return nil, InvalidParams(errs)
   113  	}
   114  
   115  	currentNode, errDetails := h.getNode(ctx, params)
   116  	if errDetails != nil {
   117  		return nil, errDetails
   118  	}
   119  
   120  	lastBlockData, errDetails := h.getLastBlockDataFromNetwork(ctx, currentNode)
   121  	if errDetails != nil {
   122  		return nil, errDetails
   123  	}
   124  
   125  	marshaledInputData, err := wcommands.ToMarshaledInputData(request, lastBlockData.BlockHeight)
   126  	if err != nil {
   127  		return nil, InternalError(fmt.Errorf("could not marshal the input data: %w", err))
   128  	}
   129  
   130  	signature, err := w.SignTx(params.PublicKey, commands.BundleInputDataForSigning(marshaledInputData, lastBlockData.ChainID))
   131  	if err != nil {
   132  		return nil, InternalError(fmt.Errorf("could not sign the transaction: %w", err))
   133  	}
   134  
   135  	// Build the transaction.
   136  	tx := commands.NewTransaction(params.PublicKey, marshaledInputData, &commandspb.Signature{
   137  		Value:   signature.Value,
   138  		Algo:    signature.Algo,
   139  		Version: signature.Version,
   140  	})
   141  
   142  	// Generate the proof of work for the transaction.
   143  	txID := vgcrypto.RandomHash()
   144  	powNonce, _, err := vgcrypto.PoW(lastBlockData.BlockHash, txID, uint(lastBlockData.ProofOfWorkDifficulty), lastBlockData.ProofOfWorkHashFunction)
   145  	if err != nil {
   146  		return nil, InternalError(fmt.Errorf("could not compute the proof-of-work: %w", err))
   147  	}
   148  
   149  	tx.Pow = &commandspb.ProofOfWork{
   150  		Nonce: powNonce,
   151  		Tid:   txID,
   152  	}
   153  
   154  	sentAt := time.Now()
   155  	txHash, err := currentNode.SendTransaction(ctx, tx, params.SendingMode)
   156  	if err != nil {
   157  		return nil, NetworkErrorFromTransactionError(err)
   158  	}
   159  
   160  	return AdminSendTransactionResult{
   161  		ReceivedAt:      receivedAt,
   162  		SentAt:          sentAt,
   163  		TransactionHash: txHash,
   164  		Transaction:     tx,
   165  		Node: AdminNodeInfoResult{
   166  			Host: currentNode.Host(),
   167  		},
   168  	}, nil
   169  }
   170  
   171  func (h *AdminSendTransaction) getNode(ctx context.Context, params ParsedAdminSendTransactionParams) (node.Node, *jsonrpc.ErrorDetails) {
   172  	hosts := []string{params.NodeAddress}
   173  	if len(params.Network) != 0 {
   174  		exists, err := h.networkStore.NetworkExists(params.Network)
   175  		if err != nil {
   176  			return nil, InternalError(fmt.Errorf("could not determine if the network exists: %w", err))
   177  		} else if !exists {
   178  			return nil, InvalidParams(ErrNetworkDoesNotExist)
   179  		}
   180  
   181  		n, err := h.networkStore.GetNetwork(params.Network)
   182  		if err != nil {
   183  			return nil, InternalError(fmt.Errorf("could not retrieve the network configuration: %w", err))
   184  		}
   185  
   186  		if err := n.EnsureCanConnectGRPCNode(); err != nil {
   187  			return nil, InvalidParams(ErrNetworkConfigurationDoesNotHaveGRPCNodes)
   188  		}
   189  		hosts = n.API.GRPC.Hosts
   190  	}
   191  
   192  	nodeSelector, err := h.nodeSelectorBuilder(hosts, params.Retries, params.MaximumRequestDuration)
   193  	if err != nil {
   194  		return nil, InternalError(fmt.Errorf("could not initialize the node selector: %w", err))
   195  	}
   196  
   197  	currentNode, err := nodeSelector.Node(ctx, noNodeSelectionReporting)
   198  	if err != nil {
   199  		return nil, NodeCommunicationError(ErrNoHealthyNodeAvailable)
   200  	}
   201  
   202  	return currentNode, nil
   203  }
   204  
   205  func (h *AdminSendTransaction) getLastBlockDataFromNetwork(ctx context.Context, node node.Node) (*AdminLastBlockData, *jsonrpc.ErrorDetails) {
   206  	lastBlock, err := node.LastBlock(ctx)
   207  	if err != nil {
   208  		return nil, NodeCommunicationError(ErrCouldNotGetLastBlockInformation)
   209  	}
   210  
   211  	if lastBlock.ChainID == "" {
   212  		return nil, NodeCommunicationError(ErrCouldNotGetChainIDFromNode)
   213  	}
   214  
   215  	return &AdminLastBlockData{
   216  		BlockHash:               lastBlock.BlockHash,
   217  		ChainID:                 lastBlock.ChainID,
   218  		BlockHeight:             lastBlock.BlockHeight,
   219  		ProofOfWorkHashFunction: lastBlock.ProofOfWorkHashFunction,
   220  		ProofOfWorkDifficulty:   lastBlock.ProofOfWorkDifficulty,
   221  	}, nil
   222  }
   223  
   224  func NewAdminSendTransaction(walletStore WalletStore, networkStore NetworkStore, nodeSelectorBuilder NodeSelectorBuilder) *AdminSendTransaction {
   225  	return &AdminSendTransaction{
   226  		walletStore:         walletStore,
   227  		networkStore:        networkStore,
   228  		nodeSelectorBuilder: nodeSelectorBuilder,
   229  	}
   230  }
   231  
   232  func validateAdminSendTransactionParams(rawParams jsonrpc.Params) (ParsedAdminSendTransactionParams, error) {
   233  	if rawParams == nil {
   234  		return ParsedAdminSendTransactionParams{}, ErrParamsRequired
   235  	}
   236  
   237  	params := AdminSendTransactionParams{}
   238  	if err := mapstructure.Decode(rawParams, &params); err != nil {
   239  		return ParsedAdminSendTransactionParams{}, ErrParamsDoNotMatch
   240  	}
   241  
   242  	if params.Wallet == "" {
   243  		return ParsedAdminSendTransactionParams{}, ErrWalletIsRequired
   244  	}
   245  
   246  	if params.PublicKey == "" {
   247  		return ParsedAdminSendTransactionParams{}, ErrPublicKeyIsRequired
   248  	}
   249  
   250  	if params.Network == "" && params.NodeAddress == "" {
   251  		return ParsedAdminSendTransactionParams{}, ErrNetworkOrNodeAddressIsRequired
   252  	}
   253  
   254  	if params.Network != "" && params.NodeAddress != "" {
   255  		return ParsedAdminSendTransactionParams{}, ErrSpecifyingNetworkAndNodeAddressIsNotSupported
   256  	}
   257  
   258  	if params.Transaction == nil || params.Transaction == "" {
   259  		return ParsedAdminSendTransactionParams{}, ErrTransactionIsRequired
   260  	}
   261  
   262  	tx, err := json.Marshal(params.Transaction)
   263  	if err != nil {
   264  		return ParsedAdminSendTransactionParams{}, ErrTransactionIsNotValidJSON
   265  	}
   266  
   267  	return ParsedAdminSendTransactionParams{
   268  		Wallet:                 params.Wallet,
   269  		PublicKey:              params.PublicKey,
   270  		RawTransaction:         string(tx),
   271  		Network:                params.Network,
   272  		NodeAddress:            params.NodeAddress,
   273  		Retries:                params.Retries,
   274  		MaximumRequestDuration: params.MaximumRequestDuration,
   275  	}, nil
   276  }