github.com/Finschia/finschia-sdk@v0.49.1/x/simulation/mock_ostracon.go (about)

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