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