code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_sign_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  	wcommands "code.vegaprotocol.io/vega/wallet/commands"
    32  
    33  	"github.com/golang/protobuf/jsonpb"
    34  	"github.com/golang/protobuf/proto"
    35  	"github.com/mitchellh/mapstructure"
    36  )
    37  
    38  type AdminLastBlockData struct {
    39  	ChainID                 string `json:"chainID"`
    40  	BlockHeight             uint64 `json:"blockHeight"`
    41  	BlockHash               string `json:"blockHash"`
    42  	ProofOfWorkHashFunction string `json:"proofOfWorkHashFunction"`
    43  	ProofOfWorkDifficulty   uint32 `json:"proofOfWorkDifficulty"`
    44  }
    45  
    46  type AdminSignTransactionParams struct {
    47  	Wallet                 string              `json:"wallet"`
    48  	PublicKey              string              `json:"publicKey"`
    49  	Network                string              `json:"network"`
    50  	Transaction            interface{}         `json:"transaction"`
    51  	Retries                uint64              `json:"retries"`
    52  	MaximumRequestDuration time.Duration       `json:"maximumRequestDuration"`
    53  	LastBlockData          *AdminLastBlockData `json:"lastBlockData"`
    54  }
    55  
    56  type ParsedAdminSignTransactionParams struct {
    57  	Wallet                 string
    58  	PublicKey              string
    59  	RawTransaction         string
    60  	Network                string
    61  	Retries                uint64
    62  	MaximumRequestDuration time.Duration
    63  	LastBlockData          *AdminLastBlockData
    64  }
    65  
    66  type AdminSignTransactionResult struct {
    67  	Transaction        *commandspb.Transaction `json:"transaction"`
    68  	EncodedTransaction string                  `json:"encodedTransaction"`
    69  }
    70  
    71  type AdminSignTransaction struct {
    72  	walletStore         WalletStore
    73  	networkStore        NetworkStore
    74  	nodeSelectorBuilder NodeSelectorBuilder
    75  }
    76  
    77  func (h *AdminSignTransaction) Handle(ctx context.Context, rawParams jsonrpc.Params) (jsonrpc.Result, *jsonrpc.ErrorDetails) {
    78  	params, err := validateAdminSignTransactionParams(rawParams)
    79  	if err != nil {
    80  		return nil, InvalidParams(err)
    81  	}
    82  
    83  	if exist, err := h.walletStore.WalletExists(ctx, params.Wallet); err != nil {
    84  		return nil, InternalError(fmt.Errorf("could not verify the wallet exists: %w", err))
    85  	} else if !exist {
    86  		return nil, InvalidParams(ErrWalletDoesNotExist)
    87  	}
    88  
    89  	alreadyUnlocked, err := h.walletStore.IsWalletAlreadyUnlocked(ctx, params.Wallet)
    90  	if err != nil {
    91  		return nil, InternalError(fmt.Errorf("could not verify whether the wallet is already unlock or not: %w", err))
    92  	}
    93  	if !alreadyUnlocked {
    94  		return nil, RequestNotPermittedError(ErrWalletIsLocked)
    95  	}
    96  
    97  	w, err := h.walletStore.GetWallet(ctx, params.Wallet)
    98  	if err != nil {
    99  		return nil, InternalError(fmt.Errorf("could not retrieve the wallet: %w", err))
   100  	}
   101  
   102  	request := &walletpb.SubmitTransactionRequest{}
   103  	if err := jsonpb.Unmarshal(strings.NewReader(params.RawTransaction), request); err != nil {
   104  		return nil, InvalidParams(fmt.Errorf("the transaction does not use a valid Vega command: %w", err))
   105  	}
   106  
   107  	request.PubKey = params.PublicKey
   108  	request.Propagate = true
   109  	if errs := wcommands.CheckSubmitTransactionRequest(request); !errs.Empty() {
   110  		return nil, InvalidParams(errs)
   111  	}
   112  
   113  	if params.Network != "" {
   114  		lastBlockData, errDetails := h.getLastBlockDataFromNetwork(ctx, params)
   115  		if errDetails != nil {
   116  			return nil, errDetails
   117  		}
   118  		params.LastBlockData = lastBlockData
   119  	}
   120  
   121  	marshaledInputData, err := wcommands.ToMarshaledInputData(request, params.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, params.LastBlockData.ChainID))
   127  	if err != nil {
   128  		return nil, InternalError(fmt.Errorf("could not sign 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(params.LastBlockData.BlockHash, txID, uint(params.LastBlockData.ProofOfWorkDifficulty), params.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  	rawTx, err := proto.Marshal(tx)
   150  	if err != nil {
   151  		return nil, InternalError(fmt.Errorf("could not marshal the transaction: %w", err))
   152  	}
   153  
   154  	return AdminSignTransactionResult{
   155  		Transaction:        tx,
   156  		EncodedTransaction: base64.StdEncoding.EncodeToString(rawTx),
   157  	}, nil
   158  }
   159  
   160  func (h *AdminSignTransaction) getLastBlockDataFromNetwork(ctx context.Context, params ParsedAdminSignTransactionParams) (*AdminLastBlockData, *jsonrpc.ErrorDetails) {
   161  	exists, err := h.networkStore.NetworkExists(params.Network)
   162  	if err != nil {
   163  		return nil, InternalError(fmt.Errorf("could not determine if the network exists: %w", err))
   164  	} else if !exists {
   165  		return nil, InvalidParams(ErrNetworkDoesNotExist)
   166  	}
   167  
   168  	n, err := h.networkStore.GetNetwork(params.Network)
   169  	if err != nil {
   170  		return nil, InternalError(fmt.Errorf("could not retrieve the network configuration: %w", err))
   171  	}
   172  
   173  	if err := n.EnsureCanConnectGRPCNode(); err != nil {
   174  		return nil, InvalidParams(ErrNetworkConfigurationDoesNotHaveGRPCNodes)
   175  	}
   176  
   177  	nodeSelector, err := h.nodeSelectorBuilder(n.API.GRPC.Hosts, params.Retries, params.MaximumRequestDuration)
   178  	if err != nil {
   179  		return nil, InternalError(fmt.Errorf("could not initialize the node selector: %w", err))
   180  	}
   181  
   182  	node, err := nodeSelector.Node(ctx, noNodeSelectionReporting)
   183  	if err != nil {
   184  		return nil, NodeCommunicationError(ErrNoHealthyNodeAvailable)
   185  	}
   186  
   187  	lastBlock, err := node.LastBlock(ctx)
   188  	if err != nil {
   189  		return nil, NodeCommunicationError(ErrCouldNotGetLastBlockInformation)
   190  	}
   191  
   192  	if lastBlock.ChainID == "" {
   193  		return nil, NodeCommunicationError(ErrCouldNotGetChainIDFromNode)
   194  	}
   195  
   196  	return &AdminLastBlockData{
   197  		BlockHash:               lastBlock.BlockHash,
   198  		ChainID:                 lastBlock.ChainID,
   199  		BlockHeight:             lastBlock.BlockHeight,
   200  		ProofOfWorkHashFunction: lastBlock.ProofOfWorkHashFunction,
   201  		ProofOfWorkDifficulty:   lastBlock.ProofOfWorkDifficulty,
   202  	}, nil
   203  }
   204  
   205  func NewAdminSignTransaction(walletStore WalletStore, networkStore NetworkStore, nodeSelectorBuilder NodeSelectorBuilder) *AdminSignTransaction {
   206  	return &AdminSignTransaction{
   207  		walletStore:         walletStore,
   208  		networkStore:        networkStore,
   209  		nodeSelectorBuilder: nodeSelectorBuilder,
   210  	}
   211  }
   212  
   213  func validateAdminSignTransactionParams(rawParams jsonrpc.Params) (ParsedAdminSignTransactionParams, error) {
   214  	if rawParams == nil {
   215  		return ParsedAdminSignTransactionParams{}, ErrParamsRequired
   216  	}
   217  
   218  	params := AdminSignTransactionParams{}
   219  	if err := mapstructure.Decode(rawParams, &params); err != nil {
   220  		return ParsedAdminSignTransactionParams{}, ErrParamsDoNotMatch
   221  	}
   222  
   223  	if params.Wallet == "" {
   224  		return ParsedAdminSignTransactionParams{}, ErrWalletIsRequired
   225  	}
   226  
   227  	if params.PublicKey == "" {
   228  		return ParsedAdminSignTransactionParams{}, ErrPublicKeyIsRequired
   229  	}
   230  
   231  	if params.Transaction == nil || params.Transaction == "" {
   232  		return ParsedAdminSignTransactionParams{}, ErrTransactionIsRequired
   233  	}
   234  
   235  	tx, err := json.Marshal(params.Transaction)
   236  	if err != nil {
   237  		return ParsedAdminSignTransactionParams{}, ErrTransactionIsNotValidJSON
   238  	}
   239  
   240  	if params.Network != "" && params.LastBlockData != nil {
   241  		return ParsedAdminSignTransactionParams{}, ErrSpecifyingNetworkAndLastBlockDataIsNotSupported
   242  	}
   243  
   244  	if params.Network == "" && params.LastBlockData == nil {
   245  		return ParsedAdminSignTransactionParams{}, ErrLastBlockDataOrNetworkIsRequired
   246  	}
   247  
   248  	if params.LastBlockData != nil {
   249  		if params.LastBlockData.BlockHeight == 0 {
   250  			return ParsedAdminSignTransactionParams{}, ErrBlockHeightIsRequired
   251  		}
   252  		if params.LastBlockData.ChainID == "" {
   253  			return ParsedAdminSignTransactionParams{}, ErrChainIDIsRequired
   254  		}
   255  		if params.LastBlockData.BlockHash == "" {
   256  			return ParsedAdminSignTransactionParams{}, ErrBlockHashIsRequired
   257  		}
   258  		if params.LastBlockData.ProofOfWorkDifficulty == 0 {
   259  			return ParsedAdminSignTransactionParams{}, ErrProofOfWorkDifficultyRequired
   260  		}
   261  		if params.LastBlockData.ProofOfWorkHashFunction == "" {
   262  			return ParsedAdminSignTransactionParams{}, ErrProofOfWorkHashFunctionRequired
   263  		}
   264  	}
   265  
   266  	return ParsedAdminSignTransactionParams{
   267  		Wallet:                 params.Wallet,
   268  		PublicKey:              params.PublicKey,
   269  		RawTransaction:         string(tx),
   270  		Network:                params.Network,
   271  		Retries:                params.Retries,
   272  		MaximumRequestDuration: params.MaximumRequestDuration,
   273  		LastBlockData:          params.LastBlockData,
   274  	}, nil
   275  }