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.

426 lines
14 KiB

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