github.com/number571/tendermint@v0.34.11-gost/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/number571/tendermint/internal/test/factory" 16 "github.com/number571/tendermint/libs/log" 17 "github.com/number571/tendermint/light" 18 "github.com/number571/tendermint/light/provider" 19 mockp "github.com/number571/tendermint/light/provider/mock" 20 dbs "github.com/number571/tendermint/light/store/db" 21 "github.com/number571/tendermint/types" 22 ) 23 24 const ( 25 chainID = "test" 26 ) 27 28 var ( 29 ctx = context.Background() 30 keys = genPrivKeys(4) 31 vals = keys.ToValidators(20, 10) 32 bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") 33 h1 = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals, 34 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) 35 // 3/3 signed 36 h2 = keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 37 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h1.Hash()}) 38 // 3/3 signed 39 h3 = keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals, 40 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: h2.Hash()}) 41 trustPeriod = 4 * time.Hour 42 trustOptions = light.TrustOptions{ 43 Period: 4 * time.Hour, 44 Height: 1, 45 Hash: h1.Hash(), 46 } 47 valSet = map[int64]*types.ValidatorSet{ 48 1: vals, 49 2: vals, 50 3: vals, 51 4: vals, 52 } 53 headerSet = map[int64]*types.SignedHeader{ 54 1: h1, 55 // interim header (3/3 signed) 56 2: h2, 57 // last header (3/3 signed) 58 3: h3, 59 } 60 l1 = &types.LightBlock{SignedHeader: h1, 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, _ := factory.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()), 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()), 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()), 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()), 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()), 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 TestClientRestoresTrustedHeaderAfterStartup(t *testing.T) { 462 // 1. options.Hash == trustedHeader.Hash 463 { 464 trustedStore := dbs.New(dbm.NewMemDB()) 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()) 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 // client take the trusted store and ignores the trusted options 524 assert.Equal(t, l.Hash(), l1.Hash()) 525 assert.NoError(t, l.ValidateBasic(chainID)) 526 } 527 } 528 } 529 530 func TestClient_Update(t *testing.T) { 531 c, err := light.NewClient( 532 ctx, 533 chainID, 534 trustOptions, 535 fullNode, 536 []provider.Provider{fullNode}, 537 dbs.New(dbm.NewMemDB()), 538 light.Logger(log.TestingLogger()), 539 ) 540 require.NoError(t, err) 541 542 // should result in downloading & verifying header #3 543 l, err := c.Update(ctx, bTime.Add(2*time.Hour)) 544 assert.NoError(t, err) 545 if assert.NotNil(t, l) { 546 assert.EqualValues(t, 3, l.Height) 547 assert.NoError(t, l.ValidateBasic(chainID)) 548 } 549 } 550 551 func TestClient_Concurrency(t *testing.T) { 552 c, err := light.NewClient( 553 ctx, 554 chainID, 555 trustOptions, 556 fullNode, 557 []provider.Provider{fullNode}, 558 dbs.New(dbm.NewMemDB()), 559 light.Logger(log.TestingLogger()), 560 ) 561 require.NoError(t, err) 562 563 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour)) 564 require.NoError(t, err) 565 566 var wg sync.WaitGroup 567 for i := 0; i < 100; i++ { 568 wg.Add(1) 569 go func() { 570 defer wg.Done() 571 572 // NOTE: Cleanup, Stop, VerifyLightBlockAtHeight and Verify are not supposed 573 // to be concurrently safe. 574 575 assert.Equal(t, chainID, c.ChainID()) 576 577 _, err := c.LastTrustedHeight() 578 assert.NoError(t, err) 579 580 _, err = c.FirstTrustedHeight() 581 assert.NoError(t, err) 582 583 l, err := c.TrustedLightBlock(1) 584 assert.NoError(t, err) 585 assert.NotNil(t, l) 586 }() 587 } 588 589 wg.Wait() 590 } 591 592 func TestClient_AddProviders(t *testing.T) { 593 c, err := light.NewClient( 594 ctx, 595 chainID, 596 trustOptions, 597 fullNode, 598 []provider.Provider{fullNode}, 599 dbs.New(dbm.NewMemDB()), 600 light.Logger(log.TestingLogger()), 601 ) 602 require.NoError(t, err) 603 604 closeCh := make(chan struct{}) 605 go func() { 606 // run verification concurrently to make sure it doesn't dead lock 607 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour)) 608 require.NoError(t, err) 609 close(closeCh) 610 }() 611 612 // NOTE: the light client doesn't check uniqueness of providers 613 c.AddProvider(fullNode) 614 require.Len(t, c.Witnesses(), 2) 615 select { 616 case <-closeCh: 617 case <-time.After(5 * time.Second): 618 t.Fatal("concurent light block verification failed to finish in 5s") 619 } 620 } 621 622 func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) { 623 c, err := light.NewClient( 624 ctx, 625 chainID, 626 trustOptions, 627 deadNode, 628 []provider.Provider{fullNode, fullNode}, 629 dbs.New(dbm.NewMemDB()), 630 light.Logger(log.TestingLogger()), 631 ) 632 633 require.NoError(t, err) 634 _, err = c.Update(ctx, bTime.Add(2*time.Hour)) 635 require.NoError(t, err) 636 637 // the primary should no longer be the deadNode 638 assert.NotEqual(t, c.Primary(), deadNode) 639 640 // we should still have the dead node as a witness because it 641 // hasn't repeatedly been unresponsive yet 642 assert.Equal(t, 2, len(c.Witnesses())) 643 } 644 645 func TestClient_BackwardsVerification(t *testing.T) { 646 { 647 trustHeader, _ := largeFullNode.LightBlock(ctx, 6) 648 c, err := light.NewClient( 649 ctx, 650 chainID, 651 light.TrustOptions{ 652 Period: 4 * time.Minute, 653 Height: trustHeader.Height, 654 Hash: trustHeader.Hash(), 655 }, 656 largeFullNode, 657 []provider.Provider{largeFullNode}, 658 dbs.New(dbm.NewMemDB()), 659 light.Logger(log.TestingLogger()), 660 ) 661 require.NoError(t, err) 662 663 // 1) verify before the trusted header using backwards => expect no error 664 h, err := c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute)) 665 require.NoError(t, err) 666 if assert.NotNil(t, h) { 667 assert.EqualValues(t, 5, h.Height) 668 } 669 670 // 2) untrusted header is expired but trusted header is not => expect no error 671 h, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(8*time.Minute)) 672 assert.NoError(t, err) 673 assert.NotNil(t, h) 674 675 // 3) already stored headers should return the header without error 676 h, err = c.VerifyLightBlockAtHeight(ctx, 5, bTime.Add(6*time.Minute)) 677 assert.NoError(t, err) 678 assert.NotNil(t, h) 679 680 // 4a) First verify latest header 681 _, err = c.VerifyLightBlockAtHeight(ctx, 9, bTime.Add(9*time.Minute)) 682 require.NoError(t, err) 683 684 // 4b) Verify backwards using bisection => expect no error 685 _, err = c.VerifyLightBlockAtHeight(ctx, 7, bTime.Add(9*time.Minute)) 686 assert.NoError(t, err) 687 // shouldn't have verified this header in the process 688 _, err = c.TrustedLightBlock(8) 689 assert.Error(t, err) 690 691 // 5) Try bisection method, but closest header (at 7) has expired 692 // so expect error 693 _, err = c.VerifyLightBlockAtHeight(ctx, 8, bTime.Add(12*time.Minute)) 694 assert.Error(t, err) 695 696 } 697 { 698 testCases := []struct { 699 provider provider.Provider 700 }{ 701 { 702 // 7) provides incorrect height 703 mockp.New( 704 chainID, 705 map[int64]*types.SignedHeader{ 706 1: h1, 707 2: keys.GenSignedHeader(chainID, 1, bTime.Add(30*time.Minute), nil, vals, vals, 708 hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)), 709 3: h3, 710 }, 711 valSet, 712 ), 713 }, 714 { 715 // 8) provides incorrect hash 716 mockp.New( 717 chainID, 718 map[int64]*types.SignedHeader{ 719 1: h1, 720 2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 721 hash("app_hash2"), hash("cons_hash23"), hash("results_hash30"), 0, len(keys)), 722 3: h3, 723 }, 724 valSet, 725 ), 726 }, 727 } 728 729 for idx, tc := range testCases { 730 c, err := light.NewClient( 731 ctx, 732 chainID, 733 light.TrustOptions{ 734 Period: 1 * time.Hour, 735 Height: 3, 736 Hash: h3.Hash(), 737 }, 738 tc.provider, 739 []provider.Provider{tc.provider}, 740 dbs.New(dbm.NewMemDB()), 741 light.Logger(log.TestingLogger()), 742 ) 743 require.NoError(t, err, idx) 744 745 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour).Add(1*time.Second)) 746 assert.Error(t, err, idx) 747 } 748 } 749 } 750 751 func TestClient_NewClientFromTrustedStore(t *testing.T) { 752 // 1) Initiate DB and fill with a "trusted" header 753 db := dbs.New(dbm.NewMemDB()) 754 err := db.SaveLightBlock(l1) 755 require.NoError(t, err) 756 757 c, err := light.NewClientFromTrustedStore( 758 chainID, 759 trustPeriod, 760 deadNode, 761 []provider.Provider{deadNode}, 762 db, 763 ) 764 require.NoError(t, err) 765 766 // 2) Check light block exists (deadNode is being used to ensure we're not getting 767 // it from primary) 768 h, err := c.TrustedLightBlock(1) 769 assert.NoError(t, err) 770 assert.EqualValues(t, l1.Height, h.Height) 771 } 772 773 func TestClientRemovesWitnessIfItSendsUsIncorrectHeader(t *testing.T) { 774 // different headers hash then primary plus less than 1/3 signed (no fork) 775 badProvider1 := mockp.New( 776 chainID, 777 map[int64]*types.SignedHeader{ 778 1: h1, 779 2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 780 hash("app_hash2"), hash("cons_hash"), hash("results_hash"), 781 len(keys), len(keys), types.BlockID{Hash: h1.Hash()}), 782 }, 783 map[int64]*types.ValidatorSet{ 784 1: vals, 785 2: vals, 786 }, 787 ) 788 // header is empty 789 badProvider2 := mockp.New( 790 chainID, 791 map[int64]*types.SignedHeader{ 792 1: h1, 793 2: h2, 794 }, 795 map[int64]*types.ValidatorSet{ 796 1: vals, 797 2: vals, 798 }, 799 ) 800 801 lb1, _ := badProvider1.LightBlock(ctx, 2) 802 require.NotEqual(t, lb1.Hash(), l1.Hash()) 803 804 c, err := light.NewClient( 805 ctx, 806 chainID, 807 trustOptions, 808 fullNode, 809 []provider.Provider{badProvider1, badProvider2}, 810 dbs.New(dbm.NewMemDB()), 811 light.Logger(log.TestingLogger()), 812 ) 813 // witness should have behaved properly -> no error 814 require.NoError(t, err) 815 assert.EqualValues(t, 2, len(c.Witnesses())) 816 817 // witness behaves incorrectly -> removed from list, no error 818 l, err := c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour)) 819 assert.NoError(t, err) 820 assert.EqualValues(t, 1, len(c.Witnesses())) 821 // light block should still be verified 822 assert.EqualValues(t, 2, l.Height) 823 824 // remaining witnesses don't have light block -> error 825 _, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour)) 826 if assert.Error(t, err) { 827 assert.Equal(t, light.ErrFailedHeaderCrossReferencing, err) 828 } 829 // witness does not have a light block -> left in the list 830 assert.EqualValues(t, 1, len(c.Witnesses())) 831 } 832 833 func TestClient_TrustedValidatorSet(t *testing.T) { 834 differentVals, _ := factory.RandValidatorSet(10, 100) 835 badValSetNode := mockp.New( 836 chainID, 837 map[int64]*types.SignedHeader{ 838 1: h1, 839 // 3/3 signed, but validator set at height 2 below is invalid -> witness 840 // should be removed. 841 2: keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals, 842 hash("app_hash2"), hash("cons_hash"), hash("results_hash"), 843 0, len(keys), types.BlockID{Hash: h1.Hash()}), 844 3: h3, 845 }, 846 map[int64]*types.ValidatorSet{ 847 1: vals, 848 2: differentVals, 849 3: differentVals, 850 }, 851 ) 852 853 c, err := light.NewClient( 854 ctx, 855 chainID, 856 trustOptions, 857 fullNode, 858 []provider.Provider{badValSetNode, fullNode}, 859 dbs.New(dbm.NewMemDB()), 860 light.Logger(log.TestingLogger()), 861 ) 862 require.NoError(t, err) 863 assert.Equal(t, 2, len(c.Witnesses())) 864 865 _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(2*time.Hour).Add(1*time.Second)) 866 assert.NoError(t, err) 867 assert.Equal(t, 1, len(c.Witnesses())) 868 } 869 870 func TestClientPrunesHeadersAndValidatorSets(t *testing.T) { 871 c, err := light.NewClient( 872 ctx, 873 chainID, 874 trustOptions, 875 fullNode, 876 []provider.Provider{fullNode}, 877 dbs.New(dbm.NewMemDB()), 878 light.Logger(log.TestingLogger()), 879 light.PruningSize(1), 880 ) 881 require.NoError(t, err) 882 _, err = c.TrustedLightBlock(1) 883 require.NoError(t, err) 884 885 h, err := c.Update(ctx, bTime.Add(2*time.Hour)) 886 require.NoError(t, err) 887 require.Equal(t, int64(3), h.Height) 888 889 _, err = c.TrustedLightBlock(1) 890 assert.Error(t, err) 891 } 892 893 func TestClientEnsureValidHeadersAndValSets(t *testing.T) { 894 emptyValSet := &types.ValidatorSet{ 895 Validators: nil, 896 Proposer: nil, 897 } 898 899 testCases := []struct { 900 headers map[int64]*types.SignedHeader 901 vals map[int64]*types.ValidatorSet 902 err bool 903 }{ 904 { 905 headerSet, 906 valSet, 907 false, 908 }, 909 { 910 headerSet, 911 map[int64]*types.ValidatorSet{ 912 1: vals, 913 2: vals, 914 3: nil, 915 }, 916 true, 917 }, 918 { 919 map[int64]*types.SignedHeader{ 920 1: h1, 921 2: h2, 922 3: nil, 923 }, 924 valSet, 925 true, 926 }, 927 { 928 headerSet, 929 map[int64]*types.ValidatorSet{ 930 1: vals, 931 2: vals, 932 3: emptyValSet, 933 }, 934 true, 935 }, 936 } 937 938 for _, tc := range testCases { 939 badNode := mockp.New( 940 chainID, 941 tc.headers, 942 tc.vals, 943 ) 944 c, err := light.NewClient( 945 ctx, 946 chainID, 947 trustOptions, 948 badNode, 949 []provider.Provider{badNode, badNode}, 950 dbs.New(dbm.NewMemDB()), 951 ) 952 require.NoError(t, err) 953 954 _, err = c.VerifyLightBlockAtHeight(ctx, 3, bTime.Add(2*time.Hour)) 955 if tc.err { 956 assert.Error(t, err) 957 } else { 958 assert.NoError(t, err) 959 } 960 } 961 962 } 963 964 func TestClientHandlesContexts(t *testing.T) { 965 p := mockp.New(genMockNode(chainID, 100, 10, 1, bTime)) 966 genBlock, err := p.LightBlock(ctx, 1) 967 require.NoError(t, err) 968 969 // instantiate the light client with a timeout 970 ctxTimeOut, cancel := context.WithTimeout(ctx, 10*time.Millisecond) 971 defer cancel() 972 _, err = light.NewClient( 973 ctxTimeOut, 974 chainID, 975 light.TrustOptions{ 976 Period: 24 * time.Hour, 977 Height: 1, 978 Hash: genBlock.Hash(), 979 }, 980 p, 981 []provider.Provider{p, p}, 982 dbs.New(dbm.NewMemDB()), 983 ) 984 require.Error(t, ctxTimeOut.Err()) 985 require.Error(t, err) 986 require.True(t, errors.Is(err, context.DeadlineExceeded)) 987 988 // instantiate the client for real 989 c, err := light.NewClient( 990 ctx, 991 chainID, 992 light.TrustOptions{ 993 Period: 24 * time.Hour, 994 Height: 1, 995 Hash: genBlock.Hash(), 996 }, 997 p, 998 []provider.Provider{p, p}, 999 dbs.New(dbm.NewMemDB()), 1000 ) 1001 require.NoError(t, err) 1002 1003 // verify a block with a timeout 1004 ctxTimeOutBlock, cancel := context.WithTimeout(ctx, 10*time.Millisecond) 1005 defer cancel() 1006 _, err = c.VerifyLightBlockAtHeight(ctxTimeOutBlock, 100, bTime.Add(100*time.Minute)) 1007 require.Error(t, ctxTimeOutBlock.Err()) 1008 require.Error(t, err) 1009 require.True(t, errors.Is(err, context.DeadlineExceeded)) 1010 1011 // verify a block with a cancel 1012 ctxCancel, cancel := context.WithCancel(ctx) 1013 defer cancel() 1014 time.AfterFunc(10*time.Millisecond, cancel) 1015 _, err = c.VerifyLightBlockAtHeight(ctxCancel, 100, bTime.Add(100*time.Minute)) 1016 require.Error(t, ctxCancel.Err()) 1017 require.Error(t, err) 1018 require.True(t, errors.Is(err, context.Canceled)) 1019 1020 }