Simulation improvements (logging fix, random genesis parameters) (#2617)

* Print out initial update on every block
* Randomize simulation parameters
* Randomize initial liveness weightings
* Randomize genesis parameters
* fixed power store invariant
* IterateValidatorsBonded -> IterateBondedValidatorsByPower
* WriteValidators uses IterateLastValidators rather than IterateBondedValidatorsByPower
* fixed democoin interface

Closes #2556
Closes #2396

Via #2671:
closes #2669
closes #2670
closes #2620

Offshoot issues:
#2618
#2619
#2620
#2661
This commit is contained in:
Christopher Goes 2018-11-05 05:44:43 +01:00 committed by Jae Kwon
parent c20fcbfd8f
commit 256ec0f07b
14 changed files with 236 additions and 87 deletions

View File

@ -43,6 +43,10 @@ IMPROVEMENTS
* SDK * SDK
- #2573 [x/distribution] add accum invariance - #2573 [x/distribution] add accum invariance
- #2556 [x/mock/simulation] Fix debugging output
- #2396 [x/mock/simulation] Change parameters to get more slashes
- #2617 [x/mock/simulation] Randomize all genesis parameters
- #2669 [x/stake] Added invarant check to make sure validator's power aligns with its spot in the power store.
- \#1924 [x/mock/simulation] Use a transition matrix for block size - \#1924 [x/mock/simulation] Use a transition matrix for block size
- \#2660 [x/mock/simulation] Staking transactions get tested far more frequently - \#2660 [x/mock/simulation] Staking transactions get tested far more frequently
- #2610 [x/stake] Block redelegation to and from the same validator - #2610 [x/stake] Block redelegation to and from the same validator
@ -58,6 +62,7 @@ BUG FIXES
* Gaia CLI (`gaiacli`) * Gaia CLI (`gaiacli`)
* Gaia * Gaia
- #2670 [x/stake] fixed incorrent `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators`
* SDK * SDK
- #2625 [x/gov] fix AppendTag function usage error - #2625 [x/gov] fix AppendTag function usage error

View File

@ -394,6 +394,7 @@ func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res
ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger). ctx := sdk.NewContext(app.cms.CacheMultiStore(), app.checkState.ctx.BlockHeader(), true, app.Logger).
WithMinimumFees(app.minimumFees) WithMinimumFees(app.minimumFees)
// Passes the rest of the path as an argument to the querier. // Passes the rest of the path as an argument to the querier.
// For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path // For example, in the path "custom/gov/proposal/test", the gov querier gets []string{"proposal", "test"} as the path
resBytes, err := querier(ctx, path[2:], req) resBytes, err := querier(ctx, path[2:], req)

View File

@ -8,6 +8,7 @@ import (
"math/rand" "math/rand"
"os" "os"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -50,42 +51,93 @@ func init() {
func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
var genesisAccounts []GenesisAccount var genesisAccounts []GenesisAccount
amt := int64(10000) amount := int64(r.Intn(1e6))
numInitiallyBonded := int64(r.Intn(250))
numAccs := int64(len(accs))
if numInitiallyBonded > numAccs {
numInitiallyBonded = numAccs
}
fmt.Printf("Selected randomly generated parameters for simulated genesis: {amount of steak per account: %v, initially bonded validators: %v}\n", amount, numInitiallyBonded)
// Randomly generate some genesis accounts // Randomly generate some genesis accounts
for _, acc := range accs { for _, acc := range accs {
coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(amt)}} coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(amount)}}
genesisAccounts = append(genesisAccounts, GenesisAccount{ genesisAccounts = append(genesisAccounts, GenesisAccount{
Address: acc.Address, Address: acc.Address,
Coins: coins, Coins: coins,
}) })
} }
// Default genesis state // Random genesis states
govGenesis := gov.DefaultGenesisState() govGenesis := gov.GenesisState{
stakeGenesis := stake.DefaultGenesisState() StartingProposalID: int64(r.Intn(100)),
slashingGenesis := slashing.DefaultGenesisState() DepositProcedure: gov.DepositProcedure{
MinDeposit: sdk.Coins{sdk.NewInt64Coin("steak", int64(r.Intn(1e3)))},
MaxDepositPeriod: time.Duration(r.Intn(2*172800)) * time.Second,
},
VotingProcedure: gov.VotingProcedure{
VotingPeriod: time.Duration(r.Intn(2*172800)) * time.Second,
},
TallyingProcedure: gov.TallyingProcedure{
Threshold: sdk.NewDecWithPrec(5, 1),
Veto: sdk.NewDecWithPrec(334, 3),
GovernancePenalty: sdk.NewDecWithPrec(1, 2),
},
}
fmt.Printf("Selected randomly generated governance parameters: %+v\n", govGenesis)
stakeGenesis := stake.GenesisState{
Pool: stake.InitialPool(),
Params: stake.Params{
UnbondingTime: time.Duration(r.Intn(60*60*24*3*2)) * time.Second,
MaxValidators: uint16(r.Intn(250)),
BondDenom: "steak",
},
}
fmt.Printf("Selected randomly generated staking parameters: %+v\n", stakeGenesis)
slashingGenesis := slashing.GenesisState{
Params: slashing.Params{
MaxEvidenceAge: stakeGenesis.Params.UnbondingTime,
DoubleSignUnbondDuration: time.Duration(r.Intn(60*60*24)) * time.Second,
SignedBlocksWindow: int64(r.Intn(1000)),
DowntimeUnbondDuration: time.Duration(r.Intn(86400)) * time.Second,
MinSignedPerWindow: sdk.NewDecWithPrec(int64(r.Intn(10)), 1),
SlashFractionDoubleSign: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(50) + 1))),
SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))),
},
}
fmt.Printf("Selected randomly generated slashing parameters: %+v\n", slashingGenesis)
mintGenesis := mint.GenesisState{
Minter: mint.Minter{
InflationLastTime: time.Unix(0, 0),
Inflation: sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
},
Params: mint.Params{
MintDenom: "steak",
InflationRateChange: sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
InflationMax: sdk.NewDecWithPrec(20, 2),
InflationMin: sdk.NewDecWithPrec(7, 2),
GoalBonded: sdk.NewDecWithPrec(67, 2),
},
}
fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis)
var validators []stake.Validator var validators []stake.Validator
var delegations []stake.Delegation var delegations []stake.Delegation
// XXX Try different numbers of initially bonded validators
numInitiallyBonded := int64(50)
valAddrs := make([]sdk.ValAddress, numInitiallyBonded) valAddrs := make([]sdk.ValAddress, numInitiallyBonded)
for i := 0; i < int(numInitiallyBonded); i++ { for i := 0; i < int(numInitiallyBonded); i++ {
valAddr := sdk.ValAddress(accs[i].Address) valAddr := sdk.ValAddress(accs[i].Address)
valAddrs[i] = valAddr valAddrs[i] = valAddr
validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{}) validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{})
validator.Tokens = sdk.NewDec(amt) validator.Tokens = sdk.NewDec(amount)
validator.DelegatorShares = sdk.NewDec(amt) validator.DelegatorShares = sdk.NewDec(amount)
delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amt), 0} delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount), 0}
validators = append(validators, validator) validators = append(validators, validator)
delegations = append(delegations, delegation) delegations = append(delegations, delegation)
} }
stakeGenesis.Pool.LooseTokens = sdk.NewDec(amt*250 + (numInitiallyBonded * amt)) stakeGenesis.Pool.LooseTokens = sdk.NewDec((amount * numAccs) + (numInitiallyBonded * amount))
stakeGenesis.Validators = validators stakeGenesis.Validators = validators
stakeGenesis.Bonds = delegations stakeGenesis.Bonds = delegations
mintGenesis := mint.DefaultGenesisState()
genesis := GenesisState{ genesis := GenesisState{
Accounts: genesisAccounts, Accounts: genesisAccounts,

View File

@ -82,8 +82,13 @@ func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64,
} }
} }
// IterateValidatorsBonded implements sdk.ValidatorSet // IterateBondedValidatorsByPower implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) { func (vs *ValidatorSet) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
vs.IterateValidators(ctx, fn)
}
// IterateLastValidators implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateLastValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
vs.IterateValidators(ctx, fn) vs.IterateValidators(ctx, fn)
} }

View File

@ -1,6 +1,7 @@
#!/bin/bash #!/bin/bash
seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823782 989182 89182391) seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823782 989182 89182391 \
11 22 44 77 99 2020 3232 123123 124124 582582 18931893 29892989 30123012 47284728 37827)
blocks=$1 blocks=$1
echo "Running multi-seed simulation with seeds ${seeds[@]}" echo "Running multi-seed simulation with seeds ${seeds[@]}"

View File

@ -64,7 +64,11 @@ type ValidatorSet interface {
func(index int64, validator Validator) (stop bool)) func(index int64, validator Validator) (stop bool))
// iterate through bonded validators by operator address, execute func for each validator // iterate through bonded validators by operator address, execute func for each validator
IterateValidatorsBonded(Context, IterateBondedValidatorsByPower(Context,
func(index int64, validator Validator) (stop bool))
// iterate through the consensus validator set of the last block by operator address, execute func for each validator
IterateLastValidators(Context,
func(index int64, validator Validator) (stop bool)) func(index int64, validator Validator) (stop bool))
Validator(Context, ValAddress) Validator // get a particular validator by operator address Validator(Context, ValAddress) Validator // get a particular validator by operator address

View File

@ -23,7 +23,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall
totalVotingPower := sdk.ZeroDec() totalVotingPower := sdk.ZeroDec()
currValidators := make(map[string]validatorGovInfo) currValidators := make(map[string]validatorGovInfo)
keeper.vs.IterateValidatorsBonded(ctx, func(index int64, validator sdk.Validator) (stop bool) { keeper.vs.IterateBondedValidatorsByPower(ctx, func(index int64, validator sdk.Validator) (stop bool) {
currValidators[validator.GetOperator().String()] = validatorGovInfo{ currValidators[validator.GetOperator().String()] = validatorGovInfo{
Address: validator.GetOperator(), Address: validator.GetOperator(),
Power: validator.GetPower(), Power: validator.GetPower(),

View File

@ -1,37 +0,0 @@
package simulation
const (
// Fraction of double-signing evidence from a past height
pastEvidenceFraction float64 = 0.5
// Minimum time per block
minTimePerBlock int64 = 1000 / 2
// Maximum time per block
maxTimePerBlock int64 = 1000
// Number of keys
numKeys int = 250
// Chance that double-signing evidence is found on a given block
evidenceFraction float64 = 0.5
// TODO Remove in favor of binary search for invariant violation
onOperation bool = false
)
var (
// Currently there are 3 different liveness types, fully online, spotty connection, offline.
initialLivenessWeightings = []int{40, 5, 5}
livenessTransitionMatrix, _ = CreateTransitionMatrix([][]int{
{90, 20, 1},
{10, 50, 5},
{0, 10, 1000},
})
// 3 states: rand in range [0, 4*provided blocksize], rand in range [0, 2 * provided blocksize], 0
blockSizeTransitionMatrix, _ = CreateTransitionMatrix([][]int{
{85, 5, 0},
{15, 92, 1},
{0, 3, 99},
})
)

View File

@ -0,0 +1,66 @@
package simulation
import (
"math/rand"
)
const (
// Minimum time per block
minTimePerBlock int64 = 10000 / 2
// Maximum time per block
maxTimePerBlock int64 = 10000
// TODO Remove in favor of binary search for invariant violation
onOperation bool = false
)
var (
// Currently there are 3 different liveness types, fully online, spotty connection, offline.
defaultLivenessTransitionMatrix, _ = CreateTransitionMatrix([][]int{
{90, 20, 1},
{10, 50, 5},
{0, 10, 1000},
})
// 3 states: rand in range [0, 4*provided blocksize], rand in range [0, 2 * provided blocksize], 0
defaultBlockSizeTransitionMatrix, _ = CreateTransitionMatrix([][]int{
{85, 5, 0},
{15, 92, 1},
{0, 3, 99},
})
)
// Simulation parameters
type Params struct {
PastEvidenceFraction float64
NumKeys int
EvidenceFraction float64
InitialLivenessWeightings []int
LivenessTransitionMatrix TransitionMatrix
BlockSizeTransitionMatrix TransitionMatrix
}
// Return default simulation parameters
func DefaultParams() Params {
return Params{
PastEvidenceFraction: 0.5,
NumKeys: 250,
EvidenceFraction: 0.5,
InitialLivenessWeightings: []int{40, 5, 5},
LivenessTransitionMatrix: defaultLivenessTransitionMatrix,
BlockSizeTransitionMatrix: defaultBlockSizeTransitionMatrix,
}
}
// Return random simulation parameters
func RandomParams(r *rand.Rand) Params {
return Params{
PastEvidenceFraction: r.Float64(),
NumKeys: r.Intn(250),
EvidenceFraction: r.Float64(),
InitialLivenessWeightings: []int{r.Intn(80), r.Intn(10), r.Intn(10)},
LivenessTransitionMatrix: defaultLivenessTransitionMatrix,
BlockSizeTransitionMatrix: defaultBlockSizeTransitionMatrix,
}
}

View File

@ -31,13 +31,13 @@ func Simulate(t *testing.T, app *baseapp.BaseApp,
return SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) return SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit)
} }
func initChain(r *rand.Rand, accounts []Account, setups []RandSetup, app *baseapp.BaseApp, func initChain(r *rand.Rand, params Params, accounts []Account, setups []RandSetup, app *baseapp.BaseApp,
appStateFn func(r *rand.Rand, accounts []Account) json.RawMessage) (validators map[string]mockValidator) { appStateFn func(r *rand.Rand, accounts []Account) json.RawMessage) (validators map[string]mockValidator) {
res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, accounts)}) res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, accounts)})
validators = make(map[string]mockValidator) validators = make(map[string]mockValidator)
for _, validator := range res.Validators { for _, validator := range res.Validators {
str := fmt.Sprintf("%v", validator.PubKey) str := fmt.Sprintf("%v", validator.PubKey)
validators[str] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)} validators[str] = mockValidator{validator, GetMemberOfInitialState(r, params.InitialLivenessWeightings)}
} }
for i := 0; i < len(setups); i++ { for i := 0; i < len(setups); i++ {
@ -65,11 +65,13 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
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)
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", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) fmt.Printf("Starting the simulation from time %v, unixtime %v\n", timestamp.UTC().Format(time.UnixDate), timestamp.Unix())
timeDiff := maxTimePerBlock - minTimePerBlock timeDiff := maxTimePerBlock - minTimePerBlock
accs := RandomAccounts(r, numKeys) accs := RandomAccounts(r, params.NumKeys)
// Setup event stats // Setup event stats
events := make(map[string]uint) events := make(map[string]uint)
@ -77,7 +79,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
events[what]++ events[what]++
} }
validators := initChain(r, accs, setups, app, appStateFn) validators := initChain(r, params, accs, setups, app, appStateFn)
// Second variable to keep pending validator set (delayed one block since TM 0.24) // Second variable to keep pending validator set (delayed one block since TM 0.24)
// Initially this is the same as the initial validator set // Initially this is the same as the initial validator set
nextValidators := validators nextValidators := validators
@ -90,7 +92,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
go func() { go func() {
receivedSignal := <-c receivedSignal := <-c
fmt.Printf("Exiting early due to %s, on block %d, operation %d\n", receivedSignal, header.Height, opCount) fmt.Printf("\nExiting early due to %s, on block %d, operation %d\n", receivedSignal, header.Height, opCount)
simError = fmt.Errorf("Exited due to %s", receivedSignal) simError = fmt.Errorf("Exited due to %s", receivedSignal)
stopEarly = true stopEarly = true
}() }()
@ -98,7 +100,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
var pastTimes []time.Time var pastTimes []time.Time
var pastVoteInfos [][]abci.VoteInfo var pastVoteInfos [][]abci.VoteInfo
request := RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastVoteInfos, event, header) request := RandomRequestBeginBlock(r, params, validators, pastTimes, pastVoteInfos, event, header)
// These are operations which have been queued by previous operations // These are operations which have been queued by previous operations
operationQueue := make(map[int][]Operation) operationQueue := make(map[int][]Operation)
timeOperationQueue := []FutureOperation{} timeOperationQueue := []FutureOperation{}
@ -108,7 +110,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
blockLogBuilders = make([]*strings.Builder, numBlocks) blockLogBuilders = make([]*strings.Builder, numBlocks)
} }
displayLogs := logPrinter(testingMode, blockLogBuilders) displayLogs := logPrinter(testingMode, blockLogBuilders)
blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, timeOperationQueue, numBlocks, blockSize, displayLogs) blockSimulator := createBlockSimulator(testingMode, tb, t, params, event, invariants, ops, operationQueue, timeOperationQueue, numBlocks, blockSize, displayLogs)
if !testingMode { if !testingMode {
b.ResetTimer() b.ResetTimer()
} else { } else {
@ -181,11 +183,11 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp,
} }
// Generate a random RequestBeginBlock with the current validator set for the next block // Generate a random RequestBeginBlock with the current validator set for the next block
request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastVoteInfos, event, header) request = RandomRequestBeginBlock(r, params, validators, pastTimes, pastVoteInfos, event, header)
// Update the validator set, which will be reflected in the application on the next block // Update the validator set, which will be reflected in the application on the next block
validators = nextValidators validators = nextValidators
nextValidators = updateValidators(tb, r, validators, res.ValidatorUpdates, event) nextValidators = updateValidators(tb, r, params, validators, res.ValidatorUpdates, event)
} }
if stopEarly { if stopEarly {
DisplayEvents(events) DisplayEvents(events)
@ -203,7 +205,7 @@ type blockSimFn func(
// Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize // Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize
// memory overhead // memory overhead
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params Params,
event func(string), invariants []Invariant, event func(string), invariants []Invariant,
ops []WeightedOperation, operationQueue map[int][]Operation, timeOperationQueue []FutureOperation, ops []WeightedOperation, operationQueue map[int][]Operation, timeOperationQueue []FutureOperation,
totalNumBlocks int, avgBlockSize int, displayLogs func()) blockSimFn { totalNumBlocks int, avgBlockSize int, displayLogs func()) blockSimFn {
@ -231,7 +233,8 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T,
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, return 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) {
lastBlocksizeState, blocksize = getBlockSize(r, lastBlocksizeState, avgBlockSize) fmt.Printf("\rSimulating... block %d/%d, operation %d/%d. ", header.Height, totalNumBlocks, opCount, blocksize)
lastBlocksizeState, blocksize = getBlockSize(r, params, lastBlocksizeState, avgBlockSize)
for j := 0; j < blocksize; j++ { for j := 0; j < blocksize; j++ {
logUpdate, futureOps, err := selectOp(r)(r, app, ctx, accounts, event) logUpdate, futureOps, err := selectOp(r)(r, app, ctx, accounts, event)
logWriter(logUpdate) logWriter(logUpdate)
@ -272,11 +275,10 @@ func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B
// "over stuffed" blocks with average size of 2 * avgblocksize, // "over stuffed" blocks with average size of 2 * avgblocksize,
// normal sized blocks, hitting avgBlocksize on average, // normal sized blocks, hitting avgBlocksize on average,
// and empty blocks, with no txs / only txs scheduled from the past. // and empty blocks, with no txs / only txs scheduled from the past.
func getBlockSize(r *rand.Rand, lastBlockSizeState, avgBlockSize int) (state, blocksize int) { func getBlockSize(r *rand.Rand, params Params, lastBlockSizeState, avgBlockSize int) (state, blocksize int) {
// TODO: Make blockSizeTransitionMatrix non-global
// TODO: Make default blocksize transition matrix actually make the average // TODO: Make default blocksize transition matrix actually make the average
// blocksize equal to avgBlockSize. // blocksize equal to avgBlockSize.
state = blockSizeTransitionMatrix.NextState(r, lastBlockSizeState) state = params.BlockSizeTransitionMatrix.NextState(r, lastBlockSizeState)
if state == 0 { if state == 0 {
blocksize = r.Intn(avgBlockSize * 4) blocksize = r.Intn(avgBlockSize * 4)
} else if state == 1 { } else if state == 1 {
@ -379,7 +381,7 @@ func randomProposer(r *rand.Rand, validators map[string]mockValidator) common.He
// RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction // RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction
// nolint: unparam // nolint: unparam
func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, 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 { pastTimes []time.Time, pastVoteInfos [][]abci.VoteInfo, event func(string), header abci.Header) abci.RequestBeginBlock {
if len(validators) == 0 { if len(validators) == 0 {
return abci.RequestBeginBlock{Header: header} return abci.RequestBeginBlock{Header: header}
@ -388,7 +390,7 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator,
i := 0 i := 0
for _, key := range getKeys(validators) { for _, key := range getKeys(validators) {
mVal := validators[key] mVal := validators[key]
mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState) mVal.livenessState = params.LivenessTransitionMatrix.NextState(r, mVal.livenessState)
signed := true signed := true
if mVal.livenessState == 1 { if mVal.livenessState == 1 {
@ -422,11 +424,11 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator,
evidence := make([]abci.Evidence, 0) evidence := make([]abci.Evidence, 0)
// Anything but the first block // Anything but the first block
if len(pastTimes) > 0 { if len(pastTimes) > 0 {
for r.Float64() < evidenceFraction { for r.Float64() < params.EvidenceFraction {
height := header.Height height := header.Height
time := header.Time time := header.Time
vals := voteInfos vals := voteInfos
if r.Float64() < pastEvidenceFraction { if r.Float64() < params.PastEvidenceFraction {
height = int64(r.Intn(int(header.Height) - 1)) height = int64(r.Intn(int(header.Height) - 1))
time = pastTimes[height] time = pastTimes[height]
vals = pastVoteInfos[height] vals = pastVoteInfos[height]
@ -457,7 +459,7 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator,
// updateValidators mimicks Tendermint's update logic // updateValidators mimicks Tendermint's update logic
// nolint: unparam // nolint: unparam
func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValidator, updates []abci.ValidatorUpdate, event func(string)) map[string]mockValidator { 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 { for _, update := range updates {
str := fmt.Sprintf("%v", update.PubKey) str := fmt.Sprintf("%v", update.PubKey)
@ -476,7 +478,7 @@ func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValida
event("endblock/validatorupdates/updated") event("endblock/validatorupdates/updated")
} else { } else {
// Set this new validator // Set this new validator
current[str] = mockValidator{update, GetMemberOfInitialState(r, initialLivenessWeightings)} current[str] = mockValidator{update, GetMemberOfInitialState(r, params.InitialLivenessWeightings)}
event("endblock/validatorupdates/added") event("endblock/validatorupdates/added")
} }
} }

View File

@ -72,7 +72,7 @@ func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
// WriteValidators returns a slice of bonded genesis validators. // WriteValidators returns a slice of bonded genesis validators.
func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) {
keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { keeper.IterateLastValidators(ctx, func(_ int64, validator sdk.Validator) (stop bool) {
vals = append(vals, tmtypes.GenesisValidator{ vals = append(vals, tmtypes.GenesisValidator{
PubKey: validator.GetConsPubKey(), PubKey: validator.GetConsPubKey(),
Power: validator.GetPower().RoundInt64(), Power: validator.GetPower().RoundInt64(),

View File

@ -28,9 +28,31 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato
} }
// iterate through the active validator set and perform the provided function // iterate through the active validator set and perform the provided function
func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) maxValidators := k.MaxValidators(ctx)
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey)
defer iterator.Close()
i := int64(0)
for ; iterator.Valid() && i < int64(maxValidators); iterator.Next() {
address := iterator.Value()
validator := k.mustGetValidator(ctx, address)
if validator.Status == sdk.Bonded {
stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to?
if stop {
break
}
i++
}
}
}
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateLastValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
iterator := k.LastValidatorsIterator(ctx)
i := int64(0) i := int64(0)
for ; iterator.Valid(); iterator.Next() { for ; iterator.Valid(); iterator.Next() {
address := AddressFromLastValidatorPowerKey(iterator.Key()) address := AddressFromLastValidatorPowerKey(iterator.Key())

View File

@ -261,6 +261,13 @@ func (k Keeper) GetLastValidators(ctx sdk.Context) (validators []types.Validator
return validators[:i] // trim return validators[:i] // trim
} }
// returns an iterator for the consensus validators in the last block
func (k Keeper) LastValidatorsIterator(ctx sdk.Context) (iterator sdk.Iterator) {
store := ctx.KVStore(k.storeKey)
iterator = sdk.KVStorePrefixIterator(store, LastValidatorPowerKey)
return iterator
}
// get the current group of bonded validators sorted by power-rank // get the current group of bonded validators sorted by power-rank
func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -283,6 +290,13 @@ func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator {
return validators[:i] // trim return validators[:i] // trim
} }
// returns an iterator for the current validator power store
func (k Keeper) ValidatorsPowerStoreIterator(ctx sdk.Context) (iterator sdk.Iterator) {
store := ctx.KVStore(k.storeKey)
iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey)
return iterator
}
// gets a specific validator queue timeslice. A timeslice is a slice of ValAddresses corresponding to unbonding validators // gets a specific validator queue timeslice. A timeslice is a slice of ValAddresses corresponding to unbonding validators
// that expire at a certain time. // that expire at a certain time.
func (k Keeper) GetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (valAddrs []sdk.ValAddress) { func (k Keeper) GetValidatorQueueTimeSlice(ctx sdk.Context, timestamp time.Time) (valAddrs []sdk.ValAddress) {

View File

@ -1,6 +1,7 @@
package simulation package simulation
import ( import (
"bytes"
"fmt" "fmt"
"github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/baseapp"
@ -10,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation"
"github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake"
"github.com/cosmos/cosmos-sdk/x/stake/keeper"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
) )
@ -24,10 +26,12 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper,
if err != nil { if err != nil {
return err return err
} }
err = PositivePowerInvariant(k)(app) err = PositivePowerInvariant(k)(app)
if err != nil { if err != nil {
return err return err
} }
err = ValidatorSetInvariant(k)(app) err = ValidatorSetInvariant(k)(app)
return err return err
} }
@ -100,19 +104,29 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper,
} }
} }
// PositivePowerInvariant checks that all stored validators have > 0 power // PositivePowerInvariant checks that all stored validators have > 0 power.
func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { func PositivePowerInvariant(k stake.Keeper) simulation.Invariant {
return func(app *baseapp.BaseApp) error { return func(app *baseapp.BaseApp) error {
ctx := app.NewContext(false, abci.Header{}) ctx := app.NewContext(false, abci.Header{})
var err error
k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool { iterator := k.ValidatorsPowerStoreIterator(ctx)
if !validator.GetPower().GT(sdk.ZeroDec()) { pool := k.GetPool(ctx)
err = fmt.Errorf("validator with non-positive power stored. (pubkey %v)", validator.GetConsPubKey())
return true for ; iterator.Valid(); iterator.Next() {
validator, found := k.GetValidator(ctx, iterator.Value())
if !found {
panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value()))
} }
return false
}) powerKey := keeper.GetValidatorsByPowerIndexKey(validator, pool)
return err
if !bytes.Equal(iterator.Key(), powerKey) {
return fmt.Errorf("power store invariance:\n\tvalidator.Power: %v"+
"\n\tkey should be: %v\n\tkey in store: %v", validator.GetPower(), powerKey, iterator.Key())
}
}
iterator.Close()
return nil
} }
} }