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  }