From 62601b6fe0e5882a0ab28d0c58cf7656c09a0f06 Mon Sep 17 00:00:00 2001 From: yahtoo Date: Fri, 1 Mar 2019 22:35:50 +0800 Subject: [PATCH] Mempool: add no btm input tx filter (#1605) * Mempool: add no btm input tx filter * Fix review error * Fix review error * Fix review error * Fix review error * Fix review error * Add test case --- netsync/handle.go | 2 +- protocol/tx.go | 12 ++++--- protocol/txpool.go | 27 +++++++++++++-- protocol/txpool_test.go | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 7 deletions(-) diff --git a/netsync/handle.go b/netsync/handle.go index b412bee7..643ae0ab 100644 --- a/netsync/handle.go +++ b/netsync/handle.go @@ -340,7 +340,7 @@ func (sm *SyncManager) handleTransactionMsg(peer *peer, msg *TransactionMessage) return } - if isOrphan, err := sm.chain.ValidateTx(tx); err != nil && !isOrphan { + if isOrphan, err := sm.chain.ValidateTx(tx); err != nil && err != core.ErrDustTx && !isOrphan { sm.peers.addBanScore(peer.ID(), 10, 0, "fail on validate tx transaction") } } diff --git a/protocol/tx.go b/protocol/tx.go index 4797c1fd..c5efb1f1 100644 --- a/protocol/tx.go +++ b/protocol/tx.go @@ -31,10 +31,14 @@ func (c *Chain) ValidateTx(tx *types.Tx) (bool, error) { return false, c.txPool.GetErrCache(&tx.ID) } + if c.txPool.IsDust(tx) { + c.txPool.AddErrCache(&tx.ID, ErrDustTx) + return false, ErrDustTx + } + bh := c.BestBlockHeader() - block := types.MapBlock(&types.Block{BlockHeader: *bh}) - gasStatus, err := validation.ValidateTx(tx.Tx, block) - if gasStatus.GasValid == false { + gasStatus, err := validation.ValidateTx(tx.Tx, types.MapBlock(&types.Block{BlockHeader: *bh})) + if !gasStatus.GasValid { c.txPool.AddErrCache(&tx.ID, err) return false, err } @@ -43,5 +47,5 @@ func (c *Chain) ValidateTx(tx *types.Tx) (bool, error) { log.WithFields(log.Fields{"module": logModule, "tx_id": tx.Tx.ID.String(), "error": err}).Info("transaction status fail") } - return c.txPool.ProcessTransaction(tx, err != nil, block.BlockHeader.Height, gasStatus.BTMValue) + return c.txPool.ProcessTransaction(tx, err != nil, bh.Height, gasStatus.BTMValue) } diff --git a/protocol/txpool.go b/protocol/txpool.go index edb6af03..10fbbc56 100644 --- a/protocol/txpool.go +++ b/protocol/txpool.go @@ -36,6 +36,8 @@ var ( ErrTransactionNotExist = errors.New("transaction are not existed in the mempool") // ErrPoolIsFull indicates the pool is full ErrPoolIsFull = errors.New("transaction pool reach the max number") + // ErrDustTx indicates transaction is dust tx + ErrDustTx = errors.New("transaction is dust tx") ) type TxMsgEvent struct{ TxMsg *TxPoolMsg } @@ -190,8 +192,20 @@ func (tp *TxPool) HaveTransaction(txHash *bc.Hash) bool { return tp.IsTransactionInPool(txHash) || tp.IsTransactionInErrCache(txHash) } -// ProcessTransaction is the main entry for txpool handle new tx -func (tp *TxPool) ProcessTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) { +func isTransactionNoBtmInput(tx *types.Tx) bool { + for _, input := range tx.TxData.Inputs { + if input.AssetID() == *consensus.BTMAssetID { + return false + } + } + return true +} + +func (tp *TxPool) IsDust(tx *types.Tx) bool { + return isTransactionNoBtmInput(tx) +} + +func (tp *TxPool) processTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) { tp.mtx.Lock() defer tp.mtx.Unlock() @@ -219,6 +233,15 @@ func (tp *TxPool) ProcessTransaction(tx *types.Tx, statusFail bool, height, fee return false, nil } +// ProcessTransaction is the main entry for txpool handle new tx, ignore dust tx. +func (tp *TxPool) ProcessTransaction(tx *types.Tx, statusFail bool, height, fee uint64) (bool, error) { + if tp.IsDust(tx) { + log.WithFields(log.Fields{"module": logModule, "tx_id": tx.ID.String()}).Warn("dust tx") + return false, nil + } + return tp.processTransaction(tx, statusFail, height, fee) +} + func (tp *TxPool) addOrphan(txD *TxDesc, requireParents []*bc.Hash) error { if len(tp.orphans) >= maxOrphanNum { return ErrPoolIsFull diff --git a/protocol/txpool_test.go b/protocol/txpool_test.go index 34986acc..7d87648b 100644 --- a/protocol/txpool_test.go +++ b/protocol/txpool_test.go @@ -4,6 +4,8 @@ import ( "testing" "time" + "github.com/davecgh/go-spew/spew" + "github.com/bytom/consensus" "github.com/bytom/database/storage" "github.com/bytom/event" @@ -14,6 +16,7 @@ import ( ) var testTxs = []*types.Tx{ + //tx0 types.NewTx(types.TxData{ SerializedSize: 100, Inputs: []*types.TxInput{ @@ -23,6 +26,7 @@ var testTxs = []*types.Tx{ types.NewTxOutput(*consensus.BTMAssetID, 1, []byte{0x6a}), }, }), + //tx1 types.NewTx(types.TxData{ SerializedSize: 100, Inputs: []*types.TxInput{ @@ -32,6 +36,7 @@ var testTxs = []*types.Tx{ types.NewTxOutput(*consensus.BTMAssetID, 1, []byte{0x6b}), }, }), + //tx2 types.NewTx(types.TxData{ SerializedSize: 150, TimeRange: 0, @@ -44,6 +49,7 @@ var testTxs = []*types.Tx{ types.NewTxOutput(bc.NewAssetID([32]byte{0xa1}), 4, []byte{0x61}), }, }), + //tx3 types.NewTx(types.TxData{ SerializedSize: 100, Inputs: []*types.TxInput{ @@ -54,6 +60,7 @@ var testTxs = []*types.Tx{ types.NewTxOutput(bc.NewAssetID([32]byte{0xa1}), 1, []byte{0x63}), }, }), + //tx4 types.NewTx(types.TxData{ SerializedSize: 100, Inputs: []*types.TxInput{ @@ -559,3 +566,88 @@ func TestRemoveOrphan(t *testing.T) { } } } + +type mockStore1 struct{} + +func (s *mockStore1) BlockExist(hash *bc.Hash) bool { return false } +func (s *mockStore1) GetBlock(*bc.Hash) (*types.Block, error) { return nil, nil } +func (s *mockStore1) GetStoreStatus() *BlockStoreState { return nil } +func (s *mockStore1) GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error) { return nil, nil } +func (s *mockStore1) GetTransactionsUtxo(utxoView *state.UtxoViewpoint, tx []*bc.Tx) error { + for _, hash := range testTxs[2].SpentOutputIDs { + utxoView.Entries[hash] = &storage.UtxoEntry{IsCoinBase: false, Spent: false} + } + return nil +} +func (s *mockStore1) GetUtxo(*bc.Hash) (*storage.UtxoEntry, error) { return nil, nil } +func (s *mockStore1) LoadBlockIndex(uint64) (*state.BlockIndex, error) { return nil, nil } +func (s *mockStore1) SaveBlock(*types.Block, *bc.TransactionStatus) error { return nil } +func (s *mockStore1) SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint) error { return nil } + +func TestProcessTransaction(t *testing.T) { + txPool := &TxPool{ + pool: make(map[bc.Hash]*TxDesc), + utxo: make(map[bc.Hash]*types.Tx), + orphans: make(map[bc.Hash]*orphanTx), + orphansByPrev: make(map[bc.Hash]map[bc.Hash]*orphanTx), + store: &mockStore1{}, + eventDispatcher: event.NewDispatcher(), + } + cases := []struct { + want *TxPool + addTx *TxDesc + }{ + //Dust tx + { + want: &TxPool{}, + addTx: &TxDesc{ + Tx: testTxs[3], + StatusFail: false, + }, + }, + //normal tx + { + want: &TxPool{ + pool: map[bc.Hash]*TxDesc{ + testTxs[2].ID: { + Tx: testTxs[2], + StatusFail: false, + Weight: 150, + }, + }, + utxo: map[bc.Hash]*types.Tx{ + *testTxs[2].ResultIds[0]: testTxs[2], + *testTxs[2].ResultIds[1]: testTxs[2], + }, + }, + addTx: &TxDesc{ + Tx: testTxs[2], + StatusFail: false, + }, + }, + } + + for i, c := range cases { + txPool.ProcessTransaction(c.addTx.Tx, c.addTx.StatusFail, 0, 0) + for _, txD := range txPool.pool { + txD.Added = time.Time{} + } + for _, txD := range txPool.orphans { + txD.Added = time.Time{} + txD.expiration = time.Time{} + } + + if !testutil.DeepEqual(txPool.pool, c.want.pool) { + t.Errorf("case %d: test ProcessTransaction pool mismatch got %s want %s", i, spew.Sdump(txPool.pool), spew.Sdump(c.want.pool)) + } + if !testutil.DeepEqual(txPool.utxo, c.want.utxo) { + t.Errorf("case %d: test ProcessTransaction utxo mismatch got %s want %s", i, spew.Sdump(txPool.utxo), spew.Sdump(c.want.utxo)) + } + if !testutil.DeepEqual(txPool.orphans, c.want.orphans) { + t.Errorf("case %d: test ProcessTransaction orphans mismatch got %s want %s", i, spew.Sdump(txPool.orphans), spew.Sdump(c.want.orphans)) + } + if !testutil.DeepEqual(txPool.orphansByPrev, c.want.orphansByPrev) { + t.Errorf("case %d: test ProcessTransaction orphansByPrev mismatch got %s want %s", i, spew.Sdump(txPool.orphansByPrev), spew.Sdump(c.want.orphansByPrev)) + } + } +} -- 2.11.0