github.com/bigzoro/my_simplechain@v0.0.0-20240315012955-8ad0a2a29bb9/cmd/stress/throughputs.go (about) 1 //go:build sub && old 2 // +build sub,old 3 4 package main 5 6 import ( 7 "context" 8 "crypto/ecdsa" 9 "errors" 10 "flag" 11 "log" 12 "math/big" 13 _ "net/http/pprof" 14 "os" 15 "os/signal" 16 "strings" 17 "sync/atomic" 18 "syscall" 19 "time" 20 21 "github.com/bigzoro/my_simplechain/common" 22 "github.com/bigzoro/my_simplechain/core/types" 23 "github.com/bigzoro/my_simplechain/crypto" 24 "github.com/bigzoro/my_simplechain/ethclient" 25 ) 26 27 const ( 28 warnPrefix = "\x1b[93mwarn:\x1b[0m" 29 errPrefix = "\x1b[91merror:\x1b[0m" 30 ) 31 32 var ( 33 txsCount = int64(0) 34 signer types.Signer 35 stopCh = make(chan struct{}) 36 37 errTxPoolIsFull = errors.New("txpool is full") 38 errInvalidLimit = errors.New("overflow blockLimit") 39 ) 40 41 var senderKeys = []string{ 42 "5aedb85503128685e4f92b0cc95e9e1185db99339f9b85125c1e2ddc0f7c4c48", 43 "41a6df5663e5f674baaea1a021cdee1751ca28777e352ed0467fff420017978b", 44 "868d8f8b3d50e2a3ebfd5a08b16d84ee015db519d662bb0e5878388f0c15a6e3", 45 "9259787a40ec58154e7e04ae324b965cb4f199b1ef09708319d50ad36fc1cbeb", 46 "a42531bd0a7c1df628ab141f3be6086146ed01f74628a467f9f926b3625e17a0", 47 "2d396fd91b652c687bc6796932a39f190cf7b4aab26e079f8f28baba9939847e", 48 "35daed192142a1b608b60390036e7d3ad11ec6bc2d09182f3192f70ed54d4f2f", 49 "6ce1ddaaa7cd15232fd17838ab65b7beb8b6ad8e43be7d61548535b40a2a89b0", 50 } 51 52 //var receivers []common.Address 53 //var senders []*ecdsa.PrivateKey 54 55 func init() { 56 log.SetFlags(log.Lshortfile | log.LstdFlags) 57 } 58 59 const SENDS = 1000000 60 61 func initNonce(seed uint64, count int) []uint64 { 62 ret := make([]uint64, count) 63 64 bigseed := seed * 1e10 65 for i := 0; i < count; i++ { 66 ret[i] = bigseed 67 bigseed++ 68 } 69 return ret 70 } 71 72 var ( 73 chainId *uint64 74 tps *int 75 toAddress common.Address 76 random *bool 77 checkTx *bool 78 ) 79 80 func main() { 81 url := flag.String("url", "ws://127.0.0.1:8546", "websocket url") 82 chainId = flag.Uint64("chainid", 1, "chainId") 83 tps = flag.Int("tps", -1, "send tps limit, negative is limitless") 84 85 sendTx := flag.Bool("sendtx", false, "enable only send tx") 86 senderCount := flag.Int("threads", 4, "the number of sender") 87 senderKey := flag.String("sendkey", senderKeys[0], "sender private key") 88 callcode := flag.Bool("callcode", false, "enable call contract code") 89 to := flag.String("to", "", "tx reception") 90 91 seed := flag.Uint64("seed", 1, "hash seed") 92 random = flag.Bool("rand", false, "random signer and receiver tx") 93 checkTx = flag.Bool("check", false, "whether check transaction state") 94 95 flag.Parse() 96 97 var cancels []context.CancelFunc 98 99 signer = types.NewEIP155Signer(new(big.Int).SetUint64(*chainId)) 100 101 if *callcode { 102 103 } 104 105 if *to != "" { 106 toAddress = common.HexToAddress(*to) 107 } 108 109 if *sendTx { 110 log.Printf("start send tx: %d accounts", *senderCount) 111 112 privateKey, err := crypto.HexToECDSA(*senderKey) 113 if err != nil { 114 log.Fatalf(errPrefix+" parse private key: %v", err) 115 } 116 publicKey := privateKey.Public() 117 publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 118 if !ok { 119 log.Fatalf(errPrefix + " cannot assert type: publicKey is not of type *ecdsa.PublicKey") 120 } 121 fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 122 123 nonces := initNonce(*seed, SENDS*(*senderCount)) 124 for i := 0; i < *senderCount; i++ { 125 client, err := ethclient.Dial(*url, "", "", nil) 126 if err != nil { 127 log.Fatalf(errPrefix+" connect %s: %v", *url, err) 128 } 129 ctx, cancel := context.WithCancel(context.Background()) 130 cancels = append(cancels, cancel) 131 132 go throughputs(ctx, client, i, privateKey, fromAddress, nonces[i*SENDS:(i+1)*SENDS]) 133 } 134 } 135 136 interrupt := make(chan os.Signal, 1) 137 signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) 138 defer signal.Stop(interrupt) 139 <-interrupt 140 close(stopCh) 141 142 for _, cancel := range cancels { 143 cancel() 144 } 145 146 log.Printf("Check transation results, success: %d, failed:%d, timeout:%d", successTx, failedTx, timeoutTx) 147 148 } 149 150 func getBlockLimit(ctx context.Context, client *ethclient.Client, last uint64) uint64 { 151 block, err := client.BlockByNumber(ctx, nil) 152 if err != nil { 153 log.Printf(warnPrefix+"Failed to getBlockLimit: %v", err) 154 return last + 100 155 } 156 return block.NumberU64() + 100 157 } 158 159 var big1 = big.NewInt(1) 160 var big1e20, _ = new(big.Int).SetString("100000000000000000000", 10) 161 162 func throughputs(ctx context.Context, client *ethclient.Client, index int, privateKey *ecdsa.PrivateKey, fromAddress common.Address, nonces []uint64) { 163 gasLimit := uint64(21000 + (20+64)*68) // in units 164 gasPrice, err := client.SuggestGasPrice(ctx) 165 if err != nil { 166 log.Fatalf(errPrefix+" get gas price: %v", err) 167 } 168 var ( 169 data [20 + 64]byte 170 blockLimit = getBlockLimit(ctx, client, 0) 171 meterCount = 0 172 i int 173 receivers []common.Address 174 senders []*ecdsa.PrivateKey 175 ) 176 177 copy(data[:], fromAddress.Bytes()) 178 179 if *random { 180 receivers = make([]common.Address, *tps) 181 senders = make([]*ecdsa.PrivateKey, *tps) 182 for i := 0; i < *tps; i++ { 183 pks, _ := crypto.GenerateKey() 184 pkr, _ := crypto.GenerateKey() 185 senders[i] = pks 186 receivers[i] = crypto.PubkeyToAddress(pkr.PublicKey) 187 } 188 189 sender, _ := crypto.HexToECDSA(senderKeys[0]) 190 blockLimit := getBlockLimit(ctx, client, 0) 191 nonce := nonces[0] 192 193 for i, s := range senders { 194 sendTransaction(ctx, sender, nonce+uint64(i), blockLimit, crypto.PubkeyToAddress(s.PublicKey), big1e20, uint64(21000+(20+64)*68), gasPrice, nil, client) 195 } 196 } 197 198 start := time.Now() 199 timer := time.NewTimer(0) 200 <-timer.C 201 timer.Reset(10 * time.Minute) 202 203 //tpsInterval := 10 * time.Minute 204 //if *tps > 0 { 205 // tpsInterval = time.Second 206 //} 207 tpsTicker := time.NewTicker(time.Second) 208 defer tpsTicker.Stop() 209 210 noncesLen := len(nonces) 211 sendersLen := len(senders) 212 213 for { 214 if i >= noncesLen { 215 break 216 } 217 218 select { 219 case <-stopCh: 220 return 221 case <-ctx.Done(): 222 seconds := time.Since(start).Seconds() 223 log.Printf("throughputs:%v return (total %v in %v s, %v txs/s)", index, meterCount, seconds, float64(meterCount)/seconds) 224 atomic.AddInt64(&txsCount, int64(meterCount)) 225 return 226 227 //case <-time.After(10 * time.Second): 228 // blockLimit += 10 229 230 case <-tpsTicker.C: 231 if *tps <= 0 { 232 *tps = len(nonces) 233 } 234 235 var update bool 236 for j := 0; j < *tps && i < noncesLen; j++ { 237 nonce := nonces[i] 238 239 copy(data[20:], new(big.Int).SetUint64(nonce).Bytes()) 240 //parallel.Put(func() error { 241 // sendTransaction(ctx, signer, privateKey, nonce, blockLimit, toAddress, big1, gasLimit, gasPrice, data[:], client) 242 // return nil 243 //}) 244 if *random { 245 turn := j % sendersLen 246 privateKey = senders[turn] 247 toAddress = receivers[turn] 248 } 249 250 hash, err := sendTransaction(ctx, privateKey, nonce, blockLimit, toAddress, big1, gasLimit, gasPrice, data[:], client) 251 252 if err == errTxPoolIsFull { 253 time.Sleep(time.Second * 5) // waiting block 254 continue 255 256 } 257 if err == errInvalidLimit { 258 update = true 259 break 260 } 261 262 if *checkTx && hash != (common.Hash{}) { 263 checkTransaction(ctx, hash, client) 264 } 265 266 i++ 267 meterCount++ 268 269 //switch { 270 //if i%10000 == 0 { 271 // handle pre-prepare = getBlockLimit(ctx, client, blockLimit) 272 //} 273 274 } 275 276 if update { 277 blockLimit = getBlockLimit(ctx, client, blockLimit) 278 } else { 279 blockLimit++ 280 } 281 //blockLimit = getBlockLimit(ctx, client, blockLimit) 282 //atomic.AddInt64(&txsCount, int64(meterCount)) 283 //// statistics throughputs 284 //if *tps > 0 && meterCount > *tps { 285 // // sleep to cut down throughputs if higher than limit tps 286 // time.Sleep(time.Duration(meterCount / *tps) * time.Second) 287 //} 288 // 289 //meterCount = 0 290 } 291 } 292 } 293 294 func sendTransaction(ctx context.Context, key *ecdsa.PrivateKey, nonce, limit uint64, toAddress common.Address, 295 value *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, client *ethclient.Client) (common.Hash, error) { 296 297 tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) 298 tx.SetBlockLimit(limit) 299 300 signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) 301 if err != nil { 302 log.Printf(warnPrefix+" send tx[hash:%s, nonce:%d]: %v", tx.Hash().String(), tx.Nonce(), err) 303 return common.Hash{}, err 304 } 305 signed, err := tx.WithSignature(signer, signature) 306 if err != nil { 307 log.Printf(warnPrefix+" send tx[hash:%s, nonce:%d]: %v", tx.Hash().String(), tx.Nonce(), err) 308 return common.Hash{}, err 309 } 310 err = client.SendTransaction(ctx, signed) 311 switch err { 312 case nil: 313 //recordsMu.Lock() 314 //records = append(records, signed.Hash()) 315 //recordsMu.Unlock() 316 case context.Canceled: 317 return common.Hash{}, nil 318 default: 319 log.Printf(warnPrefix+" send tx[hash:%s, nonce:%d]: %v", tx.Hash().String(), tx.Nonce(), err) 320 if strings.Contains(err.Error(), "txpool is full") { 321 return common.Hash{}, errTxPoolIsFull 322 } 323 if strings.Contains(err.Error(), "overflow blockLimit") || strings.Contains(err.Error(), "expired transaction") { 324 return common.Hash{}, errInvalidLimit 325 } 326 return common.Hash{}, err 327 } 328 329 return signed.Hash(), nil 330 } 331 332 var ( 333 timeoutTx uint32 334 successTx uint32 335 failedTx uint32 336 ) 337 338 func checkTransaction(ctx context.Context, hash common.Hash, client *ethclient.Client) { 339 go func() { 340 for { 341 select { 342 case <-time.After(time.Second): 343 r, err := client.TransactionReceipt(ctx, hash) 344 if err == nil { 345 if r.Status == 0 { 346 atomic.AddUint32(&failedTx, 1) 347 log.Printf(warnPrefix+"tx failed: hash: %s", hash.String()) 348 } else { 349 atomic.AddUint32(&successTx, 1) 350 } 351 return 352 } 353 if err == context.Canceled { 354 return 355 } 356 357 case <-time.After(time.Minute): 358 atomic.AddUint32(&timeoutTx, 1) 359 log.Printf(warnPrefix+"tx timeout: hash: %s", hash.String()) 360 return 361 } 362 } 363 }() 364 }