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.

252 lines
8.2 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.Contains(t, err.Error(), "does not match primary")
  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. // primary performs an equivocation attack
  82. var (
  83. latestHeight = int64(10)
  84. valSize = 5
  85. divergenceHeight = int64(6)
  86. primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight)
  87. primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight)
  88. )
  89. // validators don't change in this network (however we still use a map just for convenience)
  90. witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight+2, valSize, 2, bTime)
  91. witness := mockp.New(chainID, witnessHeaders, witnessValidators)
  92. for height := int64(1); height <= latestHeight; height++ {
  93. if height < divergenceHeight {
  94. primaryHeaders[height] = witnessHeaders[height]
  95. primaryValidators[height] = witnessValidators[height]
  96. continue
  97. }
  98. // we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for
  99. // a different block (which we do by adding txs)
  100. primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height,
  101. bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")},
  102. witnessValidators[height], witnessValidators[height+1], hash("app_hash"),
  103. hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1)
  104. primaryValidators[height] = witnessValidators[height]
  105. }
  106. primary := mockp.New(chainID, primaryHeaders, primaryValidators)
  107. c, err := light.NewClient(
  108. ctx,
  109. chainID,
  110. light.TrustOptions{
  111. Period: 4 * time.Hour,
  112. Height: 1,
  113. Hash: primaryHeaders[1].Hash(),
  114. },
  115. primary,
  116. []provider.Provider{witness},
  117. dbs.New(dbm.NewMemDB(), chainID),
  118. light.Logger(log.TestingLogger()),
  119. light.MaxRetryAttempts(1),
  120. )
  121. require.NoError(t, err)
  122. // Check verification returns an error.
  123. _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
  124. if assert.Error(t, err) {
  125. assert.Contains(t, err.Error(), "does not match primary")
  126. }
  127. // Check evidence was sent to both full nodes.
  128. // Common height should be set to the height of the divergent header in the instance
  129. // of an equivocation attack and the validator sets are the same as what the witness has
  130. evAgainstPrimary := &types.LightClientAttackEvidence{
  131. ConflictingBlock: &types.LightBlock{
  132. SignedHeader: primaryHeaders[divergenceHeight],
  133. ValidatorSet: primaryValidators[divergenceHeight],
  134. },
  135. CommonHeight: divergenceHeight,
  136. }
  137. assert.True(t, witness.HasEvidence(evAgainstPrimary))
  138. evAgainstWitness := &types.LightClientAttackEvidence{
  139. ConflictingBlock: &types.LightBlock{
  140. SignedHeader: witnessHeaders[divergenceHeight],
  141. ValidatorSet: witnessValidators[divergenceHeight],
  142. },
  143. CommonHeight: divergenceHeight,
  144. }
  145. assert.True(t, primary.HasEvidence(evAgainstWitness))
  146. }
  147. // 1. Different nodes therefore a divergent header is produced.
  148. // => light client returns an error upon creation because primary and witness
  149. // have a different view.
  150. func TestClientDivergentTraces1(t *testing.T) {
  151. primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
  152. firstBlock, err := primary.LightBlock(ctx, 1)
  153. require.NoError(t, err)
  154. witness := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
  155. _, err = light.NewClient(
  156. ctx,
  157. chainID,
  158. light.TrustOptions{
  159. Height: 1,
  160. Hash: firstBlock.Hash(),
  161. Period: 4 * time.Hour,
  162. },
  163. primary,
  164. []provider.Provider{witness},
  165. dbs.New(dbm.NewMemDB(), chainID),
  166. light.Logger(log.TestingLogger()),
  167. light.MaxRetryAttempts(1),
  168. )
  169. require.Error(t, err)
  170. assert.Contains(t, err.Error(), "does not match primary")
  171. }
  172. // 2. Two out of three nodes don't respond but the third has a header that matches
  173. // => verification should be successful and all the witnesses should remain
  174. func TestClientDivergentTraces2(t *testing.T) {
  175. primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime))
  176. firstBlock, err := primary.LightBlock(ctx, 1)
  177. require.NoError(t, err)
  178. c, err := light.NewClient(
  179. ctx,
  180. chainID,
  181. light.TrustOptions{
  182. Height: 1,
  183. Hash: firstBlock.Hash(),
  184. Period: 4 * time.Hour,
  185. },
  186. primary,
  187. []provider.Provider{deadNode, deadNode, primary},
  188. dbs.New(dbm.NewMemDB(), chainID),
  189. light.Logger(log.TestingLogger()),
  190. light.MaxRetryAttempts(1),
  191. )
  192. require.NoError(t, err)
  193. _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
  194. assert.NoError(t, err)
  195. assert.Equal(t, 3, len(c.Witnesses()))
  196. }
  197. // 3. witness has the same first header, but different second header
  198. // => creation should succeed, but the verification should fail
  199. func TestClientDivergentTraces3(t *testing.T) {
  200. _, primaryHeaders, primaryVals := genMockNode(chainID, 10, 5, 2, bTime)
  201. primary := mockp.New(chainID, primaryHeaders, primaryVals)
  202. firstBlock, err := primary.LightBlock(ctx, 1)
  203. require.NoError(t, err)
  204. _, mockHeaders, mockVals := genMockNode(chainID, 10, 5, 2, bTime)
  205. mockHeaders[1] = primaryHeaders[1]
  206. mockVals[1] = primaryVals[1]
  207. witness := mockp.New(chainID, mockHeaders, mockVals)
  208. c, err := light.NewClient(
  209. ctx,
  210. chainID,
  211. light.TrustOptions{
  212. Height: 1,
  213. Hash: firstBlock.Hash(),
  214. Period: 4 * time.Hour,
  215. },
  216. primary,
  217. []provider.Provider{witness},
  218. dbs.New(dbm.NewMemDB(), chainID),
  219. light.Logger(log.TestingLogger()),
  220. light.MaxRetryAttempts(1),
  221. )
  222. require.NoError(t, err)
  223. _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour))
  224. assert.Error(t, err)
  225. assert.Equal(t, 0, len(c.Witnesses()))
  226. }