mock tendermint
This commit is contained in:
parent
ff327049ee
commit
fee0763b5e
|
@ -0,0 +1,198 @@
|
||||||
|
package simulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockValidator struct {
|
||||||
|
val abci.ValidatorUpdate
|
||||||
|
livenessState int
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockValidators map[string]mockValidator
|
||||||
|
|
||||||
|
// get mockValidators from abci validators
|
||||||
|
func newMockValidators(abciVals abci.ValidatorUpdate) mockValidators {
|
||||||
|
|
||||||
|
validators = make(mockValidators)
|
||||||
|
for _, validator := range abciVals {
|
||||||
|
str := fmt.Sprintf("%v", validator.PubKey)
|
||||||
|
liveliness := GetMemberOfInitialState(r,
|
||||||
|
params.InitialLivenessWeightings)
|
||||||
|
|
||||||
|
validators[str] = mockValidator{
|
||||||
|
val: validator,
|
||||||
|
livenessState: liveliness,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validators
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO describe usage
|
||||||
|
func (vals mockValidators) getKeys() []string {
|
||||||
|
keys := make([]string, len(validators))
|
||||||
|
i := 0
|
||||||
|
for key := range validators {
|
||||||
|
keys[i] = key
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
//_________________________________________________________________________________
|
||||||
|
|
||||||
|
// randomProposer picks a random proposer from the current validator set
|
||||||
|
func randomProposer(r *rand.Rand, validators map[string]mockValidator) cmn.HexBytes {
|
||||||
|
keys := validators.getKeys()
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
key := keys[r.Intn(len(keys))]
|
||||||
|
proposer := validators[key].val
|
||||||
|
pk, err := tmtypes.PB2TM.PubKey(proposer.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return pk.Address()
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateValidators mimicks Tendermint's update logic
|
||||||
|
// nolint: unparam
|
||||||
|
func updateValidators(tb testing.TB, r *rand.Rand, params Params,
|
||||||
|
current map[string]mockValidator, updates []abci.ValidatorUpdate,
|
||||||
|
event func(string)) map[string]mockValidator {
|
||||||
|
|
||||||
|
for _, update := range updates {
|
||||||
|
str := fmt.Sprintf("%v", update.PubKey)
|
||||||
|
if update.Power == 0 {
|
||||||
|
if _, ok := current[str]; !ok {
|
||||||
|
tb.Fatalf("tried to delete a nonexistent validator")
|
||||||
|
}
|
||||||
|
event("endblock/validatorupdates/kicked")
|
||||||
|
delete(current, str)
|
||||||
|
|
||||||
|
} else if mVal, ok := current[str]; ok {
|
||||||
|
// validator already exists
|
||||||
|
mVal.val = update
|
||||||
|
event("endblock/validatorupdates/updated")
|
||||||
|
} else {
|
||||||
|
// Set this new validator
|
||||||
|
current[str] = mockValidator{
|
||||||
|
update,
|
||||||
|
GetMemberOfInitialState(r, params.InitialLivenessWeightings),
|
||||||
|
}
|
||||||
|
event("endblock/validatorupdates/added")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomRequestBeginBlock generates a list of signing validators according to
|
||||||
|
// the provided list of validators, signing fraction, and evidence fraction
|
||||||
|
func RandomRequestBeginBlock(r *rand.Rand, params Params,
|
||||||
|
validators mockValidators, pastTimes []time.Time,
|
||||||
|
pastVoteInfos [][]abci.VoteInfo,
|
||||||
|
event func(string), header abci.Header) abci.RequestBeginBlock {
|
||||||
|
|
||||||
|
if len(validators) == 0 {
|
||||||
|
return abci.RequestBeginBlock{
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voteInfos := make([]abci.VoteInfo, len(validators))
|
||||||
|
for i, key := range validators.getKeys() {
|
||||||
|
mVal := validators[key]
|
||||||
|
mVal.livenessState = params.LivenessTransitionMatrix.NextState(r, mVal.livenessState)
|
||||||
|
signed := true
|
||||||
|
|
||||||
|
if mVal.livenessState == 1 {
|
||||||
|
// spotty connection, 50% probability of success
|
||||||
|
// See https://github.com/golang/go/issues/23804#issuecomment-365370418
|
||||||
|
// for reasoning behind computing like this
|
||||||
|
signed = r.Int63()%2 == 0
|
||||||
|
} else if mVal.livenessState == 2 {
|
||||||
|
// offline
|
||||||
|
signed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if signed {
|
||||||
|
event("beginblock/signing/signed")
|
||||||
|
} else {
|
||||||
|
event("beginblock/signing/missed")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, err := tmtypes.PB2TM.PubKey(mVal.val.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
voteInfos[i] = abci.VoteInfo{
|
||||||
|
Validator: abci.Validator{
|
||||||
|
Address: pubkey.Address(),
|
||||||
|
Power: mVal.val.Power,
|
||||||
|
},
|
||||||
|
SignedLastBlock: signed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return if no past times
|
||||||
|
if len(pastTimes) <= 0 {
|
||||||
|
return abci.RequestBeginBlock{
|
||||||
|
Header: header,
|
||||||
|
LastCommitInfo: abci.LastCommitInfo{
|
||||||
|
Votes: voteInfos,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Determine capacity before allocation
|
||||||
|
evidence := make([]abci.Evidence, 0)
|
||||||
|
for r.Float64() < params.EvidenceFraction {
|
||||||
|
|
||||||
|
height := header.Height
|
||||||
|
time := header.Time
|
||||||
|
vals := voteInfos
|
||||||
|
|
||||||
|
if r.Float64() < params.PastEvidenceFraction {
|
||||||
|
height = int64(r.Intn(int(header.Height) - 1))
|
||||||
|
time = pastTimes[height]
|
||||||
|
vals = pastVoteInfos[height]
|
||||||
|
}
|
||||||
|
validator := vals[r.Intn(len(vals))].Validator
|
||||||
|
|
||||||
|
var totalVotingPower int64
|
||||||
|
for _, val := range vals {
|
||||||
|
totalVotingPower += val.Validator.Power
|
||||||
|
}
|
||||||
|
|
||||||
|
evidence = append(evidence,
|
||||||
|
abci.Evidence{
|
||||||
|
Type: tmtypes.ABCIEvidenceTypeDuplicateVote,
|
||||||
|
Validator: validator,
|
||||||
|
Height: height,
|
||||||
|
Time: time,
|
||||||
|
TotalVotingPower: totalVotingPower,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
event("beginblock/evidence")
|
||||||
|
}
|
||||||
|
|
||||||
|
return abci.RequestBeginBlock{
|
||||||
|
Header: header,
|
||||||
|
LastCommitInfo: abci.LastCommitInfo{
|
||||||
|
Votes: voteInfos,
|
||||||
|
},
|
||||||
|
ByzantineValidators: evidence,
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
@ -25,7 +24,7 @@ type RandSetup func(r *rand.Rand, accounts []Account)
|
||||||
// Simulate tests application by sending random messages.
|
// Simulate tests application by sending random messages.
|
||||||
func Simulate(t *testing.T, app *baseapp.BaseApp,
|
func Simulate(t *testing.T, app *baseapp.BaseApp,
|
||||||
appStateFn func(r *rand.Rand, accs []Account) json.RawMessage,
|
appStateFn func(r *rand.Rand, accs []Account) json.RawMessage,
|
||||||
ops []WeightedOperation, setups []RandSetup,
|
ops WeightedOperations, setups []RandSetup,
|
||||||
invariants Invariants, numBlocks int, blockSize int, commit bool) error {
|
invariants Invariants, numBlocks int, blockSize int, commit bool) error {
|
||||||
|
|
||||||
time := time.Now().UnixNano()
|
time := time.Now().UnixNano()
|
||||||
|
@ -60,13 +59,17 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
|
||||||
// in case we have to end early, don't os.Exit so that we can run cleanup code.
|
// in case we have to end early, don't os.Exit so that we can run cleanup code.
|
||||||
stopEarly := false
|
stopEarly := false
|
||||||
testingMode, t, b := getTestingMode(tb)
|
testingMode, t, b := getTestingMode(tb)
|
||||||
fmt.Printf("Starting SimulateFromSeed with randomness created with seed %d\n", int(seed))
|
fmt.Printf("Starting SimulateFromSeed with randomness "+
|
||||||
|
"created with seed %d\n", int(seed))
|
||||||
|
|
||||||
r := rand.New(rand.NewSource(seed))
|
r := rand.New(rand.NewSource(seed))
|
||||||
params := RandomParams(r) // := DefaultParams()
|
params := RandomParams(r) // := DefaultParams()
|
||||||
fmt.Printf("Randomized simulation params: %+v\n", params)
|
fmt.Printf("Randomized simulation params: %+v\n", params)
|
||||||
|
|
||||||
timestamp := RandTimestamp(r)
|
timestamp := RandTimestamp(r)
|
||||||
fmt.Printf("Starting the simulation from time %v, unixtime %v\n",
|
fmt.Printf("Starting the simulation from time %v, unixtime %v\n",
|
||||||
timestamp.UTC().Format(time.UnixDate), timestamp.Unix())
|
timestamp.UTC().Format(time.UnixDate), timestamp.Unix())
|
||||||
|
|
||||||
timeDiff := maxTimePerBlock - minTimePerBlock
|
timeDiff := maxTimePerBlock - minTimePerBlock
|
||||||
|
|
||||||
accs := RandomAccounts(r, params.NumKeys)
|
accs := RandomAccounts(r, params.NumKeys)
|
||||||
|
@ -232,10 +235,10 @@ type blockSimFn func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
|
||||||
accounts []Account, header abci.Header, logWriter func(string)) (opCount int)
|
accounts []Account, header abci.Header, logWriter func(string)) (opCount int)
|
||||||
|
|
||||||
// Returns a function to simulate blocks. Written like this to avoid constant
|
// Returns a function to simulate blocks. Written like this to avoid constant
|
||||||
// parameters being passed everytime, to minimize memory overhead
|
// parameters being passed everytime, to minimize memory overhead.
|
||||||
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params Params,
|
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params Params,
|
||||||
event func(string), invariants Invariants, ops WeightedOperations,
|
event func(string), invariants Invariants, ops WeightedOperations,
|
||||||
operationQueue map[int][]Operation, timeOperationQueue []FutureOperation,
|
operationQueue OperationQueue, timeOperationQueue []FutureOperation,
|
||||||
totalNumBlocks int, avgBlockSize int, displayLogs func()) blockSimFn {
|
totalNumBlocks int, avgBlockSize int, displayLogs func()) blockSimFn {
|
||||||
|
|
||||||
var lastBlocksizeState = 0 // state for [4 * uniform distribution]
|
var lastBlocksizeState = 0 // state for [4 * uniform distribution]
|
||||||
|
@ -323,98 +326,3 @@ func runQueuedTimeOperations(queueOps []FutureOperation,
|
||||||
}
|
}
|
||||||
return numOpsRan
|
return numOpsRan
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandomRequestBeginBlock generates a list of signing validators according to
|
|
||||||
// the provided list of validators, signing fraction, and evidence fraction
|
|
||||||
func RandomRequestBeginBlock(r *rand.Rand, params Params,
|
|
||||||
validators map[string]mockValidator, pastTimes []time.Time,
|
|
||||||
pastVoteInfos [][]abci.VoteInfo,
|
|
||||||
event func(string), header abci.Header) abci.RequestBeginBlock {
|
|
||||||
|
|
||||||
if len(validators) == 0 {
|
|
||||||
return abci.RequestBeginBlock{
|
|
||||||
Header: header,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
voteInfos := make([]abci.VoteInfo, len(validators))
|
|
||||||
|
|
||||||
for i, key := range getKeys(validators) {
|
|
||||||
mVal := validators[key]
|
|
||||||
mVal.livenessState = params.LivenessTransitionMatrix.NextState(r, mVal.livenessState)
|
|
||||||
signed := true
|
|
||||||
|
|
||||||
if mVal.livenessState == 1 {
|
|
||||||
// spotty connection, 50% probability of success
|
|
||||||
// See https://github.com/golang/go/issues/23804#issuecomment-365370418
|
|
||||||
// for reasoning behind computing like this
|
|
||||||
signed = r.Int63()%2 == 0
|
|
||||||
} else if mVal.livenessState == 2 {
|
|
||||||
// offline
|
|
||||||
signed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if signed {
|
|
||||||
event("beginblock/signing/signed")
|
|
||||||
} else {
|
|
||||||
event("beginblock/signing/missed")
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey, err := tmtypes.PB2TM.PubKey(mVal.val.PubKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
voteInfos[i] = abci.VoteInfo{
|
|
||||||
Validator: abci.Validator{
|
|
||||||
Address: pubkey.Address(),
|
|
||||||
Power: mVal.val.Power,
|
|
||||||
},
|
|
||||||
SignedLastBlock: signed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return if no past times
|
|
||||||
if len(pastTimes) <= 0 {
|
|
||||||
return abci.RequestBeginBlock{
|
|
||||||
Header: header,
|
|
||||||
LastCommitInfo: abci.LastCommitInfo{
|
|
||||||
Votes: voteInfos,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Determine capacity before allocation
|
|
||||||
evidence := make([]abci.Evidence, 0)
|
|
||||||
for r.Float64() < params.EvidenceFraction {
|
|
||||||
height := header.Height
|
|
||||||
time := header.Time
|
|
||||||
vals := voteInfos
|
|
||||||
if r.Float64() < params.PastEvidenceFraction {
|
|
||||||
height = int64(r.Intn(int(header.Height) - 1))
|
|
||||||
time = pastTimes[height]
|
|
||||||
vals = pastVoteInfos[height]
|
|
||||||
}
|
|
||||||
validator := vals[r.Intn(len(vals))].Validator
|
|
||||||
var totalVotingPower int64
|
|
||||||
|
|
||||||
for _, val := range vals {
|
|
||||||
totalVotingPower += val.Validator.Power
|
|
||||||
}
|
|
||||||
|
|
||||||
evidence = append(evidence, abci.Evidence{
|
|
||||||
Type: tmtypes.ABCIEvidenceTypeDuplicateVote,
|
|
||||||
Validator: validator,
|
|
||||||
Height: height,
|
|
||||||
Time: time,
|
|
||||||
TotalVotingPower: totalVotingPower,
|
|
||||||
})
|
|
||||||
event("beginblock/evidence")
|
|
||||||
}
|
|
||||||
|
|
||||||
return abci.RequestBeginBlock{
|
|
||||||
Header: header,
|
|
||||||
LastCommitInfo: abci.LastCommitInfo{
|
|
||||||
Votes: voteInfos,
|
|
||||||
},
|
|
||||||
ByzantineValidators: evidence,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
package simulation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockValidator struct {
|
|
||||||
val abci.ValidatorUpdate
|
|
||||||
livenessState int
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockValidators map[string]mockValidator
|
|
||||||
|
|
||||||
// get mockValidators from abci validators
|
|
||||||
func newMockValidators(abciVals abci.ValidatorUpdate) mockValidators {
|
|
||||||
|
|
||||||
validators = make(mockValidators)
|
|
||||||
for _, validator := range abciVals {
|
|
||||||
str := fmt.Sprintf("%v", validator.PubKey)
|
|
||||||
liveliness := GetMemberOfInitialState(r,
|
|
||||||
params.InitialLivenessWeightings)
|
|
||||||
|
|
||||||
validators[str] = mockValidator{
|
|
||||||
val: validator,
|
|
||||||
livenessState: liveliness,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return validators
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO describe usage
|
|
||||||
func getKeys(validators map[string]mockValidator) []string {
|
|
||||||
keys := make([]string, len(validators))
|
|
||||||
i := 0
|
|
||||||
for key := range validators {
|
|
||||||
keys[i] = key
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// randomProposer picks a random proposer from the current validator set
|
|
||||||
func randomProposer(r *rand.Rand, validators map[string]mockValidator) cmn.HexBytes {
|
|
||||||
keys := getKeys(validators)
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
key := keys[r.Intn(len(keys))]
|
|
||||||
proposer := validators[key].val
|
|
||||||
pk, err := tmtypes.PB2TM.PubKey(proposer.PubKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return pk.Address()
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateValidators mimicks Tendermint's update logic
|
|
||||||
// nolint: unparam
|
|
||||||
func updateValidators(tb testing.TB, r *rand.Rand, params Params,
|
|
||||||
current map[string]mockValidator, updates []abci.ValidatorUpdate,
|
|
||||||
event func(string)) map[string]mockValidator {
|
|
||||||
|
|
||||||
for _, update := range updates {
|
|
||||||
str := fmt.Sprintf("%v", update.PubKey)
|
|
||||||
if update.Power == 0 {
|
|
||||||
if _, ok := current[str]; !ok {
|
|
||||||
tb.Fatalf("tried to delete a nonexistent validator")
|
|
||||||
}
|
|
||||||
event("endblock/validatorupdates/kicked")
|
|
||||||
delete(current, str)
|
|
||||||
|
|
||||||
} else if mVal, ok := current[str]; ok {
|
|
||||||
// validator already exists
|
|
||||||
mVal.val = update
|
|
||||||
event("endblock/validatorupdates/updated")
|
|
||||||
} else {
|
|
||||||
// Set this new validator
|
|
||||||
current[str] = mockValidator{
|
|
||||||
update,
|
|
||||||
GetMemberOfInitialState(r, params.InitialLivenessWeightings),
|
|
||||||
}
|
|
||||||
event("endblock/validatorupdates/added")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return current
|
|
||||||
}
|
|
Loading…
Reference in New Issue