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  }