github.com/cosmos/cosmos-sdk@v0.50.10/testutil/sims/simulation_helpers.go (about) 1 package sims 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "sync" 9 10 dbm "github.com/cosmos/cosmos-db" 11 12 "cosmossdk.io/log" 13 storetypes "cosmossdk.io/store/types" 14 15 "github.com/cosmos/cosmos-sdk/codec" 16 "github.com/cosmos/cosmos-sdk/runtime" 17 sdk "github.com/cosmos/cosmos-sdk/types" 18 "github.com/cosmos/cosmos-sdk/types/kv" 19 "github.com/cosmos/cosmos-sdk/types/module" 20 moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" 21 simtypes "github.com/cosmos/cosmos-sdk/types/simulation" 22 ) 23 24 // SetupSimulation creates the config, db (levelDB), temporary directory and logger for the simulation tests. 25 // If `skip` is false it skips the current test. `skip` should be set using the `FlagEnabledValue` flag. 26 // Returns error on an invalid db intantiation or temp dir creation. 27 func SetupSimulation(config simtypes.Config, dirPrefix, dbName string, verbose, skip bool) (dbm.DB, string, log.Logger, bool, error) { 28 if !skip { 29 return nil, "", nil, true, nil 30 } 31 32 var logger log.Logger 33 if verbose { 34 logger = log.NewLogger(os.Stdout) // TODO(mr): enable selection of log destination. 35 } else { 36 logger = log.NewNopLogger() 37 } 38 39 dir, err := os.MkdirTemp("", dirPrefix) 40 if err != nil { 41 return nil, "", nil, false, err 42 } 43 44 db, err := dbm.NewDB(dbName, dbm.BackendType(config.DBBackend), dir) 45 if err != nil { 46 return nil, "", nil, false, err 47 } 48 49 return db, dir, logger, false, nil 50 } 51 52 // SimulationOperations retrieves the simulation params from the provided file path 53 // and returns all the modules weighted operations 54 func SimulationOperations(app runtime.AppI, cdc codec.JSONCodec, config simtypes.Config) []simtypes.WeightedOperation { 55 simState := module.SimulationState{ 56 AppParams: make(simtypes.AppParams), 57 Cdc: cdc, 58 TxConfig: moduletestutil.MakeTestTxConfig(), 59 BondDenom: sdk.DefaultBondDenom, 60 } 61 62 if config.ParamsFile != "" { 63 bz, err := os.ReadFile(config.ParamsFile) 64 if err != nil { 65 panic(err) 66 } 67 68 err = json.Unmarshal(bz, &simState.AppParams) 69 if err != nil { 70 panic(err) 71 } 72 } 73 74 //nolint:staticcheck // used for legacy testing 75 simState.LegacyProposalContents = app.SimulationManager().GetProposalContents(simState) 76 simState.ProposalMsgs = app.SimulationManager().GetProposalMsgs(simState) 77 return app.SimulationManager().WeightedOperations(simState) 78 } 79 80 // CheckExportSimulation exports the app state and simulation parameters to JSON 81 // if the export paths are defined. 82 func CheckExportSimulation(app runtime.AppI, config simtypes.Config, params simtypes.Params) error { 83 if config.ExportStatePath != "" { 84 fmt.Println("exporting app state...") 85 exported, err := app.ExportAppStateAndValidators(false, nil, nil) 86 if err != nil { 87 return err 88 } 89 90 if err := os.WriteFile(config.ExportStatePath, []byte(exported.AppState), 0o600); err != nil { 91 return err 92 } 93 } 94 95 if config.ExportParamsPath != "" { 96 fmt.Println("exporting simulation params...") 97 paramsBz, err := json.MarshalIndent(params, "", " ") 98 if err != nil { 99 return err 100 } 101 102 if err := os.WriteFile(config.ExportParamsPath, paramsBz, 0o600); err != nil { 103 return err 104 } 105 } 106 return nil 107 } 108 109 // PrintStats prints the corresponding statistics from the app DB. 110 func PrintStats(db dbm.DB) { 111 fmt.Println("\nLevelDB Stats") 112 fmt.Println(db.Stats()["leveldb.stats"]) 113 fmt.Println("LevelDB cached block size", db.Stats()["leveldb.cachedblock"]) 114 } 115 116 // GetSimulationLog unmarshals the KVPair's Value to the corresponding type based on the 117 // each's module store key and the prefix bytes of the KVPair's key. 118 func GetSimulationLog(storeName string, sdr simtypes.StoreDecoderRegistry, kvAs, kvBs []kv.Pair) (log string) { 119 for i := 0; i < len(kvAs); i++ { 120 if len(kvAs[i].Value) == 0 && len(kvBs[i].Value) == 0 { 121 // skip if the value doesn't have any bytes 122 continue 123 } 124 125 decoder, ok := sdr[storeName] 126 if ok { 127 log += decoder(kvAs[i], kvBs[i]) 128 } else { 129 log += fmt.Sprintf("store A %X => %X\nstore B %X => %X\n", kvAs[i].Key, kvAs[i].Value, kvBs[i].Key, kvBs[i].Value) 130 } 131 } 132 133 return log 134 } 135 136 // DiffKVStores compares two KVstores and returns all the key/value pairs 137 // that differ from one another. It also skips value comparison for a set of provided prefixes. 138 func DiffKVStores(a, b storetypes.KVStore, prefixesToSkip [][]byte) (diffA, diffB []kv.Pair) { 139 iterA := a.Iterator(nil, nil) 140 defer iterA.Close() 141 142 iterB := b.Iterator(nil, nil) 143 defer iterB.Close() 144 145 var wg sync.WaitGroup 146 147 wg.Add(1) 148 kvAs := make([]kv.Pair, 0) 149 go func() { 150 defer wg.Done() 151 kvAs = getKVPairs(iterA, prefixesToSkip) 152 }() 153 154 wg.Add(1) 155 kvBs := make([]kv.Pair, 0) 156 go func() { 157 defer wg.Done() 158 kvBs = getKVPairs(iterB, prefixesToSkip) 159 }() 160 161 wg.Wait() 162 163 if len(kvAs) != len(kvBs) { 164 fmt.Printf("KV stores are different: %d key/value pairs in store A and %d key/value pairs in store B\n", len(kvAs), len(kvBs)) 165 } 166 167 return getDiffFromKVPair(kvAs, kvBs) 168 } 169 170 // getDiffFromKVPair compares two KVstores and returns all the key/value pairs 171 func getDiffFromKVPair(kvAs, kvBs []kv.Pair) (diffA, diffB []kv.Pair) { 172 // we assume that kvBs is equal or larger than kvAs 173 // if not, we swap the two 174 if len(kvAs) > len(kvBs) { 175 kvAs, kvBs = kvBs, kvAs 176 // we need to swap the diffA and diffB as well 177 defer func() { 178 diffA, diffB = diffB, diffA 179 }() 180 } 181 182 // in case kvAs is empty we can return early 183 // since there is nothing to compare 184 // if kvAs == kvBs, then diffA and diffB will be empty 185 if len(kvAs) == 0 { 186 return []kv.Pair{}, kvBs 187 } 188 189 index := make(map[string][]byte, len(kvBs)) 190 for _, kv := range kvBs { 191 index[string(kv.Key)] = kv.Value 192 } 193 194 for _, kvA := range kvAs { 195 if kvBValue, ok := index[string(kvA.Key)]; !ok { 196 diffA = append(diffA, kvA) 197 diffB = append(diffB, kv.Pair{Key: kvA.Key}) // the key is missing from kvB so we append a pair with an empty value 198 } else if !bytes.Equal(kvA.Value, kvBValue) { 199 diffA = append(diffA, kvA) 200 diffB = append(diffB, kv.Pair{Key: kvA.Key, Value: kvBValue}) 201 } else { 202 // values are equal, so we remove the key from the index 203 delete(index, string(kvA.Key)) 204 } 205 } 206 207 // add the remaining keys from kvBs 208 for key, value := range index { 209 diffA = append(diffA, kv.Pair{Key: []byte(key)}) // the key is missing from kvA so we append a pair with an empty value 210 diffB = append(diffB, kv.Pair{Key: []byte(key), Value: value}) 211 } 212 213 return diffA, diffB 214 } 215 216 func getKVPairs(iter dbm.Iterator, prefixesToSkip [][]byte) (kvs []kv.Pair) { 217 for iter.Valid() { 218 key, value := iter.Key(), iter.Value() 219 220 // do not add the KV pair if the key is prefixed to be skipped. 221 skip := false 222 for _, prefix := range prefixesToSkip { 223 if bytes.HasPrefix(key, prefix) { 224 skip = true 225 break 226 } 227 } 228 229 if !skip { 230 kvs = append(kvs, kv.Pair{Key: key, Value: value}) 231 } 232 233 iter.Next() 234 } 235 236 return kvs 237 }