github.com/MetalBlockchain/metalgo@v1.11.9/x/sync/client_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package sync
     5  
     6  import (
     7  	"context"
     8  	"math/rand"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"github.com/stretchr/testify/require"
    14  	"go.uber.org/mock/gomock"
    15  	"google.golang.org/protobuf/proto"
    16  
    17  	"github.com/MetalBlockchain/metalgo/database"
    18  	"github.com/MetalBlockchain/metalgo/database/memdb"
    19  	"github.com/MetalBlockchain/metalgo/ids"
    20  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    21  	"github.com/MetalBlockchain/metalgo/trace"
    22  	"github.com/MetalBlockchain/metalgo/utils/logging"
    23  	"github.com/MetalBlockchain/metalgo/utils/maybe"
    24  	"github.com/MetalBlockchain/metalgo/x/merkledb"
    25  
    26  	pb "github.com/MetalBlockchain/metalgo/proto/pb/sync"
    27  )
    28  
    29  func newDefaultDBConfig() merkledb.Config {
    30  	return merkledb.Config{
    31  		IntermediateWriteBatchSize:  100,
    32  		HistoryLength:               defaultRequestKeyLimit,
    33  		ValueNodeCacheSize:          defaultRequestKeyLimit,
    34  		IntermediateWriteBufferSize: defaultRequestKeyLimit,
    35  		IntermediateNodeCacheSize:   defaultRequestKeyLimit,
    36  		Reg:                         prometheus.NewRegistry(),
    37  		Tracer:                      trace.Noop,
    38  		BranchFactor:                merkledb.BranchFactor16,
    39  	}
    40  }
    41  
    42  // Create a client and send a range proof request to a server
    43  // whose underlying database is [serverDB].
    44  // The server's response is modified with [modifyResponse] before
    45  // being returned to the server.
    46  // The client makes at most [maxAttempts] attempts to fulfill
    47  // the request before returning an error.
    48  func sendRangeProofRequest(
    49  	t *testing.T,
    50  	serverDB DB,
    51  	request *pb.SyncGetRangeProofRequest,
    52  	maxAttempts int,
    53  	modifyResponse func(*merkledb.RangeProof),
    54  ) (*merkledb.RangeProof, error) {
    55  	t.Helper()
    56  
    57  	require := require.New(t)
    58  	ctrl := gomock.NewController(t)
    59  
    60  	var (
    61  		// Number of calls from the client to the server so far.
    62  		numAttempts int
    63  
    64  		// Sends messages from server to client.
    65  		sender = common.NewMockSender(ctrl)
    66  
    67  		// Serves the range proof.
    68  		server = NewNetworkServer(sender, serverDB, logging.NoLog{})
    69  
    70  		clientNodeID, serverNodeID = ids.GenerateTestNodeID(), ids.GenerateTestNodeID()
    71  
    72  		// "Sends" the request from the client to the server and
    73  		// "receives" the response from the server. In reality,
    74  		// it just invokes the server's method and receives
    75  		// the response on [serverResponseChan].
    76  		networkClient = NewMockNetworkClient(ctrl)
    77  
    78  		serverResponseChan = make(chan []byte, 1)
    79  
    80  		// The context used in client.GetRangeProof.
    81  		// Canceled after the first response is received because
    82  		// the client will keep sending requests until its context
    83  		// expires or it succeeds.
    84  		ctx, cancel = context.WithCancel(context.Background())
    85  	)
    86  
    87  	defer cancel()
    88  
    89  	// The client fetching a range proof.
    90  	client, err := NewClient(&ClientConfig{
    91  		NetworkClient: networkClient,
    92  		Metrics:       &mockMetrics{},
    93  		Log:           logging.NoLog{},
    94  		BranchFactor:  merkledb.BranchFactor16,
    95  	})
    96  	require.NoError(err)
    97  
    98  	networkClient.EXPECT().RequestAny(
    99  		gomock.Any(), // ctx
   100  		gomock.Any(), // request
   101  	).DoAndReturn(
   102  		func(_ context.Context, request []byte) (ids.NodeID, []byte, error) {
   103  			go func() {
   104  				// Get response from server
   105  				require.NoError(server.AppRequest(context.Background(), clientNodeID, 0, time.Now().Add(time.Hour), request))
   106  			}()
   107  
   108  			// Wait for response from server
   109  			serverResponse := <-serverResponseChan
   110  
   111  			numAttempts++
   112  
   113  			if numAttempts >= maxAttempts {
   114  				defer cancel()
   115  			}
   116  
   117  			return serverNodeID, serverResponse, nil
   118  		},
   119  	).AnyTimes()
   120  
   121  	// The server should expect to "send" a response to the client.
   122  	sender.EXPECT().SendAppResponse(
   123  		gomock.Any(), // ctx
   124  		clientNodeID,
   125  		gomock.Any(), // requestID
   126  		gomock.Any(), // responseBytes
   127  	).DoAndReturn(
   128  		func(_ context.Context, _ ids.NodeID, _ uint32, responseBytes []byte) error {
   129  			// deserialize the response so we can modify it if needed.
   130  			var responseProto pb.RangeProof
   131  			require.NoError(proto.Unmarshal(responseBytes, &responseProto))
   132  
   133  			var response merkledb.RangeProof
   134  			require.NoError(response.UnmarshalProto(&responseProto))
   135  
   136  			// modify if needed
   137  			if modifyResponse != nil {
   138  				modifyResponse(&response)
   139  			}
   140  
   141  			// reserialize the response and pass it to the client to complete the handling.
   142  			responseBytes, err := proto.Marshal(response.ToProto())
   143  			require.NoError(err)
   144  
   145  			serverResponseChan <- responseBytes
   146  
   147  			return nil
   148  		},
   149  	).AnyTimes()
   150  
   151  	return client.GetRangeProof(ctx, request)
   152  }
   153  
   154  func TestGetRangeProof(t *testing.T) {
   155  	now := time.Now().UnixNano()
   156  	t.Logf("seed: %d", now)
   157  	r := rand.New(rand.NewSource(now)) // #nosec G404
   158  
   159  	smallTrieKeyCount := defaultRequestKeyLimit
   160  	smallTrieDB, _, err := generateTrieWithMinKeyLen(t, r, smallTrieKeyCount, 1)
   161  	require.NoError(t, err)
   162  	smallTrieRoot, err := smallTrieDB.GetMerkleRoot(context.Background())
   163  	require.NoError(t, err)
   164  
   165  	largeTrieKeyCount := 3 * defaultRequestKeyLimit
   166  	largeTrieDB, largeTrieKeys, err := generateTrieWithMinKeyLen(t, r, largeTrieKeyCount, 1)
   167  	require.NoError(t, err)
   168  	largeTrieRoot, err := largeTrieDB.GetMerkleRoot(context.Background())
   169  	require.NoError(t, err)
   170  
   171  	tests := map[string]struct {
   172  		db                  DB
   173  		request             *pb.SyncGetRangeProofRequest
   174  		modifyResponse      func(*merkledb.RangeProof)
   175  		expectedErr         error
   176  		expectedResponseLen int
   177  	}{
   178  		"proof restricted by BytesLimit": {
   179  			db: smallTrieDB,
   180  			request: &pb.SyncGetRangeProofRequest{
   181  				RootHash:   smallTrieRoot[:],
   182  				KeyLimit:   defaultRequestKeyLimit,
   183  				BytesLimit: 10000,
   184  			},
   185  		},
   186  		"full response for small (single request) trie": {
   187  			db: smallTrieDB,
   188  			request: &pb.SyncGetRangeProofRequest{
   189  				RootHash:   smallTrieRoot[:],
   190  				KeyLimit:   defaultRequestKeyLimit,
   191  				BytesLimit: defaultRequestByteSizeLimit,
   192  			},
   193  			expectedResponseLen: defaultRequestKeyLimit,
   194  		},
   195  		"too many leaves in response": {
   196  			db: smallTrieDB,
   197  			request: &pb.SyncGetRangeProofRequest{
   198  				RootHash:   smallTrieRoot[:],
   199  				KeyLimit:   defaultRequestKeyLimit,
   200  				BytesLimit: defaultRequestByteSizeLimit,
   201  			},
   202  			modifyResponse: func(response *merkledb.RangeProof) {
   203  				response.KeyValues = append(response.KeyValues, merkledb.KeyValue{})
   204  			},
   205  			expectedErr: errTooManyKeys,
   206  		},
   207  		"partial response to request for entire trie (full leaf limit)": {
   208  			db: largeTrieDB,
   209  			request: &pb.SyncGetRangeProofRequest{
   210  				RootHash:   largeTrieRoot[:],
   211  				KeyLimit:   defaultRequestKeyLimit,
   212  				BytesLimit: defaultRequestByteSizeLimit,
   213  			},
   214  			expectedResponseLen: defaultRequestKeyLimit,
   215  		},
   216  		"full response from near end of trie to end of trie (less than leaf limit)": {
   217  			db: largeTrieDB,
   218  			request: &pb.SyncGetRangeProofRequest{
   219  				RootHash: largeTrieRoot[:],
   220  				StartKey: &pb.MaybeBytes{
   221  					Value:     largeTrieKeys[len(largeTrieKeys)-30], // Set start 30 keys from the end of the large trie
   222  					IsNothing: false,
   223  				},
   224  				KeyLimit:   defaultRequestKeyLimit,
   225  				BytesLimit: defaultRequestByteSizeLimit,
   226  			},
   227  			expectedResponseLen: 30,
   228  		},
   229  		"full response for intermediate range of trie (less than leaf limit)": {
   230  			db: largeTrieDB,
   231  			request: &pb.SyncGetRangeProofRequest{
   232  				RootHash: largeTrieRoot[:],
   233  				StartKey: &pb.MaybeBytes{
   234  					Value:     largeTrieKeys[1000], // Set the range for 1000 leafs in an intermediate range of the trie
   235  					IsNothing: false,
   236  				},
   237  				EndKey:     &pb.MaybeBytes{Value: largeTrieKeys[1099]}, // (inclusive range)
   238  				KeyLimit:   defaultRequestKeyLimit,
   239  				BytesLimit: defaultRequestByteSizeLimit,
   240  			},
   241  			expectedResponseLen: 100,
   242  		},
   243  		"removed first key in response": {
   244  			db: largeTrieDB,
   245  			request: &pb.SyncGetRangeProofRequest{
   246  				RootHash:   largeTrieRoot[:],
   247  				KeyLimit:   defaultRequestKeyLimit,
   248  				BytesLimit: defaultRequestByteSizeLimit,
   249  			},
   250  			modifyResponse: func(response *merkledb.RangeProof) {
   251  				response.KeyValues = response.KeyValues[1:]
   252  			},
   253  			expectedErr: merkledb.ErrInvalidProof,
   254  		},
   255  		"removed first key in response and replaced proof": {
   256  			db: largeTrieDB,
   257  			request: &pb.SyncGetRangeProofRequest{
   258  				RootHash:   largeTrieRoot[:],
   259  				KeyLimit:   defaultRequestKeyLimit,
   260  				BytesLimit: defaultRequestByteSizeLimit,
   261  			},
   262  			modifyResponse: func(response *merkledb.RangeProof) {
   263  				start := maybe.Some(response.KeyValues[1].Key)
   264  				rootID, err := largeTrieDB.GetMerkleRoot(context.Background())
   265  				require.NoError(t, err)
   266  				proof, err := largeTrieDB.GetRangeProofAtRoot(context.Background(), rootID, start, maybe.Nothing[[]byte](), defaultRequestKeyLimit)
   267  				require.NoError(t, err)
   268  				response.KeyValues = proof.KeyValues
   269  				response.StartProof = proof.StartProof
   270  				response.EndProof = proof.EndProof
   271  			},
   272  			expectedErr: errInvalidRangeProof,
   273  		},
   274  		"removed key from middle of response": {
   275  			db: largeTrieDB,
   276  			request: &pb.SyncGetRangeProofRequest{
   277  				RootHash:   largeTrieRoot[:],
   278  				KeyLimit:   defaultRequestKeyLimit,
   279  				BytesLimit: defaultRequestByteSizeLimit,
   280  			},
   281  			modifyResponse: func(response *merkledb.RangeProof) {
   282  				response.KeyValues = append(response.KeyValues[:100], response.KeyValues[101:]...)
   283  			},
   284  			expectedErr: merkledb.ErrInvalidProof,
   285  		},
   286  		"start and end proof nodes removed": {
   287  			db: largeTrieDB,
   288  			request: &pb.SyncGetRangeProofRequest{
   289  				RootHash:   largeTrieRoot[:],
   290  				KeyLimit:   defaultRequestKeyLimit,
   291  				BytesLimit: defaultRequestByteSizeLimit,
   292  			},
   293  			modifyResponse: func(response *merkledb.RangeProof) {
   294  				response.StartProof = nil
   295  				response.EndProof = nil
   296  			},
   297  			expectedErr: merkledb.ErrNoEndProof,
   298  		},
   299  		"end proof removed": {
   300  			db: largeTrieDB,
   301  			request: &pb.SyncGetRangeProofRequest{
   302  				RootHash:   largeTrieRoot[:],
   303  				KeyLimit:   defaultRequestKeyLimit,
   304  				BytesLimit: defaultRequestByteSizeLimit,
   305  			},
   306  			modifyResponse: func(response *merkledb.RangeProof) {
   307  				response.EndProof = nil
   308  			},
   309  			expectedErr: merkledb.ErrNoEndProof,
   310  		},
   311  		"empty proof": {
   312  			db: largeTrieDB,
   313  			request: &pb.SyncGetRangeProofRequest{
   314  				RootHash:   largeTrieRoot[:],
   315  				KeyLimit:   defaultRequestKeyLimit,
   316  				BytesLimit: defaultRequestByteSizeLimit,
   317  			},
   318  			modifyResponse: func(response *merkledb.RangeProof) {
   319  				response.StartProof = nil
   320  				response.EndProof = nil
   321  				response.KeyValues = nil
   322  			},
   323  			expectedErr: merkledb.ErrEmptyProof,
   324  		},
   325  	}
   326  
   327  	for name, test := range tests {
   328  		t.Run(name, func(t *testing.T) {
   329  			require := require.New(t)
   330  			proof, err := sendRangeProofRequest(t, test.db, test.request, 1, test.modifyResponse)
   331  			require.ErrorIs(err, test.expectedErr)
   332  			if test.expectedErr != nil {
   333  				return
   334  			}
   335  			if test.expectedResponseLen > 0 {
   336  				require.Len(proof.KeyValues, test.expectedResponseLen)
   337  			}
   338  			bytes, err := proto.Marshal(proof.ToProto())
   339  			require.NoError(err)
   340  			require.Less(len(bytes), int(test.request.BytesLimit))
   341  		})
   342  	}
   343  }
   344  
   345  func sendChangeProofRequest(
   346  	t *testing.T,
   347  	serverDB DB,
   348  	clientDB DB,
   349  	request *pb.SyncGetChangeProofRequest,
   350  	maxAttempts int,
   351  	modifyChangeProof func(*merkledb.ChangeProof),
   352  	modifyRangeProof func(*merkledb.RangeProof),
   353  ) (*merkledb.ChangeOrRangeProof, error) {
   354  	t.Helper()
   355  
   356  	require := require.New(t)
   357  	ctrl := gomock.NewController(t)
   358  
   359  	var (
   360  		// Number of calls from the client to the server so far.
   361  		numAttempts int
   362  
   363  		// Sends messages from server to client.
   364  		sender = common.NewMockSender(ctrl)
   365  
   366  		// Serves the change proof.
   367  		server = NewNetworkServer(sender, serverDB, logging.NoLog{})
   368  
   369  		clientNodeID, serverNodeID = ids.GenerateTestNodeID(), ids.GenerateTestNodeID()
   370  
   371  		// "Sends" the request from the client to the server and
   372  		// "receives" the response from the server. In reality,
   373  		// it just invokes the server's method and receives
   374  		// the response on [serverResponseChan].
   375  		networkClient = NewMockNetworkClient(ctrl)
   376  
   377  		serverResponseChan = make(chan []byte, 1)
   378  
   379  		// The context used in client.GetChangeProof.
   380  		// Canceled after the first response is received because
   381  		// the client will keep sending requests until its context
   382  		// expires or it succeeds.
   383  		ctx, cancel = context.WithCancel(context.Background())
   384  	)
   385  
   386  	// The client fetching a change proof.
   387  	client, err := NewClient(&ClientConfig{
   388  		NetworkClient: networkClient,
   389  		Metrics:       &mockMetrics{},
   390  		Log:           logging.NoLog{},
   391  		BranchFactor:  merkledb.BranchFactor16,
   392  	})
   393  	require.NoError(err)
   394  
   395  	defer cancel() // avoid leaking a goroutine
   396  
   397  	networkClient.EXPECT().RequestAny(
   398  		gomock.Any(), // ctx
   399  		gomock.Any(), // request
   400  	).DoAndReturn(
   401  		func(_ context.Context, request []byte) (ids.NodeID, []byte, error) {
   402  			go func() {
   403  				// Get response from server
   404  				require.NoError(server.AppRequest(context.Background(), clientNodeID, 0, time.Now().Add(time.Hour), request))
   405  			}()
   406  
   407  			// Wait for response from server
   408  			serverResponse := <-serverResponseChan
   409  
   410  			numAttempts++
   411  
   412  			if numAttempts >= maxAttempts {
   413  				defer cancel()
   414  			}
   415  
   416  			return serverNodeID, serverResponse, nil
   417  		},
   418  	).AnyTimes()
   419  
   420  	// Expect server (serverDB) to send app response to client (clientDB)
   421  	sender.EXPECT().SendAppResponse(
   422  		gomock.Any(), // ctx
   423  		clientNodeID,
   424  		gomock.Any(), // requestID
   425  		gomock.Any(), // responseBytes
   426  	).DoAndReturn(
   427  		func(_ context.Context, _ ids.NodeID, _ uint32, responseBytes []byte) error {
   428  			// deserialize the response so we can modify it if needed.
   429  			var responseProto pb.SyncGetChangeProofResponse
   430  			require.NoError(proto.Unmarshal(responseBytes, &responseProto))
   431  
   432  			if responseProto.GetChangeProof() != nil {
   433  				// Server responded with a change proof
   434  				var changeProof merkledb.ChangeProof
   435  				require.NoError(changeProof.UnmarshalProto(responseProto.GetChangeProof()))
   436  
   437  				// modify if needed
   438  				if modifyChangeProof != nil {
   439  					modifyChangeProof(&changeProof)
   440  				}
   441  
   442  				// reserialize the response and pass it to the client to complete the handling.
   443  				responseBytes, err := proto.Marshal(&pb.SyncGetChangeProofResponse{
   444  					Response: &pb.SyncGetChangeProofResponse_ChangeProof{
   445  						ChangeProof: changeProof.ToProto(),
   446  					},
   447  				})
   448  				require.NoError(err)
   449  
   450  				serverResponseChan <- responseBytes
   451  
   452  				return nil
   453  			}
   454  
   455  			// Server responded with a range proof
   456  			var rangeProof merkledb.RangeProof
   457  			require.NoError(rangeProof.UnmarshalProto(responseProto.GetRangeProof()))
   458  
   459  			// modify if needed
   460  			if modifyRangeProof != nil {
   461  				modifyRangeProof(&rangeProof)
   462  			}
   463  
   464  			// reserialize the response and pass it to the client to complete the handling.
   465  			responseBytes, err := proto.Marshal(&pb.SyncGetChangeProofResponse{
   466  				Response: &pb.SyncGetChangeProofResponse_RangeProof{
   467  					RangeProof: rangeProof.ToProto(),
   468  				},
   469  			})
   470  			require.NoError(err)
   471  
   472  			serverResponseChan <- responseBytes
   473  
   474  			return nil
   475  		},
   476  	).AnyTimes()
   477  
   478  	return client.GetChangeProof(ctx, request, clientDB)
   479  }
   480  
   481  func TestGetChangeProof(t *testing.T) {
   482  	now := time.Now().UnixNano()
   483  	t.Logf("seed: %d", now)
   484  	r := rand.New(rand.NewSource(now)) // #nosec G404
   485  
   486  	serverDB, err := merkledb.New(
   487  		context.Background(),
   488  		memdb.New(),
   489  		newDefaultDBConfig(),
   490  	)
   491  	require.NoError(t, err)
   492  
   493  	clientDB, err := merkledb.New(
   494  		context.Background(),
   495  		memdb.New(),
   496  		newDefaultDBConfig(),
   497  	)
   498  	require.NoError(t, err)
   499  	startRoot, err := serverDB.GetMerkleRoot(context.Background())
   500  	require.NoError(t, err)
   501  
   502  	// create changes
   503  	for x := 0; x < defaultRequestKeyLimit/2; x++ {
   504  		ops := make([]database.BatchOp, 0, 11)
   505  		// add some key/values
   506  		for i := 0; i < 10; i++ {
   507  			key := make([]byte, r.Intn(100))
   508  			_, err = r.Read(key)
   509  			require.NoError(t, err)
   510  
   511  			val := make([]byte, r.Intn(100))
   512  			_, err = r.Read(val)
   513  			require.NoError(t, err)
   514  
   515  			ops = append(ops, database.BatchOp{Key: key, Value: val})
   516  		}
   517  
   518  		// delete a key
   519  		deleteKeyStart := make([]byte, r.Intn(10))
   520  		_, err = r.Read(deleteKeyStart)
   521  		require.NoError(t, err)
   522  
   523  		it := serverDB.NewIteratorWithStart(deleteKeyStart)
   524  		if it.Next() {
   525  			ops = append(ops, database.BatchOp{Key: it.Key(), Delete: true})
   526  		}
   527  		require.NoError(t, it.Error())
   528  		it.Release()
   529  
   530  		view, err := serverDB.NewView(
   531  			context.Background(),
   532  			merkledb.ViewChanges{BatchOps: ops},
   533  		)
   534  		require.NoError(t, err)
   535  		require.NoError(t, view.CommitToDB(context.Background()))
   536  	}
   537  
   538  	endRoot, err := serverDB.GetMerkleRoot(context.Background())
   539  	require.NoError(t, err)
   540  
   541  	fakeRootID := ids.GenerateTestID()
   542  
   543  	tests := map[string]struct {
   544  		db                        DB
   545  		request                   *pb.SyncGetChangeProofRequest
   546  		modifyChangeProofResponse func(*merkledb.ChangeProof)
   547  		modifyRangeProofResponse  func(*merkledb.RangeProof)
   548  		expectedErr               error
   549  		expectedResponseLen       int
   550  		expectRangeProof          bool // Otherwise expect change proof
   551  	}{
   552  		"proof restricted by BytesLimit": {
   553  			request: &pb.SyncGetChangeProofRequest{
   554  				StartRootHash: startRoot[:],
   555  				EndRootHash:   endRoot[:],
   556  				KeyLimit:      defaultRequestKeyLimit,
   557  				BytesLimit:    10000,
   558  			},
   559  		},
   560  		"full response for small (single request) trie": {
   561  			request: &pb.SyncGetChangeProofRequest{
   562  				StartRootHash: startRoot[:],
   563  				EndRootHash:   endRoot[:],
   564  				KeyLimit:      defaultRequestKeyLimit,
   565  				BytesLimit:    defaultRequestByteSizeLimit,
   566  			},
   567  			expectedResponseLen: defaultRequestKeyLimit,
   568  		},
   569  		"too many keys in response": {
   570  			request: &pb.SyncGetChangeProofRequest{
   571  				StartRootHash: startRoot[:],
   572  				EndRootHash:   endRoot[:],
   573  				KeyLimit:      defaultRequestKeyLimit,
   574  				BytesLimit:    defaultRequestByteSizeLimit,
   575  			},
   576  			modifyChangeProofResponse: func(response *merkledb.ChangeProof) {
   577  				response.KeyChanges = append(response.KeyChanges, make([]merkledb.KeyChange, defaultRequestKeyLimit)...)
   578  			},
   579  			expectedErr: errTooManyKeys,
   580  		},
   581  		"partial response to request for entire trie (full leaf limit)": {
   582  			request: &pb.SyncGetChangeProofRequest{
   583  				StartRootHash: startRoot[:],
   584  				EndRootHash:   endRoot[:],
   585  				KeyLimit:      defaultRequestKeyLimit,
   586  				BytesLimit:    defaultRequestByteSizeLimit,
   587  			},
   588  			expectedResponseLen: defaultRequestKeyLimit,
   589  		},
   590  		"removed first key in response": {
   591  			request: &pb.SyncGetChangeProofRequest{
   592  				StartRootHash: startRoot[:],
   593  				EndRootHash:   endRoot[:],
   594  				KeyLimit:      defaultRequestKeyLimit,
   595  				BytesLimit:    defaultRequestByteSizeLimit,
   596  			},
   597  			modifyChangeProofResponse: func(response *merkledb.ChangeProof) {
   598  				response.KeyChanges = response.KeyChanges[1:]
   599  			},
   600  			expectedErr: errInvalidChangeProof,
   601  		},
   602  		"removed key from middle of response": {
   603  			request: &pb.SyncGetChangeProofRequest{
   604  				StartRootHash: startRoot[:],
   605  				EndRootHash:   endRoot[:],
   606  				KeyLimit:      defaultRequestKeyLimit,
   607  				BytesLimit:    defaultRequestByteSizeLimit,
   608  			},
   609  			modifyChangeProofResponse: func(response *merkledb.ChangeProof) {
   610  				response.KeyChanges = append(response.KeyChanges[:100], response.KeyChanges[101:]...)
   611  			},
   612  			expectedErr: merkledb.ErrInvalidProof,
   613  		},
   614  		"all proof keys removed from response": {
   615  			request: &pb.SyncGetChangeProofRequest{
   616  				StartRootHash: startRoot[:],
   617  				EndRootHash:   endRoot[:],
   618  				KeyLimit:      defaultRequestKeyLimit,
   619  				BytesLimit:    defaultRequestByteSizeLimit,
   620  			},
   621  			modifyChangeProofResponse: func(response *merkledb.ChangeProof) {
   622  				response.StartProof = nil
   623  				response.EndProof = nil
   624  			},
   625  			expectedErr: merkledb.ErrInvalidProof,
   626  		},
   627  		"range proof response; remove first key": {
   628  			request: &pb.SyncGetChangeProofRequest{
   629  				// Server doesn't have the (non-existent) start root
   630  				// so should respond with range proof.
   631  				StartRootHash: fakeRootID[:],
   632  				EndRootHash:   endRoot[:],
   633  				KeyLimit:      defaultRequestKeyLimit,
   634  				BytesLimit:    defaultRequestByteSizeLimit,
   635  			},
   636  			modifyChangeProofResponse: nil,
   637  			modifyRangeProofResponse: func(response *merkledb.RangeProof) {
   638  				response.KeyValues = response.KeyValues[1:]
   639  			},
   640  			expectedErr:      errInvalidRangeProof,
   641  			expectRangeProof: true,
   642  		},
   643  	}
   644  
   645  	for name, test := range tests {
   646  		t.Run(name, func(t *testing.T) {
   647  			require := require.New(t)
   648  
   649  			// Ensure test is well-formed.
   650  			if test.expectRangeProof {
   651  				require.Nil(test.modifyChangeProofResponse)
   652  			} else {
   653  				require.Nil(test.modifyRangeProofResponse)
   654  			}
   655  
   656  			changeOrRangeProof, err := sendChangeProofRequest(
   657  				t,
   658  				serverDB,
   659  				clientDB,
   660  				test.request,
   661  				1,
   662  				test.modifyChangeProofResponse,
   663  				test.modifyRangeProofResponse,
   664  			)
   665  			require.ErrorIs(err, test.expectedErr)
   666  			if test.expectedErr != nil {
   667  				return
   668  			}
   669  
   670  			if test.expectRangeProof {
   671  				require.NotNil(changeOrRangeProof.RangeProof)
   672  				require.Nil(changeOrRangeProof.ChangeProof)
   673  			} else {
   674  				require.NotNil(changeOrRangeProof.ChangeProof)
   675  				require.Nil(changeOrRangeProof.RangeProof)
   676  			}
   677  
   678  			if test.expectedResponseLen > 0 {
   679  				if test.expectRangeProof {
   680  					require.LessOrEqual(len(changeOrRangeProof.RangeProof.KeyValues), test.expectedResponseLen)
   681  				} else {
   682  					require.LessOrEqual(len(changeOrRangeProof.ChangeProof.KeyChanges), test.expectedResponseLen)
   683  				}
   684  			}
   685  
   686  			var bytes []byte
   687  			if test.expectRangeProof {
   688  				bytes, err = proto.Marshal(&pb.SyncGetChangeProofResponse{
   689  					Response: &pb.SyncGetChangeProofResponse_RangeProof{
   690  						RangeProof: changeOrRangeProof.RangeProof.ToProto(),
   691  					},
   692  				})
   693  			} else {
   694  				bytes, err = proto.Marshal(&pb.SyncGetChangeProofResponse{
   695  					Response: &pb.SyncGetChangeProofResponse_ChangeProof{
   696  						ChangeProof: changeOrRangeProof.ChangeProof.ToProto(),
   697  					},
   698  				})
   699  			}
   700  			require.NoError(err)
   701  			require.LessOrEqual(len(bytes), int(test.request.BytesLimit))
   702  		})
   703  	}
   704  }
   705  
   706  func TestRangeProofRetries(t *testing.T) {
   707  	now := time.Now().UnixNano()
   708  	t.Logf("seed: %d", now)
   709  	r := rand.New(rand.NewSource(now)) // #nosec G404
   710  	require := require.New(t)
   711  
   712  	keyCount := defaultRequestKeyLimit
   713  	db, _, err := generateTrieWithMinKeyLen(t, r, keyCount, 1)
   714  	require.NoError(err)
   715  	root, err := db.GetMerkleRoot(context.Background())
   716  	require.NoError(err)
   717  
   718  	maxRequests := 4
   719  	request := &pb.SyncGetRangeProofRequest{
   720  		RootHash:   root[:],
   721  		KeyLimit:   uint32(keyCount),
   722  		BytesLimit: defaultRequestByteSizeLimit,
   723  	}
   724  
   725  	responseCount := 0
   726  	modifyResponse := func(response *merkledb.RangeProof) {
   727  		responseCount++
   728  		if responseCount < maxRequests {
   729  			// corrupt the first [maxRequests] responses, to force the client to retry.
   730  			response.KeyValues = nil
   731  		}
   732  	}
   733  	proof, err := sendRangeProofRequest(t, db, request, maxRequests, modifyResponse)
   734  	require.NoError(err)
   735  	require.Len(proof.KeyValues, keyCount)
   736  
   737  	require.Equal(responseCount, maxRequests) // check the client performed retries.
   738  }
   739  
   740  // Test that a failure to send an AppRequest is propagated
   741  // and returned by GetRangeProof and GetChangeProof.
   742  func TestAppRequestSendFailed(t *testing.T) {
   743  	require := require.New(t)
   744  	ctrl := gomock.NewController(t)
   745  	defer ctrl.Finish()
   746  
   747  	networkClient := NewMockNetworkClient(ctrl)
   748  
   749  	client, err := NewClient(
   750  		&ClientConfig{
   751  			NetworkClient: networkClient,
   752  			Log:           logging.NoLog{},
   753  			Metrics:       &mockMetrics{},
   754  			BranchFactor:  merkledb.BranchFactor16,
   755  		},
   756  	)
   757  	require.NoError(err)
   758  
   759  	// Mock failure to send app request
   760  	networkClient.EXPECT().RequestAny(
   761  		gomock.Any(),
   762  		gomock.Any(),
   763  	).Return(ids.EmptyNodeID, nil, errAppSendFailed).Times(2)
   764  
   765  	_, err = client.GetChangeProof(
   766  		context.Background(),
   767  		&pb.SyncGetChangeProofRequest{},
   768  		nil, // database is unused
   769  	)
   770  	require.ErrorIs(err, errAppSendFailed)
   771  
   772  	_, err = client.GetRangeProof(
   773  		context.Background(),
   774  		&pb.SyncGetRangeProofRequest{},
   775  	)
   776  	require.ErrorIs(err, errAppSendFailed)
   777  }