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.

508 lines
18 KiB

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