2018-07-16 18:25:15 -07:00
package simulation
2018-07-06 13:19:11 -07:00
import (
2018-07-17 11:33:53 -07:00
"encoding/json"
2018-07-06 13:19:11 -07:00
"fmt"
2018-08-26 18:04:52 -07:00
"math"
2018-07-06 13:19:11 -07:00
"math/rand"
2018-08-29 23:02:15 -07:00
"os"
2018-08-16 14:45:07 -07:00
"sort"
2018-07-06 13:19:11 -07:00
"testing"
"time"
2018-08-16 08:36:15 -07:00
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
tmtypes "github.com/tendermint/tendermint/types"
2018-07-16 18:15:50 -07:00
"github.com/cosmos/cosmos-sdk/baseapp"
2018-07-17 15:04:10 -07:00
sdk "github.com/cosmos/cosmos-sdk/types"
2018-07-16 18:25:15 -07:00
"github.com/cosmos/cosmos-sdk/x/mock"
2018-07-06 13:19:11 -07:00
)
2018-07-17 11:33:53 -07:00
// Simulate tests application by sending random messages.
func Simulate (
2018-08-26 18:04:52 -07:00
t * testing . T , app * baseapp . BaseApp , appStateFn func ( r * rand . Rand , keys [ ] crypto . PrivKey , accs [ ] sdk . AccAddress ) json . RawMessage , ops [ ] Operation , setups [ ] RandSetup ,
2018-08-16 14:45:07 -07:00
invariants [ ] Invariant , numBlocks int , blockSize int , commit bool ,
2018-07-06 13:19:11 -07:00
) {
time := time . Now ( ) . UnixNano ( )
2018-08-16 14:45:07 -07:00
SimulateFromSeed ( t , app , appStateFn , time , ops , setups , invariants , numBlocks , blockSize , commit )
2018-07-06 13:19:11 -07:00
}
2018-08-29 23:02:15 -07:00
func initChain ( r * rand . Rand , keys [ ] crypto . PrivKey , accs [ ] sdk . AccAddress , setups [ ] RandSetup , app * baseapp . BaseApp ,
appStateFn func ( r * rand . Rand , keys [ ] crypto . PrivKey , accs [ ] sdk . AccAddress ) json . RawMessage ) ( validators map [ string ] mockValidator ) {
res := app . InitChain ( abci . RequestInitChain { AppStateBytes : appStateFn ( r , keys , accs ) } )
validators = make ( map [ string ] mockValidator )
for _ , validator := range res . Validators {
validators [ string ( validator . Address ) ] = mockValidator { validator , GetMemberOfInitialState ( r , initialLivenessWeightings ) }
}
for i := 0 ; i < len ( setups ) ; i ++ {
setups [ i ] ( r , keys )
}
return
}
func randTimestamp ( r * rand . Rand ) time . Time {
unixTime := r . Int63n ( int64 ( math . Pow ( 2 , 40 ) ) )
return time . Unix ( unixTime , 0 )
}
2018-07-17 11:33:53 -07:00
// SimulateFromSeed tests an application by running the provided
2018-07-06 13:19:11 -07:00
// operations, testing the provided invariants, but using the provided seed.
2018-07-17 11:33:53 -07:00
func SimulateFromSeed (
2018-08-30 00:13:31 -07:00
tb testing . TB , app * baseapp . BaseApp , appStateFn func ( r * rand . Rand , keys [ ] crypto . PrivKey , accs [ ] sdk . AccAddress ) json . RawMessage , seed int64 , ops [ ] Operation , setups [ ] RandSetup ,
2018-08-16 14:45:07 -07:00
invariants [ ] Invariant , numBlocks int , blockSize int , commit bool ,
2018-07-06 13:19:11 -07:00
) {
2018-09-01 12:32:24 -07:00
testingMode , t , b := getTestingMode ( tb )
2018-07-17 15:04:10 -07:00
log := fmt . Sprintf ( "Starting SimulateFromSeed with randomness created with seed %d" , int ( seed ) )
2018-07-06 13:19:11 -07:00
r := rand . New ( rand . NewSource ( seed ) )
2018-08-29 23:02:15 -07:00
timestamp := randTimestamp ( r )
2018-09-01 12:32:24 -07:00
log = updateLog ( testingMode , log , "Starting the simulation from time %v, unixtime %v" , timestamp . UTC ( ) . Format ( time . UnixDate ) , timestamp . Unix ( ) )
2018-08-26 18:04:52 -07:00
fmt . Printf ( "%s\n" , log )
timeDiff := maxTimePerBlock - minTimePerBlock
2018-08-16 08:36:15 -07:00
keys , accs := mock . GeneratePrivKeyAddressPairsFromRand ( r , numKeys )
2018-07-06 13:19:11 -07:00
2018-07-17 16:27:51 -07:00
// Setup event stats
events := make ( map [ string ] uint )
event := func ( what string ) {
2018-09-01 12:32:24 -07:00
log = updateLog ( testingMode , log , "event - %s" , what )
2018-07-17 22:50:04 -07:00
events [ what ] ++
2018-07-17 16:27:51 -07:00
}
2018-08-29 23:02:15 -07:00
validators := initChain ( r , keys , accs , setups , app , appStateFn )
2018-07-06 13:19:11 -07:00
2018-08-16 08:36:15 -07:00
header := abci . Header { Height : 0 , Time : timestamp }
opCount := 0
2018-09-01 12:32:24 -07:00
var pastTimes [ ] time . Time
2018-08-31 17:01:23 -07:00
var pastSigningValidators [ ] [ ] abci . SigningValidator
2018-09-01 12:34:59 -07:00
request := RandomRequestBeginBlock ( r , validators , livenessTransitionMatrix , evidenceFraction , pastTimes , pastSigningValidators , event , header , log )
2018-08-27 14:27:00 -07:00
// These are operations which have been queued by previous operations
operationQueue := make ( map [ int ] [ ] Operation )
2018-07-06 13:19:11 -07:00
2018-09-01 12:32:24 -07:00
if ! testingMode {
2018-08-30 00:13:31 -07:00
b . ResetTimer ( )
}
2018-09-01 12:32:24 -07:00
blockSimulator := createBlockSimulator ( testingMode , tb , t , event , invariants , ops , operationQueue , numBlocks )
2018-08-30 00:13:31 -07:00
2018-07-06 13:19:11 -07:00
for i := 0 ; i < numBlocks ; i ++ {
2018-09-01 12:32:24 -07:00
// Log the header time for future lookup
pastTimes = append ( pastTimes , header . Time )
2018-08-31 17:01:23 -07:00
pastSigningValidators = append ( pastSigningValidators , request . LastCommitInfo . Validators )
2018-09-01 12:32:24 -07:00
2018-08-16 08:36:15 -07:00
// Run the BeginBlock handler
app . BeginBlock ( request )
2018-09-01 12:32:24 -07:00
log = updateLog ( testingMode , log , "BeginBlock" )
2018-08-16 08:36:15 -07:00
2018-09-01 12:32:24 -07:00
if testingMode {
2018-08-30 00:13:31 -07:00
// Make sure invariants hold at beginning of block
AssertAllInvariants ( t , app , invariants , log )
}
2018-07-06 13:19:11 -07:00
ctx := app . NewContext ( false , header )
2018-08-29 23:02:15 -07:00
thisBlockSize := getBlockSize ( r , blockSize )
2018-08-27 14:27:00 -07:00
// Run queued operations. Ignores blocksize if blocksize is too small
2018-08-30 00:13:31 -07:00
log , numQueuedOpsRan := runQueuedOperations ( operationQueue , int ( header . Height ) , tb , r , app , ctx , keys , log , event )
2018-08-27 14:27:00 -07:00
opCount += numQueuedOpsRan
thisBlockSize -= numQueuedOpsRan
2018-08-30 11:43:56 -07:00
log , operations := blockSimulator ( thisBlockSize , r , app , ctx , keys , log , header )
opCount += operations
2018-07-06 13:19:11 -07:00
2018-08-16 08:36:15 -07:00
res := app . EndBlock ( abci . RequestEndBlock { } )
2018-07-06 13:19:11 -07:00
header . Height ++
2018-08-16 08:36:15 -07:00
header . Time = header . Time . Add ( time . Duration ( minTimePerBlock ) * time . Second ) . Add ( time . Duration ( int64 ( r . Intn ( int ( timeDiff ) ) ) ) * time . Second )
2018-09-01 12:32:24 -07:00
log = updateLog ( testingMode , log , "EndBlock" )
2018-08-16 08:36:15 -07:00
2018-09-01 12:32:24 -07:00
if testingMode {
2018-08-30 00:13:31 -07:00
// Make sure invariants hold at end of block
AssertAllInvariants ( t , app , invariants , log )
}
2018-08-17 07:19:33 -07:00
if commit {
app . Commit ( )
}
2018-08-16 08:36:15 -07:00
// Generate a random RequestBeginBlock with the current validator set for the next block
2018-09-01 12:34:59 -07:00
request = RandomRequestBeginBlock ( r , validators , livenessTransitionMatrix , evidenceFraction , pastTimes , pastSigningValidators , event , header , log )
2018-08-16 08:36:15 -07:00
// Update the validator set
2018-08-30 00:13:31 -07:00
validators = updateValidators ( tb , r , validators , res . ValidatorUpdates , event )
2018-07-06 13:19:11 -07:00
}
2018-07-17 16:27:51 -07:00
2018-08-30 11:43:56 -07:00
fmt . Printf ( "\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n" , header . Height , header . Time , opCount )
DisplayEvents ( events )
}
// Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize
// memory overhead
2018-09-01 12:32:24 -07:00
func createBlockSimulator ( testingMode bool , tb testing . TB , t * testing . T , event func ( string ) , invariants [ ] Invariant , ops [ ] Operation , operationQueue map [ int ] [ ] Operation , totalNumBlocks int ) func (
2018-08-30 11:43:56 -07:00
blocksize int , r * rand . Rand , app * baseapp . BaseApp , ctx sdk . Context , privKeys [ ] crypto . PrivKey , log string , header abci . Header ) ( updatedLog string , opCount int ) {
return func ( blocksize int , r * rand . Rand , app * baseapp . BaseApp , ctx sdk . Context ,
keys [ ] crypto . PrivKey , log string , header abci . Header ) ( updatedLog string , opCount int ) {
for j := 0 ; j < blocksize ; j ++ {
logUpdate , futureOps , err := ops [ r . Intn ( len ( ops ) ) ] ( tb , r , app , ctx , keys , log , event )
2018-09-01 12:32:24 -07:00
log = updateLog ( testingMode , log , logUpdate )
2018-08-30 11:43:56 -07:00
if err != nil {
tb . Fatalf ( "error on operation %d within block %d, %v, log %s" , header . Height , opCount , err , log )
}
queueOperations ( operationQueue , futureOps )
2018-09-01 12:32:24 -07:00
if testingMode {
2018-08-30 11:43:56 -07:00
if onOperation {
AssertAllInvariants ( t , app , invariants , log )
}
if opCount % 50 == 0 {
fmt . Printf ( "\rSimulating... block %d/%d, operation %d/%d." , header . Height , totalNumBlocks , opCount , blocksize )
}
}
opCount ++
}
return log , opCount
}
}
2018-09-01 12:32:24 -07:00
func getTestingMode ( tb testing . TB ) ( testingMode bool , t * testing . T , b * testing . B ) {
testingMode = false
2018-08-30 11:43:56 -07:00
if _t , ok := tb . ( * testing . T ) ; ok {
t = _t
2018-09-01 12:32:24 -07:00
testingMode = true
2018-08-30 00:35:02 -07:00
} else {
2018-08-30 11:43:56 -07:00
b = tb . ( * testing . B )
2018-08-30 00:13:31 -07:00
}
2018-08-30 11:43:56 -07:00
return
2018-07-06 13:19:11 -07:00
}
2018-09-01 12:32:24 -07:00
func updateLog ( testingMode bool , log string , update string , args ... interface { } ) ( updatedLog string ) {
if testingMode {
2018-08-30 00:35:02 -07:00
update = fmt . Sprintf ( update , args ... )
2018-08-30 00:13:31 -07:00
return fmt . Sprintf ( "%s\n%s" , log , update )
}
return ""
}
2018-08-29 23:02:15 -07:00
func getBlockSize ( r * rand . Rand , blockSize int ) int {
load := r . Float64 ( )
switch {
case load < 0.33 :
return 0
case load < 0.66 :
return r . Intn ( blockSize * 2 )
default :
return r . Intn ( blockSize * 4 )
}
}
2018-08-27 14:27:00 -07:00
// adds all future operations into the operation queue.
func queueOperations ( queuedOperations map [ int ] [ ] Operation , futureOperations [ ] FutureOperation ) {
if futureOperations == nil {
return
}
for _ , futureOp := range futureOperations {
if val , ok := queuedOperations [ futureOp . BlockHeight ] ; ok {
queuedOperations [ futureOp . BlockHeight ] = append ( val , futureOp . Op )
} else {
queuedOperations [ futureOp . BlockHeight ] = [ ] Operation { futureOp . Op }
}
}
}
2018-08-29 23:02:15 -07:00
func runQueuedOperations ( queueOperations map [ int ] [ ] Operation , height int , tb testing . TB , r * rand . Rand , app * baseapp . BaseApp , ctx sdk . Context ,
2018-08-27 14:27:00 -07:00
privKeys [ ] crypto . PrivKey , log string , event func ( string ) ) ( updatedLog string , numOpsRan int ) {
updatedLog = log
if queuedOps , ok := queueOperations [ height ] ; ok {
numOps := len ( queuedOps )
for i := 0 ; i < numOps ; i ++ {
// 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.
2018-08-29 23:02:15 -07:00
logUpdate , _ , err := queuedOps [ i ] ( tb , r , app , ctx , privKeys , updatedLog , event )
updatedLog = fmt . Sprintf ( "%s\n%s" , updatedLog , logUpdate )
if err != nil {
fmt . Fprint ( os . Stderr , updatedLog )
tb . FailNow ( )
}
2018-08-27 14:27:00 -07:00
}
delete ( queueOperations , height )
return updatedLog , numOps
}
return log , 0
}
2018-08-16 14:45:07 -07:00
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
}
2018-08-16 08:36:15 -07:00
// RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction
2018-08-31 15:22:37 -07:00
// nolint: unparam
2018-08-29 23:02:15 -07:00
func RandomRequestBeginBlock ( r * rand . Rand , validators map [ string ] mockValidator , livenessTransitions TransitionMatrix , evidenceFraction float64 ,
2018-08-31 17:01:23 -07:00
pastTimes [ ] time . Time , pastSigningValidators [ ] [ ] abci . SigningValidator , event func ( string ) , header abci . Header , log string ) abci . RequestBeginBlock {
2018-08-16 08:36:15 -07:00
if len ( validators ) == 0 {
return abci . RequestBeginBlock { Header : header }
}
signingValidators := make ( [ ] abci . SigningValidator , len ( validators ) )
i := 0
2018-08-16 14:45:07 -07:00
for _ , key := range getKeys ( validators ) {
mVal := validators [ key ]
2018-08-16 08:36:15 -07:00
mVal . livenessState = livenessTransitions . 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" )
}
signingValidators [ i ] = abci . SigningValidator {
Validator : mVal . val ,
SignedLastBlock : signed ,
}
i ++
}
2018-08-29 23:02:15 -07:00
// TODO: Determine capacity before allocation
2018-08-16 08:36:15 -07:00
evidence := make ( [ ] abci . Evidence , 0 )
2018-08-31 17:01:23 -07:00
// Anything but the first block
if len ( pastTimes ) > 0 {
for r . Float64 ( ) < evidenceFraction {
height := header . Height
time := header . Time
vals := signingValidators
if r . Float64 ( ) < pastEvidenceFraction {
height = int64 ( r . Intn ( int ( header . Height ) ) )
time = pastTimes [ height ]
vals = pastSigningValidators [ 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" )
2018-08-16 08:36:15 -07:00
}
}
return abci . RequestBeginBlock {
Header : header ,
LastCommitInfo : abci . LastCommitInfo {
Validators : signingValidators ,
} ,
ByzantineValidators : evidence ,
}
}
2018-07-17 15:04:10 -07:00
// AssertAllInvariants asserts a list of provided invariants against application state
2018-07-16 18:15:50 -07:00
func AssertAllInvariants ( t * testing . T , app * baseapp . BaseApp , tests [ ] Invariant , log string ) {
2018-07-06 13:19:11 -07:00
for i := 0 ; i < len ( tests ) ; i ++ {
tests [ i ] ( t , app , log )
}
}
2018-08-16 08:36:15 -07:00
// updateValidators mimicks Tendermint's update logic
2018-08-29 23:02:15 -07:00
func updateValidators ( tb testing . TB , r * rand . Rand , current map [ string ] mockValidator , updates [ ] abci . Validator , event func ( string ) ) map [ string ] mockValidator {
2018-08-16 08:36:15 -07:00
for _ , update := range updates {
switch {
case update . Power == 0 :
2018-08-29 23:02:15 -07:00
// // TEMPORARY DEBUG CODE TO PROVE THAT THE OLD METHOD WAS BROKEN
// // (i.e. didn't catch in the event of problem)
// if val, ok := tb.(*testing.T); ok {
// require.NotNil(val, current[string(update.PubKey.Data)])
// }
// // CORRECT CHECK
// if _, ok := current[string(update.PubKey.Data)]; !ok {
// tb.Fatalf("tried to delete a nonexistent validator")
// }
2018-08-16 08:36:15 -07:00
event ( "endblock/validatorupdates/kicked" )
delete ( current , string ( update . PubKey . Data ) )
default :
// Does validator already exist?
if mVal , ok := current [ string ( update . PubKey . Data ) ] ; ok {
mVal . val = update
event ( "endblock/validatorupdates/updated" )
} else {
// Set this new validator
current [ string ( update . PubKey . Data ) ] = mockValidator { update , GetMemberOfInitialState ( r , initialLivenessWeightings ) }
event ( "endblock/validatorupdates/added" )
}
}
}
return current
}