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 }