github.com/igggame/nebulas-go@v2.1.0+incompatible/core/transaction_pool.go (about)

     1  // Copyright (C) 2017 go-nebulas authors
     2  //
     3  // This file is part of the go-nebulas library.
     4  //
     5  // the go-nebulas library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // the go-nebulas library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU General Public License
    16  // along with the go-nebulas library.  If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  
    19  package core
    20  
    21  import (
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/nebulasio/go-nebulas/core/state"
    26  
    27  	"github.com/gogo/protobuf/proto"
    28  	"github.com/nebulasio/go-nebulas/common/sorted"
    29  	"github.com/nebulasio/go-nebulas/core/pb"
    30  	"github.com/nebulasio/go-nebulas/net"
    31  	"github.com/nebulasio/go-nebulas/util"
    32  	"github.com/nebulasio/go-nebulas/util/byteutils"
    33  	"github.com/nebulasio/go-nebulas/util/logging"
    34  	"github.com/sirupsen/logrus"
    35  )
    36  
    37  var (
    38  	metricUpdateInterval = time.Second
    39  	txEvictInterval      = time.Minute
    40  	txLifetime           = time.Minute * 90
    41  )
    42  
    43  // TransactionPool cache txs, is thread safe
    44  type TransactionPool struct {
    45  	receivedMessageCh chan net.Message
    46  	quitCh            chan int
    47  
    48  	size              int
    49  	candidates        *sorted.Slice
    50  	buckets           map[byteutils.HexHash]*sorted.Slice
    51  	all               map[byteutils.HexHash]*Transaction
    52  	bucketsLastUpdate map[byteutils.HexHash]time.Time
    53  
    54  	ns net.Service
    55  	mu sync.RWMutex
    56  
    57  	minGasPrice *util.Uint128 // the lowest gasPrice.
    58  	maxGasLimit *util.Uint128 // the maximum gasLimit.
    59  
    60  	eventEmitter *EventEmitter
    61  	bc           *BlockChain
    62  
    63  	access *Access
    64  }
    65  
    66  func nonceCmp(a interface{}, b interface{}) int {
    67  	txa := a.(*Transaction)
    68  	txb := b.(*Transaction)
    69  	if txa.Nonce() < txb.Nonce() {
    70  		return -1
    71  	} else if txa.Nonce() > txb.Nonce() {
    72  		return 1
    73  	} else {
    74  		return txb.GasPrice().Cmp(txa.GasPrice())
    75  	}
    76  }
    77  
    78  func gasCmp(a interface{}, b interface{}) int {
    79  	txa := a.(*Transaction)
    80  	txb := b.(*Transaction)
    81  	return txb.GasPrice().Cmp(txa.GasPrice())
    82  }
    83  
    84  // NewTransactionPool create a new TransactionPool
    85  func NewTransactionPool(size int) (*TransactionPool, error) {
    86  	return &TransactionPool{
    87  		receivedMessageCh: make(chan net.Message, size),
    88  		quitCh:            make(chan int, 1),
    89  		size:              size,
    90  		candidates:        sorted.NewSlice(gasCmp),
    91  		buckets:           make(map[byteutils.HexHash]*sorted.Slice),
    92  		all:               make(map[byteutils.HexHash]*Transaction),
    93  		bucketsLastUpdate: make(map[byteutils.HexHash]time.Time),
    94  		minGasPrice:       TransactionGasPrice,
    95  		maxGasLimit:       TransactionMaxGas,
    96  	}, nil
    97  }
    98  
    99  // SetGasConfig config the lowest gasPrice and the maximum gasLimit.
   100  func (pool *TransactionPool) SetGasConfig(gasPrice, gasLimit *util.Uint128) error {
   101  	if gasPrice == nil || gasPrice.Cmp(util.NewUint128()) <= 0 {
   102  		pool.minGasPrice = TransactionGasPrice
   103  	} else if gasPrice.Cmp(TransactionMaxGasPrice) <= 0 {
   104  		pool.minGasPrice = gasPrice
   105  	} else {
   106  		return ErrInvalidGasPrice
   107  	}
   108  	if gasLimit == nil || gasLimit.Cmp(util.NewUint128()) <= 0 {
   109  		pool.maxGasLimit = TransactionMaxGas
   110  	} else if gasPrice.Cmp(TransactionMaxGas) <= 0 {
   111  		pool.maxGasLimit = gasLimit
   112  	} else {
   113  		return ErrInvalidGasLimit
   114  	}
   115  	return nil
   116  }
   117  
   118  // RegisterInNetwork register message subscriber in network.
   119  func (pool *TransactionPool) RegisterInNetwork(ns net.Service) {
   120  	ns.Register(net.NewSubscriber(pool, pool.receivedMessageCh, true, MessageTypeNewTx, net.MessageWeightNewTx))
   121  	pool.ns = ns
   122  }
   123  
   124  func (pool *TransactionPool) setBlockChain(bc *BlockChain) {
   125  	pool.bc = bc
   126  }
   127  
   128  func (pool *TransactionPool) setEventEmitter(emitter *EventEmitter) {
   129  	pool.eventEmitter = emitter
   130  }
   131  
   132  func (pool *TransactionPool) setAccess(access *Access) {
   133  	pool.access = access
   134  }
   135  
   136  // Start start loop.
   137  func (pool *TransactionPool) Start() {
   138  	logging.CLog().WithFields(logrus.Fields{
   139  		"size": pool.size,
   140  	}).Info("Starting TransactionPool...")
   141  
   142  	go pool.loop()
   143  }
   144  
   145  // Stop stop loop.
   146  func (pool *TransactionPool) Stop() {
   147  	logging.CLog().WithFields(logrus.Fields{
   148  		"size": pool.size,
   149  	}).Info("Stop TransactionPool.")
   150  
   151  	pool.quitCh <- 0
   152  }
   153  
   154  func (pool *TransactionPool) loop() {
   155  	logging.CLog().WithFields(logrus.Fields{
   156  		"size": pool.size,
   157  	}).Info("Started TransactionPool.")
   158  
   159  	metricsUpdateChan := time.NewTicker(metricUpdateInterval).C
   160  	evictChan := time.NewTicker(txEvictInterval).C
   161  
   162  	for {
   163  		select {
   164  		case <-metricsUpdateChan:
   165  			metricsReceivedTx.Update(int64(len(pool.receivedMessageCh)))
   166  			metricsCachedTx.Update(int64(len(pool.all)))
   167  			metricsBucketTx.Update(int64(len(pool.buckets)))
   168  			metricsCandidates.Update(int64(pool.candidates.Len()))
   169  
   170  		case <-evictChan:
   171  			pool.evictExpiredTransactions()
   172  
   173  		case <-pool.quitCh:
   174  			logging.CLog().WithFields(logrus.Fields{
   175  				"size": pool.size,
   176  			}).Info("Stopped TransactionPool.")
   177  			return
   178  		case msg := <-pool.receivedMessageCh:
   179  			if msg.MessageType() != MessageTypeNewTx {
   180  				logging.VLog().WithFields(logrus.Fields{
   181  					"messageType": msg.MessageType(),
   182  					"message":     msg,
   183  					"err":         "not new tx msg",
   184  				}).Debug("Received unregistered message.")
   185  				continue
   186  			}
   187  
   188  			tx := new(Transaction)
   189  			pbTx := new(corepb.Transaction)
   190  			if err := proto.Unmarshal(msg.Data(), pbTx); err != nil {
   191  				logging.VLog().WithFields(logrus.Fields{
   192  					"msgType": msg.MessageType(),
   193  					"msg":     msg,
   194  					"err":     err,
   195  				}).Debug("Failed to unmarshal data.")
   196  				continue
   197  			}
   198  			if err := tx.FromProto(pbTx); err != nil {
   199  				logging.VLog().WithFields(logrus.Fields{
   200  					"msgType": msg.MessageType(),
   201  					"msg":     msg,
   202  					"err":     err,
   203  				}).Debug("Failed to recover a tx from proto data.")
   204  				continue
   205  			}
   206  
   207  			if err := pool.PushAndRelay(tx); err != nil {
   208  				logging.VLog().WithFields(logrus.Fields{
   209  					"func":        "TxPool.loop",
   210  					"messageType": msg.MessageType(),
   211  					"transaction": tx,
   212  					"err":         err,
   213  				}).Debug("Failed to push a tx into tx pool.")
   214  				continue
   215  			}
   216  		}
   217  	}
   218  }
   219  
   220  // GetMinGasPrice return the minGasPrice
   221  func (pool *TransactionPool) GetMinGasPrice() *util.Uint128 {
   222  	return pool.minGasPrice
   223  }
   224  
   225  // GetMaxGasLimit return the maxGasLimit
   226  func (pool *TransactionPool) GetMaxGasLimit() *util.Uint128 {
   227  	return pool.maxGasLimit
   228  }
   229  
   230  // GetTransaction return transaction of given hash from transaction pool.
   231  func (pool *TransactionPool) GetTransaction(hash byteutils.Hash) *Transaction {
   232  	pool.mu.Lock()
   233  	defer pool.mu.Unlock()
   234  
   235  	return pool.all[hash.Hex()]
   236  }
   237  
   238  // PushAndRelay push tx into pool and relay it
   239  func (pool *TransactionPool) PushAndRelay(tx *Transaction) error {
   240  	if err := pool.Push(tx); err != nil {
   241  		logging.VLog().WithFields(logrus.Fields{
   242  			"tx":  tx.StringWithoutData(),
   243  			"err": err,
   244  		}).Debug("Failed to push tx")
   245  		return err
   246  	}
   247  
   248  	// TODO: if tx relay , don't relay again @fengzi @roy
   249  	pool.ns.Relay(MessageTypeNewTx, tx, net.MessagePriorityNormal)
   250  	return nil
   251  }
   252  
   253  // PushAndBroadcast push tx into pool and broadcast it
   254  func (pool *TransactionPool) PushAndBroadcast(tx *Transaction) error {
   255  	if err := pool.Push(tx); err != nil {
   256  		logging.VLog().WithFields(logrus.Fields{
   257  			"tx":  tx.StringWithoutData(),
   258  			"err": err,
   259  		}).Debug("Failed to push tx")
   260  		return err
   261  	}
   262  
   263  	pool.ns.Broadcast(MessageTypeNewTx, tx, net.MessagePriorityNormal)
   264  	return nil
   265  }
   266  
   267  // Push tx into pool
   268  func (pool *TransactionPool) Push(tx *Transaction) error {
   269  	pool.mu.Lock()
   270  	defer pool.mu.Unlock()
   271  	// add tx log in super node
   272  	if pool.bc.superNode == true {
   273  		logging.VLog().WithFields(logrus.Fields{
   274  			"tx": tx,
   275  		}).Debug("Push tx to transaction pool")
   276  	}
   277  
   278  	// only super node need the access control
   279  	//if pool.bc.superNode == true {
   280  	if err := pool.access.CheckTransaction(tx); err != nil {
   281  		logging.VLog().WithFields(logrus.Fields{
   282  			"tx.hash": tx.hash,
   283  			"error":   err,
   284  		}).Debug("Failed to check transaction in access.")
   285  		return err
   286  	}
   287  
   288  	// check dip reward
   289  	if err := pool.bc.dip.CheckReward(tx); err != nil {
   290  		logging.VLog().WithFields(logrus.Fields{
   291  			"tx.hash": tx.hash,
   292  			"error":   err,
   293  		}).Debug("Failed to check transaction for dip reward.")
   294  		return err
   295  	}
   296  
   297  	// verify non-dup tx
   298  	if _, ok := pool.all[tx.hash.Hex()]; ok {
   299  		metricsDuplicateTx.Inc(1)
   300  		return ErrDuplicatedTransaction
   301  	} // ToRefine: refine the lock scope
   302  
   303  	// if tx's gasPrice below the pool config lowest gasPrice, return ErrBelowGasPrice
   304  	if tx.gasPrice.Cmp(pool.minGasPrice) < 0 {
   305  		metricsTxPoolBelowGasPrice.Inc(1)
   306  		return ErrBelowGasPrice
   307  	}
   308  
   309  	if tx.gasLimit.Cmp(util.NewUint128()) <= 0 {
   310  		metricsTxPoolGasLimitLessOrEqualToZero.Inc(1)
   311  		return ErrGasLimitLessOrEqualToZero
   312  	}
   313  
   314  	if tx.gasLimit.Cmp(pool.maxGasLimit) > 0 {
   315  		metricsTxPoolOutOfGasLimit.Inc(1)
   316  		return ErrOutOfGasLimit
   317  	}
   318  
   319  	// verify hash & sign of tx
   320  	if err := tx.VerifyIntegrity(pool.bc.chainID); err != nil {
   321  		metricsInvalidTx.Inc(1)
   322  		return err
   323  	}
   324  
   325  	// cache the verified tx
   326  	pool.pushTx(tx)
   327  	// drop max tx in longest bucket if full
   328  	if len(pool.all) > pool.size {
   329  		poollen := len(pool.all)
   330  		pool.dropTx()
   331  
   332  		logging.VLog().WithFields(logrus.Fields{
   333  			"tx":         tx.StringWithoutData(),
   334  			"size":       pool.size,
   335  			"bpoolsize":  poollen,
   336  			"apoolsize":  len(pool.all),
   337  			"bucketsize": len(pool.buckets),
   338  		}).Debug("drop tx")
   339  	}
   340  
   341  	// trigger pending transaction
   342  	event := &state.Event{
   343  		Topic: TopicPendingTransaction,
   344  		Data:  tx.JSONString(),
   345  	}
   346  	pool.eventEmitter.Trigger(event)
   347  
   348  	return nil
   349  }
   350  
   351  func (pool *TransactionPool) pushTx(tx *Transaction) {
   352  	slot := tx.from.address.Hex()
   353  	bucket, ok := pool.buckets[slot]
   354  	if !ok {
   355  		bucket = sorted.NewSlice(nonceCmp)
   356  		pool.buckets[slot] = bucket
   357  	}
   358  	oldCandidate := bucket.Left()
   359  	bucket.Push(tx)
   360  	pool.all[tx.hash.Hex()] = tx
   361  	newCandidate := bucket.Left()
   362  	// replace candidate
   363  	if oldCandidate == nil {
   364  		pool.candidates.Push(newCandidate)
   365  	} else if oldCandidate != newCandidate {
   366  		pool.candidates.Del(oldCandidate)
   367  		pool.candidates.Push(newCandidate)
   368  	}
   369  
   370  	// Initialize bucket time. Do not update in pushTx() after init.
   371  	// Because tx could be taken out and then push back if verification fail
   372  	if _, ok := pool.bucketsLastUpdate[slot]; !ok {
   373  		pool.bucketsLastUpdate[slot] = time.Now()
   374  	}
   375  }
   376  
   377  func (pool *TransactionPool) popTx(tx *Transaction) {
   378  	bucket := pool.buckets[tx.from.address.Hex()]
   379  	delete(pool.all, tx.hash.Hex())
   380  	bucket.PopLeft()
   381  	if bucket.Len() != 0 {
   382  		candidate := bucket.Left()
   383  		pool.candidates.Push(candidate)
   384  	} else {
   385  		delete(pool.buckets, tx.from.address.Hex())
   386  		delete(pool.bucketsLastUpdate, tx.from.address.Hex())
   387  	}
   388  }
   389  
   390  func (pool *TransactionPool) dropTx() {
   391  	var longestSlice *sorted.Slice
   392  	longestLen := 0
   393  	for _, v := range pool.buckets {
   394  		if v.Len() > longestLen {
   395  			longestLen = v.Len()
   396  			longestSlice = v
   397  		}
   398  	}
   399  
   400  	logging.VLog().WithFields(logrus.Fields{
   401  		"longestsize": longestLen,
   402  	}).Debug("Drop tx from longest bucket.")
   403  
   404  	if longestLen > 0 {
   405  		drop := longestSlice.PopRight().(*Transaction)
   406  		if drop != nil {
   407  			delete(pool.all, drop.Hash().Hex())
   408  			if longestLen == 1 {
   409  				pool.candidates.Del(drop)
   410  				delete(pool.buckets, drop.from.address.Hex())
   411  				delete(pool.bucketsLastUpdate, drop.from.address.Hex())
   412  			}
   413  		}
   414  	}
   415  }
   416  
   417  // PopWithBlacklist return a tx with highest gasprice and not in the blocklist
   418  func (pool *TransactionPool) PopWithBlacklist(fromBlacklist *sync.Map, toBlacklist *sync.Map) *Transaction {
   419  	pool.mu.Lock()
   420  	defer pool.mu.Unlock()
   421  
   422  	if fromBlacklist == nil {
   423  		fromBlacklist = new(sync.Map)
   424  	}
   425  	if toBlacklist == nil {
   426  		toBlacklist = new(sync.Map)
   427  	}
   428  
   429  	size := pool.candidates.Len()
   430  	for i := 0; i < size; i++ {
   431  		tx := pool.candidates.Index(i).(*Transaction)
   432  		if _, ok := fromBlacklist.Load(tx.from.address.Hex()); !ok {
   433  			if _, ok := toBlacklist.Load(tx.to.address.Hex()); !ok {
   434  				pool.candidates.Del(tx)
   435  				pool.popTx(tx)
   436  				return tx
   437  			}
   438  		}
   439  	}
   440  	return nil
   441  }
   442  
   443  // Pop a transaction from pool
   444  func (pool *TransactionPool) Pop() *Transaction {
   445  	pool.mu.Lock()
   446  	defer pool.mu.Unlock()
   447  
   448  	candidates := pool.candidates
   449  	val := candidates.PopLeft()
   450  	if val == nil {
   451  		return nil
   452  	}
   453  	tx := val.(*Transaction)
   454  	pool.popTx(tx)
   455  	return tx
   456  }
   457  
   458  // Del a transaction from pool
   459  func (pool *TransactionPool) Del(tx *Transaction) {
   460  	pool.mu.Lock()
   461  	defer pool.mu.Unlock()
   462  
   463  	bucket := pool.buckets[tx.from.address.Hex()]
   464  	if bucket != nil && bucket.Len() > 0 {
   465  		oldCandidate := bucket.Left()
   466  		left := oldCandidate.(*Transaction)
   467  		for left.Nonce() <= tx.Nonce() {
   468  			bucket.PopLeft()
   469  			delete(pool.all, left.Hash().Hex())
   470  
   471  			// trigger pending transaction
   472  			event := &state.Event{
   473  				Topic: TopicDropTransaction,
   474  				Data:  left.String(),
   475  			}
   476  			pool.eventEmitter.Trigger(event)
   477  
   478  			logging.VLog().WithFields(logrus.Fields{
   479  				"tx":         left.Hash().Hex(),
   480  				"size":       pool.size,
   481  				"poolsize":   len(pool.all),
   482  				"bucketsize": len(pool.buckets),
   483  			}).Debug("Delete transaction")
   484  
   485  			if bucket.Len() > 0 {
   486  				left = bucket.Left().(*Transaction)
   487  			} else {
   488  				delete(pool.buckets, left.from.address.Hex())
   489  				delete(pool.bucketsLastUpdate, left.from.address.Hex())
   490  				break
   491  			}
   492  		}
   493  
   494  		newCandidate := bucket.Left()
   495  		// replace candidate
   496  		if oldCandidate != newCandidate {
   497  			pool.candidates.Del(oldCandidate)
   498  			delete(pool.bucketsLastUpdate, tx.from.address.Hex())
   499  			if newCandidate != nil {
   500  				pool.candidates.Push(newCandidate)
   501  
   502  				//update bucket update time when txs are put on chain
   503  				pool.bucketsLastUpdate[tx.from.address.Hex()] = time.Now()
   504  			}
   505  		}
   506  
   507  	} else {
   508  		//remove key of bucketsLastUpdate when bucket is empty
   509  		delete(pool.bucketsLastUpdate, tx.from.address.Hex())
   510  	}
   511  }
   512  
   513  // Empty return if the pool is empty
   514  func (pool *TransactionPool) Empty() bool {
   515  	pool.mu.Lock()
   516  	defer pool.mu.Unlock()
   517  	return len(pool.all) == 0
   518  }
   519  
   520  func (pool *TransactionPool) evictExpiredTransactions() {
   521  	pool.mu.Lock()
   522  	defer pool.mu.Unlock()
   523  
   524  	for slot := range pool.buckets {
   525  		if timeLastDate, ok := pool.bucketsLastUpdate[slot]; ok {
   526  			if time.Since(timeLastDate) > txLifetime {
   527  				bucket := pool.buckets[slot]
   528  
   529  				val := bucket.PopLeft()
   530  				if tx := val.(*Transaction); tx != nil && tx.hash != nil {
   531  					pool.candidates.Del(tx) // only remove the first from candidates
   532  				}
   533  				for val != nil {
   534  					if tx := val.(*Transaction); tx != nil && tx.hash != nil {
   535  						delete(pool.all, tx.hash.Hex())
   536  						logging.VLog().WithFields(logrus.Fields{
   537  							"tx.hash":    tx.hash.Hex(),
   538  							"size":       pool.size,
   539  							"poolsize":   len(pool.all),
   540  							"bucketsize": len(pool.buckets),
   541  							"tx":         tx.StringWithoutData(),
   542  						}).Debug("Remove expired transactions.")
   543  						// trigger pending transaction
   544  						event := &state.Event{
   545  							Topic: TopicDropTransaction,
   546  							Data:  tx.JSONString(),
   547  						}
   548  						pool.eventEmitter.Trigger(event)
   549  					}
   550  
   551  					val = bucket.PopLeft()
   552  				}
   553  				delete(pool.buckets, slot)
   554  				delete(pool.bucketsLastUpdate, slot)
   555  			}
   556  		}
   557  	}
   558  }
   559  
   560  // get pending tx count
   561  func (pool *TransactionPool) GetPending(addr *Address) uint64 {
   562  	pool.mu.Lock()
   563  	defer pool.mu.Unlock()
   564  
   565  	slot := addr.address.Hex()
   566  	bucket, ok := pool.buckets[slot]
   567  	if !ok {
   568  		return 0
   569  	}
   570  	return uint64(bucket.Len())
   571  }