You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

569 lines
19 KiB

  1. package light_test
  2. import (
  3. "bytes"
  4. "context"
  5. "testing"
  6. "time"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/mock"
  9. "github.com/stretchr/testify/require"
  10. dbm "github.com/tendermint/tm-db"
  11. "github.com/tendermint/tendermint/libs/log"
  12. "github.com/tendermint/tendermint/light"
  13. "github.com/tendermint/tendermint/light/provider"
  14. provider_mocks "github.com/tendermint/tendermint/light/provider/mocks"
  15. dbs "github.com/tendermint/tendermint/light/store/db"
  16. "github.com/tendermint/tendermint/types"
  17. )
  18. func TestLightClientAttackEvidence_Lunatic(t *testing.T) {
  19. logger := log.NewNopLogger()
  20. // primary performs a lunatic attack
  21. var (
  22. latestHeight = int64(3)
  23. valSize = 5
  24. divergenceHeight = int64(2)
  25. primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight)
  26. primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight)
  27. )
  28. ctx, cancel := context.WithCancel(context.Background())
  29. defer cancel()
  30. witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, chainID, latestHeight, valSize, 2, bTime)
  31. forgedKeys := chainKeys[divergenceHeight-1].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain)
  32. forgedVals := forgedKeys.ToValidators(2, 0)
  33. for height := int64(1); height <= latestHeight; height++ {
  34. if height < divergenceHeight {
  35. primaryHeaders[height] = witnessHeaders[height]
  36. primaryValidators[height] = witnessValidators[height]
  37. continue
  38. }
  39. primaryHeaders[height] = forgedKeys.GenSignedHeader(t, chainID, height, bTime.Add(time.Duration(height)*time.Minute),
  40. nil, forgedVals, forgedVals, hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(forgedKeys))
  41. primaryValidators[height] = forgedVals
  42. }
  43. // never called, delete it to make mockery asserts pass
  44. delete(witnessHeaders, 2)
  45. delete(primaryHeaders, 2)
  46. mockWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
  47. mockWitness.On("ID").Return("mockWitness")
  48. mockWitness.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
  49. evAgainstPrimary := &types.LightClientAttackEvidence{
  50. // after the divergence height the valset doesn't change so we expect the evidence to be for the latest height
  51. ConflictingBlock: &types.LightBlock{
  52. SignedHeader: primaryHeaders[latestHeight],
  53. ValidatorSet: primaryValidators[latestHeight],
  54. },
  55. CommonHeight: 1,
  56. }
  57. return bytes.Equal(evidence.Hash(), evAgainstPrimary.Hash())
  58. })).Return(nil)
  59. mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryValidators)
  60. mockPrimary.On("ID").Return("mockPrimary")
  61. mockPrimary.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
  62. evAgainstWitness := &types.LightClientAttackEvidence{
  63. // when forming evidence against witness we learn that the canonical chain continued to change validator sets
  64. // hence the conflicting block is at 7
  65. ConflictingBlock: &types.LightBlock{
  66. SignedHeader: witnessHeaders[divergenceHeight+1],
  67. ValidatorSet: witnessValidators[divergenceHeight+1],
  68. },
  69. CommonHeight: divergenceHeight - 1,
  70. }
  71. return bytes.Equal(evidence.Hash(), evAgainstWitness.Hash())
  72. })).Return(nil)
  73. c, err := light.NewClient(
  74. ctx,
  75. chainID,
  76. light.TrustOptions{
  77. Period: 4 * time.Hour,
  78. Height: 1,
  79. Hash: primaryHeaders[1].Hash(),
  80. },
  81. mockPrimary,
  82. []provider.Provider{mockWitness},
  83. dbs.New(dbm.NewMemDB()),
  84. light.Logger(logger),
  85. )
  86. require.NoError(t, err)
  87. // Check verification returns an error.
  88. _, err = c.VerifyLightBlockAtHeight(ctx, latestHeight, bTime.Add(1*time.Hour))
  89. if assert.Error(t, err) {
  90. assert.Equal(t, light.ErrLightClientAttack, err)
  91. }
  92. mockWitness.AssertExpectations(t)
  93. mockPrimary.AssertExpectations(t)
  94. }
  95. func TestLightClientAttackEvidence_Equivocation(t *testing.T) {
  96. cases := []struct {
  97. name string
  98. lightOption light.Option
  99. unusedWitnessBlockHeights []int64
  100. unusedPrimaryBlockHeights []int64
  101. latestHeight int64
  102. divergenceHeight int64
  103. }{
  104. {
  105. name: "sequential",
  106. lightOption: light.SequentialVerification(),
  107. unusedWitnessBlockHeights: []int64{4, 6},
  108. latestHeight: int64(5),
  109. divergenceHeight: int64(3),
  110. },
  111. {
  112. name: "skipping",
  113. lightOption: light.SkippingVerification(light.DefaultTrustLevel),
  114. unusedWitnessBlockHeights: []int64{2, 4, 6},
  115. unusedPrimaryBlockHeights: []int64{2, 4, 6},
  116. latestHeight: int64(5),
  117. divergenceHeight: int64(3),
  118. },
  119. }
  120. bctx, bcancel := context.WithCancel(context.Background())
  121. defer bcancel()
  122. for _, tc := range cases {
  123. testCase := tc
  124. t.Run(testCase.name, func(t *testing.T) {
  125. ctx, cancel := context.WithCancel(bctx)
  126. defer cancel()
  127. logger := log.NewNopLogger()
  128. // primary performs an equivocation attack
  129. var (
  130. valSize = 5
  131. primaryHeaders = make(map[int64]*types.SignedHeader, testCase.latestHeight)
  132. // validators don't change in this network (however we still use a map just for convenience)
  133. primaryValidators = make(map[int64]*types.ValidatorSet, testCase.latestHeight)
  134. )
  135. witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, chainID,
  136. testCase.latestHeight+1, valSize, 2, bTime)
  137. for height := int64(1); height <= testCase.latestHeight; height++ {
  138. if height < testCase.divergenceHeight {
  139. primaryHeaders[height] = witnessHeaders[height]
  140. primaryValidators[height] = witnessValidators[height]
  141. continue
  142. }
  143. // we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for
  144. // a different block (which we do by adding txs)
  145. primaryHeaders[height] = chainKeys[height].GenSignedHeader(t, chainID, height,
  146. bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")},
  147. witnessValidators[height], witnessValidators[height+1], hash("app_hash"),
  148. hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1)
  149. primaryValidators[height] = witnessValidators[height]
  150. }
  151. for _, height := range testCase.unusedWitnessBlockHeights {
  152. delete(witnessHeaders, height)
  153. }
  154. mockWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
  155. mockWitness.On("ID").Return("mockWitness")
  156. for _, height := range testCase.unusedPrimaryBlockHeights {
  157. delete(primaryHeaders, height)
  158. }
  159. mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryValidators)
  160. mockPrimary.On("ID").Return("mockPrimary")
  161. // Check evidence was sent to both full nodes.
  162. // Common height should be set to the height of the divergent header in the instance
  163. // of an equivocation attack and the validator sets are the same as what the witness has
  164. mockWitness.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
  165. evAgainstPrimary := &types.LightClientAttackEvidence{
  166. ConflictingBlock: &types.LightBlock{
  167. SignedHeader: primaryHeaders[testCase.divergenceHeight],
  168. ValidatorSet: primaryValidators[testCase.divergenceHeight],
  169. },
  170. CommonHeight: testCase.divergenceHeight,
  171. }
  172. return bytes.Equal(evidence.Hash(), evAgainstPrimary.Hash())
  173. })).Return(nil)
  174. mockPrimary.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
  175. evAgainstWitness := &types.LightClientAttackEvidence{
  176. ConflictingBlock: &types.LightBlock{
  177. SignedHeader: witnessHeaders[testCase.divergenceHeight],
  178. ValidatorSet: witnessValidators[testCase.divergenceHeight],
  179. },
  180. CommonHeight: testCase.divergenceHeight,
  181. }
  182. return bytes.Equal(evidence.Hash(), evAgainstWitness.Hash())
  183. })).Return(nil)
  184. c, err := light.NewClient(
  185. ctx,
  186. chainID,
  187. light.TrustOptions{
  188. Period: 4 * time.Hour,
  189. Height: 1,
  190. Hash: primaryHeaders[1].Hash(),
  191. },
  192. mockPrimary,
  193. []provider.Provider{mockWitness},
  194. dbs.New(dbm.NewMemDB()),
  195. light.Logger(logger),
  196. testCase.lightOption,
  197. )
  198. require.NoError(t, err)
  199. // Check verification returns an error.
  200. _, err = c.VerifyLightBlockAtHeight(ctx, testCase.latestHeight, bTime.Add(300*time.Second))
  201. if assert.Error(t, err) {
  202. assert.Equal(t, light.ErrLightClientAttack, err)
  203. }
  204. mockWitness.AssertExpectations(t)
  205. mockPrimary.AssertExpectations(t)
  206. })
  207. }
  208. }
  209. func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) {
  210. // primary performs a lunatic attack but changes the time of the header to
  211. // something in the future relative to the blockchain
  212. var (
  213. latestHeight = int64(10)
  214. valSize = 5
  215. forgedHeight = int64(12)
  216. proofHeight = int64(11)
  217. primaryHeaders = make(map[int64]*types.SignedHeader, forgedHeight)
  218. primaryValidators = make(map[int64]*types.ValidatorSet, forgedHeight)
  219. )
  220. ctx, cancel := context.WithCancel(context.Background())
  221. defer cancel()
  222. logger := log.NewNopLogger()
  223. witnessHeaders, witnessValidators, chainKeys := genLightBlocksWithKeys(t, chainID, latestHeight, valSize, 2, bTime)
  224. for _, unusedHeader := range []int64{3, 5, 6, 8} {
  225. delete(witnessHeaders, unusedHeader)
  226. }
  227. // primary has the exact same headers except it forges one extra header in the future using keys from 2/5ths of
  228. // the validators
  229. for h := range witnessHeaders {
  230. primaryHeaders[h] = witnessHeaders[h]
  231. primaryValidators[h] = witnessValidators[h]
  232. }
  233. for _, unusedHeader := range []int64{3, 5, 6, 8} {
  234. delete(primaryHeaders, unusedHeader)
  235. }
  236. forgedKeys := chainKeys[latestHeight].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain)
  237. primaryValidators[forgedHeight] = forgedKeys.ToValidators(2, 0)
  238. primaryHeaders[forgedHeight] = forgedKeys.GenSignedHeader(t,
  239. chainID,
  240. forgedHeight,
  241. bTime.Add(time.Duration(latestHeight+1)*time.Minute), // 11 mins
  242. nil,
  243. primaryValidators[forgedHeight],
  244. primaryValidators[forgedHeight],
  245. hash("app_hash"),
  246. hash("cons_hash"),
  247. hash("results_hash"),
  248. 0, len(forgedKeys),
  249. )
  250. mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryValidators)
  251. mockPrimary.On("ID").Return("mockPrimary")
  252. lastBlock, _ := mockPrimary.LightBlock(ctx, forgedHeight)
  253. mockPrimary.On("LightBlock", mock.Anything, int64(0)).Return(lastBlock, nil)
  254. mockPrimary.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrLightBlockNotFound)
  255. mockWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
  256. mockWitness.On("ID").Return("mockWitness")
  257. lastBlock, _ = mockWitness.LightBlock(ctx, latestHeight)
  258. mockWitness.On("LightBlock", mock.Anything, int64(0)).Return(lastBlock, nil).Once()
  259. mockWitness.On("LightBlock", mock.Anything, int64(12)).Return(nil, provider.ErrHeightTooHigh)
  260. mockWitness.On("ReportEvidence", mock.Anything, mock.MatchedBy(func(evidence types.Evidence) bool {
  261. // Check evidence was sent to the witness against the full node
  262. evAgainstPrimary := &types.LightClientAttackEvidence{
  263. ConflictingBlock: &types.LightBlock{
  264. SignedHeader: primaryHeaders[forgedHeight],
  265. ValidatorSet: primaryValidators[forgedHeight],
  266. },
  267. CommonHeight: latestHeight,
  268. }
  269. return bytes.Equal(evidence.Hash(), evAgainstPrimary.Hash())
  270. })).Return(nil).Twice()
  271. // In order to perform the attack, the primary needs at least one accomplice as a witness to also
  272. // send the forged block
  273. accomplice := mockNodeFromHeadersAndVals(primaryHeaders, primaryValidators)
  274. accomplice.On("ID").Return("accomplice")
  275. lastBlock, _ = accomplice.LightBlock(ctx, forgedHeight)
  276. accomplice.On("LightBlock", mock.Anything, int64(0)).Return(lastBlock, nil)
  277. accomplice.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrLightBlockNotFound)
  278. c, err := light.NewClient(
  279. ctx,
  280. chainID,
  281. light.TrustOptions{
  282. Period: 4 * time.Hour,
  283. Height: 1,
  284. Hash: primaryHeaders[1].Hash(),
  285. },
  286. mockPrimary,
  287. []provider.Provider{mockWitness, accomplice},
  288. dbs.New(dbm.NewMemDB()),
  289. light.Logger(logger),
  290. light.MaxClockDrift(1*time.Second),
  291. light.MaxBlockLag(1*time.Second),
  292. )
  293. require.NoError(t, err)
  294. // two seconds later, the supporting withness should receive the header that can be used
  295. // to prove that there was an attack
  296. vals := chainKeys[latestHeight].ToValidators(2, 0)
  297. newLb := &types.LightBlock{
  298. SignedHeader: chainKeys[latestHeight].GenSignedHeader(t,
  299. chainID,
  300. proofHeight,
  301. bTime.Add(time.Duration(proofHeight+1)*time.Minute), // 12 mins
  302. nil,
  303. vals,
  304. vals,
  305. hash("app_hash"),
  306. hash("cons_hash"),
  307. hash("results_hash"),
  308. 0, len(chainKeys),
  309. ),
  310. ValidatorSet: vals,
  311. }
  312. go func() {
  313. time.Sleep(2 * time.Second)
  314. mockWitness.On("LightBlock", mock.Anything, int64(0)).Return(newLb, nil)
  315. }()
  316. // Now assert that verification returns an error. We craft the light clients time to be a little ahead of the chain
  317. // to allow a window for the attack to manifest itself.
  318. _, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute))
  319. if assert.Error(t, err) {
  320. assert.Equal(t, light.ErrLightClientAttack, err)
  321. }
  322. // We attempt the same call but now the supporting witness has a block which should
  323. // immediately conflict in time with the primary
  324. _, err = c.VerifyLightBlockAtHeight(ctx, forgedHeight, bTime.Add(time.Duration(forgedHeight)*time.Minute))
  325. if assert.Error(t, err) {
  326. assert.Equal(t, light.ErrLightClientAttack, err)
  327. }
  328. // Lastly we test the unfortunate case where the light clients supporting witness doesn't update
  329. // in enough time
  330. mockLaggingWitness := mockNodeFromHeadersAndVals(witnessHeaders, witnessValidators)
  331. mockLaggingWitness.On("ID").Return("mockLaggingWitness")
  332. mockLaggingWitness.On("LightBlock", mock.Anything, int64(12)).Return(nil, provider.ErrHeightTooHigh)
  333. lastBlock, _ = mockLaggingWitness.LightBlock(ctx, latestHeight)
  334. mockLaggingWitness.On("LightBlock", mock.Anything, int64(0)).Return(lastBlock, nil)
  335. c, err = light.NewClient(
  336. ctx,
  337. chainID,
  338. light.TrustOptions{
  339. Period: 4 * time.Hour,
  340. Height: 1,
  341. Hash: primaryHeaders[1].Hash(),
  342. },
  343. mockPrimary,
  344. []provider.Provider{mockLaggingWitness, accomplice},
  345. dbs.New(dbm.NewMemDB()),
  346. light.Logger(logger),
  347. light.MaxClockDrift(1*time.Second),
  348. light.MaxBlockLag(1*time.Second),
  349. )
  350. require.NoError(t, err)
  351. _, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute))
  352. assert.NoError(t, err)
  353. mockPrimary.AssertExpectations(t)
  354. mockWitness.AssertExpectations(t)
  355. }
  356. // 1. Different nodes therefore a divergent header is produced.
  357. // => light client returns an error upon creation because primary and witness
  358. // have a different view.
  359. func TestClientDivergentTraces1(t *testing.T) {
  360. ctx, cancel := context.WithCancel(context.Background())
  361. defer cancel()
  362. headers, vals, _ := genLightBlocksWithKeys(t, chainID, 1, 5, 2, bTime)
  363. mockPrimary := mockNodeFromHeadersAndVals(headers, vals)
  364. mockPrimary.On("ID").Return("mockPrimary")
  365. firstBlock, err := mockPrimary.LightBlock(ctx, 1)
  366. require.NoError(t, err)
  367. headers, vals, _ = genLightBlocksWithKeys(t, chainID, 1, 5, 2, bTime)
  368. mockWitness := mockNodeFromHeadersAndVals(headers, vals)
  369. mockWitness.On("ID").Return("mockWitness")
  370. logger := log.NewNopLogger()
  371. _, err = light.NewClient(
  372. ctx,
  373. chainID,
  374. light.TrustOptions{
  375. Height: 1,
  376. Hash: firstBlock.Hash(),
  377. Period: 4 * time.Hour,
  378. },
  379. mockPrimary,
  380. []provider.Provider{mockWitness},
  381. dbs.New(dbm.NewMemDB()),
  382. light.Logger(logger),
  383. )
  384. require.Error(t, err)
  385. assert.Contains(t, err.Error(), "does not match primary")
  386. mockWitness.AssertExpectations(t)
  387. mockPrimary.AssertExpectations(t)
  388. }
  389. // 2. Two out of three nodes don't respond but the third has a header that matches
  390. // => verification should be successful and all the witnesses should remain
  391. func TestClientDivergentTraces2(t *testing.T) {
  392. ctx, cancel := context.WithCancel(context.Background())
  393. defer cancel()
  394. logger := log.NewNopLogger()
  395. headers, vals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime)
  396. mockPrimaryNode := mockNodeFromHeadersAndVals(headers, vals)
  397. mockPrimaryNode.On("ID").Return("mockPrimaryNode")
  398. mockGoodWitness := mockNodeFromHeadersAndVals(headers, vals)
  399. mockGoodWitness.On("ID").Return("mockGoodWitness")
  400. mockDeadNode1 := &provider_mocks.Provider{}
  401. mockDeadNode1.On("ID").Return("mockDeadNode1")
  402. mockDeadNode1.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrNoResponse)
  403. mockDeadNode2 := &provider_mocks.Provider{}
  404. mockDeadNode2.On("ID").Return("mockDeadNode2")
  405. mockDeadNode2.On("LightBlock", mock.Anything, mock.Anything).Return(nil, provider.ErrNoResponse)
  406. firstBlock, err := mockPrimaryNode.LightBlock(ctx, 1)
  407. require.NoError(t, err)
  408. c, err := light.NewClient(
  409. ctx,
  410. chainID,
  411. light.TrustOptions{
  412. Height: 1,
  413. Hash: firstBlock.Hash(),
  414. Period: 4 * time.Hour,
  415. },
  416. mockPrimaryNode,
  417. []provider.Provider{mockDeadNode1, mockDeadNode2, mockGoodWitness},
  418. dbs.New(dbm.NewMemDB()),
  419. light.Logger(logger),
  420. )
  421. require.NoError(t, err)
  422. _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour))
  423. assert.NoError(t, err)
  424. assert.Equal(t, 3, len(c.Witnesses()))
  425. mockDeadNode1.AssertExpectations(t)
  426. mockPrimaryNode.AssertExpectations(t)
  427. }
  428. // 3. witness has the same first header, but different second header
  429. // => creation should succeed, but the verification should fail
  430. //nolint: dupl
  431. func TestClientDivergentTraces3(t *testing.T) {
  432. logger := log.NewNopLogger()
  433. //
  434. primaryHeaders, primaryVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime)
  435. mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryVals)
  436. mockPrimary.On("ID").Return("mockPrimary")
  437. ctx, cancel := context.WithCancel(context.Background())
  438. defer cancel()
  439. firstBlock, err := mockPrimary.LightBlock(ctx, 1)
  440. require.NoError(t, err)
  441. mockHeaders, mockVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime)
  442. mockHeaders[1] = primaryHeaders[1]
  443. mockVals[1] = primaryVals[1]
  444. mockWitness := mockNodeFromHeadersAndVals(mockHeaders, mockVals)
  445. mockWitness.On("ID").Return("mockWitness")
  446. c, err := light.NewClient(
  447. ctx,
  448. chainID,
  449. light.TrustOptions{
  450. Height: 1,
  451. Hash: firstBlock.Hash(),
  452. Period: 4 * time.Hour,
  453. },
  454. mockPrimary,
  455. []provider.Provider{mockWitness},
  456. dbs.New(dbm.NewMemDB()),
  457. light.Logger(logger),
  458. )
  459. require.NoError(t, err)
  460. _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour))
  461. assert.Error(t, err)
  462. assert.Equal(t, 1, len(c.Witnesses()))
  463. mockWitness.AssertExpectations(t)
  464. mockPrimary.AssertExpectations(t)
  465. }
  466. // 4. Witness has a divergent header but can not produce a valid trace to back it up.
  467. // It should be ignored
  468. //nolint: dupl
  469. func TestClientDivergentTraces4(t *testing.T) {
  470. logger := log.NewNopLogger()
  471. //
  472. primaryHeaders, primaryVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime)
  473. mockPrimary := mockNodeFromHeadersAndVals(primaryHeaders, primaryVals)
  474. mockPrimary.On("ID").Return("mockPrimary")
  475. ctx, cancel := context.WithCancel(context.Background())
  476. defer cancel()
  477. firstBlock, err := mockPrimary.LightBlock(ctx, 1)
  478. require.NoError(t, err)
  479. witnessHeaders, witnessVals, _ := genLightBlocksWithKeys(t, chainID, 2, 5, 2, bTime)
  480. primaryHeaders[2] = witnessHeaders[2]
  481. primaryVals[2] = witnessVals[2]
  482. mockWitness := mockNodeFromHeadersAndVals(primaryHeaders, primaryVals)
  483. mockWitness.On("ID").Return("mockWitness")
  484. c, err := light.NewClient(
  485. ctx,
  486. chainID,
  487. light.TrustOptions{
  488. Height: 1,
  489. Hash: firstBlock.Hash(),
  490. Period: 4 * time.Hour,
  491. },
  492. mockPrimary,
  493. []provider.Provider{mockWitness},
  494. dbs.New(dbm.NewMemDB()),
  495. light.Logger(logger),
  496. )
  497. require.NoError(t, err)
  498. _, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour))
  499. assert.Error(t, err)
  500. assert.Equal(t, 1, len(c.Witnesses()))
  501. mockWitness.AssertExpectations(t)
  502. mockPrimary.AssertExpectations(t)
  503. }