github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/simulation/simulate.go (about) 1 package simulation 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "math/rand" 8 "os" 9 "os/signal" 10 "syscall" 11 "testing" 12 "time" 13 14 abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types" 15 16 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/baseapp" 17 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 18 ) 19 20 // AppStateFn returns the app state json bytes and the genesis accounts 21 type AppStateFn func(r *rand.Rand, accs []Account, config Config) ( 22 appState json.RawMessage, accounts []Account, chainId string, genesisTimestamp time.Time, 23 ) 24 25 // initialize the chain for the simulation 26 func initChain( 27 r *rand.Rand, params Params, accounts []Account, app *baseapp.BaseApp, 28 appStateFn AppStateFn, config Config, 29 ) (mockValidators, time.Time, []Account, string) { 30 31 appState, accounts, chainID, genesisTimestamp := appStateFn(r, accounts, config) 32 33 req := abci.RequestInitChain{ 34 AppStateBytes: appState, 35 ChainId: chainID, 36 } 37 res := app.InitChain(req) 38 validators := newMockValidators(r, res.Validators, params) 39 40 return validators, genesisTimestamp, accounts, chainID 41 } 42 43 // SimulateFromSeed tests an application by running the provided 44 // operations, testing the provided invariants, but using the provided config.Seed. 45 // TODO: split this monster function up 46 func SimulateFromSeed( 47 tb testing.TB, w io.Writer, app *baseapp.BaseApp, 48 appStateFn AppStateFn, ops WeightedOperations, 49 blackListedAccs map[string]bool, config Config, 50 ) (stopEarly bool, exportedParams Params, err error) { 51 52 // in case we have to end early, don't os.Exit so that we can run cleanup code. 53 testingMode, t, b := getTestingMode(tb) 54 fmt.Fprintf(w, "Starting SimulateFromSeed with randomness created with seed %d\n", int(config.Seed)) 55 56 r := rand.New(rand.NewSource(config.Seed)) 57 params := RandomParams(r) 58 fmt.Fprintf(w, "Randomized simulation params: \n%s\n", mustMarshalJSONIndent(params)) 59 60 timeDiff := maxTimePerBlock - minTimePerBlock 61 accs := RandomAccounts(r, params.NumKeys) 62 eventStats := NewEventStats() 63 64 // Second variable to keep pending validator set (delayed one block since 65 // TM 0.24) Initially this is the same as the initial validator set 66 validators, genesisTimestamp, accs, chainID := initChain(r, params, accs, app, appStateFn, config) 67 if len(accs) == 0 { 68 return true, params, fmt.Errorf("must have greater than zero genesis accounts") 69 } 70 71 config.ChainID = chainID 72 73 fmt.Printf( 74 "Starting the simulation from time %v (unixtime %v)\n", 75 genesisTimestamp.UTC().Format(time.UnixDate), genesisTimestamp.Unix(), 76 ) 77 78 // remove module account address if they exist in accs 79 var tmpAccs []Account 80 for _, acc := range accs { 81 if !blackListedAccs[acc.Address.String()] { 82 tmpAccs = append(tmpAccs, acc) 83 } 84 } 85 86 accs = tmpAccs 87 88 nextValidators := validators 89 90 header := abci.Header{ 91 ChainID: config.ChainID, 92 Height: 1, 93 Time: genesisTimestamp, 94 ProposerAddress: validators.randomProposer(r), 95 } 96 opCount := 0 97 98 // Setup code to catch SIGTERM's 99 c := make(chan os.Signal) 100 signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 101 go func() { 102 receivedSignal := <-c 103 fmt.Fprintf(w, "\nExiting early due to %s, on block %d, operation %d\n", receivedSignal, header.Height, opCount) 104 err = fmt.Errorf("exited due to %s", receivedSignal) 105 stopEarly = true 106 }() 107 108 var pastTimes []time.Time 109 var pastVoteInfos [][]abci.VoteInfo 110 111 request := RandomRequestBeginBlock(r, params, 112 validators, pastTimes, pastVoteInfos, eventStats.Tally, header) 113 114 // These are operations which have been queued by previous operations 115 operationQueue := NewOperationQueue() 116 timeOperationQueue := []FutureOperation{} 117 118 logWriter := NewLogWriter(testingMode) 119 120 blockSimulator := createBlockSimulator( 121 testingMode, tb, t, w, params, eventStats.Tally, 122 ops, operationQueue, timeOperationQueue, logWriter, config) 123 124 if !testingMode { 125 b.ResetTimer() 126 } else { 127 // recover logs in case of panic 128 defer func() { 129 if r := recover(); r != nil { 130 _, _ = fmt.Fprintf(w, "simulation halted due to panic on block %d\n", header.Height) 131 logWriter.PrintLogs() 132 panic(r) 133 } 134 }() 135 } 136 137 // set exported params to the initial state 138 if config.ExportParamsPath != "" && config.ExportParamsHeight == 0 { 139 exportedParams = params 140 } 141 142 // TODO: split up the contents of this for loop into new functions 143 for height := config.InitialBlockHeight; height < config.NumBlocks+config.InitialBlockHeight && !stopEarly; height++ { 144 145 // Log the header time for future lookup 146 pastTimes = append(pastTimes, header.Time) 147 pastVoteInfos = append(pastVoteInfos, request.LastCommitInfo.Votes) 148 149 // Run the BeginBlock handler 150 logWriter.AddEntry(BeginBlockEntry(int64(height))) 151 app.BeginBlock(request) 152 153 ctx := app.NewContext(false, header) 154 155 // Run queued operations. Ignores blocksize if blocksize is too small 156 numQueuedOpsRan := runQueuedOperations( 157 operationQueue, int(header.Height), tb, r, app, ctx, accs, logWriter, 158 eventStats.Tally, config.Lean, config.ChainID, 159 ) 160 161 numQueuedTimeOpsRan := runQueuedTimeOperations( 162 timeOperationQueue, int(header.Height), header.Time, 163 tb, r, app, ctx, accs, logWriter, eventStats.Tally, 164 config.Lean, config.ChainID, 165 ) 166 167 // run standard operations 168 operations := blockSimulator(r, app, ctx, accs, header) 169 opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan 170 171 res := app.EndBlock(abci.RequestEndBlock{}) 172 header.Height++ 173 header.Time = header.Time.Add( 174 time.Duration(minTimePerBlock) * time.Second) 175 header.Time = header.Time.Add( 176 time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) 177 header.ProposerAddress = validators.randomProposer(r) 178 logWriter.AddEntry(EndBlockEntry(int64(height))) 179 180 if config.Commit { 181 app.Commit(abci.RequestCommit{}) 182 } 183 184 if header.ProposerAddress == nil { 185 fmt.Fprintf(w, "\nSimulation stopped early as all validators have been unbonded; nobody left to propose a block!\n") 186 stopEarly = true 187 break 188 } 189 190 // Generate a random RequestBeginBlock with the current validator set 191 // for the next block 192 request = RandomRequestBeginBlock(r, params, validators, 193 pastTimes, pastVoteInfos, eventStats.Tally, header) 194 195 // Update the validator set, which will be reflected in the application 196 // on the next block 197 validators = nextValidators 198 nextValidators = updateValidators(tb, r, params, 199 validators, res.ValidatorUpdates, eventStats.Tally) 200 201 // update the exported params 202 if config.ExportParamsPath != "" && config.ExportParamsHeight == height { 203 exportedParams = params 204 } 205 } 206 207 if stopEarly { 208 if config.ExportStatsPath != "" { 209 fmt.Println("Exporting simulation statistics...") 210 eventStats.ExportJSON(config.ExportStatsPath) 211 } else { 212 eventStats.Print(w) 213 } 214 215 return true, exportedParams, err 216 } 217 218 fmt.Fprintf( 219 w, 220 "\nSimulation complete; Final height (blocks): %d, final time (seconds): %v, operations ran: %d\n", 221 header.Height, header.Time, opCount, 222 ) 223 224 if config.ExportStatsPath != "" { 225 fmt.Println("Exporting simulation statistics...") 226 eventStats.ExportJSON(config.ExportStatsPath) 227 } else { 228 eventStats.Print(w) 229 } 230 231 return false, exportedParams, nil 232 } 233 234 //______________________________________________________________________________ 235 236 type blockSimFn func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, 237 accounts []Account, header abci.Header) (opCount int) 238 239 // Returns a function to simulate blocks. Written like this to avoid constant 240 // parameters being passed everytime, to minimize memory overhead. 241 func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, w io.Writer, params Params, 242 event func(route, op, evResult string), ops WeightedOperations, 243 operationQueue OperationQueue, timeOperationQueue []FutureOperation, 244 logWriter LogWriter, config Config) blockSimFn { 245 246 lastBlockSizeState := 0 // state for [4 * uniform distribution] 247 blocksize := 0 248 selectOp := ops.getSelectOpFn() 249 250 return func( 251 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, header abci.Header, 252 ) (opCount int) { 253 254 _, _ = fmt.Fprintf( 255 w, "\rSimulating... block %d/%d, operation %d/%d.", 256 header.Height, config.NumBlocks, opCount, blocksize, 257 ) 258 lastBlockSizeState, blocksize = getBlockSize(r, params, lastBlockSizeState, config.BlockSize) 259 260 type opAndR struct { 261 op Operation 262 rand *rand.Rand 263 } 264 265 opAndRz := make([]opAndR, 0, blocksize) 266 267 // Predetermine the blocksize slice so that we can do things like block 268 // out certain operations without changing the ops that follow. 269 for i := 0; i < blocksize; i++ { 270 opAndRz = append(opAndRz, opAndR{ 271 op: selectOp(r), 272 rand: DeriveRand(r), 273 }) 274 } 275 276 for i := 0; i < blocksize; i++ { 277 // NOTE: the Rand 'r' should not be used here. 278 opAndR := opAndRz[i] 279 op, r2 := opAndR.op, opAndR.rand 280 opMsg, futureOps, err := op(r2, app, ctx, accounts, config.ChainID) 281 opMsg.LogEvent(event) 282 283 if !config.Lean || opMsg.OK { 284 logWriter.AddEntry(MsgEntry(header.Height, int64(i), opMsg)) 285 } 286 287 if err != nil { 288 logWriter.PrintLogs() 289 tb.Fatalf(`error on block %d/%d, operation (%d/%d) from x/%s: 290 %v 291 Comment: %s`, 292 header.Height, config.NumBlocks, opCount, blocksize, opMsg.Route, err, opMsg.Comment) 293 } 294 295 queueOperations(operationQueue, timeOperationQueue, futureOps) 296 297 if testingMode && opCount%50 == 0 { 298 fmt.Fprintf(w, "\rSimulating... block %d/%d, operation %d/%d. ", 299 header.Height, config.NumBlocks, opCount, blocksize) 300 } 301 302 opCount++ 303 } 304 return opCount 305 } 306 } 307 308 // nolint: errcheck 309 func runQueuedOperations(queueOps map[int][]Operation, 310 height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, 311 ctx sdk.Context, accounts []Account, logWriter LogWriter, 312 event func(route, op, evResult string), lean bool, chainID string) (numOpsRan int) { 313 314 queuedOp, ok := queueOps[height] 315 if !ok { 316 return 0 317 } 318 319 numOpsRan = len(queuedOp) 320 for i := 0; i < numOpsRan; i++ { 321 322 // For now, queued operations cannot queue more operations. 323 // If a need arises for us to support queued messages to queue more messages, this can 324 // be changed. 325 opMsg, _, err := queuedOp[i](r, app, ctx, accounts, chainID) 326 opMsg.LogEvent(event) 327 if !lean || opMsg.OK { 328 logWriter.AddEntry((QueuedMsgEntry(int64(height), opMsg))) 329 } 330 if err != nil { 331 logWriter.PrintLogs() 332 tb.FailNow() 333 } 334 } 335 delete(queueOps, height) 336 return numOpsRan 337 } 338 339 func runQueuedTimeOperations(queueOps []FutureOperation, 340 height int, currentTime time.Time, tb testing.TB, r *rand.Rand, 341 app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, 342 logWriter LogWriter, event func(route, op, evResult string), 343 lean bool, chainID string) (numOpsRan int) { 344 345 numOpsRan = 0 346 for len(queueOps) > 0 && currentTime.After(queueOps[0].BlockTime) { 347 348 // For now, queued operations cannot queue more operations. 349 // If a need arises for us to support queued messages to queue more messages, this can 350 // be changed. 351 opMsg, _, err := queueOps[0].Op(r, app, ctx, accounts, chainID) 352 opMsg.LogEvent(event) 353 if !lean || opMsg.OK { 354 logWriter.AddEntry(QueuedMsgEntry(int64(height), opMsg)) 355 } 356 if err != nil { 357 logWriter.PrintLogs() 358 tb.FailNow() 359 } 360 361 queueOps = queueOps[1:] 362 numOpsRan++ 363 } 364 return numOpsRan 365 }