github.com/MetalBlockchain/metalgo@v1.11.9/x/sync/network_server_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/stretchr/testify/require"
    13  	"go.uber.org/mock/gomock"
    14  	"google.golang.org/protobuf/proto"
    15  
    16  	"github.com/MetalBlockchain/metalgo/database"
    17  	"github.com/MetalBlockchain/metalgo/ids"
    18  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    19  	"github.com/MetalBlockchain/metalgo/utils/logging"
    20  	"github.com/MetalBlockchain/metalgo/x/merkledb"
    21  
    22  	pb "github.com/MetalBlockchain/metalgo/proto/pb/sync"
    23  )
    24  
    25  func Test_Server_GetRangeProof(t *testing.T) {
    26  	now := time.Now().UnixNano()
    27  	t.Logf("seed: %d", now)
    28  	r := rand.New(rand.NewSource(now)) // #nosec G404
    29  
    30  	smallTrieDB, _, err := generateTrieWithMinKeyLen(t, r, defaultRequestKeyLimit, 1)
    31  	require.NoError(t, err)
    32  	smallTrieRoot, err := smallTrieDB.GetMerkleRoot(context.Background())
    33  	require.NoError(t, err)
    34  
    35  	tests := map[string]struct {
    36  		request                  *pb.SyncGetRangeProofRequest
    37  		expectedErr              error
    38  		expectedResponseLen      int
    39  		expectedMaxResponseBytes int
    40  		nodeID                   ids.NodeID
    41  		proofNil                 bool
    42  	}{
    43  		"proof too large": {
    44  			request: &pb.SyncGetRangeProofRequest{
    45  				RootHash:   smallTrieRoot[:],
    46  				KeyLimit:   defaultRequestKeyLimit,
    47  				BytesLimit: 1000,
    48  			},
    49  			proofNil:    true,
    50  			expectedErr: ErrMinProofSizeIsTooLarge,
    51  		},
    52  		"byteslimit is 0": {
    53  			request: &pb.SyncGetRangeProofRequest{
    54  				RootHash:   smallTrieRoot[:],
    55  				KeyLimit:   defaultRequestKeyLimit,
    56  				BytesLimit: 0,
    57  			},
    58  			proofNil: true,
    59  		},
    60  		"keylimit is 0": {
    61  			request: &pb.SyncGetRangeProofRequest{
    62  				RootHash:   smallTrieRoot[:],
    63  				KeyLimit:   defaultRequestKeyLimit,
    64  				BytesLimit: 0,
    65  			},
    66  			proofNil: true,
    67  		},
    68  		"keys out of order": {
    69  			request: &pb.SyncGetRangeProofRequest{
    70  				RootHash:   smallTrieRoot[:],
    71  				KeyLimit:   defaultRequestKeyLimit,
    72  				BytesLimit: defaultRequestByteSizeLimit,
    73  				StartKey:   &pb.MaybeBytes{Value: []byte{1}},
    74  				EndKey:     &pb.MaybeBytes{Value: []byte{0}},
    75  			},
    76  			proofNil: true,
    77  		},
    78  		"key limit too large": {
    79  			request: &pb.SyncGetRangeProofRequest{
    80  				RootHash:   smallTrieRoot[:],
    81  				KeyLimit:   2 * defaultRequestKeyLimit,
    82  				BytesLimit: defaultRequestByteSizeLimit,
    83  			},
    84  			expectedResponseLen: defaultRequestKeyLimit,
    85  		},
    86  		"bytes limit too large": {
    87  			request: &pb.SyncGetRangeProofRequest{
    88  				RootHash:   smallTrieRoot[:],
    89  				KeyLimit:   defaultRequestKeyLimit,
    90  				BytesLimit: 2 * defaultRequestByteSizeLimit,
    91  			},
    92  			expectedMaxResponseBytes: defaultRequestByteSizeLimit,
    93  		},
    94  		"empty proof": {
    95  			request: &pb.SyncGetRangeProofRequest{
    96  				RootHash:   ids.Empty[:],
    97  				KeyLimit:   defaultRequestKeyLimit,
    98  				BytesLimit: defaultRequestByteSizeLimit,
    99  			},
   100  			proofNil: true,
   101  		},
   102  	}
   103  
   104  	for name, test := range tests {
   105  		t.Run(name, func(t *testing.T) {
   106  			require := require.New(t)
   107  			ctrl := gomock.NewController(t)
   108  			sender := common.NewMockSender(ctrl)
   109  			var proof *merkledb.RangeProof
   110  			sender.EXPECT().SendAppResponse(
   111  				gomock.Any(), // ctx
   112  				gomock.Any(), // nodeID
   113  				gomock.Any(), // requestID
   114  				gomock.Any(), // responseBytes
   115  			).DoAndReturn(
   116  				func(_ context.Context, _ ids.NodeID, _ uint32, responseBytes []byte) error {
   117  					// grab a copy of the proof so we can inspect it later
   118  					if !test.proofNil {
   119  						var proofProto pb.RangeProof
   120  						require.NoError(proto.Unmarshal(responseBytes, &proofProto))
   121  
   122  						var p merkledb.RangeProof
   123  						require.NoError(p.UnmarshalProto(&proofProto))
   124  						proof = &p
   125  					}
   126  					return nil
   127  				},
   128  			).AnyTimes()
   129  			handler := NewNetworkServer(sender, smallTrieDB, logging.NoLog{})
   130  			err := handler.HandleRangeProofRequest(context.Background(), test.nodeID, 0, test.request)
   131  			require.ErrorIs(err, test.expectedErr)
   132  			if test.expectedErr != nil {
   133  				return
   134  			}
   135  			if test.proofNil {
   136  				require.Nil(proof)
   137  				return
   138  			}
   139  			require.NotNil(proof)
   140  			if test.expectedResponseLen > 0 {
   141  				require.LessOrEqual(len(proof.KeyValues), test.expectedResponseLen)
   142  			}
   143  
   144  			bytes, err := proto.Marshal(proof.ToProto())
   145  			require.NoError(err)
   146  			require.LessOrEqual(len(bytes), int(test.request.BytesLimit))
   147  			if test.expectedMaxResponseBytes > 0 {
   148  				require.LessOrEqual(len(bytes), test.expectedMaxResponseBytes)
   149  			}
   150  		})
   151  	}
   152  }
   153  
   154  func Test_Server_GetChangeProof(t *testing.T) {
   155  	now := time.Now().UnixNano()
   156  	t.Logf("seed: %d", now)
   157  	r := rand.New(rand.NewSource(now)) // #nosec G404
   158  	trieDB, _, err := generateTrieWithMinKeyLen(t, r, defaultRequestKeyLimit, 1)
   159  	require.NoError(t, err)
   160  
   161  	startRoot, err := trieDB.GetMerkleRoot(context.Background())
   162  	require.NoError(t, err)
   163  
   164  	// create changes
   165  	ops := make([]database.BatchOp, 0, 300)
   166  	for x := 0; x < 300; x++ {
   167  		key := make([]byte, r.Intn(100))
   168  		_, err = r.Read(key)
   169  		require.NoError(t, err)
   170  
   171  		val := make([]byte, r.Intn(100))
   172  		_, err = r.Read(val)
   173  		require.NoError(t, err)
   174  
   175  		ops = append(ops, database.BatchOp{Key: key, Value: val})
   176  
   177  		deleteKeyStart := make([]byte, r.Intn(10))
   178  		_, err = r.Read(deleteKeyStart)
   179  		require.NoError(t, err)
   180  
   181  		it := trieDB.NewIteratorWithStart(deleteKeyStart)
   182  		if it.Next() {
   183  			ops = append(ops, database.BatchOp{Key: it.Key(), Delete: true})
   184  		}
   185  		require.NoError(t, it.Error())
   186  		it.Release()
   187  
   188  		view, err := trieDB.NewView(
   189  			context.Background(),
   190  			merkledb.ViewChanges{BatchOps: ops},
   191  		)
   192  		require.NoError(t, err)
   193  		require.NoError(t, view.CommitToDB(context.Background()))
   194  	}
   195  
   196  	endRoot, err := trieDB.GetMerkleRoot(context.Background())
   197  	require.NoError(t, err)
   198  
   199  	fakeRootID := ids.GenerateTestID()
   200  
   201  	tests := map[string]struct {
   202  		request                  *pb.SyncGetChangeProofRequest
   203  		expectedErr              error
   204  		expectedResponseLen      int
   205  		expectedMaxResponseBytes int
   206  		nodeID                   ids.NodeID
   207  		proofNil                 bool
   208  		expectRangeProof         bool // Otherwise expect change proof
   209  	}{
   210  		"byteslimit is 0": {
   211  			request: &pb.SyncGetChangeProofRequest{
   212  				StartRootHash: startRoot[:],
   213  				EndRootHash:   endRoot[:],
   214  				KeyLimit:      defaultRequestKeyLimit,
   215  				BytesLimit:    0,
   216  			},
   217  			proofNil: true,
   218  		},
   219  		"keylimit is 0": {
   220  			request: &pb.SyncGetChangeProofRequest{
   221  				StartRootHash: startRoot[:],
   222  				EndRootHash:   endRoot[:],
   223  				KeyLimit:      defaultRequestKeyLimit,
   224  				BytesLimit:    0,
   225  			},
   226  			proofNil: true,
   227  		},
   228  		"keys out of order": {
   229  			request: &pb.SyncGetChangeProofRequest{
   230  				StartRootHash: startRoot[:],
   231  				EndRootHash:   endRoot[:],
   232  				KeyLimit:      defaultRequestKeyLimit,
   233  				BytesLimit:    defaultRequestByteSizeLimit,
   234  				StartKey:      &pb.MaybeBytes{Value: []byte{1}},
   235  				EndKey:        &pb.MaybeBytes{Value: []byte{0}},
   236  			},
   237  			proofNil: true,
   238  		},
   239  		"key limit too large": {
   240  			request: &pb.SyncGetChangeProofRequest{
   241  				StartRootHash: startRoot[:],
   242  				EndRootHash:   endRoot[:],
   243  				KeyLimit:      2 * defaultRequestKeyLimit,
   244  				BytesLimit:    defaultRequestByteSizeLimit,
   245  			},
   246  			expectedResponseLen: defaultRequestKeyLimit,
   247  		},
   248  		"bytes limit too large": {
   249  			request: &pb.SyncGetChangeProofRequest{
   250  				StartRootHash: startRoot[:],
   251  				EndRootHash:   endRoot[:],
   252  				KeyLimit:      defaultRequestKeyLimit,
   253  				BytesLimit:    2 * defaultRequestByteSizeLimit,
   254  			},
   255  			expectedMaxResponseBytes: defaultRequestByteSizeLimit,
   256  		},
   257  		"insufficient history for change proof; return range proof": {
   258  			request: &pb.SyncGetChangeProofRequest{
   259  				// This root doesn't exist so server has insufficient history
   260  				// to serve a change proof
   261  				StartRootHash: fakeRootID[:],
   262  				EndRootHash:   endRoot[:],
   263  				KeyLimit:      defaultRequestKeyLimit,
   264  				BytesLimit:    defaultRequestByteSizeLimit,
   265  			},
   266  			expectedMaxResponseBytes: defaultRequestByteSizeLimit,
   267  			expectRangeProof:         true,
   268  		},
   269  		"insufficient history for change proof or range proof": {
   270  			request: &pb.SyncGetChangeProofRequest{
   271  				// These roots don't exist so server has insufficient history
   272  				// to serve a change proof or range proof
   273  				StartRootHash: ids.Empty[:],
   274  				EndRootHash:   fakeRootID[:],
   275  				KeyLimit:      defaultRequestKeyLimit,
   276  				BytesLimit:    defaultRequestByteSizeLimit,
   277  			},
   278  			expectedMaxResponseBytes: defaultRequestByteSizeLimit,
   279  			proofNil:                 true,
   280  		},
   281  		"empt proof": {
   282  			request: &pb.SyncGetChangeProofRequest{
   283  				StartRootHash: fakeRootID[:],
   284  				EndRootHash:   ids.Empty[:],
   285  				KeyLimit:      defaultRequestKeyLimit,
   286  				BytesLimit:    defaultRequestByteSizeLimit,
   287  			},
   288  			expectedMaxResponseBytes: defaultRequestByteSizeLimit,
   289  			proofNil:                 true,
   290  		},
   291  	}
   292  
   293  	for name, test := range tests {
   294  		t.Run(name, func(t *testing.T) {
   295  			require := require.New(t)
   296  			ctrl := gomock.NewController(t)
   297  			defer ctrl.Finish()
   298  
   299  			// Store proof returned by server in [proofResult]
   300  			var proofResult *pb.SyncGetChangeProofResponse
   301  			var proofBytes []byte
   302  			sender := common.NewMockSender(ctrl)
   303  			sender.EXPECT().SendAppResponse(
   304  				gomock.Any(), // ctx
   305  				gomock.Any(), // nodeID
   306  				gomock.Any(), // requestID
   307  				gomock.Any(), // responseBytes
   308  			).DoAndReturn(
   309  				func(_ context.Context, _ ids.NodeID, _ uint32, responseBytes []byte) error {
   310  					if test.proofNil {
   311  						return nil
   312  					}
   313  					proofBytes = responseBytes
   314  
   315  					// grab a copy of the proof so we can inspect it later
   316  					var responseProto pb.SyncGetChangeProofResponse
   317  					require.NoError(proto.Unmarshal(responseBytes, &responseProto))
   318  					proofResult = &responseProto
   319  
   320  					return nil
   321  				},
   322  			).AnyTimes()
   323  
   324  			handler := NewNetworkServer(sender, trieDB, logging.NoLog{})
   325  			err := handler.HandleChangeProofRequest(context.Background(), test.nodeID, 0, test.request)
   326  			require.ErrorIs(err, test.expectedErr)
   327  			if test.expectedErr != nil {
   328  				return
   329  			}
   330  
   331  			if test.proofNil {
   332  				require.Nil(proofResult)
   333  				return
   334  			}
   335  			require.NotNil(proofResult)
   336  
   337  			if test.expectRangeProof {
   338  				require.NotNil(proofResult.GetRangeProof())
   339  			} else {
   340  				require.NotNil(proofResult.GetChangeProof())
   341  			}
   342  
   343  			if test.expectedResponseLen > 0 {
   344  				if test.expectRangeProof {
   345  					require.LessOrEqual(len(proofResult.GetRangeProof().KeyValues), test.expectedResponseLen)
   346  				} else {
   347  					require.LessOrEqual(len(proofResult.GetChangeProof().KeyChanges), test.expectedResponseLen)
   348  				}
   349  			}
   350  
   351  			require.NoError(err)
   352  			require.LessOrEqual(len(proofBytes), int(test.request.BytesLimit))
   353  			if test.expectedMaxResponseBytes > 0 {
   354  				require.LessOrEqual(len(proofBytes), test.expectedMaxResponseBytes)
   355  			}
   356  		})
   357  	}
   358  }
   359  
   360  // Test that AppRequest returns a non-nil error if we fail to send
   361  // an AppRequest or AppResponse.
   362  func TestAppRequestErrAppSendFailed(t *testing.T) {
   363  	startRootID := ids.GenerateTestID()
   364  	endRootID := ids.GenerateTestID()
   365  
   366  	type test struct {
   367  		name        string
   368  		request     *pb.Request
   369  		handlerFunc func(*gomock.Controller) *NetworkServer
   370  		expectedErr error
   371  	}
   372  
   373  	tests := []test{
   374  		{
   375  			name: "GetChangeProof",
   376  			request: &pb.Request{
   377  				Message: &pb.Request_ChangeProofRequest{
   378  					ChangeProofRequest: &pb.SyncGetChangeProofRequest{
   379  						StartRootHash: startRootID[:],
   380  						EndRootHash:   endRootID[:],
   381  						StartKey:      &pb.MaybeBytes{Value: []byte{1}},
   382  						EndKey:        &pb.MaybeBytes{Value: []byte{2}},
   383  						KeyLimit:      100,
   384  						BytesLimit:    100,
   385  					},
   386  				},
   387  			},
   388  			handlerFunc: func(ctrl *gomock.Controller) *NetworkServer {
   389  				sender := common.NewMockSender(ctrl)
   390  				sender.EXPECT().SendAppResponse(
   391  					gomock.Any(),
   392  					gomock.Any(),
   393  					gomock.Any(),
   394  					gomock.Any(),
   395  				).Return(errAppSendFailed).AnyTimes()
   396  
   397  				db := merkledb.NewMockMerkleDB(ctrl)
   398  				db.EXPECT().GetChangeProof(
   399  					gomock.Any(),
   400  					gomock.Any(),
   401  					gomock.Any(),
   402  					gomock.Any(),
   403  					gomock.Any(),
   404  					gomock.Any(),
   405  				).Return(&merkledb.ChangeProof{}, nil).Times(1)
   406  
   407  				return NewNetworkServer(sender, db, logging.NoLog{})
   408  			},
   409  			expectedErr: errAppSendFailed,
   410  		},
   411  		{
   412  			name: "GetRangeProof",
   413  			request: &pb.Request{
   414  				Message: &pb.Request_RangeProofRequest{
   415  					RangeProofRequest: &pb.SyncGetRangeProofRequest{
   416  						RootHash:   endRootID[:],
   417  						StartKey:   &pb.MaybeBytes{Value: []byte{1}},
   418  						EndKey:     &pb.MaybeBytes{Value: []byte{2}},
   419  						KeyLimit:   100,
   420  						BytesLimit: 100,
   421  					},
   422  				},
   423  			},
   424  			handlerFunc: func(ctrl *gomock.Controller) *NetworkServer {
   425  				sender := common.NewMockSender(ctrl)
   426  				sender.EXPECT().SendAppResponse(
   427  					gomock.Any(),
   428  					gomock.Any(),
   429  					gomock.Any(),
   430  					gomock.Any(),
   431  				).Return(errAppSendFailed).AnyTimes()
   432  
   433  				db := merkledb.NewMockMerkleDB(ctrl)
   434  				db.EXPECT().GetRangeProofAtRoot(
   435  					gomock.Any(),
   436  					gomock.Any(),
   437  					gomock.Any(),
   438  					gomock.Any(),
   439  					gomock.Any(),
   440  				).Return(&merkledb.RangeProof{}, nil).Times(1)
   441  
   442  				return NewNetworkServer(sender, db, logging.NoLog{})
   443  			},
   444  			expectedErr: errAppSendFailed,
   445  		},
   446  	}
   447  
   448  	for _, tt := range tests {
   449  		t.Run(tt.name, func(t *testing.T) {
   450  			require := require.New(t)
   451  			ctrl := gomock.NewController(t)
   452  
   453  			handler := tt.handlerFunc(ctrl)
   454  			requestBytes, err := proto.Marshal(tt.request)
   455  			require.NoError(err)
   456  
   457  			err = handler.AppRequest(
   458  				context.Background(),
   459  				ids.EmptyNodeID,
   460  				0,
   461  				time.Now().Add(10*time.Second),
   462  				requestBytes,
   463  			)
   464  			require.ErrorIs(err, tt.expectedErr)
   465  		})
   466  	}
   467  }