github.com/Finschia/finschia-sdk@v0.48.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  	abci "github.com/tendermint/tendermint/abci/types"
    11  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    12  
    13  	ocabci "github.com/Finschia/ostracon/abci/types"
    14  	cryptoenc "github.com/Finschia/ostracon/crypto/encoding"
    15  	ostbytes "github.com/Finschia/ostracon/libs/bytes"
    16  )
    17  
    18  type mockValidator struct {
    19  	val           abci.ValidatorUpdate
    20  	livenessState int
    21  }
    22  
    23  func (mv mockValidator) String() string {
    24  	return fmt.Sprintf("mockValidator{%s power:%v state:%v}",
    25  		mv.val.PubKey.String(),
    26  		mv.val.Power,
    27  		mv.livenessState)
    28  }
    29  
    30  type mockValidators map[string]mockValidator
    31  
    32  // get mockValidators from abci validators
    33  func newMockValidators(r *rand.Rand, abciVals []abci.ValidatorUpdate, params Params) mockValidators {
    34  	validators := make(mockValidators)
    35  
    36  	for _, validator := range abciVals {
    37  		str := fmt.Sprintf("%X", validator.PubKey.GetEd25519())
    38  		liveliness := GetMemberOfInitialState(r, params.InitialLivenessWeightings())
    39  
    40  		validators[str] = mockValidator{
    41  			val:           validator,
    42  			livenessState: liveliness,
    43  		}
    44  	}
    45  
    46  	return validators
    47  }
    48  
    49  // TODO describe usage
    50  func (vals mockValidators) getKeys() []string {
    51  	keys := make([]string, len(vals))
    52  	i := 0
    53  
    54  	for key := range vals {
    55  		keys[i] = key
    56  		i++
    57  	}
    58  
    59  	sort.Strings(keys)
    60  
    61  	return keys
    62  }
    63  
    64  // randomProposer picks a random proposer from the current validator set
    65  func (vals mockValidators) randomProposer(r *rand.Rand) ostbytes.HexBytes {
    66  	keys := vals.getKeys()
    67  	if len(keys) == 0 {
    68  		return nil
    69  	}
    70  
    71  	key := keys[r.Intn(len(keys))]
    72  
    73  	proposer := vals[key].val
    74  	pk, err := cryptoenc.PubKeyFromProto(&proposer.PubKey)
    75  	if err != nil { //nolint:wsl
    76  		panic(err)
    77  	}
    78  
    79  	return pk.Address()
    80  }
    81  
    82  // updateValidators mimics Ostracon's update logic.
    83  func updateValidators(
    84  	tb testing.TB,
    85  	r *rand.Rand,
    86  	params Params,
    87  	current map[string]mockValidator,
    88  	updates []abci.ValidatorUpdate,
    89  	event func(route, op, evResult string),
    90  ) map[string]mockValidator {
    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  }