code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_rotate_key_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 "fmt" 21 "testing" 22 23 "code.vegaprotocol.io/vega/libs/jsonrpc" 24 vgrand "code.vegaprotocol.io/vega/libs/rand" 25 "code.vegaprotocol.io/vega/wallet/api" 26 "code.vegaprotocol.io/vega/wallet/api/mocks" 27 28 "github.com/golang/mock/gomock" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 ) 32 33 func TestAdminRotateKey(t *testing.T) { 34 t.Run("Documentation matches the code", testAdminRotateKeySchemaCorrect) 35 t.Run("Rotating a key with invalid params fails", testRotatingKeyWithInvalidParamsFails) 36 t.Run("Rotating a key with valid params succeeds", testRotatingKeyWithValidParamsSucceeds) 37 t.Run("Rotating a key from wallet that does not exists fails", testRotatingKeyFromWalletThatDoesNotExistsFails) 38 t.Run("Getting internal error during wallet verification fails", testRotatingKeyGettingInternalErrorDuringWalletVerificationFails) 39 t.Run("Getting internal error during wallet retrieval fails", testRotatingKeyGettingInternalErrorDuringWalletRetrievalFails) 40 t.Run("Rotating key on an isolated wallet fails", testRotatingKeyWithIsolatedWalletFails) 41 t.Run("Rotating a key from a public key that does not exists fails", testRotatingKeyFromPublicKeyThatDoesNotExistsFails) 42 t.Run("Rotating a key to a public key that does not exists fails", testRotatingKeyToPublicKeyThatDoesNotExistsFails) 43 t.Run("Rotating a key to a tainted public key that does not exists fails", testRotatingKeyToTaintedPublicKeyDoesNotExistsFails) 44 } 45 46 func testAdminRotateKeySchemaCorrect(t *testing.T) { 47 assertEqualSchema(t, "admin.rotate_key", api.AdminRotateKeyParams{}, api.AdminRotateKeyResult{}) 48 } 49 50 func testRotatingKeyWithInvalidParamsFails(t *testing.T) { 51 tcs := []struct { 52 name string 53 params interface{} 54 expectedError error 55 }{ 56 { 57 name: "with nil params", 58 params: nil, 59 expectedError: api.ErrParamsRequired, 60 }, { 61 name: "with wrong type of params", 62 params: "test", 63 expectedError: api.ErrParamsDoNotMatch, 64 }, { 65 name: "with empty name", 66 params: api.AdminRotateKeyParams{ 67 Wallet: "", 68 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 69 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 70 ChainID: vgrand.RandomStr(5), 71 SubmissionBlockHeight: 10, 72 EnactmentBlockHeight: 15, 73 }, 74 expectedError: api.ErrWalletIsRequired, 75 }, { 76 name: "with empty chain ID", 77 params: api.AdminRotateKeyParams{ 78 Wallet: vgrand.RandomStr(5), 79 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 80 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 81 ChainID: "", 82 SubmissionBlockHeight: 10, 83 EnactmentBlockHeight: 15, 84 }, 85 expectedError: api.ErrChainIDIsRequired, 86 }, { 87 name: "with empty current public key", 88 params: api.AdminRotateKeyParams{ 89 Wallet: vgrand.RandomStr(5), 90 FromPublicKey: "", 91 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 92 ChainID: vgrand.RandomStr(5), 93 SubmissionBlockHeight: 10, 94 EnactmentBlockHeight: 15, 95 }, 96 expectedError: api.ErrCurrentPublicKeyIsRequired, 97 }, { 98 name: "with empty next public key", 99 params: api.AdminRotateKeyParams{ 100 Wallet: vgrand.RandomStr(5), 101 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 102 ToPublicKey: "", 103 ChainID: vgrand.RandomStr(5), 104 SubmissionBlockHeight: 10, 105 EnactmentBlockHeight: 15, 106 }, 107 expectedError: api.ErrNextPublicKeyIsRequired, 108 }, { 109 name: "with unset submission block height", 110 params: api.AdminRotateKeyParams{ 111 Wallet: vgrand.RandomStr(5), 112 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 113 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 114 ChainID: vgrand.RandomStr(5), 115 SubmissionBlockHeight: 0, 116 EnactmentBlockHeight: 15, 117 }, 118 expectedError: api.ErrSubmissionBlockHeightIsRequired, 119 }, { 120 name: "with unset enactment block height", 121 params: api.AdminRotateKeyParams{ 122 Wallet: vgrand.RandomStr(5), 123 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 124 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 125 ChainID: vgrand.RandomStr(5), 126 SubmissionBlockHeight: 10, 127 EnactmentBlockHeight: 0, 128 }, 129 expectedError: api.ErrEnactmentBlockHeightIsRequired, 130 }, { 131 name: "with same next and current public key", 132 params: api.AdminRotateKeyParams{ 133 Wallet: vgrand.RandomStr(5), 134 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 135 ToPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 136 ChainID: vgrand.RandomStr(5), 137 SubmissionBlockHeight: 10, 138 EnactmentBlockHeight: 15, 139 }, 140 expectedError: api.ErrNextAndCurrentPublicKeysCannotBeTheSame, 141 }, { 142 name: "with equal block height for enactment and submission", 143 params: api.AdminRotateKeyParams{ 144 Wallet: vgrand.RandomStr(5), 145 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 146 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 147 ChainID: vgrand.RandomStr(5), 148 SubmissionBlockHeight: 10, 149 EnactmentBlockHeight: 10, 150 }, 151 expectedError: api.ErrEnactmentBlockHeightMustBeGreaterThanSubmissionOne, 152 }, { 153 name: "with enactment block height lower than submission one", 154 params: api.AdminRotateKeyParams{ 155 Wallet: vgrand.RandomStr(5), 156 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 157 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 158 ChainID: vgrand.RandomStr(5), 159 SubmissionBlockHeight: 10, 160 EnactmentBlockHeight: 5, 161 }, 162 expectedError: api.ErrEnactmentBlockHeightMustBeGreaterThanSubmissionOne, 163 }, 164 } 165 166 for _, tc := range tcs { 167 t.Run(tc.name, func(tt *testing.T) { 168 // given 169 ctx := context.Background() 170 171 // setup 172 handler := newRotateKeyHandler(tt) 173 174 // when 175 result, errorDetails := handler.handle(t, ctx, tc.params) 176 177 // then 178 require.Empty(tt, result) 179 assertInvalidParams(tt, errorDetails, tc.expectedError) 180 }) 181 } 182 } 183 184 func testRotatingKeyWithValidParamsSucceeds(t *testing.T) { 185 // given 186 ctx := context.Background() 187 expectedWallet, firstKey := walletWithKey(t) 188 secondKey := generateKey(t, expectedWallet) 189 190 // setup 191 handler := newRotateKeyHandler(t) 192 // -- expected calls 193 handler.walletStore.EXPECT().WalletExists(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 194 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 195 handler.walletStore.EXPECT().GetWallet(ctx, expectedWallet.Name()).Times(1).Return(expectedWallet, nil) 196 197 // when 198 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 199 Wallet: expectedWallet.Name(), 200 FromPublicKey: firstKey.PublicKey(), 201 ToPublicKey: secondKey.PublicKey(), 202 ChainID: "test", 203 SubmissionBlockHeight: 10, 204 EnactmentBlockHeight: 15, 205 }) 206 207 // then 208 require.Nil(t, errorDetails) 209 assert.NotEmpty(t, result) 210 } 211 212 func testRotatingKeyFromWalletThatDoesNotExistsFails(t *testing.T) { 213 // given 214 ctx := context.Background() 215 name := vgrand.RandomStr(5) 216 217 // setup 218 handler := newRotateKeyHandler(t) 219 // -- expected calls 220 handler.walletStore.EXPECT().WalletExists(ctx, name).Times(1).Return(false, nil) 221 222 // when 223 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 224 Wallet: name, 225 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 226 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 227 ChainID: vgrand.RandomStr(5), 228 SubmissionBlockHeight: 10, 229 EnactmentBlockHeight: 15, 230 }) 231 232 // then 233 require.NotNil(t, errorDetails) 234 assert.Empty(t, result) 235 assertInvalidParams(t, errorDetails, api.ErrWalletDoesNotExist) 236 } 237 238 func testRotatingKeyGettingInternalErrorDuringWalletVerificationFails(t *testing.T) { 239 // given 240 ctx := context.Background() 241 name := vgrand.RandomStr(5) 242 243 // setup 244 handler := newRotateKeyHandler(t) 245 // -- expected calls 246 handler.walletStore.EXPECT().WalletExists(ctx, name).Times(1).Return(false, assert.AnError) 247 248 // when 249 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 250 Wallet: name, 251 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 252 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 253 ChainID: vgrand.RandomStr(5), 254 SubmissionBlockHeight: 10, 255 EnactmentBlockHeight: 15, 256 }) 257 258 // then 259 require.NotNil(t, errorDetails) 260 assert.Empty(t, result) 261 assertInternalError(t, errorDetails, fmt.Errorf("could not verify the wallet exists: %w", assert.AnError)) 262 } 263 264 func testRotatingKeyGettingInternalErrorDuringWalletRetrievalFails(t *testing.T) { 265 // given 266 ctx := context.Background() 267 name := vgrand.RandomStr(5) 268 269 // setup 270 handler := newRotateKeyHandler(t) 271 // -- expected calls 272 handler.walletStore.EXPECT().WalletExists(ctx, name).Times(1).Return(true, nil) 273 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, name).Times(1).Return(true, nil) 274 handler.walletStore.EXPECT().GetWallet(ctx, name).Times(1).Return(nil, assert.AnError) 275 276 // when 277 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 278 Wallet: name, 279 FromPublicKey: "b5fd9d3c4ad553cb3196303b6e6df7f484cf7f5331a572a45031239fd71ad8a0", 280 ToPublicKey: "988eae323a07f12363c17025c23ee58ea32ac3912398e16bb0b56969f57adc52", 281 ChainID: vgrand.RandomStr(5), 282 SubmissionBlockHeight: 10, 283 EnactmentBlockHeight: 15, 284 }) 285 286 // then 287 require.NotNil(t, errorDetails) 288 assert.Empty(t, result) 289 assertInternalError(t, errorDetails, fmt.Errorf("could not retrieve the wallet: %w", assert.AnError)) 290 } 291 292 func testRotatingKeyWithIsolatedWalletFails(t *testing.T) { 293 // given 294 ctx := context.Background() 295 w, firstKey := walletWithKey(t) 296 secondKey := generateKey(t, w) 297 isolatedWallet, err := w.IsolateWithKey(firstKey.PublicKey()) 298 if err != nil { 299 t.Fatalf("could not isolate key for test: %v", err) 300 } 301 302 // setup 303 handler := newRotateKeyHandler(t) 304 // -- expected calls 305 handler.walletStore.EXPECT().WalletExists(ctx, isolatedWallet.Name()).Times(1).Return(true, nil) 306 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, isolatedWallet.Name()).Times(1).Return(true, nil) 307 handler.walletStore.EXPECT().GetWallet(ctx, isolatedWallet.Name()).Times(1).Return(isolatedWallet, nil) 308 309 // when 310 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 311 Wallet: isolatedWallet.Name(), 312 FromPublicKey: firstKey.PublicKey(), 313 ToPublicKey: secondKey.PublicKey(), 314 ChainID: vgrand.RandomStr(5), 315 SubmissionBlockHeight: 10, 316 EnactmentBlockHeight: 15, 317 }) 318 319 // then 320 require.NotNil(t, errorDetails) 321 assert.Empty(t, result) 322 assertInvalidParams(t, errorDetails, api.ErrCannotRotateKeysOnIsolatedWallet) 323 } 324 325 func testRotatingKeyFromPublicKeyThatDoesNotExistsFails(t *testing.T) { 326 // given 327 ctx := context.Background() 328 expectedWallet, _ := walletWithKey(t) 329 secondKey := generateKey(t, expectedWallet) 330 331 // setup 332 handler := newRotateKeyHandler(t) 333 // -- expected calls 334 handler.walletStore.EXPECT().WalletExists(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 335 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 336 handler.walletStore.EXPECT().GetWallet(ctx, expectedWallet.Name()).Times(1).Return(expectedWallet, nil) 337 338 // when 339 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 340 Wallet: expectedWallet.Name(), 341 FromPublicKey: vgrand.RandomStr(5), 342 ToPublicKey: secondKey.PublicKey(), 343 ChainID: vgrand.RandomStr(5), 344 SubmissionBlockHeight: 10, 345 EnactmentBlockHeight: 15, 346 }) 347 348 // then 349 require.NotNil(t, errorDetails) 350 assert.Empty(t, result) 351 assertInvalidParams(t, errorDetails, api.ErrCurrentPublicKeyDoesNotExist) 352 } 353 354 func testRotatingKeyToPublicKeyThatDoesNotExistsFails(t *testing.T) { 355 // given 356 ctx := context.Background() 357 expectedWallet, firstKey := walletWithKey(t) 358 359 // setup 360 handler := newRotateKeyHandler(t) 361 // -- expected calls 362 handler.walletStore.EXPECT().WalletExists(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 363 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 364 handler.walletStore.EXPECT().GetWallet(ctx, expectedWallet.Name()).Times(1).Return(expectedWallet, nil) 365 366 // when 367 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 368 Wallet: expectedWallet.Name(), 369 FromPublicKey: firstKey.PublicKey(), 370 ToPublicKey: vgrand.RandomStr(5), 371 ChainID: vgrand.RandomStr(5), 372 SubmissionBlockHeight: 10, 373 EnactmentBlockHeight: 15, 374 }) 375 376 // then 377 require.NotNil(t, errorDetails) 378 assert.Empty(t, result) 379 assertInvalidParams(t, errorDetails, api.ErrNextPublicKeyDoesNotExist) 380 } 381 382 func testRotatingKeyToTaintedPublicKeyDoesNotExistsFails(t *testing.T) { 383 // given 384 ctx := context.Background() 385 expectedWallet, firstKey := walletWithKey(t) 386 secondKey := generateKey(t, expectedWallet) 387 if err := expectedWallet.TaintKey(secondKey.PublicKey()); err != nil { 388 t.Fatalf("could not taint the second key for test: %v", err) 389 } 390 391 // setup 392 handler := newRotateKeyHandler(t) 393 // -- expected calls 394 handler.walletStore.EXPECT().WalletExists(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 395 handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, expectedWallet.Name()).Times(1).Return(true, nil) 396 handler.walletStore.EXPECT().GetWallet(ctx, expectedWallet.Name()).Times(1).Return(expectedWallet, nil) 397 398 // when 399 result, errorDetails := handler.handle(t, ctx, api.AdminRotateKeyParams{ 400 Wallet: expectedWallet.Name(), 401 FromPublicKey: firstKey.PublicKey(), 402 ToPublicKey: secondKey.PublicKey(), 403 ChainID: vgrand.RandomStr(5), 404 SubmissionBlockHeight: 10, 405 EnactmentBlockHeight: 15, 406 }) 407 408 // then 409 require.NotNil(t, errorDetails) 410 assert.Empty(t, result) 411 assertInvalidParams(t, errorDetails, api.ErrNextPublicKeyIsTainted) 412 } 413 414 type rotateKeyHandler struct { 415 *api.AdminRotateKey 416 ctrl *gomock.Controller 417 walletStore *mocks.MockWalletStore 418 } 419 420 func (h *rotateKeyHandler) handle(t *testing.T, ctx context.Context, params jsonrpc.Params) (api.AdminRotateKeyResult, *jsonrpc.ErrorDetails) { 421 t.Helper() 422 423 rawResult, err := h.Handle(ctx, params) 424 if rawResult != nil { 425 result, ok := rawResult.(api.AdminRotateKeyResult) 426 if !ok { 427 t.Fatal("AdminRotateKey handler result is not a AdminRotateKeyResult") 428 } 429 return result, err 430 } 431 return api.AdminRotateKeyResult{}, err 432 } 433 434 func newRotateKeyHandler(t *testing.T) *rotateKeyHandler { 435 t.Helper() 436 437 ctrl := gomock.NewController(t) 438 walletStore := mocks.NewMockWalletStore(ctrl) 439 440 return &rotateKeyHandler{ 441 AdminRotateKey: api.NewAdminRotateKey(walletStore), 442 ctrl: ctrl, 443 walletStore: walletStore, 444 } 445 }