diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index b92d307f9..0a22cf76b 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -611,9 +611,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { bc.topBlock.Store(block) atomic.StoreUint32(&bc.blockHeight, block.Index) updateBlockHeightMetric(block.Index) - for _, tx := range block.Transactions { - bc.memPool.Remove(tx.Hash()) - } + bc.memPool.RemoveStale(bc.isTxStillRelevant) return nil } @@ -1043,6 +1041,35 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e return bc.verifyTxWitnesses(t, block) } +// isTxStillRelevant is a callback for mempool transaction filtering after the +// new block addition. It returns false for transactions already present in the +// chain (added by the new block), transactions using some inputs that are +// already used (double spends) and does witness reverification for non-standard +// contracts. It operates under the assumption that full transaction verification +// was already done so we don't need to check basic things like size, input/output +// correctness, etc. +func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction) bool { + var recheckWitness bool + + if bc.dao.HasTransaction(t.Hash()) { + return false + } + if bc.dao.IsDoubleSpend(t) { + return false + } + for i := range t.Scripts { + if !vm.IsStandardContract(t.Scripts[i].VerificationScript) { + recheckWitness = true + break + } + } + if recheckWitness { + return bc.verifyTxWitnesses(t, nil) == nil + } + return true + +} + // VerifyTx verifies whether a transaction is bonafide or not. Block parameter // is used for easy interop access and can be omitted for transactions that are // not yet added into any block. diff --git a/pkg/core/mempool/mem_pool.go b/pkg/core/mempool/mem_pool.go index f0e9cdc48..7935f9241 100644 --- a/pkg/core/mempool/mem_pool.go +++ b/pkg/core/mempool/mem_pool.go @@ -207,6 +207,25 @@ func (mp *Pool) Remove(hash util.Uint256) { mp.lock.Unlock() } +// RemoveStale filters verified transactions through the given function keeping +// only the transactions for which it returns a true result. It's used to quickly +// drop part of the mempool that is now invalid after the block acceptance. +func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool) { + mp.lock.Lock() + // We expect a lot of changes, so it's easier to allocate a new slice + // rather than move things in an old one. + newVerifiedTxes := make([]*item, 0, mp.capacity) + for _, itm := range mp.verifiedTxes { + if isOK(itm.txn) { + newVerifiedTxes = append(newVerifiedTxes, itm) + } else { + delete(mp.verifiedMap, itm.txn.Hash()) + } + } + mp.verifiedTxes = newVerifiedTxes + mp.lock.Unlock() +} + // RemoveOverCapacity removes transactions with lowest fees until the total number of transactions // in the Pool is within the capacity of the Pool. func (mp *Pool) RemoveOverCapacity() {