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 }