github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/light/client_test.go (about) 1 package light_test 2 3 import ( 4 "context" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/lazyledger/lazyledger-core/libs/db/memdb" 13 "github.com/lazyledger/lazyledger-core/libs/log" 14 "github.com/lazyledger/lazyledger-core/light" 15 "github.com/lazyledger/lazyledger-core/light/provider" 16 mockp "github.com/lazyledger/lazyledger-core/light/provider/mock" 17 dbs "github.com/lazyledger/lazyledger-core/light/store/db" 18 "github.com/lazyledger/lazyledger-core/types" 19 ) 20 21 const ( 22 chainID = "test" 23 ) 24 25 var ( 26 ctx = context.Background() 27 keys = genPrivKeys(4) 28 vals = keys.ToValidators(20, 10) 29 bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") 30 h1 = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals, 31 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) 32 // 3/3 signed 33 h2 = keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 34 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h1.Hash()}) 35 // 3/3 signed 36 h3 = keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals, 37 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h2.Hash()}) 38 trustPeriod = 4 * time.Hour 39 trustOptions = light.TrustOptions{ 40 Period: 4 * time.Hour, 41 Height: 1, 42 Hash: h1.Hash(), 43 } 44 valSet = map[int64]*types.ValidatorSet{ 45 1: vals, 46 2: vals, 47 3: vals, 48 4: vals, 49 } 50 headerSet = map[int64]*types.SignedHeader{ 51 1: h1, 52 // interim header (3/3 signed) 53 2: h2, 54 // last header (3/3 signed) 55 3: h3, 56 } 57 l1 = &types.LightBlock{SignedHeader: h1, ValidatorSet: vals} 58 l2 = &types.LightBlock{SignedHeader: h2, ValidatorSet: vals} 59 fullNode = mockp.New( 60 chainID, 61 headerSet, 62 valSet, 63 ) 64 deadNode = mockp.NewDeadMock(chainID) 65 largeFullNode = mockp.New(genMockNode(chainID, 10, 3, 0, bTime)) 66 ) 67 68 func TestValidateTrustOptions(t *testing.T) { 69 testCases := []struct { 70 err bool 71 to light.TrustOptions 72 }{ 73 { 74 false, 75 trustOptions, 76 }, 77 { 78 true, 79 light.TrustOptions{ 80 Period: -1 * time.Hour, 81 Height: 1, 82 Hash: h1.Hash(), 83 }, 84 }, 85 { 86 true, 87 light.TrustOptions{ 88 Period: 1 * time.Hour, 89 Height: 0, 90 Hash: h1.Hash(), 91 }, 92 }, 93 { 94 true, 95 light.TrustOptions{ 96 Period: 1 * time.Hour, 97 Height: 1, 98 Hash: []byte("incorrect hash"), 99 }, 100 }, 101 } 102 103 for _, tc := range testCases { 104 err := tc.to.ValidateBasic() 105 if tc.err { 106 assert.Error(t, err) 107 } else { 108 assert.NoError(t, err) 109 } 110 } 111 112 } 113 114 func TestMock(t *testing.T) { 115 l, _ := fullNode.LightBlock(ctx, 3) 116 assert.Equal(t, int64(3), l.Height) 117 } 118 119 func TestClient_SequentialVerification(t *testing.T) { 120 newKeys := genPrivKeys(4) 121 newVals := newKeys.ToValidators(10, 1) 122 differentVals, _ := types.RandValidatorSet(10, 100) 123 124 testCases := []struct { 125 name string 126 otherHeaders map[int64]*types.SignedHeader // all except ^ 127 vals map[int64]*types.ValidatorSet 128 initErr bool 129 verifyErr bool 130 }{ 131 { 132 "good", 133 headerSet, 134 valSet, 135 false, 136 false, 137 }, 138 { 139 "bad: different first header", 140 map[int64]*types.SignedHeader{ 141 // different header 142 1: keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals, 143 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)), 144 }, 145 map[int64]*types.ValidatorSet{ 146 1: vals, 147 }, 148 true, 149 false, 150 }, 151 { 152 "bad: no first signed header", 153 map[int64]*types.SignedHeader{}, 154 map[int64]*types.ValidatorSet{ 155 1: differentVals, 156 }, 157 true, 158 true, 159 }, 160 { 161 "bad: different first validator set", 162 map[int64]*types.SignedHeader{ 163 1: h1, 164 }, 165 map[int64]*types.ValidatorSet{ 166 1: differentVals, 167 }, 168 true, 169 true, 170 }, 171 { 172 "bad: 1/3 signed interim header", 173 map[int64]*types.SignedHeader{ 174 // trusted header 175 1: h1, 176 // interim header (1/3 signed) 177 2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals, 178 hash("app_hash"), hash("cons_hash"), hash("results_hash"), len(keys)-1, len(keys)), 179 // last header (3/3 signed) 180 3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals, 181 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)), 182 }, 183 valSet, 184 false, 185 true, 186 }, 187 { 188 "bad: 1/3 signed last header", 189 map[int64]*types.SignedHeader{ 190 // trusted header 191 1: h1, 192 // interim header (3/3 signed) 193 2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals, 194 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)), 195 // last header (1/3 signed) 196 3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals, 197 hash("app_hash"), hash("cons_hash"), hash("results_hash"), len(keys)-1, len(keys)), 198 }, 199 valSet, 200 false, 201 true, 202 }, 203 { 204 "bad: different validator set at height 3", 205 headerSet, 206 map[int64]*types.ValidatorSet{ 207 1: vals, 208 2: vals, 209 3: newVals, 210 }, 211 false, 212 true, 213 }, 214 } 215 216 for _, tc := range testCases { 217 tc := tc 218 t.Run(tc.name, func(t *testing.T) { 219 c, err := light.NewClient( 220 ctx, 221 chainID, 222 trustOptions, 223 mockp.New( 224 chainID, 225 tc.otherHeaders, 226 tc.vals, 227 ), 228 []provider.Provider{mockp.New( 229 chainID, 230 tc.otherHeaders, 231 tc.vals, 232 )}, 233 dbs.New(memdb.NewDB(), chainID), 234 light.SequentialVerification(), 235 light.Logger(log.TestingLogger()), 236 ) 237 238 if tc.initErr { 239 require.Error(t, err) 240 return 241 } 242 243 require.NoError(t, err) 244 245 _, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(3*time.Hour)) 246 if tc.verifyErr { 247 assert.Error(t, err) 248 } else { 249 assert.NoError(t, err) 250 } 251 }) 252 } 253 } 254 255 func TestClient_SkippingVerification(t *testing.T) { 256 // required for 2nd test case 257 newKeys := genPrivKeys(4) 258 newVals := newKeys.ToValidators(10, 1) 259 260 // 1/3+ of vals, 2/3- of newVals 261 transitKeys := keys.Extend(3) 262 transitVals := transitKeys.ToValidators(10, 1) 263 264 testCases := []struct { 265 name string 266 otherHeaders map[int64]*types.SignedHeader // all except ^ 267 vals map[int64]*types.ValidatorSet 268 initErr bool 269 verifyErr bool 270 }{ 271 { 272 "good", 273 map[int64]*types.SignedHeader{ 274 // trusted header 275 1: h1, 276 // last header (3/3 signed) 277 3: h3, 278 }, 279 valSet, 280 false, 281 false, 282 }, 283 { 284 "good, but val set changes by 2/3 (1/3 of vals is still present)", 285 map[int64]*types.SignedHeader{ 286 // trusted header 287 1: h1, 288 3: transitKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, transitVals, transitVals, 289 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(transitKeys)), 290 }, 291 map[int64]*types.ValidatorSet{ 292 1: vals, 293 2: vals, 294 3: transitVals, 295 }, 296 false, 297 false, 298 }, 299 { 300 "good, but val set changes 100% at height 2", 301 map[int64]*types.SignedHeader{ 302 // trusted header 303 1: h1, 304 // interim header (3/3 signed) 305 2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals, 306 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)), 307 // last header (0/4 of the original val set signed) 308 3: newKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, newVals, newVals, 309 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(newKeys)), 310 }, 311 map[int64]*types.ValidatorSet{ 312 1: vals, 313 2: vals, 314 3: newVals, 315 }, 316 false, 317 false, 318 }, 319 { 320 "bad: last header signed by newVals, interim header has no signers", 321 map[int64]*types.SignedHeader{ 322 // trusted header 323 1: h1, 324 // last header (0/4 of the original val set signed) 325 2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals, 326 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, 0), 327 // last header (0/4 of the original val set signed) 328 3: newKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, newVals, newVals, 329 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(newKeys)), 330 }, 331 map[int64]*types.ValidatorSet{ 332 1: vals, 333 2: vals, 334 3: newVals, 335 }, 336 false, 337 true, 338 }, 339 } 340 341 for _, tc := range testCases { 342 tc := tc 343 t.Run(tc.name, func(t *testing.T) { 344 c, err := light.NewClient( 345 ctx, 346 chainID, 347 trustOptions, 348 mockp.New( 349 chainID, 350 tc.otherHeaders, 351 tc.vals, 352 ), 353 []provider.Provider{mockp.New( 354 chainID, 355 tc.otherHeaders, 356 tc.vals, 357 )}, 358 dbs.New(memdb.NewDB(), chainID), 359 light.SkippingVerification(light.DefaultTrustLevel), 360 light.Logger(log.TestingLogger()), 361 ) 362 if tc.initErr { 363 require.Error(t, err) 364 return 365 } 366 367 require.NoError(t, err) 368 369 _, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(3*time.Hour)) 370 if tc.verifyErr { 371 assert.Error(t, err) 372 } else { 373 assert.NoError(t, err) 374 } 375 }) 376 } 377 378 } 379 380 // start from a large light block to make sure that the pivot height doesn't select a height outside 381 // the appropriate range 382 func TestClientLargeBisectionVerification(t *testing.T) { 383 veryLargeFullNode := mockp.New(genMockNode(chainID, 100, 3, 0, bTime)) 384 trustedLightBlock, err := veryLargeFullNode.LightBlock(ctx, 5) 385 require.NoError(t, err) 386 c, err := light.NewClient( 387 ctx, 388 chainID, 389 light.TrustOptions{ 390 Period: 4 * time.Hour, 391 Height: trustedLightBlock.Height, 392 Hash: trustedLightBlock.Hash(), 393 }, 394 veryLargeFullNode, 395 []provider.Provider{veryLargeFullNode}, 396 dbs.New(memdb.NewDB(), chainID), 397 light.SkippingVerification(light.DefaultTrustLevel), 398 ) 399 require.NoError(t, err) 400 h, err := c.Update(ctx, bTime.Add(100*time.Minute)) 401 assert.NoError(t, err) 402 h2, err := veryLargeFullNode.LightBlock(ctx, 100) 403 require.NoError(t, err) 404 assert.Equal(t, h, h2) 405 } 406 407 func TestClientBisectionBetweenTrustedHeaders(t *testing.T) { 408 c, err := light.NewClient( 409 ctx, 410 chainID, 411 light.TrustOptions{ 412 Period: 4 * time.Hour, 413 Height: 1, 414 Hash: h1.Hash(), 415 }, 416 fullNode, 417 []provider.Provider{fullNode}, 418 dbs.New(memdb.NewDB(), chainID), 419 light.SkippingVerification(light.DefaultTrustLevel), 420 ) 421 require.NoError(t, err) 422 423 _, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour)) 424 require.NoError(t, err) 425 426 // confirm that the client already doesn't have the light block 427 _, err = c.TrustedLightBlock(2) 428 require.Error(t, err) 429 430 // verify using bisection the light block between the two trusted light blocks 431 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour)) 432 assert.NoError(t, err) 433 } 434 435 func TestClient_Cleanup(t *testing.T) { 436 c, err := light.NewClient( 437 ctx, 438 chainID, 439 trustOptions, 440 fullNode, 441 []provider.Provider{fullNode}, 442 dbs.New(memdb.NewDB(), chainID), 443 light.Logger(log.TestingLogger()), 444 ) 445 require.NoError(t, err) 446 _, err = c.TrustedLightBlock(1) 447 require.NoError(t, err) 448 449 err = c.Cleanup() 450 require.NoError(t, err) 451 452 // Check no light blocks exist after Cleanup. 453 l, err := c.TrustedLightBlock(1) 454 assert.Error(t, err) 455 assert.Nil(t, l) 456 } 457 458 // trustedHeader.Height == options.Height 459 func TestClientRestoresTrustedHeaderAfterStartup1(t *testing.T) { 460 // 1. options.Hash == trustedHeader.Hash 461 { 462 trustedStore := dbs.New(memdb.NewDB(), chainID) 463 err := trustedStore.SaveLightBlock(l1) 464 require.NoError(t, err) 465 466 c, err := light.NewClient( 467 ctx, 468 chainID, 469 trustOptions, 470 fullNode, 471 []provider.Provider{fullNode}, 472 trustedStore, 473 light.Logger(log.TestingLogger()), 474 ) 475 require.NoError(t, err) 476 477 l, err := c.TrustedLightBlock(1) 478 assert.NoError(t, err) 479 assert.NotNil(t, l) 480 assert.Equal(t, l.Hash(), h1.Hash()) 481 assert.Equal(t, l.ValidatorSet.Hash(), h1.ValidatorsHash.Bytes()) 482 } 483 484 // 2. options.Hash != trustedHeader.Hash 485 { 486 trustedStore := dbs.New(memdb.NewDB(), chainID) 487 err := trustedStore.SaveLightBlock(l1) 488 require.NoError(t, err) 489 490 // header1 != h1 491 header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals, 492 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) 493 494 primary := mockp.New( 495 chainID, 496 map[int64]*types.SignedHeader{ 497 // trusted header 498 1: header1, 499 }, 500 valSet, 501 ) 502 503 c, err := light.NewClient( 504 ctx, 505 chainID, 506 light.TrustOptions{ 507 Period: 4 * time.Hour, 508 Height: 1, 509 Hash: header1.Hash(), 510 }, 511 primary, 512 []provider.Provider{primary}, 513 trustedStore, 514 light.Logger(log.TestingLogger()), 515 ) 516 require.NoError(t, err) 517 518 l, err := c.TrustedLightBlock(1) 519 assert.NoError(t, err) 520 if assert.NotNil(t, l) { 521 assert.Equal(t, l.Hash(), header1.Hash()) 522 assert.NoError(t, l.ValidateBasic(chainID)) 523 } 524 } 525 } 526 527 // trustedHeader.Height < options.Height 528 func TestClientRestoresTrustedHeaderAfterStartup2(t *testing.T) { 529 // 1. options.Hash == trustedHeader.Hash 530 { 531 trustedStore := dbs.New(memdb.NewDB(), chainID) 532 err := trustedStore.SaveLightBlock(l1) 533 require.NoError(t, err) 534 535 c, err := light.NewClient( 536 ctx, 537 chainID, 538 light.TrustOptions{ 539 Period: 4 * time.Hour, 540 Height: 2, 541 Hash: h2.Hash(), 542 }, 543 fullNode, 544 []provider.Provider{fullNode}, 545 trustedStore, 546 light.Logger(log.TestingLogger()), 547 ) 548 require.NoError(t, err) 549 550 // Check we still have the 1st header (+header+). 551 l, err := c.TrustedLightBlock(1) 552 assert.NoError(t, err) 553 assert.NotNil(t, l) 554 assert.Equal(t, l.Hash(), h1.Hash()) 555 assert.NoError(t, l.ValidateBasic(chainID)) 556 } 557 558 // 2. options.Hash != trustedHeader.Hash 559 // This could happen if previous provider was lying to us. 560 { 561 trustedStore := dbs.New(memdb.NewDB(), chainID) 562 err := trustedStore.SaveLightBlock(l1) 563 require.NoError(t, err) 564 565 // header1 != header 566 diffHeader1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals, 567 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) 568 569 diffHeader2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals, 570 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) 571 572 primary := mockp.New( 573 chainID, 574 map[int64]*types.SignedHeader{ 575 1: diffHeader1, 576 2: diffHeader2, 577 }, 578 valSet, 579 ) 580 581 c, err := light.NewClient( 582 ctx, 583 chainID, 584 light.TrustOptions{ 585 Period: 4 * time.Hour, 586 Height: 2, 587 Hash: diffHeader2.Hash(), 588 }, 589 primary, 590 []provider.Provider{primary}, 591 trustedStore, 592 light.Logger(log.TestingLogger()), 593 ) 594 require.NoError(t, err) 595 596 // Check we no longer have the invalid 1st header (+header+). 597 l, err := c.TrustedLightBlock(1) 598 assert.Error(t, err) 599 assert.Nil(t, l) 600 } 601 } 602 603 // trustedHeader.Height > options.Height 604 func TestClientRestoresTrustedHeaderAfterStartup3(t *testing.T) { 605 // 1. options.Hash == trustedHeader.Hash 606 { 607 // load the first three headers into the trusted store 608 trustedStore := dbs.New(memdb.NewDB(), chainID) 609 err := trustedStore.SaveLightBlock(l1) 610 require.NoError(t, err) 611 612 err = trustedStore.SaveLightBlock(l2) 613 require.NoError(t, err) 614 615 c, err := light.NewClient( 616 ctx, 617 chainID, 618 trustOptions, 619 fullNode, 620 []provider.Provider{fullNode}, 621 trustedStore, 622 light.Logger(log.TestingLogger()), 623 ) 624 require.NoError(t, err) 625 626 // Check we still have the 1st light block. 627 l, err := c.TrustedLightBlock(1) 628 assert.NoError(t, err) 629 assert.NotNil(t, l) 630 assert.Equal(t, l.Hash(), h1.Hash()) 631 assert.NoError(t, l.ValidateBasic(chainID)) 632 633 // Check we no longer have 2nd light block. 634 l, err = c.TrustedLightBlock(2) 635 assert.Error(t, err) 636 assert.Nil(t, l) 637 638 l, err = c.TrustedLightBlock(3) 639 assert.Error(t, err) 640 assert.Nil(t, l) 641 } 642 643 // 2. options.Hash != trustedHeader.Hash 644 // This could happen if previous provider was lying to us. 645 { 646 trustedStore := dbs.New(memdb.NewDB(), chainID) 647 err := trustedStore.SaveLightBlock(l1) 648 require.NoError(t, err) 649 650 // header1 != header 651 header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals, 652 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) 653 654 header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals, 655 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) 656 err = trustedStore.SaveLightBlock(&types.LightBlock{ 657 SignedHeader: header2, 658 ValidatorSet: vals, 659 }) 660 require.NoError(t, err) 661 662 primary := mockp.New( 663 chainID, 664 map[int64]*types.SignedHeader{ 665 1: header1, 666 }, 667 valSet, 668 ) 669 670 c, err := light.NewClient( 671 ctx, 672 chainID, 673 light.TrustOptions{ 674 Period: 4 * time.Hour, 675 Height: 1, 676 Hash: header1.Hash(), 677 }, 678 primary, 679 []provider.Provider{primary}, 680 trustedStore, 681 light.Logger(log.TestingLogger()), 682 ) 683 require.NoError(t, err) 684 685 // Check we have swapped invalid 1st light block (+lightblock+) with correct one (+lightblock2+). 686 l, err := c.TrustedLightBlock(1) 687 assert.NoError(t, err) 688 assert.NotNil(t, l) 689 assert.Equal(t, l.Hash(), header1.Hash()) 690 assert.NoError(t, l.ValidateBasic(chainID)) 691 692 // Check we no longer have invalid 2nd light block (+lightblock2+). 693 l, err = c.TrustedLightBlock(2) 694 assert.Error(t, err) 695 assert.Nil(t, l) 696 } 697 } 698 699 func TestClient_Update(t *testing.T) { 700 c, err := light.NewClient( 701 ctx, 702 chainID, 703 trustOptions, 704 fullNode, 705 []provider.Provider{fullNode}, 706 dbs.New(memdb.NewDB(), chainID), 707 light.Logger(log.TestingLogger()), 708 ) 709 require.NoError(t, err) 710 711 // should result in downloading & verifying header #3 712 l, err := c.Update(ctx, bTime.Add(2*time.Hour)) 713 assert.NoError(t, err) 714 if assert.NotNil(t, l) { 715 assert.EqualValues(t, 3, l.Height) 716 assert.NoError(t, l.ValidateBasic(chainID)) 717 } 718 } 719 720 func TestClient_Concurrency(t *testing.T) { 721 c, err := light.NewClient( 722 ctx, 723 chainID, 724 trustOptions, 725 fullNode, 726 []provider.Provider{fullNode}, 727 dbs.New(memdb.NewDB(), chainID), 728 light.Logger(log.TestingLogger()), 729 ) 730 require.NoError(t, err) 731 732 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour)) 733 require.NoError(t, err) 734 735 var wg sync.WaitGroup 736 for i := 0; i < 100; i++ { 737 wg.Add(1) 738 go func() { 739 defer wg.Done() 740 741 // NOTE: Cleanup, Stop, VerifyLightBlockAtHeight and Verify are not supposed 742 // to be concurrenly safe. 743 744 assert.Equal(t, chainID, c.ChainID()) 745 746 _, err := c.LastTrustedHeight() 747 assert.NoError(t, err) 748 749 _, err = c.FirstTrustedHeight() 750 assert.NoError(t, err) 751 752 l, err := c.TrustedLightBlock(1) 753 assert.NoError(t, err) 754 assert.NotNil(t, l) 755 }() 756 } 757 758 wg.Wait() 759 } 760 761 func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) { 762 c, err := light.NewClient( 763 ctx, 764 chainID, 765 trustOptions, 766 deadNode, 767 []provider.Provider{fullNode, fullNode}, 768 dbs.New(memdb.NewDB(), chainID), 769 light.Logger(log.TestingLogger()), 770 light.MaxRetryAttempts(1), 771 ) 772 773 require.NoError(t, err) 774 _, err = c.Update(ctx, bTime.Add(2*time.Hour)) 775 require.NoError(t, err) 776 777 assert.NotEqual(t, c.Primary(), deadNode) 778 assert.Equal(t, 1, len(c.Witnesses())) 779 } 780 781 func TestClient_BackwardsVerification(t *testing.T) { 782 { 783 trustHeader, _ := largeFullNode.LightBlock(ctx, 6) 784 c, err := light.NewClient( 785 ctx, 786 chainID, 787 light.TrustOptions{ 788 Period: 4 * time.Minute, 789 Height: trustHeader.Height, 790 Hash: trustHeader.Hash(), 791 }, 792 largeFullNode, 793 []provider.Provider{largeFullNode}, 794 dbs.New(memdb.NewDB(), chainID), 795 light.Logger(log.TestingLogger()), 796 ) 797 require.NoError(t, err) 798 799 // 1) verify before the trusted header using backwards => expect no error 800 h, err := c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute)) 801 require.NoError(t, err) 802 if assert.NotNil(t, h) { 803 assert.EqualValues(t, 5, h.Height) 804 } 805 806 // 2) untrusted header is expired but trusted header is not => expect no error 807 h, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(8*time.Minute)) 808 assert.NoError(t, err) 809 assert.NotNil(t, h) 810 811 // 3) already stored headers should return the header without error 812 h, err = c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute)) 813 assert.NoError(t, err) 814 assert.NotNil(t, h) 815 816 // 4a) First verify latest header 817 _, err = c.VerifyLightBlockAtHeight(ctx, 9, bTime.Add(9*time.Minute)) 818 require.NoError(t, err) 819 820 // 4b) Verify backwards using bisection => expect no error 821 _, err = c.VerifyLightBlockAtHeight(ctx, 7, bTime.Add(9*time.Minute)) 822 assert.NoError(t, err) 823 // shouldn't have verified this header in the process 824 _, err = c.TrustedLightBlock(8) 825 assert.Error(t, err) 826 827 // 5) Try bisection method, but closest header (at 7) has expired 828 // so expect error 829 _, err = c.VerifyLightBlockAtHeight(ctx, 8, bTime.Add(12*time.Minute)) 830 assert.Error(t, err) 831 832 } 833 { 834 testCases := []struct { 835 provider provider.Provider 836 }{ 837 { 838 // 7) provides incorrect height 839 mockp.New( 840 chainID, 841 map[int64]*types.SignedHeader{ 842 1: h1, 843 2: keys.GenSignedHeader(chainID, 1, bTime.Add(30*time.Minute), nil, vals, vals, 844 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)), 845 3: h3, 846 }, 847 valSet, 848 ), 849 }, 850 { 851 // 8) provides incorrect hash 852 mockp.New( 853 chainID, 854 map[int64]*types.SignedHeader{ 855 1: h1, 856 2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 857 hash("app_hash2"), hash("cons_hash23"), hash("results_hash30"), 0, len(keys)), 858 3: h3, 859 }, 860 valSet, 861 ), 862 }, 863 } 864 865 for idx, tc := range testCases { 866 c, err := light.NewClient( 867 ctx, 868 chainID, 869 light.TrustOptions{ 870 Period: 1 * time.Hour, 871 Height: 3, 872 Hash: h3.Hash(), 873 }, 874 tc.provider, 875 []provider.Provider{tc.provider}, 876 dbs.New(memdb.NewDB(), chainID), 877 light.Logger(log.TestingLogger()), 878 ) 879 require.NoError(t, err, idx) 880 881 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour).Add(1*time.Second)) 882 assert.Error(t, err, idx) 883 } 884 } 885 } 886 887 func TestClient_NewClientFromTrustedStore(t *testing.T) { 888 // 1) Initiate DB and fill with a "trusted" header 889 db := dbs.New(memdb.NewDB(), chainID) 890 err := db.SaveLightBlock(l1) 891 require.NoError(t, err) 892 893 c, err := light.NewClientFromTrustedStore( 894 chainID, 895 trustPeriod, 896 deadNode, 897 []provider.Provider{deadNode}, 898 db, 899 ) 900 require.NoError(t, err) 901 902 // 2) Check light block exists (deadNode is being used to ensure we're not getting 903 // it from primary) 904 h, err := c.TrustedLightBlock(1) 905 assert.NoError(t, err) 906 assert.EqualValues(t, l1.Height, h.Height) 907 } 908 909 func TestClientRemovesWitnessIfItSendsUsIncorrectHeader(t *testing.T) { 910 // different headers hash then primary plus less than 1/3 signed (no fork) 911 badProvider1 := mockp.New( 912 chainID, 913 map[int64]*types.SignedHeader{ 914 1: h1, 915 2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 916 hash("app_hash2"), hash("cons_hash"), hash("results_hash"), 917 len(keys), len(keys), types.BlockID{Hash: h1.Hash()}), 918 }, 919 map[int64]*types.ValidatorSet{ 920 1: vals, 921 2: vals, 922 }, 923 ) 924 // header is empty 925 badProvider2 := mockp.New( 926 chainID, 927 map[int64]*types.SignedHeader{ 928 1: h1, 929 2: h2, 930 }, 931 map[int64]*types.ValidatorSet{ 932 1: vals, 933 2: vals, 934 }, 935 ) 936 937 lb1, _ := badProvider1.LightBlock(ctx, 2) 938 require.NotEqual(t, lb1.Hash(), l1.Hash()) 939 940 c, err := light.NewClient( 941 ctx, 942 chainID, 943 trustOptions, 944 fullNode, 945 []provider.Provider{badProvider1, badProvider2}, 946 dbs.New(memdb.NewDB(), chainID), 947 light.Logger(log.TestingLogger()), 948 light.MaxRetryAttempts(1), 949 ) 950 // witness should have behaved properly -> no error 951 require.NoError(t, err) 952 assert.EqualValues(t, 2, len(c.Witnesses())) 953 954 // witness behaves incorrectly -> removed from list, no error 955 l, err := c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour)) 956 assert.NoError(t, err) 957 assert.EqualValues(t, 1, len(c.Witnesses())) 958 // light block should still be verified 959 assert.EqualValues(t, 2, l.Height) 960 961 // remaining witnesses don't have light block -> error 962 _, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour)) 963 if assert.Error(t, err) { 964 assert.Equal(t, light.ErrFailedHeaderCrossReferencing, err) 965 } 966 // witness does not have a light block -> left in the list 967 assert.EqualValues(t, 1, len(c.Witnesses())) 968 } 969 970 func TestClient_TrustedValidatorSet(t *testing.T) { 971 differentVals, _ := types.RandValidatorSet(10, 100) 972 badValSetNode := mockp.New( 973 chainID, 974 map[int64]*types.SignedHeader{ 975 1: h1, 976 // 3/3 signed, but validator set at height 2 below is invalid -> witness 977 // should be removed. 978 2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 979 hash("app_hash2"), hash("cons_hash"), hash("results_hash"), 980 0, len(keys), types.BlockID{Hash: h1.Hash()}), 981 3: h3, 982 }, 983 map[int64]*types.ValidatorSet{ 984 1: vals, 985 2: differentVals, 986 3: differentVals, 987 }, 988 ) 989 990 c, err := light.NewClient( 991 ctx, 992 chainID, 993 trustOptions, 994 fullNode, 995 []provider.Provider{badValSetNode, fullNode}, 996 dbs.New(memdb.NewDB(), chainID), 997 light.Logger(log.TestingLogger()), 998 ) 999 require.NoError(t, err) 1000 assert.Equal(t, 2, len(c.Witnesses())) 1001 1002 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour).Add(1*time.Second)) 1003 assert.NoError(t, err) 1004 assert.Equal(t, 1, len(c.Witnesses())) 1005 } 1006 1007 func TestClientPrunesHeadersAndValidatorSets(t *testing.T) { 1008 c, err := light.NewClient( 1009 ctx, 1010 chainID, 1011 trustOptions, 1012 fullNode, 1013 []provider.Provider{fullNode}, 1014 dbs.New(memdb.NewDB(), chainID), 1015 light.Logger(log.TestingLogger()), 1016 light.PruningSize(1), 1017 ) 1018 require.NoError(t, err) 1019 _, err = c.TrustedLightBlock(1) 1020 require.NoError(t, err) 1021 1022 h, err := c.Update(ctx, bTime.Add(2*time.Hour)) 1023 require.NoError(t, err) 1024 require.Equal(t, int64(3), h.Height) 1025 1026 _, err = c.TrustedLightBlock(1) 1027 assert.Error(t, err) 1028 } 1029 1030 func TestClientEnsureValidHeadersAndValSets(t *testing.T) { 1031 emptyValSet := &types.ValidatorSet{ 1032 Validators: nil, 1033 Proposer: nil, 1034 } 1035 1036 testCases := []struct { 1037 headers map[int64]*types.SignedHeader 1038 vals map[int64]*types.ValidatorSet 1039 err bool 1040 }{ 1041 { 1042 headerSet, 1043 valSet, 1044 false, 1045 }, 1046 { 1047 headerSet, 1048 map[int64]*types.ValidatorSet{ 1049 1: vals, 1050 2: vals, 1051 3: nil, 1052 }, 1053 true, 1054 }, 1055 { 1056 map[int64]*types.SignedHeader{ 1057 1: h1, 1058 2: h2, 1059 3: nil, 1060 }, 1061 valSet, 1062 true, 1063 }, 1064 { 1065 headerSet, 1066 map[int64]*types.ValidatorSet{ 1067 1: vals, 1068 2: vals, 1069 3: emptyValSet, 1070 }, 1071 true, 1072 }, 1073 } 1074 1075 for _, tc := range testCases { 1076 badNode := mockp.New( 1077 chainID, 1078 tc.headers, 1079 tc.vals, 1080 ) 1081 c, err := light.NewClient( 1082 ctx, 1083 chainID, 1084 trustOptions, 1085 badNode, 1086 []provider.Provider{badNode, badNode}, 1087 dbs.New(memdb.NewDB(), chainID), 1088 light.MaxRetryAttempts(1), 1089 ) 1090 require.NoError(t, err) 1091 1092 _, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour)) 1093 if tc.err { 1094 assert.Error(t, err) 1095 } else { 1096 assert.NoError(t, err) 1097 } 1098 } 1099 1100 }