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