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