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  }