github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/protocol/txpool.go (about)

     1  package protocol
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"github.com/golang/groupcache/lru"
    10  	log "github.com/sirupsen/logrus"
    11  
    12  	"github.com/bytom/bytom/consensus"
    13  	"github.com/bytom/bytom/event"
    14  	"github.com/bytom/bytom/protocol/bc"
    15  	"github.com/bytom/bytom/protocol/bc/types"
    16  	"github.com/bytom/bytom/protocol/state"
    17  )
    18  
    19  // msg type
    20  const (
    21  	MsgNewTx = iota
    22  	MsgRemoveTx
    23  	logModule = "protocol"
    24  )
    25  
    26  var (
    27  	maxCachedErrTxs = 1000
    28  	maxMsgChSize    = 1000
    29  	maxNewTxNum     = 10000
    30  	maxOrphanNum    = 2000
    31  
    32  	orphanTTL                = 10 * time.Minute
    33  	orphanExpireScanInterval = 3 * time.Minute
    34  
    35  	// ErrTransactionNotExist is the pre-defined error message
    36  	ErrTransactionNotExist = errors.New("transaction are not existed in the mempool")
    37  	// ErrPoolIsFull indicates the pool is full
    38  	ErrPoolIsFull = errors.New("transaction pool reach the max number")
    39  	// ErrDustTx indicates transaction is dust tx
    40  	ErrDustTx = errors.New("transaction is dust tx")
    41  )
    42  
    43  type TxMsgEvent struct{ TxMsg *TxPoolMsg }
    44  
    45  // TxDesc store tx and related info for mining strategy
    46  type TxDesc struct {
    47  	Tx         *types.Tx `json:"transaction"`
    48  	Added      time.Time `json:"-"`
    49  	StatusFail bool      `json:"status_fail"`
    50  	Height     uint64    `json:"-"`
    51  	Weight     uint64    `json:"-"`
    52  	Fee        uint64    `json:"-"`
    53  }
    54  
    55  // TxPoolMsg is use for notify pool changes
    56  type TxPoolMsg struct {
    57  	*TxDesc
    58  	MsgType int
    59  }
    60  
    61  type orphanTx struct {
    62  	*TxDesc
    63  	expiration time.Time
    64  }
    65  
    66  // TxPool is use for store the unconfirmed transaction
    67  type TxPool struct {
    68  	lastUpdated     int64
    69  	mtx             sync.RWMutex
    70  	store           Store
    71  	pool            map[bc.Hash]*TxDesc
    72  	utxo            map[bc.Hash]*types.Tx
    73  	orphans         map[bc.Hash]*orphanTx
    74  	orphansByPrev   map[bc.Hash]map[bc.Hash]*orphanTx
    75  	errCache        *lru.Cache
    76  	eventDispatcher *event.Dispatcher
    77  }
    78  
    79  // NewTxPool init a new TxPool
    80  func NewTxPool(store Store, dispatcher *event.Dispatcher) *TxPool {
    81  	tp := &TxPool{
    82  		lastUpdated:     time.Now().Unix(),
    83  		store:           store,
    84  		pool:            make(map[bc.Hash]*TxDesc),
    85  		utxo:            make(map[bc.Hash]*types.Tx),
    86  		orphans:         make(map[bc.Hash]*orphanTx),
    87  		orphansByPrev:   make(map[bc.Hash]map[bc.Hash]*orphanTx),
    88  		errCache:        lru.New(maxCachedErrTxs),
    89  		eventDispatcher: dispatcher,
    90  	}
    91  	go tp.orphanExpireWorker()
    92  	return tp
    93  }
    94  
    95  // AddErrCache add a failed transaction record to lru cache
    96  func (tp *TxPool) AddErrCache(txHash *bc.Hash, err error) {
    97  	tp.mtx.Lock()
    98  	defer tp.mtx.Unlock()
    99  
   100  	tp.errCache.Add(txHash, err)
   101  }
   102  
   103  // ExpireOrphan expire all the orphans that before the input time range
   104  func (tp *TxPool) ExpireOrphan(now time.Time) {
   105  	tp.mtx.Lock()
   106  	defer tp.mtx.Unlock()
   107  
   108  	for hash, orphan := range tp.orphans {
   109  		if orphan.expiration.Before(now) {
   110  			tp.removeOrphan(&hash)
   111  		}
   112  	}
   113  }
   114  
   115  // GetErrCache return the error of the transaction
   116  func (tp *TxPool) GetErrCache(txHash *bc.Hash) error {
   117  	tp.mtx.Lock()
   118  	defer tp.mtx.Unlock()
   119  
   120  	v, ok := tp.errCache.Get(txHash)
   121  	if !ok {
   122  		return nil
   123  	}
   124  	return v.(error)
   125  }
   126  
   127  // RemoveTransaction remove a transaction from the pool
   128  func (tp *TxPool) RemoveTransaction(txHash *bc.Hash) {
   129  	tp.mtx.Lock()
   130  	defer tp.mtx.Unlock()
   131  
   132  	txD, ok := tp.pool[*txHash]
   133  	if !ok {
   134  		return
   135  	}
   136  
   137  	for _, output := range txD.Tx.ResultIds {
   138  		delete(tp.utxo, *output)
   139  	}
   140  	delete(tp.pool, *txHash)
   141  
   142  	atomic.StoreInt64(&tp.lastUpdated, time.Now().Unix())
   143  	tp.eventDispatcher.Post(TxMsgEvent{TxMsg: &TxPoolMsg{TxDesc: txD, MsgType: MsgRemoveTx}})
   144  	log.WithFields(log.Fields{"module": logModule, "tx_id": txHash}).Debug("remove tx from mempool")
   145  }
   146  
   147  // GetTransaction return the TxDesc by hash
   148  func (tp *TxPool) GetTransaction(txHash *bc.Hash) (*TxDesc, error) {
   149  	tp.mtx.RLock()
   150  	defer tp.mtx.RUnlock()
   151  
   152  	if txD, ok := tp.pool[*txHash]; ok {
   153  		return txD, nil
   154  	}
   155  	return nil, ErrTransactionNotExist
   156  }
   157  
   158  // GetTransactions return all the transactions in the pool
   159  func (tp *TxPool) GetTransactions() []*TxDesc {
   160  	tp.mtx.RLock()
   161  	defer tp.mtx.RUnlock()
   162  
   163  	txDs := make([]*TxDesc, len(tp.pool))
   164  	i := 0
   165  	for _, desc := range tp.pool {
   166  		txDs[i] = desc
   167  		i++
   168  	}
   169  	return txDs
   170  }
   171  
   172  // IsTransactionInPool check wheather a transaction in pool or not
   173  func (tp *TxPool) IsTransactionInPool(txHash *bc.Hash) bool {
   174  	tp.mtx.RLock()
   175  	defer tp.mtx.RUnlock()
   176  
   177  	_, ok := tp.pool[*txHash]
   178  	return ok
   179  }
   180  
   181  // IsTransactionInErrCache check wheather a transaction in errCache or not
   182  func (tp *TxPool) IsTransactionInErrCache(txHash *bc.Hash) bool {
   183  	tp.mtx.RLock()
   184  	defer tp.mtx.RUnlock()
   185  
   186  	_, ok := tp.errCache.Get(txHash)
   187  	return ok
   188  }
   189  
   190  // HaveTransaction IsTransactionInErrCache check is  transaction in errCache or pool
   191  func (tp *TxPool) HaveTransaction(txHash *bc.Hash) bool {
   192  	return tp.IsTransactionInPool(txHash) || tp.IsTransactionInErrCache(txHash)
   193  }
   194  
   195  func isTransactionNoBtmInput(tx *types.Tx) bool {
   196  	for _, input := range tx.TxData.Inputs {
   197  		if input.AssetID() == *consensus.BTMAssetID {
   198  			return false
   199  		}
   200  	}
   201  	return true
   202  }
   203  
   204  func isTransactionZeroOutput(tx *types.Tx) bool {
   205  	for _, output := range tx.TxData.Outputs {
   206  		if output.Amount == uint64(0) {
   207  			return true
   208  		}
   209  	}
   210  	return false
   211  }
   212  
   213  func (tp *TxPool) IsDust(tx *types.Tx) bool {
   214  	return isTransactionNoBtmInput(tx) || isTransactionZeroOutput(tx)
   215  }
   216  
   217  func (tp *TxPool) processTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) {
   218  	tp.mtx.Lock()
   219  	defer tp.mtx.Unlock()
   220  
   221  	txD := &TxDesc{
   222  		Tx:         tx,
   223  		StatusFail: statusFail,
   224  		Weight:     tx.SerializedSize,
   225  		Height:     height,
   226  		Fee:        fee,
   227  	}
   228  	requireParents, err := tp.checkOrphanUtxos(tx)
   229  	if err != nil {
   230  		return false, err
   231  	}
   232  
   233  	if len(requireParents) > 0 {
   234  		return true, tp.addOrphan(txD, requireParents)
   235  	}
   236  
   237  	if err := tp.addTransaction(txD); err != nil {
   238  		return false, err
   239  	}
   240  
   241  	tp.processOrphans(txD)
   242  	return false, nil
   243  }
   244  
   245  // ProcessTransaction is the main entry for txpool handle new tx, ignore dust tx.
   246  func (tp *TxPool) ProcessTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) {
   247  	if tp.IsDust(tx) {
   248  		log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("dust tx")
   249  		return false, nil
   250  	}
   251  	return tp.processTransaction(tx, statusFail, height, fee)
   252  }
   253  
   254  func (tp *TxPool) addOrphan(txD *TxDesc, requireParents []*bc.Hash) error {
   255  	if len(tp.orphans) >= maxOrphanNum {
   256  		return ErrPoolIsFull
   257  	}
   258  
   259  	orphan := &orphanTx{txD, time.Now().Add(orphanTTL)}
   260  	tp.orphans[txD.Tx.ID] = orphan
   261  	for _, hash := range requireParents {
   262  		if _, ok := tp.orphansByPrev[*hash]; !ok {
   263  			tp.orphansByPrev[*hash] = make(map[bc.Hash]*orphanTx)
   264  		}
   265  		tp.orphansByPrev[*hash][txD.Tx.ID] = orphan
   266  	}
   267  	return nil
   268  }
   269  
   270  func (tp *TxPool) addTransaction(txD *TxDesc) error {
   271  	if len(tp.pool) >= maxNewTxNum {
   272  		return ErrPoolIsFull
   273  	}
   274  
   275  	tx := txD.Tx
   276  	txD.Added = time.Now()
   277  	tp.pool[tx.ID] = txD
   278  	for _, id := range tx.ResultIds {
   279  		output, err := tx.Output(*id)
   280  		if err != nil {
   281  			// error due to it's a retirement, utxo doesn't care this output type so skip it
   282  			continue
   283  		}
   284  		if !txD.StatusFail || *output.Source.Value.AssetId == *consensus.BTMAssetID {
   285  			tp.utxo[*id] = tx
   286  		}
   287  	}
   288  
   289  	atomic.StoreInt64(&tp.lastUpdated, time.Now().Unix())
   290  	tp.eventDispatcher.Post(TxMsgEvent{TxMsg: &TxPoolMsg{TxDesc: txD, MsgType: MsgNewTx}})
   291  	log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Debug("Add tx to mempool")
   292  	return nil
   293  }
   294  
   295  func (tp *TxPool) checkOrphanUtxos(tx *types.Tx) ([]*bc.Hash, error) {
   296  	view := state.NewUtxoViewpoint()
   297  	if err := tp.store.GetTransactionsUtxo(view, []*bc.Tx{tx.Tx}); err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	hashes := []*bc.Hash{}
   302  	for _, hash := range tx.SpentOutputIDs {
   303  		if !view.CanSpend(&hash) && tp.utxo[hash] == nil {
   304  			hashes = append(hashes, &hash)
   305  		}
   306  	}
   307  	return hashes, nil
   308  }
   309  
   310  func (tp *TxPool) orphanExpireWorker() {
   311  	ticker := time.NewTicker(orphanExpireScanInterval)
   312  	defer ticker.Stop()
   313  
   314  	for now := range ticker.C {
   315  		tp.ExpireOrphan(now)
   316  	}
   317  }
   318  
   319  func (tp *TxPool) processOrphans(txD *TxDesc) {
   320  	processOrphans := []*orphanTx{}
   321  	addRely := func(tx *types.Tx) {
   322  		for _, outHash := range tx.ResultIds {
   323  			orphans, ok := tp.orphansByPrev[*outHash]
   324  			if !ok {
   325  				continue
   326  			}
   327  
   328  			for _, orphan := range orphans {
   329  				processOrphans = append(processOrphans, orphan)
   330  			}
   331  			delete(tp.orphansByPrev, *outHash)
   332  		}
   333  	}
   334  
   335  	addRely(txD.Tx)
   336  	for ; len(processOrphans) > 0; processOrphans = processOrphans[1:] {
   337  		processOrphan := processOrphans[0]
   338  		requireParents, err := tp.checkOrphanUtxos(processOrphan.Tx)
   339  		if err != nil {
   340  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("processOrphans got unexpect error")
   341  			continue
   342  		}
   343  
   344  		if len(requireParents) == 0 {
   345  			addRely(processOrphan.Tx)
   346  			tp.removeOrphan(&processOrphan.Tx.ID)
   347  			tp.addTransaction(processOrphan.TxDesc)
   348  		}
   349  	}
   350  }
   351  
   352  func (tp *TxPool) removeOrphan(hash *bc.Hash) {
   353  	orphan, ok := tp.orphans[*hash]
   354  	if !ok {
   355  		return
   356  	}
   357  
   358  	for _, spend := range orphan.Tx.SpentOutputIDs {
   359  		orphans, ok := tp.orphansByPrev[spend]
   360  		if !ok {
   361  			continue
   362  		}
   363  
   364  		if delete(orphans, *hash); len(orphans) == 0 {
   365  			delete(tp.orphansByPrev, spend)
   366  		}
   367  	}
   368  	delete(tp.orphans, *hash)
   369  }