code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_send_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 TestAdminSendTransaction(t *testing.T) {
    39  	t.Run("Documentation matches the code", testAdminSendTransactionSchemaCorrect)
    40  	t.Run("Sending transaction with invalid params fails", testAdminSendingTransactionWithInvalidParamsFails)
    41  	t.Run("Sending transaction with valid params succeeds", testAdminSendingTransactionWithValidParamsSucceeds)
    42  	t.Run("Getting internal error during wallet verification fails", testAdminSendTransactionGettingInternalErrorDuringWalletVerificationFails)
    43  	t.Run("Sending transaction with wallet that doesn't exist fails", testAdminSendingTransactionWithWalletThatDoesntExistFails)
    44  	t.Run("Getting internal error during wallet retrieval fails", testAdminSendTransactionGettingInternalErrorDuringWalletRetrievalFails)
    45  	t.Run("Sending transaction with malformed transaction fails", testAdminSendingTransactionWithMalformedTransactionFails)
    46  	t.Run("Sending transaction which is invalid fails", testAdminSendingTransactionWithInvalidTransactionFails)
    47  }
    48  
    49  func testAdminSendTransactionSchemaCorrect(t *testing.T) {
    50  	assertEqualSchema(t, "admin.send_transaction", api.AdminSendTransactionParams{}, api.AdminSendTransactionResult{})
    51  }
    52  
    53  func testAdminSendingTransactionWithInvalidParamsFails(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.AdminSendTransactionParams{
    72  				Wallet:      "",
    73  				PublicKey:   vgrand.RandomStr(5),
    74  				Transaction: testTransaction(t),
    75  				Network:     vgrand.RandomStr(5),
    76  			},
    77  			expectedError: api.ErrWalletIsRequired,
    78  		},
    79  		{
    80  			name: "with empty public key",
    81  			params: api.AdminSendTransactionParams{
    82  				Wallet:      vgrand.RandomStr(5),
    83  				PublicKey:   "",
    84  				Transaction: testTransaction(t),
    85  				Network:     vgrand.RandomStr(5),
    86  			},
    87  			expectedError: api.ErrPublicKeyIsRequired,
    88  		},
    89  		{
    90  			name: "with empty transaction",
    91  			params: api.AdminSendTransactionParams{
    92  				Wallet:      vgrand.RandomStr(5),
    93  				PublicKey:   vgrand.RandomStr(5),
    94  				Transaction: "",
    95  				Network:     vgrand.RandomStr(5),
    96  			},
    97  			expectedError: api.ErrTransactionIsRequired,
    98  		},
    99  		{
   100  			name: "with no network or node address",
   101  			params: api.AdminSendTransactionParams{
   102  				Wallet:      vgrand.RandomStr(5),
   103  				PublicKey:   vgrand.RandomStr(5),
   104  				Network:     "",
   105  				Transaction: testTransaction(t),
   106  			},
   107  			expectedError: api.ErrNetworkOrNodeAddressIsRequired,
   108  		},
   109  		{
   110  			name: "with no network and node address",
   111  			params: api.AdminSendTransactionParams{
   112  				Wallet:      vgrand.RandomStr(5),
   113  				PublicKey:   vgrand.RandomStr(5),
   114  				Network:     "some_network",
   115  				NodeAddress: "some_node_address",
   116  				Transaction: testTransaction(t),
   117  			},
   118  			expectedError: api.ErrSpecifyingNetworkAndNodeAddressIsNotSupported,
   119  		},
   120  	}
   121  
   122  	for _, tc := range tcs {
   123  		t.Run(tc.name, func(tt *testing.T) {
   124  			// given
   125  			ctx := context.Background()
   126  
   127  			// setup
   128  			handler := newAdminSendTransactionHandler(tt, unexpectedNodeSelectorCall(tt))
   129  
   130  			// when
   131  			result, errorDetails := handler.handle(t, ctx, tc.params)
   132  
   133  			// then
   134  			assertInvalidParams(tt, errorDetails, tc.expectedError)
   135  			assert.Empty(tt, result)
   136  		})
   137  	}
   138  }
   139  
   140  func testAdminSendingTransactionWithValidParamsSucceeds(t *testing.T) {
   141  	// given
   142  	ctx := context.Background()
   143  	network := newNetwork(t)
   144  	nodeHost := vgrand.RandomStr(5)
   145  	w, kp := walletWithKey(t)
   146  	hash := "hashy mchashface"
   147  
   148  	// setup
   149  	handler := newAdminSendTransactionHandler(t, func(hosts []string, _ uint64, _ time.Duration) (walletnode.Selector, error) {
   150  		ctrl := gomock.NewController(t)
   151  		nodeSelector := nodemocks.NewMockSelector(ctrl)
   152  		node := nodemocks.NewMockNode(ctrl)
   153  		nodeSelector.EXPECT().Node(ctx, gomock.Any()).Times(1).Return(node, nil)
   154  		node.EXPECT().LastBlock(ctx).Times(1).Return(types.LastBlock{
   155  			BlockHeight:             150,
   156  			BlockHash:               vgrand.RandomStr(64),
   157  			ProofOfWorkHashFunction: vgcrypto.Sha3,
   158  			ProofOfWorkDifficulty:   1,
   159  			ChainID:                 vgrand.RandomStr(5),
   160  		}, nil)
   161  		node.EXPECT().SendTransaction(ctx, gomock.Any(), gomock.Any()).Times(1).Return(hash, nil)
   162  		node.EXPECT().Host().Times(1).Return(nodeHost)
   163  		return nodeSelector, nil
   164  	})
   165  
   166  	// -- expected calls
   167  	handler.walletStore.EXPECT().WalletExists(ctx, w.Name()).Times(1).Return(true, nil)
   168  	handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, w.Name()).Times(1).Return(true, nil)
   169  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   170  	handler.networkStore.EXPECT().NetworkExists(network.Name).Times(1).Return(true, nil)
   171  	handler.networkStore.EXPECT().GetNetwork(network.Name).Times(1).Return(&network, nil)
   172  
   173  	// when
   174  	result, errorDetails := handler.handle(t, ctx, api.AdminSendTransactionParams{
   175  		Wallet:      w.Name(),
   176  		PublicKey:   kp.PublicKey(),
   177  		Network:     network.Name,
   178  		Transaction: testTransaction(t),
   179  	})
   180  
   181  	// then
   182  	assert.Nil(t, errorDetails)
   183  	assert.Equal(t, hash, result.TransactionHash)
   184  	assert.NotEmpty(t, result.Transaction)
   185  }
   186  
   187  func testAdminSendTransactionGettingInternalErrorDuringWalletVerificationFails(t *testing.T) {
   188  	// given
   189  	ctx := context.Background()
   190  	network := newNetwork(t)
   191  	walletName := vgrand.RandomStr(5)
   192  
   193  	// setup
   194  	handler := newAdminSendTransactionHandler(t, func(hosts []string, _ uint64, _ time.Duration) (walletnode.Selector, error) {
   195  		ctrl := gomock.NewController(t)
   196  		nodeSelector := nodemocks.NewMockSelector(ctrl)
   197  		node := nodemocks.NewMockNode(ctrl)
   198  		nodeSelector.EXPECT().Node(ctx, gomock.Any()).Times(1).Return(node, nil)
   199  		node.EXPECT().LastBlock(ctx).Times(1).Return(types.LastBlock{
   200  			BlockHeight:             150,
   201  			BlockHash:               vgrand.RandomStr(64),
   202  			ProofOfWorkHashFunction: vgcrypto.Sha3,
   203  			ProofOfWorkDifficulty:   1,
   204  			ChainID:                 vgrand.RandomStr(5),
   205  		}, nil)
   206  		return nodeSelector, nil
   207  	})
   208  
   209  	// -- expected calls
   210  	handler.walletStore.EXPECT().WalletExists(ctx, walletName).Times(1).Return(false, assert.AnError)
   211  
   212  	// when
   213  	result, errorDetails := handler.handle(t, ctx, api.AdminSendTransactionParams{
   214  		Wallet:      walletName,
   215  		PublicKey:   vgrand.RandomStr(5),
   216  		Network:     network.Name,
   217  		Transaction: testTransaction(t),
   218  	})
   219  
   220  	// then
   221  	assertInternalError(t, errorDetails, fmt.Errorf("could not verify the wallet exists: %w", assert.AnError))
   222  	assert.Empty(t, result)
   223  }
   224  
   225  func testAdminSendingTransactionWithWalletThatDoesntExistFails(t *testing.T) {
   226  	// given
   227  	ctx := context.Background()
   228  
   229  	params := api.AdminSendTransactionParams{
   230  		Wallet:      vgrand.RandomStr(5),
   231  		PublicKey:   vgrand.RandomStr(5),
   232  		Network:     "fairground",
   233  		Transaction: testTransaction(t),
   234  	}
   235  
   236  	// setup
   237  	handler := newAdminSendTransactionHandler(t, unexpectedNodeSelectorCall(t))
   238  
   239  	// -- expected calls
   240  	handler.walletStore.EXPECT().WalletExists(ctx, params.Wallet).Times(1).Return(false, nil)
   241  
   242  	// when
   243  	result, errorDetails := handler.handle(t, ctx, params)
   244  
   245  	// then
   246  	assertInvalidParams(t, errorDetails, api.ErrWalletDoesNotExist)
   247  	assert.Empty(t, result)
   248  }
   249  
   250  func testAdminSendTransactionGettingInternalErrorDuringWalletRetrievalFails(t *testing.T) {
   251  	// given
   252  	ctx := context.Background()
   253  	network := newNetwork(t)
   254  	walletName := vgrand.RandomStr(5)
   255  
   256  	// setup
   257  	handler := newAdminSendTransactionHandler(t, func(hosts []string, _ uint64, _ time.Duration) (walletnode.Selector, error) {
   258  		ctrl := gomock.NewController(t)
   259  		nodeSelector := nodemocks.NewMockSelector(ctrl)
   260  		node := nodemocks.NewMockNode(ctrl)
   261  		nodeSelector.EXPECT().Node(ctx, gomock.Any()).Times(1).Return(node, nil)
   262  		node.EXPECT().LastBlock(ctx).Times(1).Return(types.LastBlock{
   263  			BlockHeight:             150,
   264  			BlockHash:               vgrand.RandomStr(64),
   265  			ProofOfWorkHashFunction: vgcrypto.Sha3,
   266  			ProofOfWorkDifficulty:   1,
   267  			ChainID:                 vgrand.RandomStr(5),
   268  		}, nil)
   269  		return nodeSelector, nil
   270  	})
   271  
   272  	// -- expected calls
   273  	handler.walletStore.EXPECT().WalletExists(ctx, walletName).Times(1).Return(true, nil)
   274  	handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, walletName).Times(1).Return(true, nil)
   275  	handler.walletStore.EXPECT().GetWallet(ctx, walletName).Times(1).Return(nil, assert.AnError)
   276  
   277  	// when
   278  	result, errorDetails := handler.handle(t, ctx, api.AdminSendTransactionParams{
   279  		Wallet:      walletName,
   280  		PublicKey:   vgrand.RandomStr(5),
   281  		Network:     network.Name,
   282  		Transaction: testTransaction(t),
   283  	})
   284  
   285  	// then
   286  	assertInternalError(t, errorDetails, fmt.Errorf("could not retrieve the wallet: %w", assert.AnError))
   287  	assert.Empty(t, result)
   288  }
   289  
   290  func testAdminSendingTransactionWithMalformedTransactionFails(t *testing.T) {
   291  	// given
   292  	ctx := context.Background()
   293  	network := vgrand.RandomStr(5)
   294  	w, kp := walletWithKey(t)
   295  
   296  	// setup
   297  	handler := newAdminSendTransactionHandler(t, unexpectedNodeSelectorCall(t))
   298  
   299  	// -- expected calls
   300  	handler.walletStore.EXPECT().WalletExists(ctx, w.Name()).Times(1).Return(true, nil)
   301  	handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, w.Name()).Times(1).Return(true, nil)
   302  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   303  
   304  	// when
   305  	result, errorDetails := handler.handle(t, ctx, api.AdminSendTransactionParams{
   306  		Wallet:      w.Name(),
   307  		PublicKey:   kp.PublicKey(),
   308  		Network:     network,
   309  		Transaction: map[string]int{"bob": 5},
   310  	})
   311  
   312  	// then
   313  	assertInvalidParams(t, errorDetails, errors.New("the transaction does not use a valid Vega command: unknown field \"bob\" in vega.wallet.v1.SubmitTransactionRequest"))
   314  	assert.Empty(t, result)
   315  }
   316  
   317  func testAdminSendingTransactionWithInvalidTransactionFails(t *testing.T) {
   318  	// given
   319  	ctx := context.Background()
   320  	network := newNetwork(t)
   321  	w, kp := walletWithKey(t)
   322  
   323  	// setup
   324  	handler := newAdminSendTransactionHandler(t, unexpectedNodeSelectorCall(t))
   325  
   326  	// -- expected calls
   327  	handler.walletStore.EXPECT().WalletExists(ctx, w.Name()).Times(1).Return(true, nil)
   328  	handler.walletStore.EXPECT().IsWalletAlreadyUnlocked(ctx, w.Name()).Times(1).Return(true, nil)
   329  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   330  
   331  	// when
   332  	result, errorDetails := handler.handle(t, ctx, api.AdminSendTransactionParams{
   333  		Wallet:      w.Name(),
   334  		PublicKey:   kp.PublicKey(),
   335  		Network:     network.Name,
   336  		Transaction: testMalformedTransaction(t),
   337  	})
   338  
   339  	// then
   340  	assertInvalidParams(t, errorDetails, fmt.Errorf("vote_submission.proposal_id (should be a valid Vega ID)"))
   341  	assert.Empty(t, result)
   342  }
   343  
   344  type AdminSendTransactionHandler struct {
   345  	*api.AdminSendTransaction
   346  	ctrl         *gomock.Controller
   347  	walletStore  *mocks.MockWalletStore
   348  	networkStore *mocks.MockNetworkStore
   349  }
   350  
   351  func (h *AdminSendTransactionHandler) handle(t *testing.T, ctx context.Context, params jsonrpc.Params) (api.AdminSendTransactionResult, *jsonrpc.ErrorDetails) {
   352  	t.Helper()
   353  
   354  	rawResult, err := h.Handle(ctx, params)
   355  	if rawResult != nil {
   356  		result, ok := rawResult.(api.AdminSendTransactionResult)
   357  		if !ok {
   358  			t.Fatal("AdminUpdatePermissions handler result is not a AdminSendTransactionResult")
   359  		}
   360  		return result, err
   361  	}
   362  	return api.AdminSendTransactionResult{}, err
   363  }
   364  
   365  func newAdminSendTransactionHandler(t *testing.T, builder api.NodeSelectorBuilder) *AdminSendTransactionHandler {
   366  	t.Helper()
   367  
   368  	ctrl := gomock.NewController(t)
   369  	walletStore := mocks.NewMockWalletStore(ctrl)
   370  	networkStore := mocks.NewMockNetworkStore(ctrl)
   371  
   372  	return &AdminSendTransactionHandler{
   373  		AdminSendTransaction: api.NewAdminSendTransaction(walletStore, networkStore, builder),
   374  		ctrl:                 ctrl,
   375  		walletStore:          walletStore,
   376  		networkStore:         networkStore,
   377  	}
   378  }