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  }