github.com/cosmos/cosmos-sdk@v0.50.10/x/simulation/mock_cometbft.go (about) 1 package simulation 2 3 import ( 4 "fmt" 5 "math/rand" 6 "sort" 7 "testing" 8 "time" 9 10 abci "github.com/cometbft/cometbft/abci/types" 11 cryptoenc "github.com/cometbft/cometbft/crypto/encoding" 12 cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" 13 ) 14 15 type mockValidator struct { 16 val abci.ValidatorUpdate 17 livenessState int 18 } 19 20 func (mv mockValidator) String() string { 21 return fmt.Sprintf("mockValidator{%s power:%v state:%v}", 22 mv.val.PubKey.String(), 23 mv.val.Power, 24 mv.livenessState) 25 } 26 27 type mockValidators map[string]mockValidator 28 29 // get mockValidators from abci validators 30 func newMockValidators(r *rand.Rand, abciVals []abci.ValidatorUpdate, params Params) mockValidators { 31 validators := make(mockValidators) 32 33 for _, validator := range abciVals { 34 str := fmt.Sprintf("%X", validator.PubKey.GetEd25519()) 35 liveliness := GetMemberOfInitialState(r, params.InitialLivenessWeightings()) 36 37 validators[str] = mockValidator{ 38 val: validator, 39 livenessState: liveliness, 40 } 41 } 42 43 return validators 44 } 45 46 // TODO describe usage 47 func (vals mockValidators) getKeys() []string { 48 keys := make([]string, len(vals)) 49 i := 0 50 51 for key := range vals { 52 keys[i] = key 53 i++ 54 } 55 56 sort.Strings(keys) 57 58 return keys 59 } 60 61 // randomProposer picks a random proposer from the current validator set 62 func (vals mockValidators) randomProposer(r *rand.Rand) []byte { 63 keys := vals.getKeys() 64 if len(keys) == 0 { 65 return nil 66 } 67 68 key := keys[r.Intn(len(keys))] 69 70 proposer := vals[key].val 71 pk, err := cryptoenc.PubKeyFromProto(proposer.PubKey) 72 if err != nil { 73 panic(err) 74 } 75 76 return pk.Address() 77 } 78 79 // updateValidators mimics CometBFT's update logic. 80 func updateValidators( 81 tb testing.TB, 82 r *rand.Rand, 83 params Params, 84 current map[string]mockValidator, 85 updates []abci.ValidatorUpdate, 86 event func(route, op, evResult string), 87 ) map[string]mockValidator { 88 for _, update := range updates { 89 str := fmt.Sprintf("%X", update.PubKey.GetEd25519()) 90 91 if update.Power == 0 { 92 if _, ok := current[str]; !ok { 93 tb.Fatalf("tried to delete a nonexistent validator: %s", str) 94 } 95 96 event("end_block", "validator_updates", "kicked") 97 delete(current, str) 98 } else if _, ok := current[str]; ok { 99 // validator already exists 100 event("end_block", "validator_updates", "updated") 101 } else { 102 // Set this new validator 103 current[str] = mockValidator{ 104 update, 105 GetMemberOfInitialState(r, params.InitialLivenessWeightings()), 106 } 107 event("end_block", "validator_updates", "added") 108 } 109 } 110 111 return current 112 } 113 114 // RandomRequestFinalizeBlock generates a list of signing validators according to 115 // the provided list of validators, signing fraction, and evidence fraction 116 func RandomRequestFinalizeBlock( 117 r *rand.Rand, 118 params Params, 119 validators mockValidators, 120 pastTimes []time.Time, 121 pastVoteInfos [][]abci.VoteInfo, 122 event func(route, op, evResult string), 123 blockHeight int64, 124 time time.Time, 125 proposer []byte, 126 ) *abci.RequestFinalizeBlock { 127 if len(validators) == 0 { 128 return &abci.RequestFinalizeBlock{ 129 Height: blockHeight, 130 Time: time, 131 ProposerAddress: proposer, 132 } 133 } 134 135 voteInfos := make([]abci.VoteInfo, len(validators)) 136 137 for i, key := range validators.getKeys() { 138 mVal := validators[key] 139 mVal.livenessState = params.LivenessTransitionMatrix().NextState(r, mVal.livenessState) 140 signed := true 141 142 if mVal.livenessState == 1 { 143 // spotty connection, 50% probability of success 144 // See https://github.com/golang/go/issues/23804#issuecomment-365370418 145 // for reasoning behind computing like this 146 signed = r.Int63()%2 == 0 147 } else if mVal.livenessState == 2 { 148 // offline 149 signed = false 150 } 151 152 if signed { 153 event("begin_block", "signing", "signed") 154 } else { 155 event("begin_block", "signing", "missed") 156 } 157 158 pubkey, err := cryptoenc.PubKeyFromProto(mVal.val.PubKey) 159 if err != nil { 160 panic(err) 161 } 162 163 voteInfos[i] = abci.VoteInfo{ 164 Validator: abci.Validator{ 165 Address: pubkey.Address(), 166 Power: mVal.val.Power, 167 }, 168 BlockIdFlag: cmtproto.BlockIDFlagCommit, 169 } 170 } 171 172 // return if no past times 173 if len(pastTimes) == 0 { 174 return &abci.RequestFinalizeBlock{ 175 Height: blockHeight, 176 Time: time, 177 ProposerAddress: proposer, 178 DecidedLastCommit: abci.CommitInfo{ 179 Votes: voteInfos, 180 }, 181 } 182 } 183 184 // TODO: Determine capacity before allocation 185 evidence := make([]abci.Misbehavior, 0) 186 187 for r.Float64() < params.EvidenceFraction() { 188 vals := voteInfos 189 height := blockHeight 190 misbehaviorTime := time 191 if r.Float64() < params.PastEvidenceFraction() && height > 1 { 192 height = int64(r.Intn(int(height)-1)) + 1 // CometBFT starts at height 1 193 // array indices offset by one 194 misbehaviorTime = pastTimes[height-1] 195 vals = pastVoteInfos[height-1] 196 } 197 198 validator := vals[r.Intn(len(vals))].Validator 199 200 var totalVotingPower int64 201 for _, val := range vals { 202 totalVotingPower += val.Validator.Power 203 } 204 205 evidence = append(evidence, 206 abci.Misbehavior{ 207 Type: abci.MisbehaviorType_DUPLICATE_VOTE, 208 Validator: validator, 209 Height: height, 210 Time: misbehaviorTime, 211 TotalVotingPower: totalVotingPower, 212 }, 213 ) 214 215 event("begin_block", "evidence", "ok") 216 } 217 218 return &abci.RequestFinalizeBlock{ 219 Height: blockHeight, 220 Time: time, 221 ProposerAddress: proposer, 222 DecidedLastCommit: abci.CommitInfo{ 223 Votes: voteInfos, 224 }, 225 Misbehavior: evidence, 226 } 227 }