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.

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