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