github.com/iotexproject/iotex-core@v1.14.1-rc1/actpool/queueworker.go (about)

     1  package actpool
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"errors"
     7  	"math/big"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"sync/atomic"
    12  
    13  	"github.com/iotexproject/go-pkgs/cache/ttl"
    14  	"github.com/iotexproject/iotex-address/address"
    15  	"go.uber.org/zap"
    16  
    17  	"github.com/iotexproject/iotex-core/action"
    18  	"github.com/iotexproject/iotex-core/action/protocol"
    19  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    20  	"github.com/iotexproject/iotex-core/pkg/log"
    21  	"github.com/iotexproject/iotex-core/pkg/tracer"
    22  )
    23  
    24  type (
    25  	queueWorker struct {
    26  		queue         chan workerJob
    27  		ap            *actPool
    28  		mu            sync.RWMutex
    29  		accountActs   *accountPool
    30  		emptyAccounts *ttl.Cache
    31  	}
    32  
    33  	workerJob struct {
    34  		ctx context.Context
    35  		act *action.SealedEnvelope
    36  		rep bool
    37  		err chan error
    38  	}
    39  
    40  	pendingActions struct {
    41  		sender string
    42  		acts   []*action.SealedEnvelope
    43  	}
    44  )
    45  
    46  func newQueueWorker(ap *actPool, jobQueue chan workerJob) *queueWorker {
    47  	acc, _ := ttl.NewCache()
    48  	return &queueWorker{
    49  		queue:         jobQueue,
    50  		ap:            ap,
    51  		accountActs:   newAccountPool(),
    52  		emptyAccounts: acc,
    53  	}
    54  }
    55  
    56  func (worker *queueWorker) Start() error {
    57  	if worker.queue == nil || worker.ap == nil {
    58  		return errors.New("worker is invalid")
    59  	}
    60  	go func() {
    61  		for {
    62  			job, more := <-worker.queue
    63  			if !more { // worker chan is closed
    64  				return
    65  			}
    66  			job.err <- worker.Handle(job)
    67  		}
    68  	}()
    69  	return nil
    70  }
    71  
    72  func (worker *queueWorker) Stop() error {
    73  	close(worker.queue)
    74  	return nil
    75  }
    76  
    77  // Hanlde is called sequentially by worker
    78  func (worker *queueWorker) Handle(job workerJob) error {
    79  	ctx := job.ctx
    80  	// ctx is canceled or timeout
    81  	if ctx.Err() != nil {
    82  		return ctx.Err()
    83  	}
    84  
    85  	var (
    86  		span            = tracer.SpanFromContext(ctx)
    87  		act             = job.act
    88  		sender          = act.SenderAddress().String()
    89  		actHash, _      = act.Hash()
    90  		intrinsicGas, _ = act.IntrinsicGas()
    91  		replace         = job.rep
    92  	)
    93  	defer span.End()
    94  
    95  	nonce, balance, err := worker.getConfirmedState(ctx, act.SenderAddress())
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	if err := worker.checkSelpWithState(act, nonce, balance); err != nil {
   101  		return err
   102  	}
   103  	if err := worker.putAction(sender, act, nonce, balance); err != nil {
   104  		return err
   105  	}
   106  
   107  	worker.ap.allActions.Set(actHash, act)
   108  
   109  	if desAddress, ok := act.Destination(); ok && !strings.EqualFold(sender, desAddress) {
   110  		if err := worker.ap.accountDesActs.addAction(act); err != nil {
   111  			log.L().Debug("fail to add destionation map", zap.Error(err))
   112  		}
   113  	}
   114  
   115  	atomic.AddUint64(&worker.ap.gasInPool, intrinsicGas)
   116  
   117  	worker.mu.Lock()
   118  	defer worker.mu.Unlock()
   119  	if replace {
   120  		// TODO: early return if sender is the account to pop and nonce is larger than largest in the queue
   121  		actToReplace := worker.accountActs.PopPeek()
   122  		if actToReplace == nil {
   123  			log.L().Warn("UNEXPECTED ERROR: action pool is full, but no action to drop")
   124  			return nil
   125  		}
   126  		worker.ap.removeInvalidActs([]*action.SealedEnvelope{actToReplace})
   127  		if actToReplace.SenderAddress().String() == sender && actToReplace.Nonce() == nonce {
   128  			err = action.ErrTxPoolOverflow
   129  			_actpoolMtc.WithLabelValues("overMaxNumActsPerPool").Inc()
   130  		}
   131  	}
   132  
   133  	worker.removeEmptyAccounts()
   134  
   135  	return err
   136  }
   137  
   138  func (worker *queueWorker) getConfirmedState(ctx context.Context, sender address.Address) (uint64, *big.Int, error) {
   139  	worker.mu.RLock()
   140  	queue := worker.accountActs.Account(sender.String())
   141  	worker.mu.RUnlock()
   142  	// account state isn't cached in the actpool
   143  	if queue == nil {
   144  		confirmedState, err := accountutil.AccountState(ctx, worker.ap.sf, sender)
   145  		if err != nil {
   146  			return 0, nil, err
   147  		}
   148  		var nonce uint64
   149  		if protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount {
   150  			nonce = confirmedState.PendingNonceConsideringFreshAccount()
   151  		} else {
   152  			nonce = confirmedState.PendingNonce()
   153  		}
   154  		return nonce, confirmedState.Balance, nil
   155  	}
   156  	nonce, balance := queue.AccountState()
   157  	return nonce, balance, nil
   158  }
   159  
   160  func (worker *queueWorker) checkSelpWithState(act *action.SealedEnvelope, pendingNonce uint64, balance *big.Int) error {
   161  	if act.Nonce() < pendingNonce {
   162  		_actpoolMtc.WithLabelValues("nonceTooSmall").Inc()
   163  		return action.ErrNonceTooLow
   164  	}
   165  
   166  	// Nonce exceeds current range
   167  	if act.Nonce()-pendingNonce >= worker.ap.cfg.MaxNumActsPerAcct {
   168  		hash, _ := act.Hash()
   169  		log.L().Debug("Rejecting action because nonce is too large.",
   170  			log.Hex("hash", hash[:]),
   171  			zap.Uint64("startNonce", pendingNonce),
   172  			zap.Uint64("actNonce", act.Nonce()))
   173  		_actpoolMtc.WithLabelValues("nonceTooLarge").Inc()
   174  		return action.ErrNonceTooHigh
   175  	}
   176  
   177  	if cost, _ := act.Cost(); balance.Cmp(cost) < 0 {
   178  		_actpoolMtc.WithLabelValues("insufficientBalance").Inc()
   179  		sender := act.SenderAddress().String()
   180  		actHash, _ := act.Hash()
   181  		log.L().Debug("insufficient balance for action",
   182  			zap.String("actionHash", hex.EncodeToString(actHash[:])),
   183  			zap.String("cost", cost.String()),
   184  			zap.String("balance", balance.String()),
   185  			zap.String("sender", sender),
   186  		)
   187  		return action.ErrInsufficientFunds
   188  	}
   189  	return nil
   190  }
   191  
   192  func (worker *queueWorker) putAction(sender string, act *action.SealedEnvelope, pendingNonce uint64, confirmedBalance *big.Int) error {
   193  	worker.mu.Lock()
   194  	err := worker.accountActs.PutAction(
   195  		sender,
   196  		worker.ap,
   197  		pendingNonce,
   198  		confirmedBalance,
   199  		worker.ap.cfg.ActionExpiry,
   200  		act,
   201  	)
   202  	worker.mu.Unlock()
   203  	if err != nil {
   204  		actHash, _ := act.Hash()
   205  		_actpoolMtc.WithLabelValues("failedPutActQueue").Inc()
   206  		log.L().Debug("failed put action into ActQueue",
   207  			zap.String("actionHash", hex.EncodeToString(actHash[:])),
   208  			zap.Error(err))
   209  		return err
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  func (worker *queueWorker) removeEmptyAccounts() {
   216  	if worker.emptyAccounts.Count() == 0 {
   217  		return
   218  	}
   219  
   220  	worker.emptyAccounts.Range(func(key, _ interface{}) error {
   221  		worker.accountActs.DeleteIfEmpty(key.(string))
   222  		return nil
   223  	})
   224  
   225  	worker.emptyAccounts.Reset()
   226  }
   227  
   228  func (worker *queueWorker) Reset(ctx context.Context) {
   229  	worker.mu.RLock()
   230  	defer worker.mu.RUnlock()
   231  
   232  	worker.accountActs.Range(func(from string, queue ActQueue) {
   233  		addr, _ := address.FromString(from)
   234  		confirmedState, err := accountutil.AccountState(ctx, worker.ap.sf, addr)
   235  		if err != nil {
   236  			log.L().Error("Error when removing confirmed actions", zap.Error(err))
   237  			queue.Reset()
   238  			worker.emptyAccounts.Set(from, struct{}{})
   239  			return
   240  		}
   241  		var pendingNonce uint64
   242  		if protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount {
   243  			pendingNonce = confirmedState.PendingNonceConsideringFreshAccount()
   244  		} else {
   245  			pendingNonce = confirmedState.PendingNonce()
   246  		}
   247  		// Remove all actions that are committed to new block
   248  		acts := queue.UpdateAccountState(pendingNonce, confirmedState.Balance)
   249  		acts2 := queue.UpdateQueue()
   250  		worker.ap.removeInvalidActs(append(acts, acts2...))
   251  		// Delete the queue entry if it becomes empty
   252  		if queue.Empty() {
   253  			worker.emptyAccounts.Set(from, struct{}{})
   254  		}
   255  	})
   256  }
   257  
   258  // PendingActions returns all accepted actions
   259  func (worker *queueWorker) PendingActions(ctx context.Context) []*pendingActions {
   260  	actionArr := make([]*pendingActions, 0)
   261  
   262  	worker.mu.RLock()
   263  	defer worker.mu.RUnlock()
   264  	worker.accountActs.Range(func(from string, queue ActQueue) {
   265  		if queue.Empty() {
   266  			return
   267  		}
   268  		// Remove the actions that are already timeout
   269  		acts := queue.UpdateQueue()
   270  		worker.ap.removeInvalidActs(acts)
   271  		pd := queue.PendingActs(ctx)
   272  		if len(pd) == 0 {
   273  			return
   274  		}
   275  		actionArr = append(actionArr, &pendingActions{
   276  			sender: from,
   277  			acts:   pd,
   278  		})
   279  	})
   280  	return actionArr
   281  }
   282  
   283  // AllActions returns the all actions of sender
   284  func (worker *queueWorker) AllActions(sender address.Address) ([]*action.SealedEnvelope, bool) {
   285  	worker.mu.RLock()
   286  	defer worker.mu.RUnlock()
   287  	if actQueue := worker.accountActs.Account(sender.String()); actQueue != nil {
   288  		acts := actQueue.AllActs()
   289  		sort.Slice(acts, func(i, j int) bool {
   290  			return acts[i].Nonce() < acts[j].Nonce()
   291  		})
   292  		return acts, true
   293  	}
   294  	return nil, false
   295  }
   296  
   297  // PendingNonce returns the pending nonce of sender
   298  func (worker *queueWorker) PendingNonce(sender address.Address) (uint64, bool) {
   299  	worker.mu.RLock()
   300  	defer worker.mu.RUnlock()
   301  	if actQueue := worker.accountActs.Account(sender.String()); actQueue != nil {
   302  		return actQueue.PendingNonce(), true
   303  	}
   304  	return 0, false
   305  }
   306  
   307  // ResetAccount resets account in the accountActs of worker
   308  func (worker *queueWorker) ResetAccount(sender address.Address) []*action.SealedEnvelope {
   309  	senderStr := sender.String()
   310  	worker.mu.RLock()
   311  	actQueue := worker.accountActs.PopAccount(senderStr)
   312  	worker.mu.RUnlock()
   313  	if actQueue != nil {
   314  		pendingActs := actQueue.AllActs()
   315  		actQueue.Reset()
   316  		// the following line is thread safe with worker.mu.RLock
   317  		worker.emptyAccounts.Set(senderStr, struct{}{})
   318  		return pendingActs
   319  	}
   320  	return nil
   321  }