github.com/klaytn/klaytn@v1.12.1/tests/addtx_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  	"crypto/ecdsa"
    21  	"flag"
    22  	"fmt"
    23  	"math/big"
    24  	"math/rand"
    25  	"os"
    26  	"runtime/pprof"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/klaytn/klaytn/blockchain"
    32  	"github.com/klaytn/klaytn/blockchain/types"
    33  	"github.com/klaytn/klaytn/common"
    34  	"github.com/klaytn/klaytn/common/profile"
    35  	"github.com/klaytn/klaytn/params"
    36  )
    37  
    38  var cpuprofile bool = false
    39  
    40  func init() {
    41  	flag.BoolVar(&cpuprofile, "addtx_profile", false, "Enable cpu profiling for AddTx")
    42  }
    43  
    44  ////////////////////////////////////////////////////////////////////////////////
    45  // BenchmarkAddTx
    46  ////////////////////////////////////////////////////////////////////////////////
    47  func BenchmarkAddTx(b *testing.B) {
    48  	cacheSender := []bool{false, true}
    49  	maxAccounts := []int{1000 + 100, 1000 + 1000, 1000 + 10000}
    50  	numValidators := []int{4}
    51  	parallels := []string{"parallel", "sequential", "queueing"}
    52  	numQueues := []int{1, 2, 4, 8}
    53  
    54  	fmt.Printf("addtx_profile = %t\n", cpuprofile)
    55  
    56  	for _, cs := range cacheSender {
    57  		for _, ma := range maxAccounts {
    58  			for _, nv := range numValidators {
    59  				for _, p := range parallels {
    60  
    61  					numQueues := numQueues
    62  					if p != "queueing" {
    63  						numQueues = []int{1}
    64  					}
    65  					for _, nq := range numQueues {
    66  						testName := fmt.Sprintf("%t,%d,%d,%s,%d", cs, ma-1000, nv, p, nq)
    67  						b.Run(testName, func(b *testing.B) {
    68  							benchAddTx(b, ma, nv, p, nq, cs)
    69  						})
    70  					}
    71  				}
    72  			}
    73  		}
    74  	}
    75  }
    76  
    77  func txDispatcher(ch <-chan *types.Transaction, txpool *blockchain.TxPool, wait *sync.WaitGroup) {
    78  	for {
    79  		if t, ok := <-ch; ok {
    80  			err := txpool.AddLocal(t)
    81  			if err != nil {
    82  				fmt.Fprintf(os.Stderr, "%s\n", err)
    83  			}
    84  			wait.Done()
    85  		} else {
    86  			break
    87  		}
    88  	}
    89  }
    90  
    91  func benchAddTx(b *testing.B, maxAccounts, numValidators int, parallel string, numQueue int,
    92  	cacheSender bool,
    93  ) {
    94  	// Initialize blockchain
    95  	start := time.Now()
    96  	bcdata, err := NewBCData(maxAccounts, numValidators)
    97  	if err != nil {
    98  		b.Fatal(err)
    99  	}
   100  	profile.Prof.Profile("main_init_blockchain", time.Now().Sub(start))
   101  	defer bcdata.Shutdown()
   102  
   103  	// Initialize address-balance map for verification
   104  	start = time.Now()
   105  	accountMap := NewAccountMap()
   106  	if err := accountMap.Initialize(bcdata); err != nil {
   107  		b.Fatal(err)
   108  	}
   109  	profile.Prof.Profile("main_init_accountMap", time.Now().Sub(start))
   110  
   111  	poolConfig := blockchain.TxPoolConfig{
   112  		Journal:         transactionsJournalFilename,
   113  		JournalInterval: time.Hour,
   114  
   115  		PriceLimit: 1,
   116  		PriceBump:  10,
   117  
   118  		ExecSlotsAccount:    16,
   119  		ExecSlotsAll:        40000,
   120  		NonExecSlotsAccount: 64,
   121  		NonExecSlotsAll:     40000,
   122  
   123  		Lifetime: 5 * time.Minute,
   124  	}
   125  	txpool := blockchain.NewTxPool(poolConfig, bcdata.bc.Config(), bcdata.bc)
   126  
   127  	signer := types.MakeSigner(bcdata.bc.Config(), bcdata.bc.CurrentBlock().Number())
   128  
   129  	var wait sync.WaitGroup
   130  
   131  	var txChs []chan *types.Transaction
   132  
   133  	if parallel == "queueing" {
   134  		txChs = make([]chan *types.Transaction, numQueue)
   135  		for i := 0; i < numQueue; i++ {
   136  			txChs[i] = make(chan *types.Transaction, 100)
   137  		}
   138  
   139  		for i := 0; i < numQueue; i++ {
   140  			go txDispatcher(txChs[i], txpool, &wait)
   141  		}
   142  	}
   143  
   144  	var f *os.File = nil
   145  	if cpuprofile {
   146  		f, err = os.Create(fmt.Sprintf("profile_cpu_%t_%d_%d_%s_%d.out",
   147  			cacheSender, maxAccounts-1000, numValidators, parallel, numQueue))
   148  		if err != nil {
   149  			b.Fatal("could not create CPU profile :", err)
   150  		}
   151  	}
   152  
   153  	txs := make([]types.Transactions, b.N)
   154  	for i := 0; i < b.N; i++ {
   155  		txs[i], err = makeTransactions(accountMap,
   156  			bcdata.addrs[1000:maxAccounts], bcdata.privKeys[1000:maxAccounts],
   157  			signer, bcdata.addrs[0:maxAccounts-1000], nil, i, cacheSender)
   158  		if err != nil {
   159  			b.Fatal(err)
   160  		}
   161  	}
   162  
   163  	b.ResetTimer()
   164  	if cpuprofile {
   165  		if err := pprof.StartCPUProfile(f); err != nil {
   166  			b.Fatal("could not start CPU profile : ", err)
   167  		}
   168  	}
   169  	for i := 0; i < b.N; i++ {
   170  		switch parallel {
   171  		case "parallel":
   172  			txParallel(txs[i], txpool)
   173  
   174  		case "sequential":
   175  			txSequential(txs[i], txpool)
   176  
   177  		case "queueing":
   178  			txQueue(txs[i], txpool, txChs, numQueue, &wait)
   179  		}
   180  
   181  		pending, _ := txpool.Stats()
   182  		if pending%(maxAccounts-1000) != 0 {
   183  			b.Fatalf("pending(%d) should be divided by %d!\n", pending, maxAccounts-1000)
   184  		}
   185  	}
   186  	b.StopTimer()
   187  	if cpuprofile {
   188  		pprof.StopCPUProfile()
   189  	}
   190  
   191  	txpool.Stop()
   192  	if cpuprofile {
   193  		f.Close()
   194  	}
   195  
   196  	if testing.Verbose() {
   197  		profile.Prof.PrintProfileInfo()
   198  	}
   199  }
   200  
   201  func makeTransactions(accountMap *AccountMap, fromAddrs []*common.Address, privKeys []*ecdsa.PrivateKey,
   202  	signer types.Signer, toAddrs []*common.Address, amount *big.Int, additionalNonce int,
   203  	cacheSender bool,
   204  ) (types.Transactions, error) {
   205  	txs := make(types.Transactions, 0, len(toAddrs))
   206  	for i, from := range fromAddrs {
   207  		nonce := accountMap.GetNonce(*from)
   208  		nonce += uint64(additionalNonce)
   209  
   210  		txamount := amount
   211  		if txamount == nil {
   212  			txamount = big.NewInt(rand.Int63n(10))
   213  			txamount = txamount.Add(txamount, big.NewInt(1))
   214  		}
   215  
   216  		var gasLimit uint64 = 1000000
   217  		gasPrice := new(big.Int).SetInt64(25 * params.Ston)
   218  		data := []byte{}
   219  
   220  		tx := types.NewTransaction(nonce, *toAddrs[i], txamount, gasLimit, gasPrice, data)
   221  		signedTx, err := types.SignTx(tx, signer, privKeys[i])
   222  		if err != nil {
   223  			return nil, err
   224  		}
   225  
   226  		if cacheSender {
   227  			signed_addr, err := types.Sender(signer, signedTx)
   228  			if signed_addr != *from {
   229  				fmt.Printf("signed address(%s) != from(%s)\n", signed_addr.Hex(), from.Hex())
   230  			}
   231  			if err != nil {
   232  				return nil, err
   233  			}
   234  		}
   235  
   236  		txs = append(txs, signedTx)
   237  	}
   238  
   239  	return txs, nil
   240  }
   241  
   242  func txParallel(txs types.Transactions, txpool *blockchain.TxPool) {
   243  	var wait sync.WaitGroup
   244  
   245  	wait.Add(len(txs))
   246  
   247  	for _, tx := range txs {
   248  		go func(t *types.Transaction) {
   249  			err := txpool.AddLocal(t)
   250  			if err != nil {
   251  				fmt.Fprintf(os.Stderr, "%s\n", err)
   252  			}
   253  			wait.Done()
   254  		}(tx)
   255  	}
   256  
   257  	wait.Wait()
   258  }
   259  
   260  func txSequential(txs types.Transactions, txpool *blockchain.TxPool) {
   261  	for _, tx := range txs {
   262  		err := txpool.AddLocal(tx)
   263  		if err != nil {
   264  			fmt.Fprintf(os.Stderr, "%s\n", err)
   265  		}
   266  	}
   267  }
   268  
   269  func txQueue(txs types.Transactions, txpool *blockchain.TxPool,
   270  	txChs []chan *types.Transaction, numQueue int, wait *sync.WaitGroup,
   271  ) {
   272  	wait.Add(len(txs))
   273  
   274  	for i, tx := range txs {
   275  		txChs[i%numQueue] <- tx
   276  	}
   277  
   278  	wait.Wait()
   279  }