github.com/DxChainNetwork/dxc@v0.8.1-0.20220824085222-1162e304b6e7/cmd/stress-test/common.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"encoding/hex"
     7  	"math/big"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/DxChainNetwork/dxc/accounts/abi/bind"
    12  	"github.com/DxChainNetwork/dxc/cmd/utils"
    13  	"github.com/DxChainNetwork/dxc/common"
    14  	"github.com/DxChainNetwork/dxc/core/types"
    15  	"github.com/DxChainNetwork/dxc/crypto"
    16  	"github.com/DxChainNetwork/dxc/ethclient"
    17  	"github.com/DxChainNetwork/dxc/log"
    18  	"github.com/DxChainNetwork/dxc/params"
    19  	"gopkg.in/urfave/cli.v1"
    20  )
    21  
    22  const (
    23  	separator = ","
    24  )
    25  
    26  type buildTxFn func(nonce uint64, to common.Address, amount *big.Int, token common.Address) *types.Transaction
    27  
    28  // newClient creates a client with specified remote URL.
    29  func newClient(url string) *ethclient.Client {
    30  	client, err := ethclient.Dial(url)
    31  	if err != nil {
    32  		utils.Fatalf("Failed to connect to Ethereum node: %v", err)
    33  	}
    34  
    35  	return client
    36  }
    37  
    38  func newClients(urls []string) []*ethclient.Client {
    39  	clients := make([]*ethclient.Client, 0)
    40  
    41  	for _, url := range urls {
    42  		client, err := ethclient.Dial(url)
    43  		if err != nil {
    44  			continue
    45  		}
    46  
    47  		clients = append(clients, client)
    48  	}
    49  
    50  	return clients
    51  }
    52  
    53  func getRPCList(ctx *cli.Context) []string {
    54  	urlStr := ctx.GlobalString(nodeURLFlag.Name)
    55  	list := make([]string, 0)
    56  
    57  	for _, url := range strings.Split(urlStr, separator) {
    58  		if url = strings.Trim(url, " "); len(url) != 0 {
    59  			list = append(list, url)
    60  		}
    61  	}
    62  	if len(list) == 0 {
    63  		utils.Fatalf("Failed to find any valid rpc url in: %v", urlStr)
    64  	}
    65  
    66  	return list
    67  }
    68  
    69  // newAccount creates a ethereum account with bind transactor by plaintext key string in hex format .
    70  func newAccount(hexKey string) *bind.TransactOpts {
    71  	key, err := crypto.HexToECDSA(hexKey)
    72  	if err != nil {
    73  		utils.Fatalf("Failed to get privkey by hex key: %v", err)
    74  	}
    75  
    76  	return bind.NewKeyedTransactor(key)
    77  }
    78  
    79  func newAccounts(keys []*ecdsa.PrivateKey) []*bind.TransactOpts {
    80  	accounts := make([]*bind.TransactOpts, 0)
    81  
    82  	for _, k := range keys {
    83  		accounts = append(accounts, bind.NewKeyedTransactor(k))
    84  	}
    85  
    86  	return accounts
    87  }
    88  
    89  // newRandomAccount generates a random ethereum account with bind transactor
    90  func newRandomAccount() *bind.TransactOpts {
    91  	key, err := crypto.GenerateKey()
    92  	if err != nil {
    93  		utils.Fatalf("Failed to genreate random key: %v", err)
    94  	}
    95  
    96  	return bind.NewKeyedTransactor(key)
    97  }
    98  
    99  // generateRandomAccounts generates servial random accounts
   100  // concurrent do this if account amount is to big.
   101  func generateRandomAccounts(amount int) ([]*ecdsa.PrivateKey, []*bind.TransactOpts) {
   102  	keys := make([]*ecdsa.PrivateKey, 0)
   103  	result := make([]*bind.TransactOpts, 0)
   104  
   105  	workFn := func(start, end int, data ...interface{}) []interface{} {
   106  		tmpAccounts := make([]interface{}, 0)
   107  		for i := start; i < end; i++ {
   108  			key, _ := crypto.GenerateKey()
   109  
   110  			tmpAccounts = append(tmpAccounts, key)
   111  		}
   112  
   113  		return tmpAccounts
   114  	}
   115  	for _, account := range concurrentWork(amount/jobsPerThread+1, amount, workFn, nil) {
   116  		keys = append(keys, account.(*ecdsa.PrivateKey))
   117  		result = append(result, bind.NewKeyedTransactor(account.(*ecdsa.PrivateKey)))
   118  	}
   119  
   120  	return keys, result
   121  }
   122  
   123  // newSendEtherTransaction creates a normal transfer transaction.
   124  func newHBStansferTransaction(nonce uint64, to common.Address, amount *big.Int) *types.Transaction {
   125  	gasPrice := big.NewInt(10)
   126  	gasPrice.Mul(gasPrice, big.NewInt(params.GWei))
   127  
   128  	return types.NewTransaction(nonce, to, amount, hbTransferLimit, gasPrice, []byte{})
   129  }
   130  
   131  func newTokenTransferTransaction(nonce uint64, token, to common.Address, amount *big.Int) *types.Transaction {
   132  	gasPrice := big.NewInt(10)
   133  	gasPrice.Mul(gasPrice, big.NewInt(params.GWei))
   134  
   135  	return types.NewTransaction(nonce, token, new(big.Int), tokenTransferLimit, gasPrice, packData(to, amount))
   136  }
   137  
   138  func generateTx(nonce uint64, to common.Address, amount *big.Int, token common.Address) *types.Transaction {
   139  	if (token == common.Address{}) {
   140  		return newHBStansferTransaction(nonce, to, amount)
   141  	}
   142  
   143  	return newTokenTransferTransaction(nonce, token, to, amount)
   144  }
   145  
   146  func packData(to common.Address, amount *big.Int) []byte {
   147  	data := make([]byte, 68)
   148  
   149  	sig, _ := hex.DecodeString(tokenTransferSig)
   150  	copy(data[:4], sig[:])
   151  
   152  	toBytes := to.Bytes()
   153  	copy(data[36-len(toBytes):36], toBytes[:])
   154  
   155  	vBytes := amount.Bytes()
   156  	copy(data[68-len(vBytes):], vBytes[:])
   157  
   158  	return data
   159  }
   160  
   161  func sendEtherToRandomAccount(mainAccount *bind.TransactOpts, accounts []*bind.TransactOpts, amount *big.Int, token common.Address, client *ethclient.Client) {
   162  	nonce, err := client.NonceAt(context.Background(), mainAccount.From, nil)
   163  	if err != nil {
   164  		utils.Fatalf("Failed to get account nonce: %v", err)
   165  	}
   166  
   167  	var lastHash common.Hash
   168  	for _, account := range accounts {
   169  		signedTx, _ := mainAccount.Signer(mainAccount.From, generateTx(nonce, account.From, amount, token))
   170  		if err := client.SendTransaction(context.Background(), signedTx); err != nil {
   171  			utils.Fatalf("Failed to send ether to random account: %v", err)
   172  		}
   173  
   174  		lastHash = signedTx.Hash()
   175  		nonce++
   176  	}
   177  
   178  	waitForTx(lastHash, client)
   179  }
   180  
   181  // generateSignedTransactions generates transactions.
   182  func generateSignedTransactions(total int, accounts []*bind.TransactOpts, amount *big.Int, token common.Address, client *ethclient.Client) (txs []*types.Transaction) {
   183  	// total txs
   184  	workFn := func(start, end int, data ...interface{}) []interface{} {
   185  		// like 15 threads, 15 account, 1000 txs
   186  		account := accounts[start/(total/len(accounts))]
   187  		currentNonce, err := client.NonceAt(context.Background(), account.From, nil)
   188  		if err != nil {
   189  			utils.Fatalf("Failed to get account nonce: %v", err)
   190  		}
   191  
   192  		result := make([]interface{}, 0)
   193  		for i := start; i < end; i++ {
   194  			signedTx, _ := account.Signer(account.From, generateTx(currentNonce, receiver, amount, token))
   195  			result = append(result, signedTx)
   196  
   197  			currentNonce++
   198  		}
   199  
   200  		return result
   201  	}
   202  
   203  	// accounts
   204  	result := concurrentWork(len(accounts), total, workFn, nil)
   205  	for _, tx := range result {
   206  		txs = append(txs, tx.(*types.Transaction))
   207  	}
   208  
   209  	return
   210  }
   211  
   212  func waitForTx(hash common.Hash, client *ethclient.Client) {
   213  	log.Info("wait for transaction packed", "tx", hash.Hex())
   214  	for {
   215  		receipt, _ := client.TransactionReceipt(context.Background(), hash)
   216  		if receipt != nil {
   217  			log.Info("transaction packed!")
   218  			return
   219  		}
   220  
   221  		time.Sleep(time.Second)
   222  	}
   223  }
   224  
   225  func stressSendTransactions(txs []*types.Transaction, threads int, clients []*ethclient.Client, client *ethclient.Client) {
   226  	jobsPerThreadTmp := len(txs) / threads
   227  
   228  	workFn := func(start, end int, data ...interface{}) []interface{} {
   229  		c := clients[(start/jobsPerThreadTmp)%len(clients)]
   230  
   231  		for i := start; i < end; i++ {
   232  			if err := c.SendTransaction(context.Background(), txs[i]); err != nil {
   233  				log.Error("send tx failed", "err", err)
   234  			}
   235  		}
   236  
   237  		return []interface{}{}
   238  	}
   239  
   240  	concurrentWork(threads, len(txs), workFn, nil)
   241  }
   242  
   243  func divisor(decimal int) *big.Int {
   244  	if decimal <= 0 {
   245  		return big.NewInt(1)
   246  	}
   247  
   248  	d := big.NewInt(10)
   249  	for i := 0; i < decimal; i++ {
   250  		d.Mul(d, big.NewInt(10))
   251  	}
   252  
   253  	return d
   254  }
   255  
   256  type workFunc func(start, end int, data ...interface{}) []interface{}
   257  
   258  func concurrentWork(threads, totalWorks int, job workFunc, data ...interface{}) []interface{} {
   259  
   260  	dataChan := make(chan []interface{})
   261  	doJobFunc := func(i int) {
   262  		start := i * totalWorks / threads
   263  		// cal end of the work
   264  		end := (i + 1) * totalWorks / threads
   265  		if end > totalWorks {
   266  			end = totalWorks
   267  		}
   268  
   269  		dataChan <- job(start, end, data)
   270  	}
   271  
   272  	for i := 0; i < threads; i++ {
   273  		go doJobFunc(i)
   274  	}
   275  
   276  	// wait for all job done
   277  	doneJob := 0
   278  	result := make([]interface{}, 0)
   279  	for {
   280  		if doneJob == threads {
   281  			break
   282  		}
   283  
   284  		select {
   285  		case data := <-dataChan:
   286  			result = append(result, data...)
   287  			doneJob++
   288  		}
   289  	}
   290  
   291  	return result
   292  }