github.com/iotexproject/iotex-core@v1.14.1-rc1/tools/actioninjector.v2/internal/cmd/inject.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package cmd
     7  
     8  import (
     9  	"context"
    10  	"crypto/sha256"
    11  	"crypto/tls"
    12  	"encoding/binary"
    13  	"encoding/hex"
    14  	"fmt"
    15  	"math/big"
    16  	"math/rand"
    17  	"os"
    18  	"path/filepath"
    19  	"strings"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/cenkalti/backoff"
    24  	"github.com/ethereum/go-ethereum/accounts/abi"
    25  	"github.com/iotexproject/go-pkgs/cache/ttl"
    26  	"github.com/iotexproject/go-pkgs/crypto"
    27  	"github.com/iotexproject/go-pkgs/hash"
    28  	"github.com/iotexproject/iotex-address/address"
    29  	"github.com/iotexproject/iotex-antenna-go/v2/account"
    30  	"github.com/iotexproject/iotex-antenna-go/v2/iotex"
    31  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    32  	"github.com/pkg/errors"
    33  	"github.com/spf13/cobra"
    34  	"go.uber.org/zap"
    35  	"google.golang.org/grpc"
    36  	"google.golang.org/grpc/credentials"
    37  	yaml "gopkg.in/yaml.v2"
    38  
    39  	"github.com/iotexproject/iotex-core/pkg/log"
    40  )
    41  
    42  // KeyPairs indicate the keypair of accounts getting transfers from Creator in genesis block
    43  type KeyPairs struct {
    44  	Pairs []KeyPair `yaml:"pkPairs"`
    45  }
    46  
    47  // KeyPair contains the public and private key of an address
    48  type KeyPair struct {
    49  	PK string `yaml:"pubKey"`
    50  	SK string `yaml:"priKey"`
    51  }
    52  
    53  // AddressKey contains the encoded address and private key of an account
    54  type AddressKey struct {
    55  	EncodedAddr string
    56  	PriKey      crypto.PrivateKey
    57  }
    58  
    59  type injectProcessor struct {
    60  	api      iotexapi.APIServiceClient
    61  	nonces   *ttl.Cache
    62  	accounts []*AddressKey
    63  }
    64  
    65  func newInjectionProcessor() (*injectProcessor, error) {
    66  	var conn *grpc.ClientConn
    67  	var err error
    68  	grpcctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    69  	defer cancel()
    70  	log.L().Info("Server Addr", zap.String("endpoint", injectCfg.serverAddr))
    71  	if injectCfg.insecure {
    72  		log.L().Info("insecure connection")
    73  		conn, err = grpc.DialContext(grpcctx, injectCfg.serverAddr, grpc.WithBlock(), grpc.WithInsecure())
    74  	} else {
    75  		log.L().Info("secure connection")
    76  		conn, err = grpc.DialContext(grpcctx, injectCfg.serverAddr, grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})))
    77  	}
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	api := iotexapi.NewAPIServiceClient(conn)
    82  	nonceCache, err := ttl.NewCache()
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	p := &injectProcessor{
    87  		api:    api,
    88  		nonces: nonceCache,
    89  	}
    90  	if err = p.randAccounts(injectCfg.randAccounts); err != nil {
    91  		return p, err
    92  	}
    93  	if injectCfg.loadTokenAmount.BitLen() != 0 {
    94  		if err := p.loadAccounts(injectCfg.configPath); err != nil {
    95  			return p, err
    96  		}
    97  	}
    98  	p.syncNonces(context.Background())
    99  	return p, nil
   100  }
   101  
   102  func (p *injectProcessor) randAccounts(num int) error {
   103  	addrKeys := make([]*AddressKey, 0, num)
   104  	for i := 0; i < num; i++ {
   105  		private, err := crypto.GenerateKey()
   106  		if err != nil {
   107  			return err
   108  		}
   109  		a, _ := account.PrivateKeyToAccount(private)
   110  		p.nonces.Set(a.Address().String(), 1)
   111  		addrKeys = append(addrKeys, &AddressKey{PriKey: private, EncodedAddr: a.Address().String()})
   112  	}
   113  	p.accounts = addrKeys
   114  	return nil
   115  }
   116  
   117  func (p *injectProcessor) loadAccounts(keypairsPath string) error {
   118  	keyPairBytes, err := os.ReadFile(filepath.Clean(keypairsPath))
   119  	if err != nil {
   120  		return errors.Wrap(err, "failed to read key pairs file")
   121  	}
   122  	var keypairs KeyPairs
   123  	if err := yaml.Unmarshal(keyPairBytes, &keypairs); err != nil {
   124  		return errors.Wrap(err, "failed to unmarshal key pairs bytes")
   125  	}
   126  
   127  	// Construct iotex addresses from loaded key pairs
   128  	addrKeys := make([]*AddressKey, 0)
   129  	for _, pair := range keypairs.Pairs {
   130  		pk, err := crypto.HexStringToPublicKey(pair.PK)
   131  		if err != nil {
   132  			return errors.Wrap(err, "failed to decode public key")
   133  		}
   134  		sk, err := crypto.HexStringToPrivateKey(pair.SK)
   135  		if err != nil {
   136  			return errors.Wrap(err, "failed to decode private key")
   137  		}
   138  		addr := pk.Address()
   139  		log.L().Info("loaded account", zap.String("addr", addr.String()))
   140  		if addr == nil {
   141  			return errors.New("failed to get address")
   142  		}
   143  		p.nonces.Set(addr.String(), 0)
   144  		addrKeys = append(addrKeys, &AddressKey{EncodedAddr: addr.String(), PriKey: sk})
   145  	}
   146  
   147  	// send tokens
   148  	for i, r := range p.accounts {
   149  		sender := addrKeys[i%len(addrKeys)]
   150  		operatorAccount, _ := account.PrivateKeyToAccount(sender.PriKey)
   151  
   152  		recipient, _ := address.FromString(r.EncodedAddr)
   153  		log.L().Info("generated account", zap.String("addr", recipient.String()))
   154  		c := iotex.NewAuthedClient(p.api, operatorAccount)
   155  		caller := c.Transfer(recipient, injectCfg.loadTokenAmount).SetGasPrice(injectCfg.transferGasPrice).SetGasLimit(injectCfg.transferGasLimit)
   156  		if _, err := caller.Call(context.Background()); err != nil {
   157  			log.L().Error("Failed to inject.", zap.Error(err))
   158  		}
   159  		if i != 0 && i%len(addrKeys) == 0 {
   160  			time.Sleep(10 * time.Second)
   161  		}
   162  	}
   163  	return nil
   164  }
   165  
   166  func (p *injectProcessor) syncNoncesProcess(ctx context.Context) {
   167  	reset := time.NewTicker(injectCfg.resetInterval)
   168  	for {
   169  		select {
   170  		case <-ctx.Done():
   171  			return
   172  		case <-reset.C:
   173  			p.syncNonces(context.Background())
   174  		}
   175  	}
   176  }
   177  
   178  func (p *injectProcessor) syncNonces(ctx context.Context) {
   179  	var addrPool []string
   180  	for _, v := range p.nonces.Keys() {
   181  		addrPool = append(addrPool, v.(string))
   182  	}
   183  	for _, addr := range addrPool {
   184  		err := backoff.Retry(func() error {
   185  			resp, err := p.api.GetAccount(ctx, &iotexapi.GetAccountRequest{Address: addr})
   186  			if err != nil {
   187  				return err
   188  			}
   189  			p.nonces.Set(addr, resp.GetAccountMeta().GetPendingNonce())
   190  			return nil
   191  		}, backoff.NewExponentialBackOff())
   192  		if err != nil {
   193  			log.L().Fatal("Failed to inject actions by APS",
   194  				zap.Error(err),
   195  				zap.String("addr", addr))
   196  		}
   197  		time.Sleep(10 * time.Millisecond)
   198  	}
   199  }
   200  
   201  func (p *injectProcessor) injectProcess(ctx context.Context) {
   202  	var workers sync.WaitGroup
   203  	ticks := make(chan uint64)
   204  	for i := uint64(0); i < injectCfg.workers; i++ {
   205  		workers.Add(1)
   206  		go p.inject(&workers, ticks)
   207  	}
   208  
   209  	defer workers.Wait()
   210  	defer close(ticks)
   211  	interval := uint64(time.Second.Nanoseconds() / int64(injectCfg.aps))
   212  	began, count := time.Now(), uint64(0)
   213  	for {
   214  		now, next := time.Now(), began.Add(time.Duration(count*interval))
   215  		time.Sleep(next.Sub(now))
   216  		select {
   217  		case <-ctx.Done():
   218  			return
   219  		case ticks <- count:
   220  			count++
   221  		default:
   222  			workers.Add(1)
   223  			go p.inject(&workers, ticks)
   224  		}
   225  	}
   226  }
   227  
   228  func (p *injectProcessor) inject(workers *sync.WaitGroup, ticks <-chan uint64) {
   229  	defer workers.Done()
   230  	for range ticks {
   231  		go func() {
   232  			caller, err := p.pickAction()
   233  			if err != nil {
   234  				log.L().Error("Failed to create an action", zap.Error(err))
   235  			}
   236  			var actionHash hash.Hash256
   237  			bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(injectCfg.retryInterval), injectCfg.retryNum)
   238  			if rerr := backoff.Retry(func() error {
   239  				actionHash, err = caller.Call(context.Background())
   240  				return err
   241  			}, bo); rerr != nil {
   242  				log.L().Error("Failed to inject.", zap.Error(rerr))
   243  			}
   244  
   245  			c := iotex.NewReadOnlyClient(p.api)
   246  
   247  			if injectCfg.checkReceipt {
   248  				time.Sleep(25 * time.Second)
   249  				var response *iotexapi.GetReceiptByActionResponse
   250  				if rerr := backoff.Retry(func() error {
   251  					response, err = c.GetReceipt(actionHash).Call(context.Background())
   252  					return err
   253  				}, bo); rerr != nil {
   254  					log.L().Error("Failed to get receipt.", zap.Error(rerr))
   255  				}
   256  				if response.ReceiptInfo.Receipt.Status != 1 {
   257  					log.L().Error("Receipt has failed status.", zap.Uint64("status", response.ReceiptInfo.Receipt.Status))
   258  				}
   259  			}
   260  		}()
   261  	}
   262  }
   263  
   264  func (p *injectProcessor) pickAction() (iotex.SendActionCaller, error) {
   265  	switch injectCfg.actionType {
   266  	case "transfer":
   267  		return p.transferCaller()
   268  	case "execution":
   269  		return p.executionCaller()
   270  	case "mixed":
   271  		if rand.Intn(2) == 0 {
   272  			return p.transferCaller()
   273  		}
   274  		return p.executionCaller()
   275  	default:
   276  		return p.transferCaller()
   277  	}
   278  }
   279  
   280  func (p *injectProcessor) executionCaller() (iotex.SendActionCaller, error) {
   281  	var nonce uint64
   282  	sender := p.accounts[rand.Intn(len(p.accounts))]
   283  	if val, ok := p.nonces.Get(sender.EncodedAddr); ok {
   284  		nonce = val.(uint64)
   285  	}
   286  	p.nonces.Set(sender.EncodedAddr, nonce+1)
   287  
   288  	operatorAccount, _ := account.PrivateKeyToAccount(sender.PriKey)
   289  	c := iotex.NewAuthedClient(p.api, operatorAccount)
   290  	address, _ := address.FromString(injectCfg.contract)
   291  	abiJSONVar, _ := abi.JSON(strings.NewReader(_abiStr))
   292  	contract := c.Contract(address, abiJSONVar)
   293  
   294  	data := rand.Int63()
   295  	var dataBuf = make([]byte, 8)
   296  	binary.BigEndian.PutUint64(dataBuf, uint64(data))
   297  	dataHash := sha256.Sum256(dataBuf)
   298  
   299  	caller := contract.Execute("addHash", uint64(time.Now().Unix()), hex.EncodeToString(dataHash[:])).
   300  		SetNonce(nonce).
   301  		SetAmount(injectCfg.executionAmount).
   302  		SetGasPrice(injectCfg.executionGasPrice).
   303  		SetGasLimit(injectCfg.executionGasLimit)
   304  
   305  	return caller, nil
   306  }
   307  
   308  func (p *injectProcessor) transferCaller() (iotex.SendActionCaller, error) {
   309  	var nonce uint64
   310  	sender := p.accounts[rand.Intn(len(p.accounts))]
   311  	if val, ok := p.nonces.Get(sender.EncodedAddr); ok {
   312  		nonce = val.(uint64)
   313  	}
   314  	p.nonces.Set(sender.EncodedAddr, nonce+1)
   315  
   316  	operatorAccount, _ := account.PrivateKeyToAccount(sender.PriKey)
   317  	c := iotex.NewAuthedClient(p.api, operatorAccount)
   318  
   319  	recipient, _ := address.FromString(p.accounts[rand.Intn(len(p.accounts))].EncodedAddr)
   320  	data := rand.Int63()
   321  	var dataBuf = make([]byte, 8)
   322  	binary.BigEndian.PutUint64(dataBuf, uint64(data))
   323  	dataHash := sha256.Sum256(dataBuf)
   324  	caller := c.Transfer(recipient, injectCfg.transferAmount).
   325  		SetPayload(dataHash[:]).
   326  		SetNonce(nonce).
   327  		SetGasPrice(injectCfg.transferGasPrice).
   328  		SetGasLimit(injectCfg.transferGasLimit)
   329  	return caller, nil
   330  }
   331  
   332  // injectCmd represents the inject command
   333  var injectCmd = &cobra.Command{
   334  	Use:   "inject",
   335  	Short: "inject actions [options : -m] (default:random)",
   336  	Long:  `inject actions [options : -m] (default:random).`,
   337  	Run: func(cmd *cobra.Command, args []string) {
   338  		fmt.Println(inject(args))
   339  	},
   340  }
   341  
   342  var rawInjectCfg = struct {
   343  	configPath       string
   344  	serverAddr       string
   345  	transferGasLimit uint64
   346  	transferGasPrice int64
   347  	transferAmount   int64
   348  
   349  	contract          string
   350  	executionAmount   int64
   351  	executionGasLimit uint64
   352  	executionGasPrice int64
   353  
   354  	actionType    string
   355  	retryNum      uint64
   356  	retryInterval time.Duration
   357  	duration      time.Duration
   358  	resetInterval time.Duration
   359  	aps           int
   360  	workers       uint64
   361  	checkReceipt  bool
   362  	insecure      bool
   363  
   364  	randAccounts    int
   365  	loadTokenAmount string
   366  }{}
   367  
   368  var injectCfg = struct {
   369  	configPath       string
   370  	serverAddr       string
   371  	transferGasLimit uint64
   372  	transferGasPrice *big.Int
   373  	transferAmount   *big.Int
   374  
   375  	contract          string
   376  	executionAmount   *big.Int
   377  	executionGasLimit uint64
   378  	executionGasPrice *big.Int
   379  
   380  	actionType      string
   381  	retryNum        uint64
   382  	retryInterval   time.Duration
   383  	duration        time.Duration
   384  	resetInterval   time.Duration
   385  	aps             int
   386  	workers         uint64
   387  	checkReceipt    bool
   388  	insecure        bool
   389  	randAccounts    int
   390  	loadTokenAmount *big.Int
   391  }{}
   392  
   393  func inject(_ []string) string {
   394  	transferAmount := big.NewInt(rawInjectCfg.transferAmount)
   395  	transferGasPrice := big.NewInt(rawInjectCfg.transferGasPrice)
   396  	executionGasPrice := big.NewInt(rawInjectCfg.executionGasPrice)
   397  	executionAmount := big.NewInt(rawInjectCfg.executionAmount)
   398  	loadTokenAmount, ok := new(big.Int).SetString(rawInjectCfg.loadTokenAmount, 10)
   399  	if !ok {
   400  		return fmt.Sprint("failed to load token amount")
   401  	}
   402  
   403  	injectCfg.configPath = rawInjectCfg.configPath
   404  	injectCfg.serverAddr = rawInjectCfg.serverAddr
   405  	injectCfg.transferGasLimit = rawInjectCfg.transferGasLimit
   406  	injectCfg.transferGasPrice = transferGasPrice
   407  	injectCfg.transferAmount = transferAmount
   408  
   409  	injectCfg.contract = rawInjectCfg.contract
   410  	injectCfg.executionAmount = executionAmount
   411  	injectCfg.executionGasLimit = rawInjectCfg.executionGasLimit
   412  	injectCfg.executionGasPrice = executionGasPrice
   413  
   414  	injectCfg.actionType = rawInjectCfg.actionType
   415  	injectCfg.retryNum = rawInjectCfg.retryNum
   416  	injectCfg.retryInterval = rawInjectCfg.retryInterval
   417  	injectCfg.duration = rawInjectCfg.duration
   418  	injectCfg.resetInterval = rawInjectCfg.resetInterval
   419  	injectCfg.aps = rawInjectCfg.aps
   420  	injectCfg.workers = rawInjectCfg.workers
   421  	injectCfg.checkReceipt = rawInjectCfg.checkReceipt
   422  	injectCfg.insecure = rawInjectCfg.insecure
   423  	injectCfg.randAccounts = rawInjectCfg.randAccounts
   424  	injectCfg.loadTokenAmount = loadTokenAmount
   425  
   426  	p, err := newInjectionProcessor()
   427  	if err != nil {
   428  		return fmt.Sprintf("failed to create injector processor: %v.", err)
   429  	}
   430  
   431  	ctx, cancel := context.WithTimeout(context.Background(), injectCfg.duration)
   432  	defer cancel()
   433  	go p.injectProcess(ctx)
   434  	go p.syncNoncesProcess(ctx)
   435  	<-ctx.Done()
   436  	return ""
   437  }
   438  
   439  func init() {
   440  	flag := injectCmd.Flags()
   441  	flag.StringVar(&rawInjectCfg.configPath, "injector-config-path", "./tools/actioninjector.v2/gentsfaddrs.yaml",
   442  		"path of config file of genesis transfer addresses")
   443  	flag.StringVar(&rawInjectCfg.serverAddr, "addr", "127.0.0.1:14014", "target ip:port for grpc connection")
   444  	flag.Int64Var(&rawInjectCfg.transferAmount, "transfer-amount", 0, "execution amount")
   445  	flag.Uint64Var(&rawInjectCfg.transferGasLimit, "transfer-gas-limit", 20000, "transfer gas limit")
   446  	flag.Int64Var(&rawInjectCfg.transferGasPrice, "transfer-gas-price", 1000000000000, "transfer gas price")
   447  	flag.StringVar(&rawInjectCfg.contract, "contract", "io1pmjhyksxmz2xpxn2qmz4gx9qq2kn2gdr8un4xq", "smart contract address")
   448  	flag.Int64Var(&rawInjectCfg.executionAmount, "execution-amount", 0, "execution amount")
   449  	flag.Uint64Var(&rawInjectCfg.executionGasLimit, "execution-gas-limit", 100000, "execution gas limit")
   450  	flag.Int64Var(&rawInjectCfg.executionGasPrice, "execution-gas-price", 1000000000000, "execution gas price")
   451  	flag.StringVar(&rawInjectCfg.actionType, "action-type", "transfer", "action type to inject")
   452  	flag.Uint64Var(&rawInjectCfg.retryNum, "retry-num", 5, "maximum number of rpc retries")
   453  	flag.DurationVar(&rawInjectCfg.retryInterval, "retry-interval", 1*time.Second, "sleep interval between two consecutive rpc retries")
   454  	flag.DurationVar(&rawInjectCfg.duration, "duration", 60*time.Hour, "duration when the injection will run")
   455  	flag.DurationVar(&rawInjectCfg.resetInterval, "reset-interval", 10*time.Second, "time interval to reset nonce counter")
   456  	flag.IntVar(&rawInjectCfg.aps, "aps", 30, "actions to be injected per second")
   457  	flag.IntVar(&rawInjectCfg.randAccounts, "rand-accounts", 20, "number of accounst to use")
   458  	flag.Uint64Var(&rawInjectCfg.workers, "workers", 10, "number of workers")
   459  	flag.BoolVar(&rawInjectCfg.insecure, "insecure", false, "insecure network")
   460  	flag.BoolVar(&rawInjectCfg.checkReceipt, "check-recipt", false, "check recept")
   461  	flag.StringVar(&rawInjectCfg.loadTokenAmount, "load-token-amount", "0", "init load how much token to inject accounts")
   462  	rootCmd.AddCommand(injectCmd)
   463  }