github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/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/consensus/bcrp"
    14  	"github.com/bytom/bytom/event"
    15  	"github.com/bytom/bytom/protocol/bc"
    16  	"github.com/bytom/bytom/protocol/bc/types"
    17  	"github.com/bytom/bytom/protocol/state"
    18  )
    19  
    20  // msg type
    21  const (
    22  	MsgNewTx = iota
    23  	MsgRemoveTx
    24  	logModule = "protocol"
    25  )
    26  
    27  var (
    28  	maxCachedErrTxs = 1000
    29  	maxMsgChSize    = 1000
    30  	maxNewTxNum     = 10000
    31  	maxOrphanNum    = 2000
    32  
    33  	orphanTTL                = 10 * time.Minute
    34  	orphanExpireScanInterval = 3 * time.Minute
    35  
    36  	// ErrTransactionNotExist is the pre-defined error message
    37  	ErrTransactionNotExist = errors.New("transaction are not existed in the mempool")
    38  	// ErrPoolIsFull indicates the pool is full
    39  	ErrPoolIsFull = errors.New("transaction pool reach the max number")
    40  	// ErrDustTx indicates transaction is dust tx
    41  	ErrDustTx = errors.New("transaction is dust tx")
    42  )
    43  
    44  type TxMsgEvent struct{ TxMsg *TxPoolMsg }
    45  
    46  // TxDesc store tx and related info for mining strategy
    47  type TxDesc struct {
    48  	Tx     *types.Tx `json:"transaction"`
    49  	Added  time.Time `json:"-"`
    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           state.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 state.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 isInvalidBCRPTx(tx *types.Tx) bool {
   214  	for _, output := range tx.TxData.Outputs {
   215  		if bcrp.IsBCRPScript(output.ControlProgram) {
   216  			return true
   217  		}
   218  	}
   219  	return false
   220  }
   221  
   222  func (tp *TxPool) IsDust(tx *types.Tx) bool {
   223  	return isTransactionNoBtmInput(tx) || isTransactionZeroOutput(tx) || isInvalidBCRPTx(tx)
   224  }
   225  
   226  func (tp *TxPool) processTransaction(tx *types.Tx, height, fee uint64) (bool, error) {
   227  	tp.mtx.Lock()
   228  	defer tp.mtx.Unlock()
   229  
   230  	txD := &TxDesc{
   231  		Tx:     tx,
   232  		Weight: tx.SerializedSize,
   233  		Height: height,
   234  		Fee:    fee,
   235  	}
   236  	requireParents, err := tp.checkOrphanUtxos(tx)
   237  	if err != nil {
   238  		return false, err
   239  	}
   240  
   241  	if len(requireParents) > 0 {
   242  		return true, tp.addOrphan(txD, requireParents)
   243  	}
   244  
   245  	if err := tp.addTransaction(txD); err != nil {
   246  		return false, err
   247  	}
   248  
   249  	tp.processOrphans(txD)
   250  	return false, nil
   251  }
   252  
   253  // ProcessTransaction is the main entry for txpool handle new tx, ignore dust tx.
   254  func (tp *TxPool) ProcessTransaction(tx *types.Tx, height, fee uint64) (bool, error) {
   255  	if tp.IsDust(tx) {
   256  		log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("dust tx")
   257  		return false, nil
   258  	}
   259  	return tp.processTransaction(tx, height, fee)
   260  }
   261  
   262  func (tp *TxPool) addOrphan(txD *TxDesc, requireParents []*bc.Hash) error {
   263  	if len(tp.orphans) >= maxOrphanNum {
   264  		return ErrPoolIsFull
   265  	}
   266  
   267  	orphan := &orphanTx{txD, time.Now().Add(orphanTTL)}
   268  	tp.orphans[txD.Tx.ID] = orphan
   269  	for _, hash := range requireParents {
   270  		if _, ok := tp.orphansByPrev[*hash]; !ok {
   271  			tp.orphansByPrev[*hash] = make(map[bc.Hash]*orphanTx)
   272  		}
   273  		tp.orphansByPrev[*hash][txD.Tx.ID] = orphan
   274  	}
   275  	return nil
   276  }
   277  
   278  func (tp *TxPool) addTransaction(txD *TxDesc) error {
   279  	if len(tp.pool) >= maxNewTxNum {
   280  		return ErrPoolIsFull
   281  	}
   282  
   283  	tx := txD.Tx
   284  	txD.Added = time.Now()
   285  	tp.pool[tx.ID] = txD
   286  	for _, id := range tx.ResultIds {
   287  		_, err := tx.OriginalOutput(*id)
   288  		if err != nil {
   289  			// error due to it's a retirement, utxo doesn't care this output type so skip it
   290  			continue
   291  		}
   292  
   293  		tp.utxo[*id] = tx
   294  	}
   295  
   296  	atomic.StoreInt64(&tp.lastUpdated, time.Now().Unix())
   297  	tp.eventDispatcher.Post(TxMsgEvent{TxMsg: &TxPoolMsg{TxDesc: txD, MsgType: MsgNewTx}})
   298  	log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Debug("Add tx to mempool")
   299  	return nil
   300  }
   301  
   302  func (tp *TxPool) checkOrphanUtxos(tx *types.Tx) ([]*bc.Hash, error) {
   303  	view := state.NewUtxoViewpoint()
   304  	if err := tp.store.GetTransactionsUtxo(view, []*bc.Tx{tx.Tx}); err != nil {
   305  		return nil, err
   306  	}
   307  
   308  	hashes := []*bc.Hash{}
   309  	for _, hash := range tx.SpentOutputIDs {
   310  		if !view.CanSpend(&hash) && tp.utxo[hash] == nil {
   311  			hashes = append(hashes, &hash)
   312  		}
   313  	}
   314  	return hashes, nil
   315  }
   316  
   317  func (tp *TxPool) orphanExpireWorker() {
   318  	ticker := time.NewTicker(orphanExpireScanInterval)
   319  	defer ticker.Stop()
   320  
   321  	for now := range ticker.C {
   322  		tp.ExpireOrphan(now)
   323  	}
   324  }
   325  
   326  func (tp *TxPool) processOrphans(txD *TxDesc) {
   327  	processOrphans := []*orphanTx{}
   328  	addRely := func(tx *types.Tx) {
   329  		for _, outHash := range tx.ResultIds {
   330  			orphans, ok := tp.orphansByPrev[*outHash]
   331  			if !ok {
   332  				continue
   333  			}
   334  
   335  			for _, orphan := range orphans {
   336  				processOrphans = append(processOrphans, orphan)
   337  			}
   338  			delete(tp.orphansByPrev, *outHash)
   339  		}
   340  	}
   341  
   342  	addRely(txD.Tx)
   343  	for ; len(processOrphans) > 0; processOrphans = processOrphans[1:] {
   344  		processOrphan := processOrphans[0]
   345  		requireParents, err := tp.checkOrphanUtxos(processOrphan.Tx)
   346  		if err != nil {
   347  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("processOrphans got unexpect error")
   348  			continue
   349  		}
   350  
   351  		if len(requireParents) == 0 {
   352  			addRely(processOrphan.Tx)
   353  			tp.removeOrphan(&processOrphan.Tx.ID)
   354  			tp.addTransaction(processOrphan.TxDesc)
   355  		}
   356  	}
   357  }
   358  
   359  func (tp *TxPool) removeOrphan(hash *bc.Hash) {
   360  	orphan, ok := tp.orphans[*hash]
   361  	if !ok {
   362  		return
   363  	}
   364  
   365  	for _, spend := range orphan.Tx.SpentOutputIDs {
   366  		orphans, ok := tp.orphansByPrev[spend]
   367  		if !ok {
   368  			continue
   369  		}
   370  
   371  		if delete(orphans, *hash); len(orphans) == 0 {
   372  			delete(tp.orphansByPrev, spend)
   373  		}
   374  	}
   375  	delete(tp.orphans, *hash)
   376  }