code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_sign_transaction_test.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_test 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "testing" 23 "time" 24 25 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 26 "code.vegaprotocol.io/vega/libs/jsonrpc" 27 vgrand "code.vegaprotocol.io/vega/libs/rand" 28 "code.vegaprotocol.io/vega/wallet/api" 29 "code.vegaprotocol.io/vega/wallet/api/mocks" 30 walletnode "code.vegaprotocol.io/vega/wallet/api/node" 31 nodemocks "code.vegaprotocol.io/vega/wallet/api/node/mocks" 32 "code.vegaprotocol.io/vega/wallet/api/node/types" 33 34 "github.com/golang/mock/gomock" 35 "github.com/stretchr/testify/assert" 36 ) 37 38 func TestAdminSignTransaction(t *testing.T) { 39 t.Run("Documentation matches the code", testAdminSignTransactionSchemaCorrect) 40 t.Run("Signing transaction with invalid params fails", testAdminSigningTransactionWithInvalidParamsFails) 41 t.Run("Signing transaction with valid params succeeds", testAdminSigningTransactionWithValidParamsSucceeds) 42 t.Run("Getting internal error during wallet verification fails", testAdminSignTransactionGettingInternalErrorDuringWalletVerificationFails) 43 t.Run("Signing transaction with wallet that doesn't exist fails", testAdminSigningTransactionWithWalletThatDoesntExistFails) 44 t.Run("Getting internal error during wallet retrieval fails", testAdminSignTransactionGettingInternalErrorDuringWalletRetrievalFails) 45 t.Run("Signing transaction with malformed transaction fails", testAdminSigningTransactionWithMalformedTransactionFails) 46 t.Run("Signing transaction which is invalid fails", testAdminSigningTransactionWithInvalidTransactionFails) 47 } 48 49 func testAdminSignTransactionSchemaCorrect(t *testing.T) { 50 assertEqualSchema(t, "admin.sign_transaction", api.AdminSignTransactionParams{}, api.AdminSignTransactionResult{}) 51 } 52 53 func testAdminSigningTransactionWithInvalidParamsFails(t *testing.T) { 54 tcs := []struct { 55 name string 56 params interface{} 57 expectedError error 58 }{ 59 { 60 name: "with nil params", 61 params: nil, 62 expectedError: api.ErrParamsRequired, 63 }, 64 { 65 name: "with wrong type of params", 66 params: "test", 67 expectedError: api.ErrParamsDoNotMatch, 68 }, 69 { 70 name: "with empty wallet", 71 params: api.AdminSignTransactionParams{ 72 Wallet: "", 73 PublicKey: vgrand.RandomStr(5), 74 Transaction: testTransaction(t), 75 Network: vgrand.RandomStr(5), 76 LastBlockData: nil, 77 }, 78 expectedError: api.ErrWalletIsRequired, 79 }, 80 { 81 name: "with empty public key", 82 params: api.AdminSignTransactionParams{ 83 Wallet: vgrand.RandomStr(5), 84 PublicKey: "", 85 Transaction: testTransaction(t), 86 Network: vgrand.RandomStr(5), 87 LastBlockData: nil, 88 }, 89 expectedError: api.ErrPublicKeyIsRequired, 90 }, 91 { 92 name: "with empty transaction", 93 params: api.AdminSignTransactionParams{ 94 Wallet: vgrand.RandomStr(5), 95 PublicKey: vgrand.RandomStr(5), 96 Transaction: "", 97 Network: vgrand.RandomStr(5), 98 LastBlockData: nil, 99 }, 100 expectedError: api.ErrTransactionIsRequired, 101 }, 102 { 103 name: "with no network of block data", 104 params: api.AdminSignTransactionParams{ 105 Wallet: vgrand.RandomStr(5), 106 PublicKey: vgrand.RandomStr(5), 107 Network: "", 108 LastBlockData: nil, 109 Transaction: testTransaction(t), 110 }, 111 expectedError: api.ErrLastBlockDataOrNetworkIsRequired, 112 }, 113 { 114 name: "with both network and block data", 115 params: api.AdminSignTransactionParams{ 116 Wallet: vgrand.RandomStr(5), 117 PublicKey: vgrand.RandomStr(5), 118 Network: "fairground", 119 LastBlockData: &api.AdminLastBlockData{}, 120 Transaction: testTransaction(t), 121 }, 122 expectedError: api.ErrSpecifyingNetworkAndLastBlockDataIsNotSupported, 123 }, 124 { 125 name: "with block data without chain ID", 126 params: api.AdminSignTransactionParams{ 127 Wallet: vgrand.RandomStr(5), 128 PublicKey: vgrand.RandomStr(5), 129 LastBlockData: &api.AdminLastBlockData{ 130 ChainID: "", 131 BlockHeight: 12, 132 BlockHash: vgrand.RandomStr(64), 133 ProofOfWorkHashFunction: "sha3_24_rounds", 134 ProofOfWorkDifficulty: 12, 135 }, 136 Transaction: testTransaction(t), 137 }, 138 expectedError: api.ErrChainIDIsRequired, 139 }, 140 { 141 name: "with block data without block hash", 142 params: api.AdminSignTransactionParams{ 143 Wallet: vgrand.RandomStr(5), 144 PublicKey: vgrand.RandomStr(5), 145 LastBlockData: &api.AdminLastBlockData{ 146 ChainID: "chain-id", 147 BlockHeight: 12, 148 BlockHash: "", 149 ProofOfWorkHashFunction: "sha3_24_rounds", 150 ProofOfWorkDifficulty: 12, 151 }, 152 Transaction: testTransaction(t), 153 }, 154 expectedError: api.ErrBlockHashIsRequired, 155 }, 156 { 157 name: "with block data without pow difficulty", 158 params: api.AdminSignTransactionParams{ 159 Wallet: vgrand.RandomStr(5), 160 PublicKey: vgrand.RandomStr(5), 161 LastBlockData: &api.AdminLastBlockData{ 162 ChainID: "chain-id", 163 BlockHeight: 12, 164 BlockHash: vgrand.RandomStr(64), 165 ProofOfWorkHashFunction: "sha3_24_rounds", 166 ProofOfWorkDifficulty: 0, 167 }, 168 Transaction: testTransaction(t), 169 }, 170 expectedError: api.ErrProofOfWorkDifficultyRequired, 171 }, 172 { 173 name: "with block data without block height", 174 params: api.AdminSignTransactionParams{ 175 Wallet: vgrand.RandomStr(5), 176 PublicKey: vgrand.RandomStr(5), 177 LastBlockData: &api.AdminLastBlockData{ 178 BlockHeight: 0, 179 ChainID: "chain-id", 180 BlockHash: vgrand.RandomStr(64), 181 ProofOfWorkDifficulty: 12, 182 ProofOfWorkHashFunction: "sha3_24_rounds", 183 }, 184 Transaction: testTransaction(t), 185 }, 186 expectedError: api.ErrBlockHeightIsRequired, 187 }, 188 { 189 name: "with block data without hash function", 190 params: api.AdminSignTransactionParams{ 191 Wallet: vgrand.RandomStr(5), 192 PublicKey: vgrand.RandomStr(5), 193 LastBlockData: &api.AdminLastBlockData{ 194 BlockHeight: 150, 195 ChainID: "chain-id", 196 BlockHash: vgrand.RandomStr(64), 197 ProofOfWorkDifficulty: 12, 198 ProofOfWorkHashFunction: "", 199 }, 200 Transaction: testTransaction(t), 201 }, 202 expectedError: api.ErrProofOfWorkHashFunctionRequired, 203 }, 204 } 205 206 for _, tc := range tcs { 207 t.Run(tc.name, func(tt *testing.T) { 208 // given 209 ctx := context.Background() 210 211 // setup 212 handler := newAdminSignTransactionHandler(tt, unexpectedNodeSelectorCall(tt)) 213 214 // when 215 result, errorDetails := handler.handle(t, ctx, tc.params) 216 217 // then 218 assertInvalidParams(tt, errorDetails, tc.expectedError) 219 assert.Empty(tt, result) 220 }) 221 } 222 } 223 224 func testAdminSigningTransactionWithValidParamsSucceeds(t *testing.T) { 225 // given 226 ctx := context.Background() 227 network := newNetwork(t) 228 w, kp := walletWithKey(t) 229 230 // setup 231 handler := newAdminSignTransactionHandler(t, func(hosts []string, _ uint64, _ time.Duration) (walletnode.Selector, error) { 232 ctrl := gomock.NewController(t) 233 nodeSelector := nodemocks.NewMockSelector(ctrl) 234 node := nodemocks.NewMockNode(ctrl) 235 nodeSelector.EXPECT().Node(ctx, gomock.Any()).Times(1).Return(node, nil) 236 node.EXPECT().LastBlock(ctx).Times(1).Return(types.LastBlock{ 237 BlockHeight: 150, 238 BlockHash: vgrand.RandomStr(64), 239 ProofOfWorkHashFunction: vgcrypto.Sha3, 240 ProofOfWorkDifficulty: 1, 241 ChainID: vgrand.RandomStr(5), 242 }, nil) 243 return nodeSelector, nil 244 }) 245 246 // -- expected calls 247 handler.walletStore.EXPECT().WalletExists(ctx, w.Name()).Times(1).Return(true, nil) 248 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, w.Name()).Times(1).Return(true, nil) 249 handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil) 250 handler.networkStore.EXPECT().NetworkExists(network.Name).Times(1).Return(true, nil) 251 handler.networkStore.EXPECT().GetNetwork(network.Name).Times(1).Return(&network, nil) 252 253 // when 254 result, errorDetails := handler.handle(t, ctx, api.AdminSignTransactionParams{ 255 Wallet: w.Name(), 256 PublicKey: kp.PublicKey(), 257 Network: network.Name, 258 Transaction: testTransaction(t), 259 }) 260 261 // then 262 assert.Nil(t, errorDetails) 263 assert.NotEmpty(t, result.EncodedTransaction) 264 assert.NotEmpty(t, result.Transaction) 265 } 266 267 func testAdminSignTransactionGettingInternalErrorDuringWalletVerificationFails(t *testing.T) { 268 // given 269 ctx := context.Background() 270 network := newNetwork(t) 271 walletName := vgrand.RandomStr(5) 272 273 // setup 274 handler := newAdminSignTransactionHandler(t, func(hosts []string, _ uint64, _ time.Duration) (walletnode.Selector, error) { 275 ctrl := gomock.NewController(t) 276 nodeSelector := nodemocks.NewMockSelector(ctrl) 277 node := nodemocks.NewMockNode(ctrl) 278 nodeSelector.EXPECT().Node(ctx, gomock.Any()).Times(1).Return(node, nil) 279 node.EXPECT().LastBlock(ctx).Times(1).Return(types.LastBlock{ 280 BlockHeight: 150, 281 BlockHash: vgrand.RandomStr(64), 282 ProofOfWorkHashFunction: vgcrypto.Sha3, 283 ProofOfWorkDifficulty: 1, 284 ChainID: vgrand.RandomStr(5), 285 }, nil) 286 return nodeSelector, nil 287 }) 288 289 // -- expected calls 290 handler.walletStore.EXPECT().WalletExists(ctx, walletName).Times(1).Return(false, assert.AnError) 291 292 // when 293 result, errorDetails := handler.handle(t, ctx, api.AdminSignTransactionParams{ 294 Wallet: walletName, 295 PublicKey: vgrand.RandomStr(5), 296 Network: network.Name, 297 Transaction: testTransaction(t), 298 }) 299 300 // then 301 assertInternalError(t, errorDetails, fmt.Errorf("could not verify the wallet exists: %w", assert.AnError)) 302 assert.Empty(t, result) 303 } 304 305 func testAdminSigningTransactionWithWalletThatDoesntExistFails(t *testing.T) { 306 // given 307 ctx := context.Background() 308 params := api.AdminSignTransactionParams{ 309 Wallet: vgrand.RandomStr(5), 310 PublicKey: vgrand.RandomStr(5), 311 Network: "fairground", 312 Transaction: testTransaction(t), 313 } 314 315 // setup 316 handler := newAdminSignTransactionHandler(t, unexpectedNodeSelectorCall(t)) 317 318 // -- expected calls 319 handler.walletStore.EXPECT().WalletExists(ctx, params.Wallet).Times(1).Return(false, nil) 320 321 // when 322 result, errorDetails := handler.handle(t, ctx, params) 323 324 // then 325 assertInvalidParams(t, errorDetails, api.ErrWalletDoesNotExist) 326 assert.Empty(t, result) 327 } 328 329 func testAdminSignTransactionGettingInternalErrorDuringWalletRetrievalFails(t *testing.T) { 330 // given 331 ctx := context.Background() 332 network := newNetwork(t) 333 walletName := vgrand.RandomStr(5) 334 335 // setup 336 handler := newAdminSignTransactionHandler(t, func(hosts []string, _ uint64, _ time.Duration) (walletnode.Selector, error) { 337 ctrl := gomock.NewController(t) 338 nodeSelector := nodemocks.NewMockSelector(ctrl) 339 node := nodemocks.NewMockNode(ctrl) 340 nodeSelector.EXPECT().Node(ctx, gomock.Any()).Times(1).Return(node, nil) 341 node.EXPECT().LastBlock(ctx).Times(1).Return(types.LastBlock{ 342 BlockHeight: 150, 343 BlockHash: vgrand.RandomStr(64), 344 ProofOfWorkHashFunction: vgcrypto.Sha3, 345 ProofOfWorkDifficulty: 1, 346 ChainID: vgrand.RandomStr(5), 347 }, nil) 348 return nodeSelector, nil 349 }) 350 351 // -- expected calls 352 handler.walletStore.EXPECT().WalletExists(ctx, walletName).Times(1).Return(true, nil) 353 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, walletName).Times(1).Return(true, nil) 354 handler.walletStore.EXPECT().GetWallet(ctx, walletName).Times(1).Return(nil, assert.AnError) 355 356 // when 357 result, errorDetails := handler.handle(t, ctx, api.AdminSignTransactionParams{ 358 Wallet: walletName, 359 PublicKey: vgrand.RandomStr(5), 360 Network: network.Name, 361 Transaction: testTransaction(t), 362 }) 363 364 // then 365 assertInternalError(t, errorDetails, fmt.Errorf("could not retrieve the wallet: %w", assert.AnError)) 366 assert.Empty(t, result) 367 } 368 369 func testAdminSigningTransactionWithMalformedTransactionFails(t *testing.T) { 370 // given 371 ctx := context.Background() 372 network := vgrand.RandomStr(5) 373 w, kp := walletWithKey(t) 374 375 // setup 376 handler := newAdminSignTransactionHandler(t, unexpectedNodeSelectorCall(t)) 377 378 // -- expected calls 379 handler.walletStore.EXPECT().WalletExists(ctx, w.Name()).Times(1).Return(true, nil) 380 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, w.Name()).Times(1).Return(true, nil) 381 handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil) 382 383 // when 384 result, errorDetails := handler.handle(t, ctx, api.AdminSignTransactionParams{ 385 Wallet: w.Name(), 386 PublicKey: kp.PublicKey(), 387 Network: network, 388 Transaction: map[string]int{"bob": 5}, 389 }) 390 391 // then 392 assertInvalidParams(t, errorDetails, errors.New("the transaction does not use a valid Vega command: unknown field \"bob\" in vega.wallet.v1.SubmitTransactionRequest")) 393 assert.Empty(t, result) 394 } 395 396 func testAdminSigningTransactionWithInvalidTransactionFails(t *testing.T) { 397 // given 398 ctx := context.Background() 399 network := newNetwork(t) 400 w, kp := walletWithKey(t) 401 402 // setup 403 handler := newAdminSignTransactionHandler(t, unexpectedNodeSelectorCall(t)) 404 405 // -- expected calls 406 handler.walletStore.EXPECT().WalletExists(ctx, w.Name()).Times(1).Return(true, nil) 407 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, w.Name()).Times(1).Return(true, nil) 408 handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil) 409 410 // when 411 result, errorDetails := handler.handle(t, ctx, api.AdminSignTransactionParams{ 412 Wallet: w.Name(), 413 PublicKey: kp.PublicKey(), 414 Network: network.Name, 415 Transaction: testMalformedTransaction(t), 416 }) 417 418 // then 419 assertInvalidParams(t, errorDetails, fmt.Errorf("vote_submission.proposal_id (should be a valid Vega ID)")) 420 assert.Empty(t, result) 421 } 422 423 type AdminSignTransactionHandler struct { 424 *api.AdminSignTransaction 425 ctrl *gomock.Controller 426 walletStore *mocks.MockWalletStore 427 networkStore *mocks.MockNetworkStore 428 } 429 430 func (h *AdminSignTransactionHandler) handle(t *testing.T, ctx context.Context, params jsonrpc.Params) (api.AdminSignTransactionResult, *jsonrpc.ErrorDetails) { 431 t.Helper() 432 433 rawResult, err := h.Handle(ctx, params) 434 if rawResult != nil { 435 result, ok := rawResult.(api.AdminSignTransactionResult) 436 if !ok { 437 t.Fatal("AdminUpdatePermissions handler result is not a AdminSignTransactionResult") 438 } 439 return result, err 440 } 441 return api.AdminSignTransactionResult{}, err 442 } 443 444 func newAdminSignTransactionHandler(t *testing.T, builder api.NodeSelectorBuilder) *AdminSignTransactionHandler { 445 t.Helper() 446 447 ctrl := gomock.NewController(t) 448 walletStore := mocks.NewMockWalletStore(ctrl) 449 networkStore := mocks.NewMockNetworkStore(ctrl) 450 451 return &AdminSignTransactionHandler{ 452 AdminSignTransaction: api.NewAdminSignTransaction(walletStore, networkStore, builder), 453 ctrl: ctrl, 454 walletStore: walletStore, 455 networkStore: networkStore, 456 } 457 }