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, ¶ms); 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 }