--- /dev/null
+package blockchain
+
+import (
+ "errors"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/golang/groupcache/lru"
+
+ "github.com/bytom/protocol/bc"
+)
+
+var (
+ maxCachedErrTxs = 1000
+
+ ErrTransactionNotExist = errors.New("transaction are not existed in the mempool")
+)
+
+type TxDesc struct {
+ Tx *bc.Tx
+ Added time.Time
+ Height uint64
+ Fee uint64
+ FeePerKB uint64
+}
+
+type TxPool struct {
+ lastUpdated int64
+ mtx sync.RWMutex
+ pool map[bc.Hash]*TxDesc
+ errCache *lru.Cache
+}
+
+func NewTxPool() *TxPool {
+ return &TxPool{
+ lastUpdated: time.Now().Unix(),
+ pool: make(map[bc.Hash]*TxDesc),
+ errCache: lru.New(maxCachedErrTxs),
+ }
+}
+
+func (mp *TxPool) AddTransaction(tx *bc.Tx, height uint64, fee uint64) *TxDesc {
+ txD := &TxDesc{
+ Tx: tx,
+ Added: time.Now(),
+ Height: height,
+ Fee: fee,
+ FeePerKB: fee * 1000 / tx.TxHeader.SerializedSize,
+ }
+
+ mp.mtx.Lock()
+ defer mp.mtx.Unlock()
+
+ mp.pool[tx.ID] = txD
+ atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix())
+ return txD
+}
+
+func (mp *TxPool) AddErrCache(txHash *bc.Hash) {
+ mp.mtx.Lock()
+ defer mp.mtx.Unlock()
+
+ mp.errCache.Add(txHash, nil)
+}
+
+func (mp *TxPool) removeTransaction(txHash *bc.Hash) {
+ mp.mtx.Lock()
+ defer mp.mtx.Unlock()
+
+ if _, ok := mp.pool[*txHash]; ok {
+ delete(mp.pool, *txHash)
+ atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix())
+ }
+}
+
+func (mp *TxPool) GetTransaction(txHash *bc.Hash) (*TxDesc, error) {
+ mp.mtx.RLock()
+ defer mp.mtx.RUnlock()
+
+ if txD, ok := mp.pool[*txHash]; ok {
+ return txD, nil
+ }
+
+ return nil, ErrTransactionNotExist
+}
+
+func (mp *TxPool) GetTransactions() []*TxDesc {
+ mp.mtx.RLock()
+ defer mp.mtx.RUnlock()
+
+ txDs := make([]*TxDesc, len(mp.pool))
+ i := 0
+ for _, desc := range mp.pool {
+ txDs[i] = desc
+ i++
+ }
+ return txDs
+}
+
+func (mp *TxPool) IsTransactionInPool(txHash *bc.Hash) bool {
+ mp.mtx.RLock()
+ defer mp.mtx.RUnlock()
+
+ if _, ok := mp.pool[*txHash]; ok {
+ return true
+ }
+ return false
+}
+
+func (mp *TxPool) IsTransactionInErrCache(txHash *bc.Hash) bool {
+ mp.mtx.RLock()
+ defer mp.mtx.RUnlock()
+
+ _, ok := mp.errCache.Get(txHash)
+ return ok
+}
+
+func (mp *TxPool) HaveTransaction(txHash *bc.Hash) bool {
+ return mp.IsTransactionInPool(txHash) || mp.IsTransactionInErrCache(txHash)
+}
+
+func (mp *TxPool) Count() int {
+ mp.mtx.RLock()
+ defer mp.mtx.RUnlock()
+
+ count := len(mp.pool)
+ return count
+}
--- /dev/null
+package blockchain
+
+import (
+ "testing"
+
+ "github.com/bytom/protocol/bc"
+ "github.com/bytom/protocol/bc/legacy"
+ "github.com/bytom/protocol/validation"
+)
+
+func TestTxPool(t *testing.T) {
+ p := NewTxPool()
+
+ txA := mockCoinbaseTx(1000, 6543)
+ txB := mockCoinbaseTx(2000, 2324)
+ txC := mockCoinbaseTx(3000, 9322)
+
+ p.AddTransaction(txA, 1000, 5000000000)
+ if !p.IsTransactionInPool(&txA.ID) {
+ t.Errorf("fail to find added txA in tx pool")
+ } else {
+ i, _ := p.GetTransaction(&txA.ID)
+ if i.Height != 1000 || i.Fee != 5000000000 || i.FeePerKB != 5000000000 {
+ t.Errorf("incorrect data of TxDesc structure")
+ }
+ }
+
+ if p.IsTransactionInPool(&txB.ID) {
+ t.Errorf("shouldn't find txB in tx pool")
+ }
+ p.AddTransaction(txB, 1000, 5000000000)
+ if !p.IsTransactionInPool(&txB.ID) {
+ t.Errorf("shouldn find txB in tx pool")
+ }
+ if p.Count() != 2 {
+ t.Errorf("get wrong number of tx in the pool")
+ }
+ p.removeTransaction(&txB.ID)
+ if p.IsTransactionInPool(&txB.ID) {
+ t.Errorf("shouldn't find txB in tx pool")
+ }
+
+ p.AddErrCache(&txC.ID)
+ if !p.IsTransactionInErrCache(&txC.ID) {
+ t.Errorf("shouldn find txC in tx err cache")
+ }
+ if !p.HaveTransaction(&txC.ID) {
+ t.Errorf("shouldn find txC in tx err cache")
+ }
+}
+
+func mockCoinbaseTx(serializedSize uint64, amount uint64) *bc.Tx {
+ return legacy.MapTx(&legacy.TxData{
+ SerializedSize: serializedSize,
+ Outputs: []*legacy.TxOutput{
+ legacy.NewTxOutput(*validation.BTMAssetID, amount, []byte{1}, nil),
+ },
+ })
+}