github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/test/e2e/runner/evidence.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "math/rand" 10 "os" 11 "path/filepath" 12 "time" 13 14 "github.com/ari-anchor/sei-tendermint/crypto" 15 "github.com/ari-anchor/sei-tendermint/internal/test/factory" 16 "github.com/ari-anchor/sei-tendermint/libs/log" 17 "github.com/ari-anchor/sei-tendermint/privval" 18 tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types" 19 e2e "github.com/ari-anchor/sei-tendermint/test/e2e/pkg" 20 "github.com/ari-anchor/sei-tendermint/types" 21 "github.com/ari-anchor/sei-tendermint/version" 22 ) 23 24 // 1 in 4 evidence is light client evidence, the rest is duplicate vote evidence 25 const lightClientEvidenceRatio = 4 26 27 // InjectEvidence takes a running testnet and generates an amount of valid 28 // evidence and broadcasts it to a random node through the rpc endpoint `/broadcast_evidence`. 29 // Evidence is random and can be a mixture of LightClientAttackEvidence and 30 // DuplicateVoteEvidence. 31 func InjectEvidence(ctx context.Context, logger log.Logger, r *rand.Rand, testnet *e2e.Testnet, amount int) error { 32 // select a random node 33 var targetNode *e2e.Node 34 35 for _, idx := range r.Perm(len(testnet.Nodes)) { 36 if !testnet.Nodes[idx].Stateless() { 37 targetNode = testnet.Nodes[idx] 38 break 39 } 40 } 41 42 if targetNode == nil { 43 return errors.New("could not find node to inject evidence into") 44 } 45 46 logger.Info(fmt.Sprintf("Injecting evidence through %v (amount: %d)...", targetNode.Name, amount)) 47 48 client, err := targetNode.Client() 49 if err != nil { 50 return err 51 } 52 53 // request the latest block and validator set from the node 54 blockRes, err := client.Block(ctx, nil) 55 if err != nil { 56 return err 57 } 58 evidenceHeight := blockRes.Block.Height - 3 59 60 nValidators := 100 61 valRes, err := client.Validators(ctx, &evidenceHeight, nil, &nValidators) 62 if err != nil { 63 return err 64 } 65 valSet, err := types.ValidatorSetFromExistingValidators(valRes.Validators) 66 if err != nil { 67 return err 68 } 69 70 // get the private keys of all the validators in the network 71 privVals, err := getPrivateValidatorKeys(testnet) 72 if err != nil { 73 return err 74 } 75 76 // request the latest block and validator set from the node 77 blockRes, err = client.Block(ctx, &evidenceHeight) 78 if err != nil { 79 return err 80 } 81 82 var ev types.Evidence 83 for i := 1; i <= amount; i++ { 84 if i%lightClientEvidenceRatio == 0 { 85 ev, err = generateLightClientAttackEvidence(ctx, 86 privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, 87 ) 88 } else { 89 var dve *types.DuplicateVoteEvidence 90 dve, err = generateDuplicateVoteEvidence(ctx, 91 privVals, evidenceHeight, valSet, testnet.Name, blockRes.Block.Time, 92 ) 93 if dve.VoteA.Height < testnet.VoteExtensionsEnableHeight { 94 dve.VoteA.StripExtension() 95 dve.VoteB.StripExtension() 96 } 97 ev = dve 98 } 99 if err != nil { 100 return err 101 } 102 103 _, err := client.BroadcastEvidence(ctx, ev) 104 if err != nil { 105 return err 106 } 107 } 108 109 logger.Info("Finished sending evidence", 110 "node", testnet.Name, 111 "amount", amount, 112 "height", evidenceHeight, 113 ) 114 115 wctx, cancel := context.WithTimeout(ctx, time.Minute) 116 defer cancel() 117 118 // wait for the node to make progress after submitting 119 // evidence (3 (forged height) + 1 (progress)) 120 _, err = waitForNode(wctx, logger, targetNode, evidenceHeight+4) 121 if err != nil { 122 return err 123 } 124 125 return nil 126 } 127 128 func getPrivateValidatorKeys(testnet *e2e.Testnet) ([]types.MockPV, error) { 129 privVals := []types.MockPV{} 130 131 for _, node := range testnet.Nodes { 132 if node.Mode == e2e.ModeValidator { 133 privKeyPath := filepath.Join(testnet.Dir, node.Name, PrivvalKeyFile) 134 privKey, err := readPrivKey(privKeyPath) 135 if err != nil { 136 return nil, err 137 } 138 // Create mock private validators from the validators private key. MockPV is 139 // stateless which means we can double vote and do other funky stuff 140 privVals = append(privVals, types.NewMockPVWithParams(privKey, false, false)) 141 } 142 } 143 144 return privVals, nil 145 } 146 147 // creates evidence of a lunatic attack. The height provided is the common height. 148 // The forged height happens 2 blocks later. 149 func generateLightClientAttackEvidence( 150 ctx context.Context, 151 privVals []types.MockPV, 152 height int64, 153 vals *types.ValidatorSet, 154 chainID string, 155 evTime time.Time, 156 ) (*types.LightClientAttackEvidence, error) { 157 // forge a random header 158 forgedHeight := height + 2 159 forgedTime := evTime.Add(1 * time.Second) 160 header := makeHeaderRandom(chainID, forgedHeight) 161 header.Time = forgedTime 162 163 // add a new bogus validator and remove an existing one to 164 // vary the validator set slightly 165 pv, conflictingVals, err := mutateValidatorSet(ctx, privVals, vals) 166 if err != nil { 167 return nil, err 168 } 169 170 header.ValidatorsHash = conflictingVals.Hash() 171 172 // create a commit for the forged header 173 blockID := makeBlockID(header.Hash(), 1000, []byte("partshash")) 174 voteSet := types.NewExtendedVoteSet(chainID, forgedHeight, 0, tmproto.SignedMsgType(2), conflictingVals) 175 176 ec, err := factory.MakeExtendedCommit(ctx, blockID, forgedHeight, 0, voteSet, pv, forgedTime) 177 if err != nil { 178 return nil, err 179 } 180 181 ev := &types.LightClientAttackEvidence{ 182 ConflictingBlock: &types.LightBlock{ 183 SignedHeader: &types.SignedHeader{ 184 Header: header, 185 Commit: ec.ToCommit(), 186 }, 187 ValidatorSet: conflictingVals, 188 }, 189 CommonHeight: height, 190 TotalVotingPower: vals.TotalVotingPower(), 191 Timestamp: evTime, 192 } 193 ev.ByzantineValidators = ev.GetByzantineValidators(vals, &types.SignedHeader{ 194 Header: makeHeaderRandom(chainID, forgedHeight), 195 }) 196 return ev, nil 197 } 198 199 // generateDuplicateVoteEvidence picks a random validator from the val set and 200 // returns duplicate vote evidence against the validator 201 func generateDuplicateVoteEvidence( 202 ctx context.Context, 203 privVals []types.MockPV, 204 height int64, 205 vals *types.ValidatorSet, 206 chainID string, 207 time time.Time, 208 ) (*types.DuplicateVoteEvidence, error) { 209 privVal, valIdx, err := getRandomValidatorIndex(privVals, vals) 210 if err != nil { 211 return nil, err 212 } 213 214 voteA, err := factory.MakeVote(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time) 215 if err != nil { 216 return nil, err 217 } 218 voteB, err := factory.MakeVote(ctx, privVal, chainID, valIdx, height, 0, 2, makeRandomBlockID(), time) 219 if err != nil { 220 return nil, err 221 } 222 ev, err := types.NewDuplicateVoteEvidence(voteA, voteB, time, vals) 223 if err != nil { 224 return nil, fmt.Errorf("could not generate evidence: %w", err) 225 } 226 227 return ev, nil 228 } 229 230 // getRandomValidatorIndex picks a random validator from a slice of mock PrivVals that's 231 // also part of the validator set, returning the PrivVal and its index in the validator set 232 func getRandomValidatorIndex(privVals []types.MockPV, vals *types.ValidatorSet) (types.MockPV, int32, error) { 233 for _, idx := range rand.Perm(len(privVals)) { 234 pv := privVals[idx] 235 valIdx, _ := vals.GetByAddress(pv.PrivKey.PubKey().Address()) 236 if valIdx >= 0 { 237 return pv, valIdx, nil 238 } 239 } 240 return types.MockPV{}, -1, errors.New("no private validator found in validator set") 241 } 242 243 func readPrivKey(keyFilePath string) (crypto.PrivKey, error) { 244 keyJSONBytes, err := os.ReadFile(keyFilePath) 245 if err != nil { 246 return nil, err 247 } 248 pvKey := privval.FilePVKey{} 249 err = json.Unmarshal(keyJSONBytes, &pvKey) 250 if err != nil { 251 return nil, fmt.Errorf("error reading PrivValidator key from %v: %w", keyFilePath, err) 252 } 253 254 return pvKey.PrivKey, nil 255 } 256 257 func makeHeaderRandom(chainID string, height int64) *types.Header { 258 return &types.Header{ 259 Version: version.Consensus{Block: version.BlockProtocol, App: 1}, 260 ChainID: chainID, 261 Height: height, 262 Time: time.Now(), 263 LastBlockID: makeBlockID([]byte("headerhash"), 1000, []byte("partshash")), 264 LastCommitHash: crypto.CRandBytes(crypto.HashSize), 265 DataHash: crypto.CRandBytes(crypto.HashSize), 266 ValidatorsHash: crypto.CRandBytes(crypto.HashSize), 267 NextValidatorsHash: crypto.CRandBytes(crypto.HashSize), 268 ConsensusHash: crypto.CRandBytes(crypto.HashSize), 269 AppHash: crypto.CRandBytes(crypto.HashSize), 270 LastResultsHash: crypto.CRandBytes(crypto.HashSize), 271 EvidenceHash: crypto.CRandBytes(crypto.HashSize), 272 ProposerAddress: crypto.CRandBytes(crypto.AddressSize), 273 } 274 } 275 276 func makeRandomBlockID() types.BlockID { 277 return makeBlockID(crypto.CRandBytes(crypto.HashSize), 100, crypto.CRandBytes(crypto.HashSize)) 278 } 279 280 func makeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) types.BlockID { 281 var ( 282 h = make([]byte, crypto.HashSize) 283 psH = make([]byte, crypto.HashSize) 284 ) 285 copy(h, hash) 286 copy(psH, partSetHash) 287 return types.BlockID{ 288 Hash: h, 289 PartSetHeader: types.PartSetHeader{ 290 Total: partSetSize, 291 Hash: psH, 292 }, 293 } 294 } 295 296 func mutateValidatorSet(ctx context.Context, privVals []types.MockPV, vals *types.ValidatorSet) ([]types.PrivValidator, *types.ValidatorSet, error) { 297 newVal, newPrivVal, err := factory.Validator(ctx, 10) 298 if err != nil { 299 return nil, nil, err 300 } 301 302 var newVals *types.ValidatorSet 303 if vals.Size() > 2 { 304 newVals = types.NewValidatorSet(append(vals.Copy().Validators[:vals.Size()-1], newVal)) 305 } else { 306 newVals = types.NewValidatorSet(append(vals.Copy().Validators, newVal)) 307 } 308 309 // we need to sort the priv validators with the same index as the validator set 310 pv := make([]types.PrivValidator, newVals.Size()) 311 for idx, val := range newVals.Validators { 312 found := false 313 for _, p := range append(privVals, newPrivVal.(types.MockPV)) { 314 if bytes.Equal(p.PrivKey.PubKey().Address(), val.Address) { 315 pv[idx] = p 316 found = true 317 break 318 } 319 } 320 if !found { 321 return nil, nil, fmt.Errorf("missing priv validator for %v", val.Address) 322 } 323 } 324 325 return pv, newVals, nil 326 }