github.com/klaytn/klaytn@v1.12.1/tests/pregenerated_data_generation_test.go (about) 1 // Copyright 2019 The klaytn Authors 2 // This file is part of the klaytn library. 3 // 4 // The klaytn library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The klaytn library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>. 16 17 package tests 18 19 import ( 20 "crypto/ecdsa" 21 "errors" 22 "fmt" 23 "math/big" 24 "math/rand" 25 "os" 26 "path" 27 "path/filepath" 28 "runtime/pprof" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/klaytn/klaytn/blockchain" 34 "github.com/klaytn/klaytn/blockchain/state" 35 "github.com/klaytn/klaytn/blockchain/types" 36 "github.com/klaytn/klaytn/common" 37 "github.com/klaytn/klaytn/params" 38 "github.com/klaytn/klaytn/storage/database" 39 "github.com/otiai10/copy" 40 "github.com/syndtr/goleveldb/leveldb/opt" 41 ) 42 43 func init() { 44 rand.Seed(time.Now().UnixNano()) 45 } 46 47 var errNoOriginalDataDir = errors.New("original data directory does not exist, aborting the test") 48 49 const ( 50 // All databases are compressed by Snappy, CompactionTableSize = 2MiB, CompactionTableSizeMultiplier = 1.0 51 aspen500_orig = "aspen500_orig" 52 // All databases are compressed by Snappy, CompactionTableSize = 4MiB, CompactionTableSizeMultiplier = 2.0 53 baobab500_orig = "baobab500_orig" 54 55 // Only receipt database is compressed by Snappy, CompactionTableSize = 2MiB, CompactionTableSizeMultiplier = 1.0 56 candidate500LevelDB_orig = "candidate500LevelDB_orig" 57 // Using BadgerDB with its default options. 58 candidate500BadgerDB_orig = "candidate500BadgerDB_orig" 59 60 // Same configuration as Baobab network, however only 10,000 accounts exist. 61 baobab1_orig = "baobab1_orig" 62 ) 63 64 // randomIndex is used to access data with random index. 65 func randomIndex(index, lenAddrs int) int { 66 return rand.Intn(lenAddrs) 67 } 68 69 // sequentialIndex is used to access data with sequential index. 70 func sequentialIndex(index, lenAddrs int) int { 71 return index % lenAddrs 72 } 73 74 // fixedIndex is used to access data with same index. 75 func fixedIndex(index int) func(int, int) int { 76 return func(int, int) int { 77 return index 78 } 79 } 80 81 // makeTxsWithStateDB generates transactions with the nonce retrieved from stateDB. 82 // stateDB is used only once to initialize nonceMap, and then nonceMap is used instead of stateDB. 83 func makeTxsWithStateDB(isGenerate bool, stateDB *state.StateDB, fromAddrs []*common.Address, fromKeys []*ecdsa.PrivateKey, toAddrs []*common.Address, signer types.Signer, numTransactions int, indexPicker func(int, int) int) (types.Transactions, map[common.Address]uint64, error) { 84 if len(fromAddrs) != len(fromKeys) { 85 return nil, nil, fmt.Errorf("len(fromAddrs) %v != len(fromKeys) %v", len(fromAddrs), len(fromKeys)) 86 } 87 88 // Use nonceMap, not to change the nonce of stateDB. 89 nonceMap := make(map[common.Address]uint64) 90 for _, addr := range fromAddrs { 91 nonce := stateDB.GetNonce(*addr) 92 nonceMap[*addr] = nonce 93 } 94 95 // Generate value transfer transactions from initial account to the given "toAddrs". 96 return makeTxsWithNonceMap(isGenerate, nonceMap, fromAddrs, fromKeys, toAddrs, signer, numTransactions, indexPicker) 97 } 98 99 // makeTxsWithStateDB generates transactions with the nonce retrieved from nonceMap. 100 func makeTxsWithNonceMap(isGenerate bool, nonceMap map[common.Address]uint64, fromAddrs []*common.Address, fromKeys []*ecdsa.PrivateKey, toAddrs []*common.Address, signer types.Signer, numTransactions int, indexPicker func(int, int) int) (types.Transactions, map[common.Address]uint64, error) { 101 txs := make(types.Transactions, 0, numTransactions) 102 lenFromAddrs := len(fromAddrs) 103 lenToAddrs := len(toAddrs) 104 105 var transferValue *big.Int 106 if isGenerate { 107 transferValue = new(big.Int).Mul(big.NewInt(1e4), big.NewInt(params.KLAY)) 108 } else { 109 transferValue = new(big.Int).Mul(big.NewInt(1e3), big.NewInt(params.Peb)) 110 } 111 112 for i := 0; i < numTransactions; i++ { 113 fromIdx := indexPicker(i, lenFromAddrs) 114 toIdx := indexPicker(i, lenToAddrs) 115 116 fromAddr := *fromAddrs[fromIdx] 117 fromKey := fromKeys[fromIdx] 118 fromNonce := nonceMap[fromAddr] 119 120 toAddr := *toAddrs[toIdx] 121 122 tx := types.NewTransaction(fromNonce, toAddr, transferValue, 1000000, new(big.Int).SetInt64(25000000000), nil) 123 signedTx, err := types.SignTx(tx, signer, fromKey) 124 if err != nil { 125 return nil, nil, err 126 } 127 128 txs = append(txs, signedTx) 129 nonceMap[fromAddr]++ 130 } 131 132 return txs, nonceMap, nil 133 } 134 135 // setupTestDir does two things. If it is a data-generating test, it will just 136 // return the target path. If it is not a data-generating test, it will remove 137 // previously existing path and then copy the original data to the target path. 138 func setupTestDir(originalDataDirName string, isGenerateTest bool) (string, error) { 139 wd, err := os.Getwd() 140 if err != nil { 141 return "", err 142 } 143 144 // Original data directory should be located at github.com/klaytn 145 // Therefore, it should be something like github.com/klaytn/testdata150_orig 146 grandParentPath := filepath.Dir(filepath.Dir(wd)) 147 originalDataDirPath := path.Join(grandParentPath, originalDataDirName) 148 149 // If it is generating test case, just returns the path. 150 if isGenerateTest { 151 return originalDataDirPath, nil 152 } 153 154 if _, err = os.Stat(originalDataDirPath); err != nil { 155 return "", errNoOriginalDataDir 156 } 157 158 testDir := strings.Split(originalDataDirName, "_orig")[0] 159 160 originalDataPath := path.Join(grandParentPath, originalDataDirName) 161 testDataPath := path.Join(grandParentPath, testDir) 162 163 os.RemoveAll(testDataPath) 164 if err := copy.Copy(originalDataPath, testDataPath); err != nil { 165 return "", err 166 } 167 return testDataPath, nil 168 } 169 170 type preGeneratedTC struct { 171 isGenerateTest bool 172 testName string 173 originalDataDir string 174 175 numTotalAccountsToGenerate int 176 numTxsPerGen int 177 178 numTotalSenders int // senders are loaded once at the test initialization time. 179 numReceiversPerRun int // receivers are loaded repetitively for every tx generation run. 180 181 filePicker func(int, int) int // determines the index of address file to use. 182 addrPicker func(int, int) int // determines the index of address while making tx. 183 184 dbc *database.DBConfig 185 levelDBOption *opt.Options 186 cacheConfig *blockchain.CacheConfig 187 } 188 189 // BenchmarkDataGeneration_Aspen generates the data with Aspen network's database configurations. 190 func BenchmarkDataGeneration_Aspen(b *testing.B) { 191 tc := getGenerationTestDefaultTC() 192 tc.testName = "BenchmarkDataGeneration_Aspen" 193 tc.originalDataDir = aspen500_orig 194 195 tc.cacheConfig = defaultCacheConfig() 196 197 tc.dbc, tc.levelDBOption = genAspenOptions() 198 199 dataGenerationTest(b, tc) 200 } 201 202 // BenchmarkDataGeneration_Baobab generates the data with Baobab network's database configurations. 203 func BenchmarkDataGeneration_Baobab(b *testing.B) { 204 tc := getGenerationTestDefaultTC() 205 tc.testName = "BenchmarkDataGeneration_Baobab" 206 tc.originalDataDir = baobab500_orig 207 208 tc.cacheConfig = defaultCacheConfig() 209 210 tc.dbc, tc.levelDBOption = genBaobabOptions() 211 212 dataGenerationTest(b, tc) 213 } 214 215 // BenchmarkDataGeneration_CandidateLevelDB generates the data for main-net's 216 // with candidate configurations, using LevelDB. 217 func BenchmarkDataGeneration_CandidateLevelDB(b *testing.B) { 218 tc := getGenerationTestDefaultTC() 219 tc.testName = "BenchmarkDataGeneration_CandidateLevelDB" 220 tc.originalDataDir = candidate500LevelDB_orig 221 222 tc.cacheConfig = defaultCacheConfig() 223 224 tc.dbc, tc.levelDBOption = genCandidateLevelDBOptions() 225 226 dataGenerationTest(b, tc) 227 } 228 229 // BenchmarkDataGeneration_CandidateBadgerDB generates the data for main-net's 230 // with candidate configurations, using BadgerDB. 231 func BenchmarkDataGeneration_CandidateBadgerDB(b *testing.B) { 232 tc := getGenerationTestDefaultTC() 233 tc.testName = "BenchmarkDataGeneration_CandidateBadgerDB" 234 tc.originalDataDir = candidate500BadgerDB_orig 235 236 tc.cacheConfig = defaultCacheConfig() 237 238 tc.dbc, tc.levelDBOption = genCandidateBadgerDBOptions() 239 240 dataGenerationTest(b, tc) 241 } 242 243 // BenchmarkDataGeneration_Baobab_ControlGroup generates the data with Baobab network's database configurations. 244 // To work as a control group, it only generates 10,000 accounts. 245 func BenchmarkDataGeneration_Baobab_ControlGroup(b *testing.B) { 246 tc := getGenerationTestDefaultTC() 247 tc.testName = "BenchmarkDataGeneration_Baobab_ControlGroup" 248 tc.originalDataDir = baobab1_orig 249 tc.numTotalAccountsToGenerate = 10000 250 251 tc.cacheConfig = defaultCacheConfig() 252 253 tc.dbc, tc.levelDBOption = genBaobabOptions() 254 255 dataGenerationTest(b, tc) 256 } 257 258 // dataGenerationTest generates given number of accounts for pre-generated tests. 259 // Newly generated data directory will be located at "$GOPATH/src/github.com/klaytn/" 260 func dataGenerationTest(b *testing.B, tc *preGeneratedTC) { 261 testDataDir, profileFile, err := setUpTest(tc) 262 if err != nil { 263 b.Fatal(err) 264 } 265 266 bcData, err := NewBCDataForPreGeneratedTest(testDataDir, tc) 267 if err != nil { 268 b.Fatal(err) 269 } 270 271 defer bcData.db.Close() 272 defer bcData.bc.Stop() 273 274 txPool := makeTxPool(bcData, tc.numTxsPerGen) 275 signer := types.MakeSigner(bcData.bc.Config(), bcData.bc.CurrentHeader().Number) 276 277 b.ResetTimer() 278 b.StopTimer() 279 280 numTxGenerationRuns := tc.numTotalAccountsToGenerate / tc.numTxsPerGen 281 for run := 1; run < numTxGenerationRuns; run++ { 282 toAddrs, _, err := makeOrGenerateAddrsAndKeys(testDataDir, run, tc) 283 if err != nil { 284 b.Fatal(err) 285 } 286 287 // Generate transactions 288 stateDB, err := bcData.bc.State() 289 if err != nil { 290 b.Fatal(err) 291 } 292 293 txs, _, err := makeTxsWithStateDB(true, stateDB, bcData.addrs, bcData.privKeys, toAddrs, signer, tc.numTxsPerGen, tc.addrPicker) 294 if err != nil { 295 b.Fatal(err) 296 } 297 298 for _, tx := range txs { 299 tx.AsMessageWithAccountKeyPicker(signer, stateDB, bcData.bc.CurrentBlock().NumberU64()) 300 } 301 302 b.StartTimer() 303 if run == numTxGenerationRuns { 304 pprof.StartCPUProfile(profileFile) 305 } 306 307 txPool.AddRemotes(txs) 308 309 for { 310 if err := bcData.GenABlockWithTxPoolWithoutAccountMap(txPool); err != nil { 311 if err == errEmptyPending { 312 break 313 } 314 b.Fatal(err) 315 } 316 } 317 318 if run == numTxGenerationRuns { 319 pprof.StopCPUProfile() 320 } 321 b.StopTimer() 322 } 323 } 324 325 // getGenerationTestDefaultTC returns default TC of data generation tests. 326 func getGenerationTestDefaultTC() *preGeneratedTC { 327 return &preGeneratedTC{ 328 isGenerateTest: true, 329 numTotalAccountsToGenerate: 500 * 10000, numTxsPerGen: 10000, 330 numTotalSenders: 10000, numReceiversPerRun: 10000, 331 filePicker: sequentialIndex, addrPicker: sequentialIndex, 332 cacheConfig: defaultCacheConfig(), 333 } 334 }