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.

271 lines
8.1 KiB

  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io/ioutil"
  7. "math/rand"
  8. "path/filepath"
  9. "time"
  10. "github.com/tendermint/tendermint/crypto"
  11. "github.com/tendermint/tendermint/crypto/tmhash"
  12. tmjson "github.com/tendermint/tendermint/libs/json"
  13. "github.com/tendermint/tendermint/privval"
  14. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  15. e2e "github.com/tendermint/tendermint/test/e2e/pkg"
  16. "github.com/tendermint/tendermint/types"
  17. "github.com/tendermint/tendermint/version"
  18. )
  19. // 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence
  20. const lightClientEvidenceRatio = 4
  21. // InjectEvidence takes a running testnet and generates an amount of valid
  22. // evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`.
  23. // Evidence is random and can be a mixture of LightClientAttackEvidence and
  24. // DuplicateVoteEvidence.
  25. func InjectEvidence(testnet *e2e.Testnet, amount int) error {
  26. // select a random node
  27. targetNode := testnet.RandomNode()
  28. logger.Info(fmt.Sprintf("Injecting evidence through %v (amount: %d)...", targetNode.Name, amount))
  29. client, err := targetNode.Client()
  30. if err != nil {
  31. return err
  32. }
  33. // request the latest block and validator set from the node
  34. blockRes, err := client.Block(context.Background(), nil)
  35. if err != nil {
  36. return err
  37. }
  38. lightEvidenceCommonHeight := blockRes.Block.Height
  39. waitHeight := blockRes.Block.Height + 3
  40. duplicateVoteHeight := waitHeight
  41. nValidators := 100
  42. valRes, err := client.Validators(context.Background(), &lightEvidenceCommonHeight, nil, &nValidators)
  43. if err != nil {
  44. return err
  45. }
  46. valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators)
  47. if err != nil {
  48. return err
  49. }
  50. // get the private keys of all the validators in the network
  51. privVals, err := getPrivateValidatorKeys(testnet)
  52. if err != nil {
  53. return err
  54. }
  55. // wait for the node to reach the height above the forged height so that
  56. // it is able to validate the evidence
  57. status, err := waitForNode(targetNode, waitHeight, 10*time.Second)
  58. if err != nil {
  59. return err
  60. }
  61. duplicateVoteTime := status.SyncInfo.LatestBlockTime
  62. var ev types.Evidence
  63. for i := 1; i <= amount; i++ {
  64. if i%lightClientEvidenceRatio == 0 {
  65. ev, err = generateLightClientAttackEvidence(
  66. privVals, lightEvidenceCommonHeight, valSet, testnet.Name, blockRes.Block.Time,
  67. )
  68. } else {
  69. ev, err = generateDuplicateVoteEvidence(
  70. privVals, duplicateVoteHeight, valSet, testnet.Name, duplicateVoteTime,
  71. )
  72. }
  73. if err != nil {
  74. return err
  75. }
  76. _, err := client.BroadcastEvidence(context.Background(), ev)
  77. if err != nil {
  78. return err
  79. }
  80. }
  81. logger.Info(fmt.Sprintf("Finished sending evidence (height %d)", blockRes.Block.Height+2))
  82. return nil
  83. }
  84. func getPrivateValidatorKeys(testnet *e2e.Testnet) ([]types.MockPV, error) {
  85. privVals := []types.MockPV{}
  86. for _, node := range testnet.Nodes {
  87. if node.Mode == e2e.ModeValidator {
  88. privKeyPath := filepath.Join(testnet.Dir, node.Name, PrivvalKeyFile)
  89. privKey, err := readPrivKey(privKeyPath)
  90. if err != nil {
  91. return nil, err
  92. }
  93. // Create mock private validators from the validators private key. MockPV is
  94. // stateless which means we can double vote and do other funky stuff
  95. privVals = append(privVals, types.NewMockPVWithParams(privKey, false, false))
  96. }
  97. }
  98. return privVals, nil
  99. }
  100. // creates evidence of a lunatic attack. The height provided is the common height.
  101. // The forged height happens 2 blocks later.
  102. func generateLightClientAttackEvidence(
  103. privVals []types.MockPV,
  104. height int64,
  105. vals *types.ValidatorSet,
  106. chainID string,
  107. evTime time.Time,
  108. ) (*types.LightClientAttackEvidence, error) {
  109. // forge a random header
  110. forgedHeight := height + 2
  111. forgedTime := evTime.Add(1 * time.Second)
  112. header := makeHeaderRandom(chainID, forgedHeight)
  113. header.Time = forgedTime
  114. // add a new bogus validator and remove an existing one to
  115. // vary the validator set slightly
  116. pv, conflictingVals, err := mutateValidatorSet(privVals, vals)
  117. if err != nil {
  118. return nil, err
  119. }
  120. header.ValidatorsHash = conflictingVals.Hash()
  121. // create a commit for the forged header
  122. blockID := makeBlockID(header.Hash(), 1000, []byte("partshash"))
  123. voteSet := types.NewVoteSet(chainID, forgedHeight, 0, tmproto.SignedMsgType(2), conflictingVals)
  124. commit, err := types.MakeCommit(blockID, forgedHeight, 0, voteSet, pv, forgedTime)
  125. if err != nil {
  126. return nil, err
  127. }
  128. ev := &types.LightClientAttackEvidence{
  129. ConflictingBlock: &types.LightBlock{
  130. SignedHeader: &types.SignedHeader{
  131. Header: header,
  132. Commit: commit,
  133. },
  134. ValidatorSet: conflictingVals,
  135. },
  136. CommonHeight: height,
  137. TotalVotingPower: vals.TotalVotingPower(),
  138. Timestamp: evTime,
  139. }
  140. ev.ByzantineValidators = ev.GetByzantineValidators(vals, &types.SignedHeader{
  141. Header: makeHeaderRandom(chainID, forgedHeight),
  142. })
  143. return ev, nil
  144. }
  145. // generateDuplicateVoteEvidence picks a random validator from the val set and
  146. // returns duplicate vote evidence against the validator
  147. func generateDuplicateVoteEvidence(
  148. privVals []types.MockPV,
  149. height int64,
  150. vals *types.ValidatorSet,
  151. chainID string,
  152. time time.Time,
  153. ) (*types.DuplicateVoteEvidence, error) {
  154. // nolint:gosec // G404: Use of weak random number generator
  155. privVal := privVals[rand.Intn(len(privVals))]
  156. voteA, err := types.MakeVote(height, makeRandomBlockID(), vals, privVal, chainID, time)
  157. if err != nil {
  158. return nil, err
  159. }
  160. voteB, err := types.MakeVote(height, makeRandomBlockID(), vals, privVal, chainID, time)
  161. if err != nil {
  162. return nil, err
  163. }
  164. return types.NewDuplicateVoteEvidence(voteA, voteB, time, vals), nil
  165. }
  166. func readPrivKey(keyFilePath string) (crypto.PrivKey, error) {
  167. keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
  168. if err != nil {
  169. return nil, err
  170. }
  171. pvKey := privval.FilePVKey{}
  172. err = tmjson.Unmarshal(keyJSONBytes, &pvKey)
  173. if err != nil {
  174. return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err)
  175. }
  176. return pvKey.PrivKey, nil
  177. }
  178. func makeHeaderRandom(chainID string, height int64) *types.Header {
  179. return &types.Header{
  180. Version: version.Consensus{Block: version.BlockProtocol, App: 1},
  181. ChainID: chainID,
  182. Height: height,
  183. Time: time.Now(),
  184. LastBlockID: makeBlockID([]byte("headerhash"), 1000, []byte("partshash")),
  185. LastCommitHash: crypto.CRandBytes(tmhash.Size),
  186. DataHash: crypto.CRandBytes(tmhash.Size),
  187. ValidatorsHash: crypto.CRandBytes(tmhash.Size),
  188. NextValidatorsHash: crypto.CRandBytes(tmhash.Size),
  189. ConsensusHash: crypto.CRandBytes(tmhash.Size),
  190. AppHash: crypto.CRandBytes(tmhash.Size),
  191. LastResultsHash: crypto.CRandBytes(tmhash.Size),
  192. EvidenceHash: crypto.CRandBytes(tmhash.Size),
  193. ProposerAddress: crypto.CRandBytes(crypto.AddressSize),
  194. }
  195. }
  196. func makeRandomBlockID() types.BlockID {
  197. return makeBlockID(crypto.CRandBytes(tmhash.Size), 100, crypto.CRandBytes(tmhash.Size))
  198. }
  199. func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID {
  200. var (
  201. h = make([]byte, tmhash.Size)
  202. psH = make([]byte, tmhash.Size)
  203. )
  204. copy(h, hash)
  205. copy(psH, partSetHash)
  206. return types.BlockID{
  207. Hash: h,
  208. PartSetHeader: types.PartSetHeader{
  209. Total: partSetSize,
  210. Hash: psH,
  211. },
  212. }
  213. }
  214. func mutateValidatorSet(privVals []types.MockPV, vals *types.ValidatorSet,
  215. ) ([]types.PrivValidator, *types.ValidatorSet, error) {
  216. newVal, newPrivVal := types.RandValidator(false, 10)
  217. var newVals *types.ValidatorSet
  218. if vals.Size() > 2 {
  219. newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal))
  220. } else {
  221. newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal))
  222. }
  223. // we need to sort the priv validators with the same index as the validator set
  224. pv := make([]types.PrivValidator, newVals.Size())
  225. for idx, val := range newVals.Validators {
  226. found := false
  227. for _, p := range append(privVals, newPrivVal.(types.MockPV)) {
  228. if bytes.Equal(p.PrivKey.PubKey().Address(), val.Address) {
  229. pv[idx] = p
  230. found = true
  231. break
  232. }
  233. }
  234. if !found {
  235. return nil, nil, fmt.Errorf("missing priv validator for %v", val.Address)
  236. }
  237. }
  238. return pv, newVals, nil
  239. }