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