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  }