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.

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