github.com/iotexproject/iotex-core@v1.14.1-rc1/actpool/actqueue.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 actpool
     7  
     8  import (
     9  	"container/heap"
    10  	"context"
    11  	"math/big"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/facebookgo/clock"
    16  	"go.uber.org/zap"
    17  
    18  	"github.com/iotexproject/iotex-address/address"
    19  
    20  	"github.com/iotexproject/iotex-core/action"
    21  	"github.com/iotexproject/iotex-core/action/protocol"
    22  	accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util"
    23  	"github.com/iotexproject/iotex-core/pkg/log"
    24  )
    25  
    26  // ActQueue is the interface of actQueue
    27  type ActQueue interface {
    28  	Put(*action.SealedEnvelope) error
    29  	UpdateQueue() []*action.SealedEnvelope
    30  	UpdateAccountState(uint64, *big.Int) []*action.SealedEnvelope
    31  	AccountState() (uint64, *big.Int)
    32  	PendingNonce() uint64
    33  	NextAction() (bool, *big.Int)
    34  	Len() int
    35  	Empty() bool
    36  	PendingActs(context.Context) []*action.SealedEnvelope
    37  	AllActs() []*action.SealedEnvelope
    38  	PopActionWithLargestNonce() *action.SealedEnvelope
    39  	Reset()
    40  }
    41  
    42  // actQueue is a queue of actions from an account
    43  type actQueue struct {
    44  	ap      *actPool
    45  	address string
    46  	// Map that stores all the actions belonging to an account associated with nonces
    47  	items map[uint64]*action.SealedEnvelope
    48  	// Priority Queue that stores all the nonces belonging to an account. Nonces are used as indices for action map
    49  	ascQueue  ascNoncePriorityQueue
    50  	descQueue descNoncePriorityQueue
    51  	// Current pending nonce tracking previous actions that can be committed to the next block for the account
    52  	pendingNonce uint64
    53  	// Pending balance map
    54  	pendingBalance map[uint64]*big.Int
    55  	// Current account nonce
    56  	accountNonce uint64
    57  	// Current account balance
    58  	accountBalance *big.Int
    59  	clock          clock.Clock
    60  	ttl            time.Duration
    61  	mu             sync.RWMutex
    62  }
    63  
    64  // NewActQueue create a new action queue
    65  func NewActQueue(ap *actPool, address string, pendingNonce uint64, balance *big.Int, ops ...ActQueueOption) ActQueue {
    66  	aq := &actQueue{
    67  		ap:             ap,
    68  		address:        address,
    69  		items:          make(map[uint64]*action.SealedEnvelope),
    70  		ascQueue:       ascNoncePriorityQueue{},
    71  		descQueue:      descNoncePriorityQueue{},
    72  		pendingNonce:   pendingNonce,
    73  		pendingBalance: make(map[uint64]*big.Int),
    74  		accountNonce:   pendingNonce,
    75  		accountBalance: new(big.Int).Set(balance),
    76  		clock:          clock.New(),
    77  		ttl:            0,
    78  	}
    79  	for _, op := range ops {
    80  		op.SetActQueueOption(aq)
    81  	}
    82  	return aq
    83  }
    84  
    85  func (q *actQueue) NextAction() (bool, *big.Int) {
    86  	q.mu.RLock()
    87  	defer q.mu.RUnlock()
    88  	if len(q.ascQueue) == 0 {
    89  		return false, nil
    90  	}
    91  	return q.pendingNonce > q.accountNonce, q.items[q.ascQueue[0].nonce].GasPrice()
    92  }
    93  
    94  // Put inserts a new action into the map, also updating the queue's nonce index
    95  func (q *actQueue) Put(act *action.SealedEnvelope) error {
    96  	q.mu.Lock()
    97  	defer q.mu.Unlock()
    98  	nonce := act.Nonce()
    99  
   100  	if cost, _ := act.Cost(); q.getPendingBalanceAtNonce(nonce).Cmp(cost) < 0 {
   101  		return action.ErrInsufficientFunds
   102  	}
   103  
   104  	if actInPool, exist := q.items[nonce]; exist {
   105  		// act of higher gas price can cut in line
   106  		if nonce < q.pendingNonce && act.GasPrice().Cmp(actInPool.GasPrice()) != 1 {
   107  			return action.ErrReplaceUnderpriced
   108  		}
   109  		// update action in q.items and q.index
   110  		q.items[nonce] = act
   111  		for i := range q.ascQueue {
   112  			if q.ascQueue[i].nonce == nonce {
   113  				q.ascQueue[i].deadline = q.clock.Now().Add(q.ttl)
   114  				break
   115  			}
   116  		}
   117  		q.updateFromNonce(nonce)
   118  		return nil
   119  	}
   120  	nttl := &nonceWithTTL{nonce: nonce, deadline: q.clock.Now().Add(q.ttl)}
   121  	heap.Push(&q.ascQueue, nttl)
   122  	heap.Push(&q.descQueue, nttl)
   123  	q.items[nonce] = act
   124  	if nonce == q.pendingNonce {
   125  		q.updateFromNonce(q.pendingNonce)
   126  	}
   127  	return nil
   128  }
   129  
   130  func (q *actQueue) getPendingBalanceAtNonce(nonce uint64) *big.Int {
   131  	if nonce > q.pendingNonce {
   132  		return q.getPendingBalanceAtNonce(q.pendingNonce)
   133  	}
   134  	if _, exist := q.pendingBalance[nonce]; !exist {
   135  		return new(big.Int).Set(q.accountBalance)
   136  	}
   137  	return new(big.Int).Set(q.pendingBalance[nonce])
   138  }
   139  
   140  func (q *actQueue) updateFromNonce(start uint64) {
   141  	if start > q.pendingNonce {
   142  		return
   143  	}
   144  
   145  	for balance := q.getPendingBalanceAtNonce(start); ; start++ {
   146  		act, exist := q.items[start]
   147  		if !exist {
   148  			break
   149  		}
   150  
   151  		cost, _ := act.Cost()
   152  		if balance.Cmp(cost) < 0 {
   153  			break
   154  		}
   155  
   156  		balance = new(big.Int).Sub(balance, cost)
   157  		q.pendingBalance[start+1] = new(big.Int).Set(balance)
   158  	}
   159  
   160  	q.pendingNonce = start
   161  }
   162  
   163  // UpdateQueue updates the pending nonce and balance of the queue
   164  func (q *actQueue) UpdateQueue() []*action.SealedEnvelope {
   165  	q.mu.Lock()
   166  	defer q.mu.Unlock()
   167  	// First remove all timed out actions
   168  	removedFromQueue := q.cleanTimeout()
   169  	// Now, starting from the current pending nonce, incrementally find the next pending nonce
   170  	q.updateFromNonce(q.pendingNonce)
   171  	return removedFromQueue
   172  }
   173  
   174  func (q *actQueue) cleanTimeout() []*action.SealedEnvelope {
   175  	if q.ttl == 0 {
   176  		return []*action.SealedEnvelope{}
   177  	}
   178  	var (
   179  		removedFromQueue = make([]*action.SealedEnvelope, 0)
   180  		timeNow          = q.clock.Now()
   181  		size             = len(q.ascQueue)
   182  	)
   183  	for i := 0; i < size; {
   184  		nonce := q.ascQueue[i].nonce
   185  		if timeNow.After(q.ascQueue[i].deadline) && nonce > q.pendingNonce {
   186  			removedFromQueue = append(removedFromQueue, q.items[nonce])
   187  			delete(q.items, nonce)
   188  			delete(q.pendingBalance, nonce)
   189  			q.ascQueue[i] = q.ascQueue[size-1]
   190  			size--
   191  			continue
   192  		}
   193  		i++
   194  	}
   195  	for i := 0; i < size; i++ {
   196  		q.descQueue[i] = q.ascQueue[i]
   197  		q.descQueue[i].ascIdx = i
   198  		q.descQueue[i].descIdx = i
   199  	}
   200  	q.ascQueue = q.ascQueue[:size]
   201  	q.descQueue = q.descQueue[:size]
   202  	// using heap.Init is better here, more detail to see BenchmarkHeapInitAndRemove
   203  	heap.Init(&q.ascQueue)
   204  	heap.Init(&q.descQueue)
   205  	return removedFromQueue
   206  }
   207  
   208  // UpdateAccountState updates the account's nonce and balance and cleans confirmed actions
   209  func (q *actQueue) UpdateAccountState(nonce uint64, balance *big.Int) []*action.SealedEnvelope {
   210  	q.mu.Lock()
   211  	defer q.mu.Unlock()
   212  	q.pendingNonce = nonce
   213  	q.pendingBalance = make(map[uint64]*big.Int)
   214  	q.accountNonce = nonce
   215  	q.accountBalance.Set(balance)
   216  	var removed []*action.SealedEnvelope
   217  	// Pop off priority queue and delete corresponding entries from map
   218  	for q.ascQueue.Len() > 0 && (q.ascQueue)[0].nonce < q.accountNonce {
   219  		nttl := heap.Pop(&q.ascQueue).(*nonceWithTTL)
   220  		heap.Remove(&q.descQueue, nttl.descIdx)
   221  		nonce := nttl.nonce
   222  		removed = append(removed, q.items[nonce])
   223  		delete(q.items, nonce)
   224  	}
   225  	return removed
   226  }
   227  
   228  // AccountState returns the current account's nonce and balance
   229  func (q *actQueue) AccountState() (uint64, *big.Int) {
   230  	q.mu.RLock()
   231  	defer q.mu.RUnlock()
   232  	return q.accountNonce, new(big.Int).Set(q.accountBalance)
   233  }
   234  
   235  // PendingNonce returns the current pending nonce of the queue
   236  func (q *actQueue) PendingNonce() uint64 {
   237  	q.mu.RLock()
   238  	defer q.mu.RUnlock()
   239  	return q.pendingNonce
   240  }
   241  
   242  // Len returns the length of the action map
   243  func (q *actQueue) Len() int {
   244  	q.mu.RLock()
   245  	defer q.mu.RUnlock()
   246  	return len(q.items)
   247  }
   248  
   249  // Empty returns whether the queue of actions is empty or not
   250  func (q *actQueue) Empty() bool {
   251  	q.mu.RLock()
   252  	defer q.mu.RUnlock()
   253  	return len(q.items) == 0
   254  }
   255  
   256  // Reset makes the queue into a dummy queue
   257  func (q *actQueue) Reset() {
   258  	q.mu.Lock()
   259  	defer q.mu.Unlock()
   260  	q.items = make(map[uint64]*action.SealedEnvelope)
   261  	q.ascQueue = ascNoncePriorityQueue{}
   262  	q.descQueue = descNoncePriorityQueue{}
   263  	q.pendingNonce = 0
   264  	q.pendingBalance = make(map[uint64]*big.Int)
   265  	q.accountNonce = 0
   266  	q.accountBalance = big.NewInt(0)
   267  }
   268  
   269  // PendingActs creates a consecutive nonce-sorted slice of actions
   270  func (q *actQueue) PendingActs(ctx context.Context) []*action.SealedEnvelope {
   271  	if q.Len() == 0 {
   272  		return nil
   273  	}
   274  	addr, err := address.FromString(q.address)
   275  	if err != nil {
   276  		log.L().Error("Error when getting the address", zap.String("address", q.address), zap.Error(err))
   277  		return nil
   278  	}
   279  	// TODO: no need to refetch confirmed state, leave it to block builder to validate
   280  	confirmedState, err := accountutil.AccountState(ctx, q.ap.sf, addr)
   281  	if err != nil {
   282  		log.L().Error("Error when getting the nonce", zap.String("address", q.address), zap.Error(err))
   283  		return nil
   284  	}
   285  
   286  	var (
   287  		nonce   uint64
   288  		balance = new(big.Int).Set(confirmedState.Balance)
   289  		acts    = make([]*action.SealedEnvelope, 0, len(q.items))
   290  	)
   291  	if protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount {
   292  		nonce = confirmedState.PendingNonceConsideringFreshAccount()
   293  	} else {
   294  		nonce = confirmedState.PendingNonce()
   295  	}
   296  	q.mu.RLock()
   297  	defer q.mu.RUnlock()
   298  	for ; ; nonce++ {
   299  		act, exist := q.items[nonce]
   300  		if !exist {
   301  			break
   302  		}
   303  
   304  		cost, _ := act.Cost()
   305  		if balance.Cmp(cost) < 0 {
   306  			break
   307  		}
   308  
   309  		balance = new(big.Int).Sub(balance, cost)
   310  		acts = append(acts, act)
   311  	}
   312  	return acts
   313  }
   314  
   315  // AllActs returns all the actions currently in queue
   316  func (q *actQueue) AllActs() []*action.SealedEnvelope {
   317  	q.mu.RLock()
   318  	defer q.mu.RUnlock()
   319  	acts := make([]*action.SealedEnvelope, 0, len(q.items))
   320  	if len(q.items) == 0 {
   321  		return acts
   322  	}
   323  	for _, nonce := range q.ascQueue {
   324  		acts = append(acts, q.items[nonce.nonce])
   325  	}
   326  	return acts
   327  }
   328  
   329  func (q *actQueue) PopActionWithLargestNonce() *action.SealedEnvelope {
   330  	q.mu.Lock()
   331  	defer q.mu.Unlock()
   332  	if len(q.items) == 0 {
   333  		return nil
   334  	}
   335  	itemMeta := heap.Pop(&q.descQueue).(*nonceWithTTL)
   336  	heap.Remove(&q.ascQueue, itemMeta.ascIdx)
   337  	item := q.items[itemMeta.nonce]
   338  	delete(q.items, itemMeta.nonce)
   339  	q.updateFromNonce(itemMeta.nonce)
   340  
   341  	return item
   342  }