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