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