github.com/iotexproject/iotex-core@v1.14.1-rc1/tools/util/injectorutil.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 util
     7  
     8  import (
     9  	"context"
    10  	"encoding/hex"
    11  	"math/big"
    12  	"math/rand"
    13  	"os"
    14  	"path/filepath"
    15  	"sync"
    16  	"sync/atomic"
    17  	"time"
    18  
    19  	"github.com/cenkalti/backoff"
    20  	"github.com/iotexproject/go-pkgs/cache/ttl"
    21  	"github.com/iotexproject/go-pkgs/crypto"
    22  	"github.com/iotexproject/go-pkgs/hash"
    23  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    24  	"github.com/pkg/errors"
    25  	"go.uber.org/zap"
    26  	"gopkg.in/yaml.v2"
    27  
    28  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    29  
    30  	"github.com/iotexproject/iotex-core/action"
    31  	"github.com/iotexproject/iotex-core/api"
    32  	"github.com/iotexproject/iotex-core/pkg/log"
    33  	"github.com/iotexproject/iotex-core/pkg/unit"
    34  	"github.com/iotexproject/iotex-core/tools/executiontester/blockchain"
    35  )
    36  
    37  // KeyPairs indicate the keypair of accounts getting transfers from Creator in genesis block
    38  type KeyPairs struct {
    39  	Pairs []KeyPair `yaml:"pkPairs"`
    40  }
    41  
    42  // KeyPair contains the public and private key of an address
    43  type KeyPair struct {
    44  	PK string `yaml:"pubKey"`
    45  	SK string `yaml:"priKey"`
    46  }
    47  
    48  // AddressKey contains the encoded address and private key of an account
    49  type AddressKey struct {
    50  	EncodedAddr string
    51  	PriKey      crypto.PrivateKey
    52  }
    53  
    54  var (
    55  	totalTsfCreated   = uint64(0)
    56  	totalTsfSentToAPI = uint64(0)
    57  	totalTsfSucceeded = uint64(0)
    58  	totalTsfFailed    = uint64(0)
    59  )
    60  
    61  // GetTotalTsfCreated returns number of total transfer action created
    62  func GetTotalTsfCreated() uint64 {
    63  	return totalTsfCreated
    64  }
    65  
    66  // GetTotalTsfSentToAPI returns number of total transfer action successfully send through GRPC
    67  func GetTotalTsfSentToAPI() uint64 {
    68  	return totalTsfSentToAPI
    69  }
    70  
    71  // GetTotalTsfSucceeded returns number of total transfer action created
    72  func GetTotalTsfSucceeded() uint64 {
    73  	return totalTsfSucceeded
    74  }
    75  
    76  // GetTotalTsfFailed returns number of total transfer action failed
    77  func GetTotalTsfFailed() uint64 {
    78  	return totalTsfFailed
    79  }
    80  
    81  // LoadAddresses loads key pairs from key pair path and construct addresses
    82  func LoadAddresses(keypairsPath string, chainID uint32) ([]*AddressKey, error) {
    83  	// Load Senders' public/private key pairs
    84  	keyPairBytes, err := os.ReadFile(filepath.Clean(keypairsPath))
    85  	if err != nil {
    86  		return nil, errors.Wrap(err, "failed to read key pairs file")
    87  	}
    88  	var keypairs KeyPairs
    89  	if err := yaml.Unmarshal(keyPairBytes, &keypairs); err != nil {
    90  		return nil, errors.Wrap(err, "failed to unmarshal key pairs bytes")
    91  	}
    92  
    93  	// Construct iotex addresses from loaded key pairs
    94  	addrKeys := make([]*AddressKey, 0)
    95  	for _, pair := range keypairs.Pairs {
    96  		pk, err := crypto.HexStringToPublicKey(pair.PK)
    97  		if err != nil {
    98  			return nil, errors.Wrap(err, "failed to decode public key")
    99  		}
   100  		sk, err := crypto.HexStringToPrivateKey(pair.SK)
   101  		if err != nil {
   102  			return nil, errors.Wrap(err, "failed to decode private key")
   103  		}
   104  		addr := pk.Address()
   105  		if addr == nil {
   106  			return nil, errors.New("failed to get address")
   107  		}
   108  		addrKeys = append(addrKeys, &AddressKey{EncodedAddr: addr.String(), PriKey: sk})
   109  	}
   110  	return addrKeys, nil
   111  }
   112  
   113  // InitCounter initializes the map of nonce counter of each address
   114  func InitCounter(client iotexapi.APIServiceClient, addrKeys []*AddressKey) (map[string]uint64, error) {
   115  	counter := make(map[string]uint64)
   116  	for _, addrKey := range addrKeys {
   117  		addr := addrKey.EncodedAddr
   118  		err := backoff.Retry(func() error {
   119  			acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr})
   120  			if err != nil {
   121  				return err
   122  			}
   123  			counter[addr] = acctDetails.GetAccountMeta().PendingNonce
   124  			return nil
   125  		}, backoff.NewExponentialBackOff())
   126  		if err != nil {
   127  			return nil, errors.Wrapf(err, "failed to get address details of %s", addrKey.EncodedAddr)
   128  		}
   129  	}
   130  	return counter, nil
   131  }
   132  
   133  // InjectByAps injects Actions in APS Mode
   134  func InjectByAps(
   135  	wg *sync.WaitGroup,
   136  	aps float64,
   137  	counter map[string]uint64,
   138  	transferGasLimit int,
   139  	transferGasPrice int64,
   140  	transferPayload string,
   141  	voteGasLimit int,
   142  	voteGasPrice int64,
   143  	contract string,
   144  	executionAmount int,
   145  	executionGasLimit int,
   146  	executionGasPrice int64,
   147  	executionData string,
   148  	fpToken blockchain.FpToken,
   149  	fpContract string,
   150  	debtor *AddressKey,
   151  	creditor *AddressKey,
   152  	client iotexapi.APIServiceClient,
   153  	admins []*AddressKey,
   154  	delegates []*AddressKey,
   155  	duration time.Duration,
   156  	retryNum int,
   157  	retryInterval int,
   158  	resetInterval int,
   159  	expectedBalances map[string]*big.Int,
   160  	cs api.CoreService,
   161  	pendingActionMap *ttl.Cache,
   162  ) {
   163  	timeout := time.After(duration)
   164  	tick := time.NewTicker(time.Duration(1/aps*1000000) * time.Microsecond)
   165  	reset := time.NewTicker(time.Duration(resetInterval) * time.Second)
   166  	rand.Seed(time.Now().UnixNano())
   167  
   168  loop:
   169  	for {
   170  		select {
   171  		case <-timeout:
   172  			break loop
   173  		case <-reset.C:
   174  			for _, admin := range admins {
   175  				addr := admin.EncodedAddr
   176  				err := backoff.Retry(func() error {
   177  					acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr})
   178  					if err != nil {
   179  						return err
   180  					}
   181  					counter[addr] = acctDetails.GetAccountMeta().PendingNonce
   182  					return nil
   183  				}, backoff.NewExponentialBackOff())
   184  				if err != nil {
   185  					log.L().Fatal("Failed to inject actions by APS",
   186  						zap.Error(err),
   187  						zap.String("addr", admin.EncodedAddr))
   188  				}
   189  			}
   190  			for _, delegate := range delegates {
   191  				addr := delegate.EncodedAddr
   192  				err := backoff.Retry(func() error {
   193  					acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr})
   194  					if err != nil {
   195  						return err
   196  					}
   197  					counter[addr] = acctDetails.GetAccountMeta().PendingNonce
   198  					return nil
   199  				}, backoff.NewExponentialBackOff())
   200  				if err != nil {
   201  					log.L().Fatal("Failed to inject actions by APS",
   202  						zap.Error(err),
   203  						zap.String("addr", delegate.EncodedAddr))
   204  				}
   205  			}
   206  		case <-tick.C:
   207  			wg.Add(1)
   208  			// TODO Currently Vote is skipped because it will fail on balance test and is planned to be removed
   209  			if _, err := CheckPendingActionList(cs,
   210  				pendingActionMap,
   211  				expectedBalances,
   212  			); err != nil {
   213  				log.L().Error(err.Error())
   214  			}
   215  		rerand:
   216  			switch rand.Intn(3) {
   217  			case 0:
   218  				sender, recipient, nonce, amount := createTransferInjection(counter, delegates)
   219  				atomic.AddUint64(&totalTsfCreated, 1)
   220  				go injectTransfer(wg, client, sender, recipient, nonce, amount, uint64(transferGasLimit),
   221  					big.NewInt(transferGasPrice), transferPayload, retryNum, retryInterval, pendingActionMap)
   222  			case 1:
   223  				if fpToken == nil {
   224  					goto rerand
   225  				}
   226  				go injectFpTokenTransfer(wg, fpToken, fpContract, debtor, creditor)
   227  			case 2:
   228  				executor, nonce := createExecutionInjection(counter, delegates)
   229  				go injectExecInteraction(wg, client, executor, contract, nonce, big.NewInt(int64(executionAmount)),
   230  					uint64(executionGasLimit), big.NewInt(executionGasPrice),
   231  					executionData, retryNum, retryInterval, pendingActionMap)
   232  			}
   233  		}
   234  	}
   235  }
   236  
   237  // InjectByInterval injects Actions in Interval Mode
   238  func InjectByInterval(
   239  	transferNum int,
   240  	transferGasLimit int,
   241  	transferGasPrice int,
   242  	transferPayload string,
   243  	voteNum int,
   244  	voteGasLimit int,
   245  	voteGasPrice int,
   246  	executionNum int,
   247  	contract string,
   248  	executionAmount int,
   249  	executionGasLimit int,
   250  	executionGasPrice int,
   251  	executionData string,
   252  	interval int,
   253  	counter map[string]uint64,
   254  	client iotexapi.APIServiceClient,
   255  	admins []*AddressKey,
   256  	delegates []*AddressKey,
   257  	retryNum int,
   258  	retryInterval int,
   259  ) {
   260  	rand.Seed(time.Now().UnixNano())
   261  	for transferNum > 0 && voteNum > 0 && executionNum > 0 {
   262  		sender, recipient, nonce, amount := createTransferInjection(counter, delegates)
   263  		injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit),
   264  			big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil)
   265  		time.Sleep(time.Second * time.Duration(interval))
   266  
   267  		executor, nonce := createExecutionInjection(counter, delegates)
   268  		injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)),
   269  			uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil)
   270  		time.Sleep(time.Second * time.Duration(interval))
   271  
   272  		transferNum--
   273  		voteNum--
   274  		executionNum--
   275  	}
   276  	switch {
   277  	case transferNum > 0 && voteNum > 0:
   278  		for transferNum > 0 && voteNum > 0 {
   279  			sender, recipient, nonce, amount := createTransferInjection(counter, delegates)
   280  			injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit),
   281  				big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil)
   282  			time.Sleep(time.Second * time.Duration(interval))
   283  
   284  			transferNum--
   285  			voteNum--
   286  		}
   287  	case transferNum > 0 && executionNum > 0:
   288  		for transferNum > 0 && executionNum > 0 {
   289  			sender, recipient, nonce, amount := createTransferInjection(counter, delegates)
   290  			injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit),
   291  				big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil)
   292  			time.Sleep(time.Second * time.Duration(interval))
   293  
   294  			executor, nonce := createExecutionInjection(counter, delegates)
   295  			injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)),
   296  				uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil)
   297  			time.Sleep(time.Second * time.Duration(interval))
   298  
   299  			transferNum--
   300  			executionNum--
   301  		}
   302  	case voteNum > 0 && executionNum > 0:
   303  		for voteNum > 0 && executionNum > 0 {
   304  			executor, nonce := createExecutionInjection(counter, delegates)
   305  			injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)),
   306  				uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil)
   307  			time.Sleep(time.Second * time.Duration(interval))
   308  
   309  			voteNum--
   310  			executionNum--
   311  		}
   312  	}
   313  	switch {
   314  	case transferNum > 0:
   315  		for transferNum > 0 {
   316  			sender, recipient, nonce, amount := createTransferInjection(counter, delegates)
   317  			injectTransfer(nil, client, sender, recipient, nonce, amount, uint64(transferGasLimit),
   318  				big.NewInt(int64(transferGasPrice)), transferPayload, retryNum, retryInterval, nil)
   319  			time.Sleep(time.Second * time.Duration(interval))
   320  			transferNum--
   321  		}
   322  	case executionNum > 0:
   323  		for executionNum > 0 {
   324  			executor, nonce := createExecutionInjection(counter, delegates)
   325  			injectExecInteraction(nil, client, executor, contract, nonce, big.NewInt(int64(executionAmount)),
   326  				uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData, retryNum, retryInterval, nil)
   327  			time.Sleep(time.Second * time.Duration(interval))
   328  			executionNum--
   329  		}
   330  	}
   331  }
   332  
   333  // DeployContract deploys a smart contract before starting action injections
   334  func DeployContract(
   335  	client iotexapi.APIServiceClient,
   336  	counter map[string]uint64,
   337  	delegates []*AddressKey,
   338  	executionGasLimit int,
   339  	executionGasPrice int64,
   340  	executionData string,
   341  	retryNum int,
   342  	retryInterval int,
   343  ) (hash.Hash256, error) {
   344  	executor, nonce := createExecutionInjection(counter, delegates)
   345  	selp, execution, err := createSignedExecution(executor, action.EmptyAddress, nonce, big.NewInt(0),
   346  		uint64(executionGasLimit), big.NewInt(int64(executionGasPrice)), executionData)
   347  	if err != nil {
   348  		return hash.ZeroHash256, errors.Wrap(err, "failed to create signed execution")
   349  	}
   350  	log.L().Info("Created signed execution")
   351  
   352  	injectExecution(selp, execution, client, retryNum, retryInterval)
   353  	selpHash, err := selp.Hash()
   354  	if err != nil {
   355  		return hash.ZeroHash256, errors.Wrap(err, "failed to get hash")
   356  	}
   357  	return selpHash, nil
   358  }
   359  
   360  func injectTransfer(
   361  	wg *sync.WaitGroup,
   362  	c iotexapi.APIServiceClient,
   363  	sender *AddressKey,
   364  	recipient *AddressKey,
   365  	nonce uint64,
   366  	amount int64,
   367  	gasLimit uint64,
   368  	gasPrice *big.Int,
   369  	payload string,
   370  	retryNum int,
   371  	retryInterval int,
   372  	pendingActionMap *ttl.Cache,
   373  ) {
   374  	selp, _, err := createSignedTransfer(sender, recipient, unit.ConvertIotxToRau(amount), nonce, gasLimit,
   375  		gasPrice, payload)
   376  	if err != nil {
   377  		log.L().Fatal("Failed to inject transfer", zap.Error(err))
   378  	}
   379  
   380  	log.L().Info("Created signed transfer")
   381  
   382  	bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Duration(retryInterval)*time.Second), uint64(retryNum))
   383  	if err := backoff.Retry(func() error {
   384  		_, err := c.SendAction(context.Background(), &iotexapi.SendActionRequest{Action: selp.Proto()})
   385  		return err
   386  	}, bo); err != nil {
   387  		log.L().Error("Failed to inject transfer", zap.Error(err))
   388  	} else if pendingActionMap != nil {
   389  		selpHash, err := selp.Hash()
   390  		if err != nil {
   391  			log.L().Fatal("Failed to get hash", zap.Error(err))
   392  		}
   393  		pendingActionMap.Set(selpHash, 1)
   394  		atomic.AddUint64(&totalTsfSentToAPI, 1)
   395  	}
   396  
   397  	if wg != nil {
   398  		wg.Done()
   399  	}
   400  }
   401  
   402  func injectExecInteraction(
   403  	wg *sync.WaitGroup,
   404  	c iotexapi.APIServiceClient,
   405  	executor *AddressKey,
   406  	contract string,
   407  	nonce uint64,
   408  	amount *big.Int,
   409  	gasLimit uint64,
   410  	gasPrice *big.Int,
   411  	data string,
   412  	retryNum int,
   413  	retryInterval int,
   414  	pendingActionMap *ttl.Cache,
   415  ) {
   416  	selp, execution, err := createSignedExecution(executor, contract, nonce, amount, gasLimit, gasPrice, data)
   417  	if err != nil {
   418  		log.L().Fatal("Failed to inject execution", zap.Error(err))
   419  	}
   420  
   421  	log.L().Info("Created signed execution")
   422  
   423  	injectExecution(selp, execution, c, retryNum, retryInterval)
   424  
   425  	if pendingActionMap != nil {
   426  		selpHash, err := selp.Hash()
   427  		if err != nil {
   428  			log.L().Error("Failed to inject transfer", zap.Error(err))
   429  		}
   430  		pendingActionMap.Set(selpHash, 1)
   431  	}
   432  
   433  	if wg != nil {
   434  		wg.Done()
   435  	}
   436  }
   437  
   438  func injectFpTokenTransfer(
   439  	wg *sync.WaitGroup,
   440  	fpToken blockchain.FpToken,
   441  	fpContract string,
   442  	debtor *AddressKey,
   443  	creditor *AddressKey,
   444  ) {
   445  	sender := debtor
   446  	recipient := creditor
   447  	balance, err := fpToken.ReadValue(fpContract, "70a08231", debtor.EncodedAddr)
   448  	if err != nil {
   449  		log.L().Error("Failed to read debtor's asset balance", zap.Error(err))
   450  	}
   451  	if balance == int64(0) {
   452  		sender = creditor
   453  		recipient = debtor
   454  		balance, err = fpToken.ReadValue(fpContract, "70a08231", creditor.EncodedAddr)
   455  		if err != nil {
   456  			log.L().Error("Failed to read creditor's asset balance", zap.Error(err))
   457  		}
   458  	}
   459  	transfer := rand.Int63n(balance)
   460  	senderPriKey := sender.PriKey.HexString()
   461  	// Transfer fp token
   462  	if _, err := fpToken.Transfer(fpContract, sender.EncodedAddr, senderPriKey,
   463  		recipient.EncodedAddr, transfer); err != nil {
   464  		log.L().Error("Failed to transfer fp token from debtor to creditor", zap.Error(err))
   465  	}
   466  	if wg != nil {
   467  		wg.Done()
   468  	}
   469  }
   470  
   471  func injectStake(
   472  	wg *sync.WaitGroup,
   473  	c iotexapi.APIServiceClient,
   474  	sender *AddressKey,
   475  	nonce uint64,
   476  	amount string,
   477  	duration uint32,
   478  	autoStake bool,
   479  	gasLimit uint64,
   480  	gasPrice *big.Int,
   481  	payload string,
   482  	retryNum int,
   483  	retryInterval int,
   484  	pendingActionMap *ttl.Cache,
   485  ) {
   486  	selp, _, err := createSignedStake(sender, nonce, sender.EncodedAddr, amount, duration, autoStake, []byte(payload), gasLimit, gasPrice)
   487  	if err != nil {
   488  		log.L().Fatal("Failed to inject Stake", zap.Error(err))
   489  	}
   490  	log.L().Info("Created signed stake")
   491  
   492  	bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Duration(retryInterval)*time.Second), uint64(retryNum))
   493  	if err := backoff.Retry(func() error {
   494  		_, err := c.SendAction(context.Background(), &iotexapi.SendActionRequest{Action: selp.Proto()})
   495  		return err
   496  	}, bo); err != nil {
   497  		log.L().Error("Failed to inject stake", zap.Error(err))
   498  	} else if pendingActionMap != nil {
   499  		selpHash, err := selp.Hash()
   500  		if err != nil {
   501  			log.L().Fatal("Failed to get hash", zap.Error(err))
   502  		}
   503  		pendingActionMap.Set(selpHash, 1)
   504  		atomic.AddUint64(&totalTsfSentToAPI, 1)
   505  	}
   506  
   507  	if wg != nil {
   508  		wg.Done()
   509  	}
   510  }
   511  
   512  // Helper function to get the sender, recipient, nonce, and amount of next injected transfer
   513  func createTransferInjection(
   514  	counter map[string]uint64,
   515  	addrs []*AddressKey,
   516  ) (*AddressKey, *AddressKey, uint64, int64) {
   517  	sender := addrs[rand.Intn(len(addrs))]
   518  	recipient := addrs[rand.Intn(len(addrs))]
   519  	nonce := counter[sender.EncodedAddr]
   520  	amount := int64(0)
   521  	for amount == int64(0) {
   522  		amount = int64(rand.Intn(5))
   523  	}
   524  	counter[sender.EncodedAddr]++
   525  	return sender, recipient, nonce, amount
   526  }
   527  
   528  // Helper function to get the sender, recipient, and nonce of next injected vote
   529  func createVoteInjection(
   530  	counter map[string]uint64,
   531  	admins []*AddressKey,
   532  	delegates []*AddressKey,
   533  ) (*AddressKey, *AddressKey, uint64) {
   534  	sender := admins[rand.Intn(len(admins))]
   535  	recipient := delegates[rand.Intn(len(delegates))]
   536  	nonce := counter[sender.EncodedAddr]
   537  	counter[sender.EncodedAddr]++
   538  	return sender, recipient, nonce
   539  }
   540  
   541  // Helper function to get the executor and nonce of next injected execution
   542  func createExecutionInjection(
   543  	counter map[string]uint64,
   544  	addrs []*AddressKey,
   545  ) (*AddressKey, uint64) {
   546  	executor := addrs[rand.Intn(len(addrs))]
   547  	nonce := counter[executor.EncodedAddr]
   548  	counter[executor.EncodedAddr]++
   549  	return executor, nonce
   550  }
   551  
   552  // Helper function to create and sign a transfer
   553  func createSignedTransfer(
   554  	sender *AddressKey,
   555  	recipient *AddressKey,
   556  	amount *big.Int,
   557  	nonce uint64,
   558  	gasLimit uint64,
   559  	gasPrice *big.Int,
   560  	payload string,
   561  ) (*action.SealedEnvelope, *action.Transfer, error) {
   562  	transferPayload, err := hex.DecodeString(payload)
   563  	if err != nil {
   564  		return nil, nil, errors.Wrapf(err, "failed to decode payload %s", payload)
   565  	}
   566  	transfer, err := action.NewTransfer(
   567  		nonce, amount, recipient.EncodedAddr, transferPayload, gasLimit, gasPrice)
   568  	if err != nil {
   569  		return nil, nil, errors.Wrap(err, "failed to create raw transfer")
   570  	}
   571  	bd := &action.EnvelopeBuilder{}
   572  	elp := bd.SetNonce(nonce).
   573  		SetGasPrice(gasPrice).
   574  		SetGasLimit(gasLimit).
   575  		SetAction(transfer).Build()
   576  	selp, err := action.Sign(elp, sender.PriKey)
   577  	if err != nil {
   578  		return nil, nil, errors.Wrapf(err, "failed to sign transfer %v", elp)
   579  	}
   580  	return selp, transfer, nil
   581  }
   582  
   583  // Helper function to create and sign an execution
   584  func createSignedExecution(
   585  	executor *AddressKey,
   586  	contract string,
   587  	nonce uint64,
   588  	amount *big.Int,
   589  	gasLimit uint64,
   590  	gasPrice *big.Int,
   591  	data string,
   592  ) (*action.SealedEnvelope, *action.Execution, error) {
   593  	executionData, err := hex.DecodeString(data)
   594  	if err != nil {
   595  		return nil, nil, errors.Wrapf(err, "failed to decode data %s", data)
   596  	}
   597  	execution, err := action.NewExecution(contract, nonce, amount, gasLimit, gasPrice, executionData)
   598  	if err != nil {
   599  		return nil, nil, errors.Wrap(err, "failed to create raw execution")
   600  	}
   601  	bd := &action.EnvelopeBuilder{}
   602  	elp := bd.SetNonce(nonce).
   603  		SetGasPrice(gasPrice).
   604  		SetGasLimit(gasLimit).
   605  		SetAction(execution).Build()
   606  	selp, err := action.Sign(elp, executor.PriKey)
   607  	if err != nil {
   608  		return nil, nil, errors.Wrapf(err, "failed to sign execution %v", elp)
   609  	}
   610  	return selp, execution, nil
   611  }
   612  
   613  func createSignedStake(
   614  	executor *AddressKey,
   615  	nonce uint64,
   616  	candidateName string,
   617  	amount string,
   618  	duration uint32,
   619  	autoStake bool,
   620  	payload []byte,
   621  	gasLimit uint64,
   622  	gasPrice *big.Int,
   623  ) (*action.SealedEnvelope, *action.CreateStake, error) {
   624  	createStake, err := action.NewCreateStake(nonce, candidateName, amount, duration, autoStake, payload, gasLimit, gasPrice)
   625  	if err != nil {
   626  		return nil, nil, err
   627  	}
   628  	bd := &action.EnvelopeBuilder{}
   629  	elp := bd.SetNonce(nonce).
   630  		SetGasPrice(gasPrice).
   631  		SetGasLimit(gasLimit).
   632  		SetAction(createStake).
   633  		Build()
   634  	selp, err := action.Sign(elp, executor.PriKey)
   635  	if err != nil {
   636  		return nil, nil, err
   637  	}
   638  	return selp, createStake, nil
   639  }
   640  
   641  func injectExecution(
   642  	selp *action.SealedEnvelope,
   643  	_ *action.Execution,
   644  	c iotexapi.APIServiceClient,
   645  	retryNum int,
   646  	retryInterval int,
   647  ) {
   648  	bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Duration(retryInterval)*time.Second), uint64(retryNum))
   649  	if err := backoff.Retry(func() error {
   650  		_, err := c.SendAction(context.Background(), &iotexapi.SendActionRequest{Action: selp.Proto()})
   651  		return err
   652  	}, bo); err != nil {
   653  		log.L().Error("Failed to inject execution", zap.Error(err))
   654  	}
   655  }
   656  
   657  // GetAllBalanceMap returns a account balance map of all admins and delegates
   658  func GetAllBalanceMap(
   659  	client iotexapi.APIServiceClient,
   660  	chainaddrs []*AddressKey,
   661  ) map[string]*big.Int {
   662  	balanceMap := make(map[string]*big.Int)
   663  	for _, chainaddr := range chainaddrs {
   664  		addr := chainaddr.EncodedAddr
   665  		err := backoff.Retry(func() error {
   666  			acctDetails, err := client.GetAccount(context.Background(), &iotexapi.GetAccountRequest{Address: addr})
   667  			if err != nil {
   668  				return err
   669  			}
   670  			if acctDetails.GetAccountMeta().Balance == "" {
   671  				balanceMap[addr] = big.NewInt(0)
   672  			} else {
   673  				baddr, ok := new(big.Int).SetString(acctDetails.GetAccountMeta().Balance, 10)
   674  				if !ok {
   675  					return errors.Errorf("invalid balance %s", acctDetails.GetAccountMeta().Balance)
   676  				}
   677  				balanceMap[addr] = baddr
   678  			}
   679  			return nil
   680  		}, backoff.NewExponentialBackOff())
   681  		if err != nil {
   682  			log.L().Fatal("Failed to Get account balance",
   683  				zap.Error(err),
   684  				zap.String("addr", chainaddr.EncodedAddr))
   685  		}
   686  	}
   687  	return balanceMap
   688  }
   689  
   690  // CheckPendingActionList will go through the pending action list, for an executed action:
   691  // 1) update the expectation balance map if the action has been run successfully
   692  // 2) remove the action from pending list
   693  func CheckPendingActionList(
   694  	cs api.CoreService,
   695  	pendingActionMap *ttl.Cache,
   696  	balancemap map[string]*big.Int,
   697  ) (bool, error) {
   698  	var retErr error
   699  	empty := true
   700  
   701  	pendingActionMap.Range(func(selphash, vi interface{}) error {
   702  		empty = false
   703  		sh, _ := selphash.(hash.Hash256)
   704  		receipt, err := GetReceiptByAction(cs, sh)
   705  		if err == nil {
   706  			actInfo, err := GetActionByActionHash(cs, selphash.(hash.Hash256))
   707  			if err != nil {
   708  				retErr = err
   709  				return nil
   710  			}
   711  			executoraddr := actInfo.GetSender()
   712  			if receipt.Status == uint64(iotextypes.ReceiptStatus_Success) {
   713  				pbAct := actInfo.GetAction().GetCore()
   714  				gasLimit := actInfo.GetAction().Core.GetGasLimit()
   715  				gasPrice, ok := new(big.Int).SetString(actInfo.GetAction().Core.GetGasPrice(), 10)
   716  				if !ok {
   717  					return errors.New("failed to set gas price")
   718  				}
   719  				switch {
   720  				case pbAct.GetTransfer() != nil:
   721  					act := &action.Transfer{}
   722  					if err := act.LoadProto(pbAct.GetTransfer()); err != nil {
   723  						retErr = err
   724  						return nil
   725  					}
   726  					updateTransferExpectedBalanceMap(balancemap, executoraddr,
   727  						act.Recipient(), act.Amount(), act.Payload(), gasLimit, gasPrice)
   728  					atomic.AddUint64(&totalTsfSucceeded, 1)
   729  				case pbAct.GetExecution() != nil:
   730  					act := &action.Execution{}
   731  					if err := act.LoadProto(pbAct.GetExecution()); err != nil {
   732  						retErr = err
   733  						return nil
   734  					}
   735  					updateExecutionExpectedBalanceMap(balancemap, executoraddr, gasLimit, gasPrice)
   736  				case pbAct.GetStakeCreate() != nil:
   737  					act := &action.CreateStake{}
   738  					if err := act.LoadProto(pbAct.GetStakeCreate()); err != nil {
   739  						retErr = err
   740  						return nil
   741  					}
   742  					cost, err := act.Cost()
   743  					if err != nil {
   744  						retErr = err
   745  						return nil
   746  					}
   747  					updateStakeExpectedBalanceMap(balancemap, executoraddr, cost)
   748  				default:
   749  					retErr = errors.New("Unsupported action type for balance check")
   750  					return nil
   751  				}
   752  			} else {
   753  				atomic.AddUint64(&totalTsfFailed, 1)
   754  			}
   755  			return errors.New("return error so LruCache will remove this key")
   756  		}
   757  		return nil
   758  	})
   759  
   760  	return empty, retErr
   761  }
   762  
   763  func updateTransferExpectedBalanceMap(
   764  	balancemap map[string]*big.Int,
   765  	senderAddr string,
   766  	recipientAddr string,
   767  	amount *big.Int,
   768  	payload []byte,
   769  	gasLimit uint64,
   770  	gasPrice *big.Int,
   771  ) {
   772  
   773  	gasLimitBig := big.NewInt(int64(gasLimit))
   774  
   775  	// calculate gas consumed by payload
   776  	gasUnitPayloadConsumed := new(big.Int).Mul(new(big.Int).SetUint64(action.TransferPayloadGas),
   777  		new(big.Int).SetUint64(uint64(len(payload))))
   778  	gasUnitTransferConsumed := new(big.Int).SetUint64(action.TransferBaseIntrinsicGas)
   779  
   780  	// calculate total gas consumed by payload and transfer action
   781  	gasUnitConsumed := new(big.Int).Add(gasUnitPayloadConsumed, gasUnitTransferConsumed)
   782  	if gasLimitBig.Cmp(gasUnitConsumed) < 0 {
   783  		log.L().Fatal("Not enough gas")
   784  	}
   785  
   786  	// convert to gas cost
   787  	gasConsumed := new(big.Int).Mul(gasUnitConsumed, gasPrice)
   788  
   789  	// total cost of transferred amount, payload, transfer intrinsic
   790  	totalUsed := new(big.Int).Add(gasConsumed, amount)
   791  
   792  	// update sender balance
   793  	senderBalance := balancemap[senderAddr]
   794  	if senderBalance.Cmp(totalUsed) < 0 {
   795  		log.L().Fatal("Not enough balance")
   796  	}
   797  	balancemap[senderAddr].Sub(senderBalance, totalUsed)
   798  
   799  	// update recipient balance
   800  	recipientBalance := balancemap[recipientAddr]
   801  	balancemap[recipientAddr].Add(recipientBalance, amount)
   802  }
   803  
   804  func updateExecutionExpectedBalanceMap(
   805  	balancemap map[string]*big.Int,
   806  	executor string,
   807  	gasLimit uint64,
   808  	gasPrice *big.Int,
   809  ) {
   810  	gasLimitBig := new(big.Int).SetUint64(gasLimit)
   811  
   812  	// NOTE: This hard-coded gas consumption value is precalculated on minicluster deployed test contract only
   813  	gasUnitConsumed := new(big.Int).SetUint64(12014)
   814  
   815  	if gasLimitBig.Cmp(gasUnitConsumed) < 0 {
   816  		log.L().Fatal("Not enough gas")
   817  	}
   818  	gasConsumed := new(big.Int).Mul(gasUnitConsumed, gasPrice)
   819  
   820  	executorBalance := balancemap[executor]
   821  	if executorBalance.Cmp(gasConsumed) < 0 {
   822  		log.L().Fatal("Not enough balance")
   823  	}
   824  	balancemap[executor].Sub(executorBalance, gasConsumed)
   825  }
   826  
   827  func updateStakeExpectedBalanceMap(
   828  	balancemap map[string]*big.Int,
   829  	candidateAddr string,
   830  	cost *big.Int,
   831  ) {
   832  	// update sender balance
   833  	senderBalance := balancemap[candidateAddr]
   834  	if senderBalance.Cmp(cost) < 0 {
   835  		log.L().Fatal("Not enough balance")
   836  	}
   837  	balancemap[candidateAddr].Sub(senderBalance, cost)
   838  }
   839  
   840  // GetActionByActionHash acquires action by calling coreService
   841  func GetActionByActionHash(api api.CoreService, actHash hash.Hash256) (*iotexapi.ActionInfo, error) {
   842  	act, err := api.Action(hex.EncodeToString(actHash[:]), false)
   843  	if err != nil {
   844  		return nil, err
   845  	}
   846  	return act, nil
   847  }
   848  
   849  // GetReceiptByAction acquires receipt by calling coreService
   850  func GetReceiptByAction(api api.CoreService, actHash hash.Hash256) (*iotextypes.Receipt, error) {
   851  	receipt, err := api.ReceiptByActionHash(actHash)
   852  	if err != nil {
   853  		return nil, err
   854  	}
   855  	return receipt.ConvertToReceiptPb(), nil
   856  }