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