code.vegaprotocol.io/vega@v0.79.0/wallet/api/node/retrying_node_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 node_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    25  	apipb "code.vegaprotocol.io/vega/protos/vega/api/v1"
    26  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    27  	"code.vegaprotocol.io/vega/wallet/api/node"
    28  	"code.vegaprotocol.io/vega/wallet/api/node/mocks"
    29  	"code.vegaprotocol.io/vega/wallet/api/node/types"
    30  
    31  	"github.com/golang/mock/gomock"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  func TestRetryingNode_Statistics(t *testing.T) {
    37  	t.Run("Getting statistics is not retried", testRetryingNodeStatisticsNotRetried)
    38  	t.Run("Getting statistics succeeds", testRetryingNodeStatisticsSucceeds)
    39  }
    40  
    41  func testRetryingNodeStatisticsNotRetried(t *testing.T) {
    42  	// given
    43  	ctx := context.Background()
    44  	log := newTestLogger(t)
    45  	ttl := 5 * time.Second
    46  
    47  	// setup
    48  	adapter := newGRPCAdapterMock(t)
    49  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
    50  	adapter.EXPECT().Statistics(gomock.Any()).Times(1).Return(types.Statistics{}, assert.AnError)
    51  
    52  	// when
    53  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
    54  	response, err := retryingNode.Statistics(ctx)
    55  
    56  	// then
    57  	require.ErrorIs(t, err, assert.AnError)
    58  	assert.Empty(t, response)
    59  }
    60  
    61  func testRetryingNodeStatisticsSucceeds(t *testing.T) {
    62  	// given
    63  	ctx := context.Background()
    64  	log := newTestLogger(t)
    65  	ttl := 5 * time.Second
    66  
    67  	// setup
    68  	adapter := newGRPCAdapterMock(t)
    69  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
    70  	statistics := types.Statistics{
    71  		BlockHash:   vgrand.RandomStr(5),
    72  		BlockHeight: 123456,
    73  		ChainID:     vgrand.RandomStr(5),
    74  		VegaTime:    vgrand.RandomStr(5),
    75  	}
    76  	adapter.EXPECT().Statistics(gomock.Any()).Times(1).Return(statistics, nil)
    77  
    78  	// when
    79  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
    80  	response, err := retryingNode.Statistics(ctx)
    81  
    82  	// then
    83  	require.NoError(t, err)
    84  	assert.Equal(t, statistics, response)
    85  }
    86  
    87  func TestRetryingNode_LastBlock(t *testing.T) {
    88  	t.Run("Retrying with one successful call succeeds", testRetryingNodeLastBlockRetryingWithOneSuccessfulCallSucceeds)
    89  	t.Run("Retrying without successful calls fails", testRetryingNodeLastBlockRetryingWithoutSuccessfulCallsFails)
    90  }
    91  
    92  func testRetryingNodeLastBlockRetryingWithOneSuccessfulCallSucceeds(t *testing.T) {
    93  	// given
    94  	ctx := context.Background()
    95  	log := newTestLogger(t)
    96  	ttl := 5 * time.Second
    97  
    98  	// setup
    99  	expectedResponse := types.LastBlock{
   100  		BlockHeight:             123,
   101  		BlockHash:               vgrand.RandomStr(5),
   102  		ProofOfWorkHashFunction: vgrand.RandomStr(5),
   103  		ProofOfWorkDifficulty:   432,
   104  	}
   105  	adapter := newGRPCAdapterMock(t)
   106  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   107  	unsuccessfulCalls := adapter.EXPECT().LastBlock(gomock.Any()).Times(2).Return(types.LastBlock{}, assert.AnError)
   108  	successfulCall := adapter.EXPECT().LastBlock(gomock.Any()).Times(1).Return(expectedResponse, nil)
   109  	gomock.InOrder(unsuccessfulCalls, successfulCall)
   110  
   111  	// when
   112  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   113  	response, err := retryingNode.LastBlock(ctx)
   114  
   115  	// then
   116  	require.NoError(t, err)
   117  	assert.Equal(t, expectedResponse, response)
   118  }
   119  
   120  func testRetryingNodeLastBlockRetryingWithoutSuccessfulCallsFails(t *testing.T) {
   121  	// given
   122  	ctx := context.Background()
   123  	log := newTestLogger(t)
   124  	ttl := 5 * time.Second
   125  
   126  	// setup
   127  	adapter := newGRPCAdapterMock(t)
   128  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   129  	adapter.EXPECT().LastBlock(gomock.Any()).Times(4).Return(types.LastBlock{}, assert.AnError)
   130  
   131  	// when
   132  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   133  	nodeID, err := retryingNode.LastBlock(ctx)
   134  
   135  	// then
   136  	require.Error(t, err, assert.AnError)
   137  	assert.Empty(t, nodeID)
   138  }
   139  
   140  func TestRetryingNode_CheckTransaction(t *testing.T) {
   141  	t.Run("Retrying with one successful call succeeds", testRetryingNodeCheckTransactionRetryingWithOneSuccessfulCallSucceeds)
   142  	t.Run("Retrying with a successful call but unsuccessful transaction fails", testRetryingNodeCheckTransactionWithSuccessfulCallBuUnsuccessfulTxFails)
   143  	t.Run("Retrying without successful calls fails", testRetryingNodeCheckTransactionRetryingWithoutSuccessfulCallsFails)
   144  }
   145  
   146  func testRetryingNodeCheckTransactionRetryingWithOneSuccessfulCallSucceeds(t *testing.T) {
   147  	// given
   148  	ctx := context.Background()
   149  	log := newTestLogger(t)
   150  	tx := &commandspb.Transaction{
   151  		Version:   3,
   152  		InputData: []byte{},
   153  		Signature: &commandspb.Signature{
   154  			Value:   "345678",
   155  			Algo:    vgrand.RandomStr(5),
   156  			Version: 2,
   157  		},
   158  		From: &commandspb.Transaction_PubKey{
   159  			PubKey: vgrand.RandomStr(5),
   160  		},
   161  		Pow: &commandspb.ProofOfWork{
   162  			Tid:   vgrand.RandomStr(5),
   163  			Nonce: 23214,
   164  		},
   165  	}
   166  	expectedResponse := &apipb.CheckTransactionResponse{
   167  		Success: true,
   168  	}
   169  	ttl := 5 * time.Second
   170  
   171  	// setup
   172  	request := &apipb.CheckTransactionRequest{
   173  		Tx: tx,
   174  	}
   175  	adapter := newGRPCAdapterMock(t)
   176  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   177  	gomock.InOrder(
   178  		adapter.EXPECT().CheckTransaction(gomock.Any(), request).Times(2).Return(nil, assert.AnError),
   179  		adapter.EXPECT().CheckTransaction(gomock.Any(), request).Times(1).Return(expectedResponse, nil),
   180  	)
   181  
   182  	// when
   183  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   184  	err := retryingNode.CheckTransaction(ctx, tx)
   185  
   186  	// then
   187  	require.NoError(t, err)
   188  }
   189  
   190  func testRetryingNodeCheckTransactionWithSuccessfulCallBuUnsuccessfulTxFails(t *testing.T) {
   191  	// given
   192  	ctx := context.Background()
   193  	log := newTestLogger(t)
   194  	tx := &commandspb.Transaction{
   195  		Version:   3,
   196  		InputData: []byte{},
   197  		Signature: &commandspb.Signature{
   198  			Value:   "345678",
   199  			Algo:    vgrand.RandomStr(5),
   200  			Version: 2,
   201  		},
   202  		From: &commandspb.Transaction_PubKey{
   203  			PubKey: vgrand.RandomStr(5),
   204  		},
   205  		Pow: &commandspb.ProofOfWork{
   206  			Tid:   vgrand.RandomStr(5),
   207  			Nonce: 23214,
   208  		},
   209  	}
   210  	ttl := 5 * time.Second
   211  
   212  	// setup
   213  	request := &apipb.CheckTransactionRequest{
   214  		Tx: tx,
   215  	}
   216  	expectedResponse := &apipb.CheckTransactionResponse{
   217  		Success: false,
   218  		Code:    42,
   219  		Data:    vgrand.RandomStr(10),
   220  	}
   221  	adapter := newGRPCAdapterMock(t)
   222  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   223  	unsuccessfulCalls := adapter.EXPECT().CheckTransaction(gomock.Any(), request).Times(2).Return(nil, assert.AnError)
   224  	successfulCall := adapter.EXPECT().CheckTransaction(gomock.Any(), request).Times(1).Return(expectedResponse, nil)
   225  	gomock.InOrder(unsuccessfulCalls, successfulCall)
   226  
   227  	// when
   228  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   229  	err := retryingNode.CheckTransaction(ctx, tx)
   230  
   231  	// then
   232  	require.EqualError(t, err, fmt.Sprintf("%s (ABCI code %d)", expectedResponse.Data, expectedResponse.Code))
   233  }
   234  
   235  func testRetryingNodeCheckTransactionRetryingWithoutSuccessfulCallsFails(t *testing.T) {
   236  	// given
   237  	ctx := context.Background()
   238  	log := newTestLogger(t)
   239  	tx := &commandspb.Transaction{
   240  		Version:   3,
   241  		InputData: []byte{},
   242  		Signature: &commandspb.Signature{
   243  			Value:   "345678",
   244  			Algo:    vgrand.RandomStr(5),
   245  			Version: 2,
   246  		},
   247  		From: &commandspb.Transaction_PubKey{
   248  			PubKey: vgrand.RandomStr(5),
   249  		},
   250  		Pow: &commandspb.ProofOfWork{
   251  			Tid:   vgrand.RandomStr(5),
   252  			Nonce: 23214,
   253  		},
   254  	}
   255  	ttl := 5 * time.Second
   256  
   257  	// setup
   258  	adapter := newGRPCAdapterMock(t)
   259  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   260  	adapter.EXPECT().CheckTransaction(gomock.Any(), &apipb.CheckTransactionRequest{
   261  		Tx: tx,
   262  	}).Times(4).Return(nil, assert.AnError)
   263  
   264  	// when
   265  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   266  	err := retryingNode.CheckTransaction(ctx, tx)
   267  
   268  	// then
   269  	require.Error(t, err, assert.AnError)
   270  }
   271  
   272  func TestRetryingNode_SendTransaction(t *testing.T) {
   273  	t.Run("Retrying with one successful call succeeds", testRetryingNodeSendTransactionRetryingWithOneSuccessfulCallSucceeds)
   274  	t.Run("Retrying with a successful call but unsuccessful transaction fails", testRetryingNodeSendTransactionWithSuccessfulCallBuUnsuccessfulTxFails)
   275  	t.Run("Retrying without successful calls fails", testRetryingNodeSendTransactionRetryingWithoutSuccessfulCallsFails)
   276  }
   277  
   278  func testRetryingNodeSendTransactionRetryingWithOneSuccessfulCallSucceeds(t *testing.T) {
   279  	// given
   280  	ctx := context.Background()
   281  	log := newTestLogger(t)
   282  	expectedTxHash := vgrand.RandomStr(10)
   283  	tx := &commandspb.Transaction{
   284  		Version:   3,
   285  		InputData: []byte{},
   286  		Signature: &commandspb.Signature{
   287  			Value:   "345678",
   288  			Algo:    vgrand.RandomStr(5),
   289  			Version: 2,
   290  		},
   291  		From: &commandspb.Transaction_PubKey{
   292  			PubKey: vgrand.RandomStr(5),
   293  		},
   294  		Pow: &commandspb.ProofOfWork{
   295  			Tid:   vgrand.RandomStr(5),
   296  			Nonce: 23214,
   297  		},
   298  	}
   299  	ttl := 5 * time.Second
   300  
   301  	// setup
   302  	request := &apipb.SubmitTransactionRequest{
   303  		Tx:   tx,
   304  		Type: apipb.SubmitTransactionRequest_TYPE_SYNC,
   305  	}
   306  	expectedResponse := &apipb.SubmitTransactionResponse{
   307  		Success: true,
   308  		TxHash:  expectedTxHash,
   309  	}
   310  	adapter := newGRPCAdapterMock(t)
   311  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   312  	unsuccessfulCalls := adapter.EXPECT().SubmitTransaction(gomock.Any(), request).Times(2).Return(nil, assert.AnError)
   313  	successfulCall := adapter.EXPECT().SubmitTransaction(gomock.Any(), request).Times(1).Return(expectedResponse, nil)
   314  	gomock.InOrder(unsuccessfulCalls, successfulCall)
   315  
   316  	// when
   317  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   318  	response, err := retryingNode.SendTransaction(ctx, tx, apipb.SubmitTransactionRequest_TYPE_SYNC)
   319  
   320  	// then
   321  	require.NoError(t, err)
   322  	assert.Equal(t, expectedResponse.TxHash, response)
   323  }
   324  
   325  func testRetryingNodeSendTransactionWithSuccessfulCallBuUnsuccessfulTxFails(t *testing.T) {
   326  	// given
   327  	ctx := context.Background()
   328  	log := newTestLogger(t)
   329  	expectedTxHash := vgrand.RandomStr(10)
   330  	tx := &commandspb.Transaction{
   331  		Version:   3,
   332  		InputData: []byte{},
   333  		Signature: &commandspb.Signature{
   334  			Value:   "345678",
   335  			Algo:    vgrand.RandomStr(5),
   336  			Version: 2,
   337  		},
   338  		From: &commandspb.Transaction_PubKey{
   339  			PubKey: vgrand.RandomStr(5),
   340  		},
   341  		Pow: &commandspb.ProofOfWork{
   342  			Tid:   vgrand.RandomStr(5),
   343  			Nonce: 23214,
   344  		},
   345  	}
   346  	ttl := 5 * time.Second
   347  
   348  	// setup
   349  	request := &apipb.SubmitTransactionRequest{
   350  		Tx:   tx,
   351  		Type: apipb.SubmitTransactionRequest_TYPE_SYNC,
   352  	}
   353  	expectedResponse := &apipb.SubmitTransactionResponse{
   354  		Success: false,
   355  		TxHash:  expectedTxHash,
   356  		Code:    42,
   357  		Data:    vgrand.RandomStr(10),
   358  	}
   359  	adapter := newGRPCAdapterMock(t)
   360  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   361  	unsuccessfulCalls := adapter.EXPECT().SubmitTransaction(gomock.Any(), request).Times(2).Return(nil, assert.AnError)
   362  	successfulCall := adapter.EXPECT().SubmitTransaction(gomock.Any(), request).Times(1).Return(expectedResponse, nil)
   363  	gomock.InOrder(unsuccessfulCalls, successfulCall)
   364  
   365  	// when
   366  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   367  	response, err := retryingNode.SendTransaction(ctx, tx, apipb.SubmitTransactionRequest_TYPE_SYNC)
   368  
   369  	// then
   370  	require.EqualError(t, err, fmt.Sprintf("%s (ABCI code %d)", expectedResponse.Data, expectedResponse.Code))
   371  	assert.Empty(t, response)
   372  }
   373  
   374  func testRetryingNodeSendTransactionRetryingWithoutSuccessfulCallsFails(t *testing.T) {
   375  	// given
   376  	ctx := context.Background()
   377  	log := newTestLogger(t)
   378  	tx := &commandspb.Transaction{
   379  		Version:   3,
   380  		InputData: []byte{},
   381  		Signature: &commandspb.Signature{
   382  			Value:   "345678",
   383  			Algo:    vgrand.RandomStr(5),
   384  			Version: 2,
   385  		},
   386  		From: &commandspb.Transaction_PubKey{
   387  			PubKey: vgrand.RandomStr(5),
   388  		},
   389  		Pow: &commandspb.ProofOfWork{
   390  			Tid:   vgrand.RandomStr(5),
   391  			Nonce: 23214,
   392  		},
   393  	}
   394  	ttl := 5 * time.Second
   395  
   396  	// setup
   397  	adapter := newGRPCAdapterMock(t)
   398  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   399  	adapter.EXPECT().SubmitTransaction(gomock.Any(), &apipb.SubmitTransactionRequest{
   400  		Tx:   tx,
   401  		Type: apipb.SubmitTransactionRequest_TYPE_SYNC,
   402  	}).Times(4).Return(nil, assert.AnError)
   403  
   404  	// when
   405  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   406  	resp, err := retryingNode.SendTransaction(ctx, tx, apipb.SubmitTransactionRequest_TYPE_SYNC)
   407  
   408  	// then
   409  	require.Error(t, err, assert.AnError)
   410  	assert.Empty(t, resp)
   411  }
   412  
   413  func TestRetryingNode_Stop(t *testing.T) {
   414  	t.Run("Stopping the node closes the underlying adapter", testRetryingNodeStoppingNodeClosesUnderlyingAdapter)
   415  	t.Run("Stopping the node returns the underlying adapter error if any", testRetryingNodeStoppingNodeReturnUnderlyingErrorIfAny)
   416  }
   417  
   418  func testRetryingNodeStoppingNodeClosesUnderlyingAdapter(t *testing.T) {
   419  	// given
   420  	log := newTestLogger(t)
   421  	ttl := 5 * time.Second
   422  
   423  	// setup
   424  	adapter := newGRPCAdapterMock(t)
   425  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   426  	adapter.EXPECT().Stop().Times(1).Return(nil)
   427  
   428  	// when
   429  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   430  	err := retryingNode.Stop()
   431  
   432  	// then
   433  	require.NoError(t, err)
   434  }
   435  
   436  func testRetryingNodeStoppingNodeReturnUnderlyingErrorIfAny(t *testing.T) {
   437  	// given
   438  	log := newTestLogger(t)
   439  	ttl := 5 * time.Second
   440  
   441  	// setup
   442  	adapter := newGRPCAdapterMock(t)
   443  	adapter.EXPECT().Host().AnyTimes().Return("test-client")
   444  	adapter.EXPECT().Stop().Times(1).Return(assert.AnError)
   445  
   446  	// when
   447  	retryingNode := node.BuildRetryingNode(log, adapter, 3, ttl)
   448  	err := retryingNode.Stop()
   449  
   450  	// then
   451  	require.EqualError(t, err, fmt.Errorf("could not close properly stop the gRPC API client: %w", assert.AnError).Error())
   452  }
   453  
   454  func newGRPCAdapterMock(t *testing.T) *mocks.MockGRPCAdapter {
   455  	t.Helper()
   456  	ctrl := gomock.NewController(t)
   457  	grpcAdapter := mocks.NewMockGRPCAdapter(ctrl)
   458  	return grpcAdapter
   459  }