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