diff --git a/PENDING.md b/PENDING.md index 1344344118..75d0fe62ac 100644 --- a/PENDING.md +++ b/PENDING.md @@ -80,6 +80,7 @@ FEATURES * [querier] added custom querier functionality, so ABCI query requests can be handled by keepers * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile" + * [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator * Tendermint diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 4ca1cdf445..168081b188 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -69,7 +69,7 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe fops := make([]simulation.FutureOperation, numVotes+1) for i := 0; i < numVotes; i++ { whenVote := ctx.BlockHeight() + r.Int63n(votingPeriod) - fops[i] = simulation.FutureOperation{int(whenVote), operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)} + fops[i] = simulation.FutureOperation{BlockHeight: int(whenVote), Op: operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)} } // 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant) // TODO: Find a way to check if a validator was slashed other than just checking their balance a block diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index de78ae0942..96dbff5725 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -98,13 +98,14 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, request := RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header) // These are operations which have been queued by previous operations operationQueue := make(map[int][]Operation) + timeOperationQueue := []FutureOperation{} var blockLogBuilders []*strings.Builder if testingMode { blockLogBuilders = make([]*strings.Builder, numBlocks) } displayLogs := logPrinter(testingMode, blockLogBuilders) - blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, numBlocks, displayLogs) + blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, timeOperationQueue, numBlocks, displayLogs) if !testingMode { b.ResetTimer() } else { @@ -139,9 +140,10 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Run queued operations. Ignores blocksize if blocksize is too small numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, logWriter, displayLogs, event) - thisBlockSize -= numQueuedOpsRan + numQueuedTimeOpsRan := runQueuedTimeOperations(timeOperationQueue, header.Time, tb, r, app, ctx, keys, logWriter, displayLogs, event) + thisBlockSize = thisBlockSize - numQueuedOpsRan - numQueuedTimeOpsRan operations := blockSimulator(thisBlockSize, r, app, ctx, keys, header, logWriter) - opCount += operations + numQueuedOpsRan + opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan res := app.EndBlock(abci.RequestEndBlock{}) header.Height++ @@ -173,7 +175,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize // memory overhead -func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []WeightedOperation, operationQueue map[int][]Operation, totalNumBlocks int, displayLogs func()) func( +func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []WeightedOperation, operationQueue map[int][]Operation, timeOperationQueue []FutureOperation, totalNumBlocks int, displayLogs func()) func( blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, header abci.Header, logWriter func(string)) (opCount int) { totalOpWeight := 0 for i := 0; i < len(ops); i++ { @@ -200,7 +202,7 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event f } logWriter(logUpdate) - queueOperations(operationQueue, futureOps) + queueOperations(operationQueue, timeOperationQueue, futureOps) if testingMode { if onOperation { assertAllInvariants(t, app, invariants, displayLogs) @@ -239,15 +241,23 @@ func getBlockSize(r *rand.Rand, blockSize int) int { } // adds all future operations into the operation queue. -func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) { +func queueOperations(queuedOperations map[int][]Operation, queuedTimeOperations []FutureOperation, futureOperations []FutureOperation) { if futureOperations == nil { return } for _, futureOp := range futureOperations { - if val, ok := queuedOperations[futureOp.BlockHeight]; ok { - queuedOperations[futureOp.BlockHeight] = append(val, futureOp.Op) + if futureOp.BlockHeight != 0 { + if val, ok := queuedOperations[futureOp.BlockHeight]; ok { + queuedOperations[futureOp.BlockHeight] = append(val, futureOp.Op) + } else { + queuedOperations[futureOp.BlockHeight] = []Operation{futureOp.Op} + } } else { - queuedOperations[futureOp.BlockHeight] = []Operation{futureOp.Op} + // TODO: Replace with proper sorted data structure, so don't have the copy entire slice + index := sort.Search(len(queuedTimeOperations), func(i int) bool { return queuedTimeOperations[i].BlockTime.After(futureOp.BlockTime) }) + queuedTimeOperations = append(queuedTimeOperations, FutureOperation{}) + copy(queuedTimeOperations[index+1:], queuedTimeOperations[index:]) + queuedTimeOperations[index] = futureOp } } } @@ -274,6 +284,26 @@ func runQueuedOperations(queueOperations map[int][]Operation, height int, tb tes return 0 } +func runQueuedTimeOperations(queueOperations []FutureOperation, currentTime time.Time, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + privKeys []crypto.PrivKey, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { + + numOpsRan = 0 + for len(queueOperations) > 0 && currentTime.After(queueOperations[0].BlockTime) { + // For now, queued operations cannot queue more operations. + // If a need arises for us to support queued messages to queue more messages, this can + // be changed. + logUpdate, _, err := queueOperations[0].Op(r, app, ctx, privKeys, event) + logWriter(logUpdate) + if err != nil { + displayLogs() + tb.FailNow() + } + queueOperations = queueOperations[1:] + numOpsRan++ + } + return numOpsRan +} + func getKeys(validators map[string]mockValidator) []string { keys := make([]string, len(validators)) i := 0 diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 401899efe3..a32edea6d6 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -2,6 +2,7 @@ package simulation import ( "math/rand" + "time" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -41,10 +42,12 @@ type ( // FutureOperation is an operation which will be ran at the // beginning of the provided BlockHeight. + // If both a BlockHeight and BlockTime are specified, it will use the BlockHeight. // In the (likely) event that multiple operations are queued at the same // block height, they will execute in a FIFO pattern. FutureOperation struct { BlockHeight int + BlockTime time.Time Op Operation }