github.com/klaytn/klaytn@v1.12.1/tests/smartcontract_execution_test.go (about) 1 // Copyright 2018 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 "encoding/json" 21 "fmt" 22 "math/big" 23 "math/rand" 24 "os" 25 "runtime/pprof" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/klaytn/klaytn/accounts/abi" 31 "github.com/klaytn/klaytn/blockchain" 32 "github.com/klaytn/klaytn/blockchain/types" 33 "github.com/klaytn/klaytn/blockchain/vm" 34 "github.com/klaytn/klaytn/common" 35 "github.com/klaytn/klaytn/common/compiler" 36 "github.com/klaytn/klaytn/common/profile" 37 "github.com/klaytn/klaytn/crypto" 38 "github.com/klaytn/klaytn/log" 39 ) 40 41 type deployedContract struct { 42 abi string 43 name string 44 address common.Address 45 } 46 47 func deployContract(filename string, bcdata *BCData, accountMap *AccountMap, 48 prof *profile.Profiler, 49 ) (map[string]*deployedContract, error) { 50 contracts, err := compiler.CompileSolidityOrLoad("", filename) 51 if err != nil { 52 return nil, err 53 } 54 55 cont := make(map[string]*deployedContract) 56 transactions := make(types.Transactions, 0, 10) 57 58 userAddr := bcdata.addrs[0] 59 nonce := accountMap.GetNonce(*userAddr) 60 61 // create a contract tx 62 for name, contract := range contracts { 63 64 abiStr, err := json.Marshal(contract.Info.AbiDefinition) 65 if err != nil { 66 return nil, err 67 } 68 69 header := bcdata.bc.CurrentHeader() 70 71 contractAddr := crypto.CreateAddress(*userAddr, nonce) 72 73 signer := types.MakeSigner(bcdata.bc.Config(), header.Number) 74 tx := types.NewContractCreation(nonce, 75 big.NewInt(0), 50000000, big.NewInt(0), common.FromHex(contract.Code)) 76 signedTx, err := types.SignTx(tx, signer, bcdata.privKeys[0]) 77 if err != nil { 78 return nil, err 79 } 80 81 transactions = append(transactions, signedTx) 82 83 cont[name] = &deployedContract{ 84 abi: string(abiStr), 85 name: name, 86 address: contractAddr, 87 } 88 89 nonce += 1 90 } 91 92 bcdata.GenABlockWithTransactions(accountMap, transactions, prof) 93 94 return cont, nil 95 } 96 97 func callContract(bcdata *BCData, tx *types.Transaction) ([]byte, error) { 98 header := bcdata.bc.CurrentHeader() 99 statedb, err := bcdata.bc.State() 100 if err != nil { 101 return nil, err 102 } 103 104 signer := types.MakeSigner(bcdata.bc.Config(), header.Number) 105 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, bcdata.bc.CurrentBlock().NumberU64()) 106 if err != nil { 107 return nil, err 108 } 109 110 txContext := blockchain.NewEVMTxContext(msg, header) 111 blockContext := blockchain.NewEVMBlockContext(header, bcdata.bc, nil) 112 vmenv := vm.NewEVM(blockContext, txContext, statedb, bcdata.bc.Config(), &vm.Config{}) 113 114 ret, err := blockchain.NewStateTransition(vmenv, msg).TransitionDb() 115 if err != nil { 116 return nil, err 117 } 118 119 return ret.Return(), nil 120 } 121 122 func makeRewardTransactions(c *deployedContract, accountMap *AccountMap, bcdata *BCData, 123 numTransactions int, 124 ) (types.Transactions, error) { 125 abii, err := abi.JSON(strings.NewReader(c.abi)) 126 if err != nil { 127 return nil, err 128 } 129 130 signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number) 131 132 transactions := make(types.Transactions, numTransactions) 133 134 numAddrs := len(bcdata.addrs) 135 fromNonces := make([]uint64, numAddrs) 136 for i, addr := range bcdata.addrs { 137 fromNonces[i] = accountMap.GetNonce(*addr) 138 } 139 for i := 0; i < numTransactions; i++ { 140 idx := i % numAddrs 141 142 addr := bcdata.addrs[idx] 143 data, err := abii.Pack("reward", addr) 144 if err != nil { 145 return nil, err 146 } 147 148 tx := types.NewTransaction(fromNonces[idx], c.address, big.NewInt(10), 5000000, big.NewInt(0), data) 149 signedTx, err := types.SignTx(tx, signer, bcdata.privKeys[idx]) 150 if err != nil { 151 return nil, err 152 } 153 154 transactions[i] = signedTx 155 fromNonces[idx]++ 156 } 157 158 return transactions, nil 159 } 160 161 func executeRewardTransactions(c *deployedContract, transactions types.Transactions, prof *profile.Profiler, bcdata *BCData, 162 accountMap *AccountMap, 163 ) error { 164 return bcdata.GenABlockWithTransactions(accountMap, transactions, prof) 165 } 166 167 func makeBalanceOf(c *deployedContract, accountMap *AccountMap, bcdata *BCData, 168 numTransactions int, 169 ) (types.Transactions, error) { 170 abii, err := abi.JSON(strings.NewReader(c.abi)) 171 if err != nil { 172 return nil, err 173 } 174 175 signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number) 176 177 transactions := make(types.Transactions, numTransactions) 178 179 numAddrs := len(bcdata.addrs) 180 fromNonces := make([]uint64, numAddrs) 181 for i, addr := range bcdata.addrs { 182 fromNonces[i] = accountMap.GetNonce(*addr) 183 } 184 for i := 0; i < numTransactions; i++ { 185 idx := i % numAddrs 186 187 addr := bcdata.addrs[idx] 188 data, err := abii.Pack("balanceOf", addr) 189 if err != nil { 190 return nil, err 191 } 192 193 tx := types.NewTransaction(fromNonces[idx], c.address, big.NewInt(0), 5000000, big.NewInt(0), data) 194 signedTx, err := types.SignTx(tx, signer, bcdata.privKeys[idx]) 195 if err != nil { 196 return nil, err 197 } 198 199 transactions[i] = signedTx 200 201 // This is not required because the transactions will not be inserted into the blockchain. 202 // fromNonces[idx]++ 203 } 204 205 return transactions, nil 206 } 207 208 func executeBalanceOf(c *deployedContract, transactions types.Transactions, prof *profile.Profiler, bcdata *BCData, 209 accountMap *AccountMap, 210 ) error { 211 abii, err := abi.JSON(strings.NewReader(c.abi)) 212 if err != nil { 213 return err 214 } 215 216 for _, tx := range transactions { 217 ret, err := callContract(bcdata, tx) 218 if err != nil { 219 return err 220 } 221 222 balance := new(big.Int) 223 abii.UnpackIntoInterface(&balance, "balanceOf", ret) 224 } 225 226 return nil 227 } 228 229 func makeQuickSortTransactions(c *deployedContract, accountMap *AccountMap, bcdata *BCData, 230 numTransactions int, 231 ) (types.Transactions, error) { 232 abii, err := abi.JSON(strings.NewReader(c.abi)) 233 if err != nil { 234 return nil, err 235 } 236 237 signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number) 238 239 transactions := make(types.Transactions, numTransactions) 240 241 numAddrs := len(bcdata.addrs) 242 fromNonces := make([]uint64, numAddrs) 243 for i, addr := range bcdata.addrs { 244 fromNonces[i] = accountMap.GetNonce(*addr) 245 } 246 for i := 0; i < numTransactions; i++ { 247 idx := i % numAddrs 248 249 data, err := abii.Pack("sort", big.NewInt(100), big.NewInt(123)) 250 if err != nil { 251 return nil, err 252 } 253 254 tx := types.NewTransaction(fromNonces[idx], c.address, nil, 10000000, big.NewInt(0), data) 255 signedTx, err := types.SignTx(tx, signer, bcdata.privKeys[idx]) 256 if err != nil { 257 return nil, err 258 } 259 260 transactions[i] = signedTx 261 fromNonces[idx]++ 262 } 263 264 return transactions, nil 265 } 266 267 func executeQuickSortTransactions(c *deployedContract, transactions types.Transactions, prof *profile.Profiler, bcdata *BCData, 268 accountMap *AccountMap, 269 ) error { 270 return bcdata.GenABlockWithTransactions(accountMap, transactions, prof) 271 } 272 273 func executeSmartContract(b *testing.B, opt *ContractExecutionOption, prof *profile.Profiler) { 274 // Initialize blockchain 275 start := time.Now() 276 bcdata, err := NewBCData(2000, 4) 277 if err != nil { 278 b.Fatal(err) 279 } 280 prof.Profile("main_init_blockchain", time.Now().Sub(start)) 281 defer bcdata.Shutdown() 282 283 // Initialize address-balance map for verification 284 start = time.Now() 285 accountMap := NewAccountMap() 286 if err := accountMap.Initialize(bcdata); err != nil { 287 b.Fatal(err) 288 } 289 prof.Profile("main_init_accountMap", time.Now().Sub(start)) 290 291 start = time.Now() 292 contracts, err := deployContract(opt.filepath, bcdata, accountMap, prof) 293 if err != nil { 294 b.Fatal(err) 295 } 296 prof.Profile("main_deployContract", time.Now().Sub(start)) 297 298 b.StopTimer() 299 b.ResetTimer() 300 for _, c := range contracts { 301 start = time.Now() 302 transactions, err := opt.makeTx(c, accountMap, bcdata, b.N) 303 if err != nil { 304 b.Fatal(err) 305 } 306 prof.Profile("main_makeTx", time.Now().Sub(start)) 307 308 start = time.Now() 309 b.StartTimer() 310 opt.executeTx(c, transactions, prof, bcdata, accountMap) 311 b.StopTimer() 312 prof.Profile("main_executeTx", time.Now().Sub(start)) 313 } 314 } 315 316 type ContractExecutionOption struct { 317 name string 318 filepath string 319 makeTx func(c *deployedContract, accountMap *AccountMap, bcdata *BCData, numTransactions int) (types.Transactions, error) 320 executeTx func(c *deployedContract, transactions types.Transactions, prof *profile.Profiler, bcdata *BCData, accountMap *AccountMap) error 321 } 322 323 func BenchmarkSmartContractExecute(b *testing.B) { 324 prof := profile.NewProfiler() 325 326 benches := []ContractExecutionOption{ 327 {"KlaytnReward:reward", "../contracts/reward/contract/KlaytnReward.sol", makeRewardTransactions, executeRewardTransactions}, 328 {"KlaytnReward:balanceOf", "../contracts/reward/contract/KlaytnReward.sol", makeBalanceOf, executeBalanceOf}, 329 {"QuickSort:sort", "./testdata/contracts/sort/QuickSort.sol", makeQuickSortTransactions, executeQuickSortTransactions}, 330 } 331 332 for _, bench := range benches { 333 b.Run(bench.name, func(b *testing.B) { 334 executeSmartContract(b, &bench, prof) 335 }) 336 } 337 338 if testing.Verbose() { 339 prof.PrintProfileInfo() 340 } 341 } 342 343 func BenchmarkStorageTrieStore(b *testing.B) { 344 log.EnableLogForTest(log.LvlCrit, log.LvlTrace) 345 prof := profile.NewProfiler() 346 347 benchOption := ContractExecutionOption{ 348 "StorageTrieStore", 349 "../contracts/storagetrie/StorageTrieStoreTest.sol", 350 makeStorageTrieTransactions, 351 nil, 352 } 353 354 executeSmartContractForStorageTrie(b, &benchOption, prof) 355 356 if testing.Verbose() { 357 prof.PrintProfileInfo() 358 } 359 } 360 361 func executeSmartContractForStorageTrie(b *testing.B, opt *ContractExecutionOption, prof *profile.Profiler) { 362 // Initialize blockchain 363 start := time.Now() 364 bcdata, err := NewBCData(2000, 4) 365 if err != nil { 366 b.Fatal(err) 367 } 368 prof.Profile("main_init_blockchain", time.Now().Sub(start)) 369 370 defer bcdata.db.Close() 371 defer bcdata.bc.Stop() 372 373 // Initialize address-balance map for verification 374 start = time.Now() 375 accountMap := NewAccountMap() 376 if err := accountMap.Initialize(bcdata); err != nil { 377 b.Fatal(err) 378 } 379 prof.Profile("main_init_accountMap", time.Now().Sub(start)) 380 381 start = time.Now() 382 contracts, err := deployContract(opt.filepath, bcdata, accountMap, prof) 383 if err != nil { 384 b.Fatal(err) 385 } 386 387 if len(contracts) != 1 { 388 b.Fatalf("contracts length should be 1 but %v!!", len(contracts)) 389 } 390 391 prof.Profile("main_deployContract", time.Now().Sub(start)) 392 393 b.StopTimer() 394 b.ResetTimer() 395 396 timeNow := time.Now() 397 f, err := os.Create(opt.name + "_" + timeNow.Format("2006-01-02-1504") + ".cpu.out") 398 if err != nil { 399 b.Fatalf("failed to create file for cpu profiling, err: %v", err) 400 } 401 402 totalRuns := 1 403 404 signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number) 405 txPool := makeTxPool(bcdata, txPoolSize) 406 for _, c := range contracts { 407 start = time.Now() 408 for run := 1; run <= totalRuns; run++ { 409 fmt.Printf("run %v started \n", run) 410 transactions, err := opt.makeTx(c, accountMap, bcdata, 20000) 411 if err != nil { 412 b.Fatal(err) 413 } 414 fmt.Printf("run %v tx generated \n", run) 415 416 state, _ := bcdata.bc.State() 417 for _, tx := range transactions { 418 if _, err := tx.AsMessageWithAccountKeyPicker(signer, state, bcdata.bc.CurrentBlock().NumberU64()); err != nil { 419 b.Fatal(err) 420 } 421 } 422 fmt.Printf("run %v tx validated \n", run) 423 424 start = time.Now() 425 b.StartTimer() 426 427 if run == totalRuns { 428 pprof.StartCPUProfile(f) 429 } 430 431 if err := executeTxs(bcdata, txPool, transactions); err != nil { 432 b.Fatal(err) 433 } 434 435 b.StopTimer() 436 if run == totalRuns { 437 pprof.StopCPUProfile() 438 } 439 } 440 } 441 } 442 443 func makeStorageTrieTransactions(c *deployedContract, accountMap *AccountMap, bcdata *BCData, 444 numTransactions int, 445 ) (types.Transactions, error) { 446 abii, err := abi.JSON(strings.NewReader(c.abi)) 447 if err != nil { 448 return nil, err 449 } 450 451 signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentHeader().Number) 452 453 transactions := make(types.Transactions, numTransactions) 454 455 stateDB, _ := bcdata.bc.State() 456 457 numAddrs := len(bcdata.addrs) 458 fromNonces := make([]uint64, numAddrs) 459 for i, addr := range bcdata.addrs { 460 fromNonces[i] = stateDB.GetNonce(*addr) 461 } 462 463 r := rand.New(rand.NewSource(time.Now().UnixNano())) 464 for i := 0; i < numTransactions; i++ { 465 idx := i % numAddrs 466 467 // function insertIdentity(string _serialNumber, string _publicKey, string _hash) 468 data, err := abii.Pack("insertIdentity", randomString(39, r), randomString(814, r), randomString(40, r)) 469 if err != nil { 470 return nil, err 471 } 472 473 tx := types.NewTransaction(fromNonces[idx], c.address, nil, 10000000, big.NewInt(25000000000), data) 474 signedTx, err := types.SignTx(tx, signer, bcdata.privKeys[idx]) 475 if err != nil { 476 return nil, err 477 } 478 479 transactions[i] = signedTx 480 fromNonces[idx]++ 481 } 482 483 return transactions, nil 484 } 485 486 func randomBytes(n int, rand *rand.Rand) []byte { 487 r := make([]byte, n) 488 if _, err := rand.Read(r); err != nil { 489 panic("rand.Read failed: " + err.Error()) 490 } 491 return r 492 } 493 494 func randomString(n int, rand *rand.Rand) string { 495 b := randomBytes(n, rand) 496 return string(b) 497 }