Repository: kaspanet/kaspad
Pull request #981 (2020-10-29 11:23:06)

[NOD-1423] Refactor the miner and mempool
This is based on #972


Patch:
diff --git a/domain/consensus/utils/estimatedsize/transaction_estimated_size.go b/domain/consensus/utils/estimatedsize/transaction_estimated_size.go
index 5977db97..8ebf8b69 100644
--- a/domain/consensus/utils/estimatedsize/transaction_estimated_size.go
+++ b/domain/consensus/utils/estimatedsize/transaction_estimated_size.go
@@ -22,7 +22,7 @@ func TransactionEstimatedSerializedSize(tx *externalapi.DomainTransaction) uint6
 
 	size += 8 // number of outputs (uint64)
 	for _, output := range tx.Outputs {
-		size += transactionOutputEstimatedSerializedSize(output)
+		size += TransactionOutputEstimatedSerializedSize(output)
 	}
 
 	size += 8 // lock time (uint64)
@@ -54,7 +54,8 @@ func outpointEstimatedSerializedSize() uint64 {
 	return size
 }
 
-func transactionOutputEstimatedSerializedSize(output *externalapi.DomainTransactionOutput) uint64 {
+// TransactionOutputEstimatedSerializedSize is the same as TransactionEstimatedSerializedSize but for outputs only
+func TransactionOutputEstimatedSerializedSize(output *externalapi.DomainTransactionOutput) uint64 {
 	size := uint64(0)
 	size += 8 // value (uint64)
 	size += 8 // length of script public key (uint64)
diff --git a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go
index d10b1ddf..be0f1eec 100644
--- a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go
+++ b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go
@@ -3,24 +3,167 @@ package blocktemplatebuilder
 import (
 	"github.com/kaspanet/kaspad/domain/consensus"
 	consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
 	miningmanagerapi "github.com/kaspanet/kaspad/domain/miningmanager/model"
+	"github.com/kaspanet/kaspad/util"
+	"github.com/pkg/errors"
+	"math"
+	"sort"
 )
 
+type candidateTx struct {
+	*consensusexternalapi.DomainTransaction
+	txValue  float64
+	gasLimit uint64
+
+	p     float64
+	start float64
+	end   float64
+
+	isMarkedForDeletion bool
+}
+
 // blockTemplateBuilder creates block templates for a miner to consume
 type blockTemplateBuilder struct {
-	consensus *consensus.Consensus
+	consensus consensus.Consensus
 	mempool   miningmanagerapi.Mempool
+	policy    policy
 }
 
 // New creates a new blockTemplateBuilder
-func New(consensus *consensus.Consensus, mempool miningmanagerapi.Mempool) miningmanagerapi.BlockTemplateBuilder {
+func New(consensus consensus.Consensus, mempool miningmanagerapi.Mempool, blockMaxMass uint64) miningmanagerapi.BlockTemplateBuilder {
 	return &blockTemplateBuilder{
 		consensus: consensus,
 		mempool:   mempool,
+		policy:    policy{BlockMaxMass: blockMaxMass},
 	}
 }
 
 // GetBlockTemplate creates a block template for a miner to consume
+// GetBlockTemplate returns a new block template that is ready to be solved
+// using the transactions from the passed transaction source pool and a coinbase
+// that either pays to the passed address if it is not nil, or a coinbase that
+// is redeemable by anyone if the passed address is nil. The nil address
+// functionality is useful since there are cases such as the getblocktemplate
+// RPC where external mining software is responsible for creating their own
+// coinbase which will replace the one generated for the block template. Thus
+// the need to have configured address can be avoided.
+//
+// The transactions selected and included are prioritized according to several
+// factors. First, each transaction has a priority calculated based on its
+// value, age of inputs, and size. Transactions which consist of larger
+// amounts, older inputs, and small sizes have the highest priority. Second, a
+// fee per kilobyte is calculated for each transaction. Transactions with a
+// higher fee per kilobyte are preferred. Finally, the block generation related
+// policy settings are all taken into account.
+//
+// Transactions which only spend outputs from other transactions already in the
+// block DAG are immediately added to a priority queue which either
+// prioritizes based on the priority (then fee per kilobyte) or the fee per
+// kilobyte (then priority) depending on whether or not the BlockPrioritySize
+// policy setting allots space for high-priority transactions. Transactions
+// which spend outputs from other transactions in the source pool are added to a
+// dependency map so they can be added to the priority queue once the
+// transactions they depend on have been included.
+//
+// Once the high-priority area (if configured) has been filled with
+// transactions, or the priority falls below what is considered high-priority,
+// the priority queue is updated to prioritize by fees per kilobyte (then
+// priority).
+//
+// When the fees per kilobyte drop below the TxMinFreeFee policy setting, the
+// transaction will be skipped unless the BlockMinSize policy setting is
+// nonzero, in which case the block will be filled with the low-fee/free
+// transactions until the block size reaches that minimum size.
+//
+// Any transactions which would cause the block to exceed the BlockMaxMass
+// policy setting, exceed the maximum allowed signature operations per block, or
+// otherwise cause the block to be invalid are skipped.
+//
+// Given the above, a block generated by this function is of the following form:
+//
+//   -----------------------------------  --  --
+//  |      Coinbase Transaction         |   |   |
+//  |-----------------------------------|   |   |
+//  |                                   |   |   | ----- policy.BlockPrioritySize
+//  |   High-priority Transactions      |   |   |
+//  |                                   |   |   |
+//  |-----------------------------------|   | --
+//  |                                   |   |
+//  |                                   |   |
+//  |                                   |   |--- policy.BlockMaxMass
+//  |  Transactions prioritized by fee  |   |
+//  |  until <= policy.TxMinFreeFee     |   |
+//  |                                   |   |
+//  |                                   |   |
+//  |                                   |   |
+//  |-----------------------------------|   |
+//  |  Low-fee/Non high-priority (free) |   |
+//  |  transactions (while block size   |   |
+//  |  <= policy.BlockMinSize)          |   |
+//   -----------------------------------  --
+
 func (btb *blockTemplateBuilder) GetBlockTemplate(coinbaseData *consensusexternalapi.DomainCoinbaseData) *consensusexternalapi.DomainBlock {
-	return nil
+	mempoolTransactions := btb.mempool.Transactions()
+	candidateTxs := make([]*candidateTx, 0, len(mempoolTransactions))
+	for _, tx := range mempoolTransactions {
+		// Calculate the tx value
+		gasLimit := uint64(0)
+		if !subnetworks.IsBuiltInOrNative(tx.SubnetworkID) {
+			panic("We currently don't support non native subnetworks")
+		}
+		candidateTxs = append(candidateTxs, &candidateTx{
+			DomainTransaction: tx,
+			txValue:           btb.calcTxValue(tx),
+			gasLimit:          gasLimit,
+		})
+	}
+
+	// Sort the candidate txs by subnetworkID.
+	sort.Slice(candidateTxs, func(i, j int) bool {
+		return subnetworks.Less(candidateTxs[i].SubnetworkID, candidateTxs[j].SubnetworkID)
+	})
+
+	log.Debugf("Considering %d transactions for inclusion to new block",
+		len(candidateTxs))
+
+	blockTxs := btb.selectTransactions(candidateTxs)
+	blk, err := btb.consensus.BuildBlock(coinbaseData, blockTxs.selectedTxs)
+
+	invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
+	if errors.As(err, &invalidTxsErr) {
+		log.Criticalf("consensus.BuildBlock returned invalid txs in GetBlockTemplate: %s", err)
+		invalidTxs := make([]*consensusexternalapi.DomainTransaction, 0, len(invalidTxsErr.InvalidTransactions))
+		for _, tx := range invalidTxsErr.InvalidTransactions {
+			invalidTxs = append(invalidTxs, tx.Transaction)
+		}
+		btb.mempool.RemoveTransactions(invalidTxs)
+		// We can call this recursively without worry because this should almost never happen
+		return btb.GetBlockTemplate(coinbaseData)
+	} else if err != nil {
+		log.Errorf("GetBlockTemplate: Failed building block: %s", err)
+		return nil
+	}
+
+	log.Debugf("Created new block template (%d transactions, %d in fees, %d mass, target difficulty %064x)",
+		len(blk.Transactions), blockTxs.totalFees, blockTxs.totalMass, util.CompactToBig(blk.Header.Bits))
+
+	return blk
+}
+
+// calcTxValue calculates a value to be used in transaction selection.
+// The higher the number the more likely it is that the transaction will be
+// included in the block.
+func (btb *blockTemplateBuilder) calcTxValue(tx *consensusexternalapi.DomainTransaction) float64 {
+	massLimit := btb.policy.BlockMaxMass
+
+	mass := tx.Mass
+	fee := tx.Fee
+	if subnetworks.IsBuiltInOrNative(tx.SubnetworkID) {
+		return float64(fee) / (float64(mass) / float64(massLimit))
+	}
+	// TODO: Replace with real gas once implemented
+	gasLimit := uint64(math.MaxUint64)
+	return float64(fee) / (float64(mass)/float64(massLimit) + float64(tx.Gas)/float64(gasLimit))
 }
diff --git a/domain/miningmanager/blocktemplatebuilder/log.go b/domain/miningmanager/blocktemplatebuilder/log.go
new file mode 100644
index 00000000..425d2192
--- /dev/null
+++ b/domain/miningmanager/blocktemplatebuilder/log.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2016 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package blocktemplatebuilder
+
+import (
+	"github.com/kaspanet/kaspad/infrastructure/logger"
+)
+
+var log, _ = logger.Get(logger.SubsystemTags.MINR)
diff --git a/domain/miningmanager/blocktemplatebuilder/policy.go b/domain/miningmanager/blocktemplatebuilder/policy.go
new file mode 100644
index 00000000..58980f8e
--- /dev/null
+++ b/domain/miningmanager/blocktemplatebuilder/policy.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2014-2016 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package blocktemplatebuilder
+
+// policy houses the policy (configuration parameters) which is used to control
+// the generation of block templates. See the documentation for
+// NewBlockTemplate for more details on each of these parameters are used.
+type policy struct {
+	// BlockMaxMass is the maximum block mass to be used when generating a
+	// block template.
+	BlockMaxMass uint64
+}
diff --git a/domain/miningmanager/blocktemplatebuilder/txselection.go b/domain/miningmanager/blocktemplatebuilder/txselection.go
new file mode 100644
index 00000000..3b3851de
--- /dev/null
+++ b/domain/miningmanager/blocktemplatebuilder/txselection.go
@@ -0,0 +1,212 @@
+package blocktemplatebuilder
+
+import (
+	consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/hashserialization"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
+	"math"
+	"math/rand"
+	"sort"
+)
+
+const (
+	// alpha is a coefficient that defines how uniform the distribution of
+	// candidate transactions should be. A smaller alpha makes the distribution
+	// more uniform. Alpha is used when determining a candidate transaction's
+	// initial p value.
+	alpha = 3
+
+	// rebalanceThreshold is the percentage of candidate transactions under which
+	// we don't rebalance. Rebalancing is a heavy operation so we prefer to avoid
+	// rebalancing very often. On the other hand, if we don't rebalance often enough
+	// we risk having too many collisions.
+	// The value is derived from the max probability of collision. That is to say,
+	// if rebalanceThreshold is 0.95, there's a 1-in-20 chance of collision.
+	// See selectTxs for further details.
+	rebalanceThreshold = 0.95
+)
+
+type selectedTransactions struct {
+	selectedTxs []*consensusexternalapi.DomainTransaction
+	txMasses    []uint64
+	txFees      []uint64
+	totalMass   uint64
+	totalFees   uint64
+}
+
+// selectTransactions implements a probabilistic transaction selection algorithm.
+// The algorithm, roughly, is as follows:
+// 1. We assign a probability to each transaction equal to:
+//    (candidateTx.Value^alpha) / Σ(tx.Value^alpha)
+//    Where the sum of the probabilities of all txs is 1.
+// 2. We draw a random number in [0,1) and select a transaction accordingly.
+// 3. If it's valid, add it to the selectedTxs and remove it from the candidates.
+// 4. Continue iterating the above until we have either selected all
+//    available transactions or ran out of gas/block space.
+//
+// Note that we make two optimizations here:
+// * Draw a number in [0,Σ(tx.Value^alpha)) to avoid normalization
+// * Instead of removing a candidate after each iteration, mark it for deletion.
+//   Once the sum of probabilities of marked transactions is greater than
+//   rebalanceThreshold percent of the sum of probabilities of all transactions,
+//   rebalance.
+
+// selectTransactions loops over the candidate transactions
+// and appends the ones that will be included in the next block into
+// txsForBlockTemplates.
+// See selectTxs for further details.
+func (btb *blockTemplateBuilder) selectTransactions(candidateTxs []*candidateTx) selectedTransactions {
+	txsForBlockTemplate := selectedTransactions{
+		selectedTxs: make([]*consensusexternalapi.DomainTransaction, 0, len(candidateTxs)),
+		txMasses:    make([]uint64, 0, len(candidateTxs)),
+		txFees:      make([]uint64, 0, len(candidateTxs)),
+		totalMass:   0,
+		totalFees:   0,
+	}
+	usedCount, usedP := 0, 0.0
+	candidateTxs, totalP := rebalanceCandidates(candidateTxs, true)
+	gasUsageMap := make(map[consensusexternalapi.DomainSubnetworkID]uint64)
+
+	markCandidateTxForDeletion := func(candidateTx *candidateTx) {
+		candidateTx.isMarkedForDeletion = true
+		usedCount++
+		usedP += candidateTx.p
+	}
+
+	selectedTxs := make([]*candidateTx, 0)
+	for len(candidateTxs)-usedCount > 0 {
+		// Rebalance the candidates if it's required
+		if usedP >= rebalanceThreshold*totalP {
+			candidateTxs, totalP = rebalanceCandidates(candidateTxs, false)
+			usedCount, usedP = 0, 0.0
+
+			// Break if we now ran out of transactions
+			if len(candidateTxs) == 0 {
+				break
+			}
+		}
+
+		// Select a candidate tx at random
+		r := rand.Float64()
+		r *= totalP
+		selectedTx := findTx(candidateTxs, r)
+
+		// If isMarkedForDeletion is set, it means we got a collision.
+		// Ignore and select another Tx.
+		if selectedTx.isMarkedForDeletion {
+			continue
+		}
+		tx := selectedTx.DomainTransaction
+
+		// Enforce maximum transaction mass per block. Also check
+		// for overflow.
+		if txsForBlockTemplate.totalMass+selectedTx.Mass < txsForBlockTemplate.totalMass ||
+			txsForBlockTemplate.totalMass+selectedTx.Mass > btb.policy.BlockMaxMass {
+			log.Tracef("Tx %s would exceed the max block mass. "+
+				"As such, stopping.", hashserialization.TransactionID(tx))
+			break
+		}
+
+		// Enforce maximum gas per subnetwork per block. Also check
+		// for overflow.
+		if !subnetworks.IsBuiltInOrNative(tx.SubnetworkID) {
+			subnetworkID := tx.SubnetworkID
+			gasUsage, ok := gasUsageMap[subnetworkID]
+			if !ok {
+				gasUsage = 0
+			}
+			txGas := tx.Gas
+			if gasUsage+txGas < gasUsage ||
+				gasUsage+txGas > selectedTx.gasLimit {
+				log.Tracef("Tx %s would exceed the gas limit in "+
+					"subnetwork %s. Removing all remaining txs from this "+
+					"subnetwork.",
+					hashserialization.TransactionID(tx), subnetworkID)
+				for _, candidateTx := range candidateTxs {
+					// candidateTxs are ordered by subnetwork, so we can safely assume
+					// that transactions after subnetworkID will not be relevant.
+					if subnetworks.Less(subnetworkID, candidateTx.SubnetworkID) {
+						break
+					}
+
+					if candidateTx.SubnetworkID == subnetworkID {
+						markCandidateTxForDeletion(candidateTx)
+					}
+				}
+				continue
+			}
+			gasUsageMap[subnetworkID] = gasUsage + txGas
+		}
+
+		// Add the transaction to the result, increment counters, and
+		// save the masses, fees, and signature operation counts to the
+		// result.
+		selectedTxs = append(selectedTxs, selectedTx)
+		txsForBlockTemplate.totalMass += selectedTx.Mass
+		txsForBlockTemplate.totalFees += selectedTx.Fee
+
+		log.Tracef("Adding tx %s (feePerMegaGram %d)",
+			hashserialization.TransactionID(tx), selectedTx.Fee*1e6/selectedTx.Mass)
+
+		markCandidateTxForDeletion(selectedTx)
+	}
+
+	sort.Slice(selectedTxs, func(i, j int) bool {
+		return subnetworks.Less(selectedTxs[i].SubnetworkID, selectedTxs[j].SubnetworkID)
+	})
+	for _, selectedTx := range selectedTxs {
+		txsForBlockTemplate.selectedTxs = append(txsForBlockTemplate.selectedTxs, selectedTx.DomainTransaction)
+		txsForBlockTemplate.txMasses = append(txsForBlockTemplate.txMasses, selectedTx.Mass)
+		txsForBlockTemplate.txFees = append(txsForBlockTemplate.txFees, selectedTx.Fee)
+	}
+	return txsForBlockTemplate
+}
+
+func rebalanceCandidates(oldCandidateTxs []*candidateTx, isFirstRun bool) (
+	candidateTxs []*candidateTx, totalP float64) {
+
+	totalP = 0.0
+
+	candidateTxs = make([]*candidateTx, 0, len(oldCandidateTxs))
+	for _, candidateTx := range oldCandidateTxs {
+		if candidateTx.isMarkedForDeletion {
+			continue
+		}
+
+		candidateTxs = append(candidateTxs, candidateTx)
+	}
+
+	for _, candidateTx := range candidateTxs {
+		if isFirstRun {
+			candidateTx.p = math.Pow(candidateTx.txValue, alpha)
+		}
+		candidateTx.start = totalP
+		candidateTx.end = totalP + candidateTx.p
+
+		totalP += candidateTx.p
+	}
+	return
+}
+
+// findTx finds the candidateTx in whose range r falls.
+// For example, if we have candidateTxs with starts and ends:
+// * tx1: start 0,   end 100
+// * tx2: start 100, end 105
+// * tx3: start 105, end 2000
+// And r=102, then findTx will return tx2.
+func findTx(candidateTxs []*candidateTx, r float64) *candidateTx {
+	min := 0
+	max := len(candidateTxs) - 1
+	for {
+		i := (min + max) / 2
+		candidateTx := candidateTxs[i]
+		if candidateTx.end < r {
+			min = i + 1
+			continue
+		} else if candidateTx.start > r {
+			max = i - 1
+			continue
+		}
+		return candidateTx
+	}
+}
diff --git a/domain/miningmanager/factory.go b/domain/miningmanager/factory.go
index 261f0f0c..4590e46c 100644
--- a/domain/miningmanager/factory.go
+++ b/domain/miningmanager/factory.go
@@ -8,15 +8,15 @@ import (
 
 // Factory instantiates new mining managers
 type Factory interface {
-	NewMiningManager(consensus *consensus.Consensus) MiningManager
+	NewMiningManager(consensus consensus.Consensus, blockMaxMass uint64) MiningManager
 }
 
 type factory struct{}
 
 // NewMiningManager instantiate a new mining manager
-func (f *factory) NewMiningManager(consensus *consensus.Consensus) MiningManager {
+func (f *factory) NewMiningManager(consensus consensus.Consensus, blockMaxMass uint64) MiningManager {
 	mempool := mempoolpkg.New(consensus)
-	blockTemplateBuilder := blocktemplatebuilder.New(consensus, mempool)
+	blockTemplateBuilder := blocktemplatebuilder.New(consensus, mempool, blockMaxMass)
 
 	return &miningManager{
 		mempool:              mempool,
diff --git a/domain/miningmanager/mempool/README.md b/domain/miningmanager/mempool/README.md
new file mode 100644
index 00000000..aed25cbd
--- /dev/null
+++ b/domain/miningmanager/mempool/README.md
@@ -0,0 +1,72 @@
+mempool
+=======
+
+[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](https://choosealicense.com/licenses/isc/)
+[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/kaspanet/kaspad/mempool)
+
+Package mempool provides a policy-enforced pool of unmined kaspa transactions.
+
+A key responsbility of the kaspa network is mining user-generated transactions
+into blocks. In order to facilitate this, the mining process relies on having a
+readily-available source of transactions to include in a block that is being
+solved.
+
+At a high level, this package satisfies that requirement by providing an
+in-memory pool of fully validated transactions that can also optionally be
+further filtered based upon a configurable policy.
+
+One of the policy configuration options controls whether or not "standard"
+transactions are accepted. In essence, a "standard" transaction is one that
+satisfies a fairly strict set of requirements that are largely intended to help
+provide fair use of the system to all users. It is important to note that what
+is considered a "standard" transaction changes over time. For some insight, at
+the time of this writing, an example of _some_ of the criteria that are required
+for a transaction to be considered standard are that it is of the most-recently
+supported version, finalized, does not exceed a specific size, and only consists
+of specific script forms.
+
+Since this package does not deal with other kaspa specifics such as network
+communication and transaction relay, it returns a list of transactions that were
+accepted which gives the caller a high level of flexibility in how they want to
+proceed. Typically, this will involve things such as relaying the transactions
+to other peers on the network and notifying the mining process that new
+transactions are available.
+
+This package has intentionally been designed so it can be used as a standalone
+package for any projects needing the ability create an in-memory pool of bitcoin
+transactions that are not only valid by consensus rules, but also adhere to a
+configurable policy.
+
+## Feature Overview
+
+The following is a quick overview of the major features. It is not intended to
+be an exhaustive list.
+
+- Maintain a pool of fully validated transactions
+  - Reject non-fully-spent duplicate transactions
+  - Reject coinbase transactions
+  - Reject double spends (both from the DAG and other transactions in pool)
+  - Reject invalid transactions according to the network consensus rules
+  - Full script execution and validation with signature cache support
+  - Individual transaction query support
+- Orphan transaction support (transactions that spend from unknown outputs)
+  - Configurable limits (see transaction acceptance policy)
+  - Automatic addition of orphan transactions that are no longer orphans as new
+    transactions are added to the pool
+  - Individual orphan transaction query support
+- Configurable transaction acceptance policy
+  - Option to accept or reject standard transactions
+  - Option to accept or reject transactions based on priority calculations
+  - Rate limiting of low-fee and free transactions
+  - Non-zero fee threshold
+  - Max signature operations per transaction
+  - Max orphan transaction size
+  - Max number of orphan transactions allowed
+- Additional metadata tracking for each transaction
+  - Timestamp when the transaction was added to the pool
+  - Most recent block height when the transaction was added to the pool
+  - The fee the transaction pays
+  - The starting priority for the transaction
+- Manual control of transaction removal
+  - Recursive removal of all dependent transactions
+
diff --git a/domain/miningmanager/mempool/doc.go b/domain/miningmanager/mempool/doc.go
new file mode 100644
index 00000000..c0dbd5d2
--- /dev/null
+++ b/domain/miningmanager/mempool/doc.go
@@ -0,0 +1,73 @@
+/*
+Package mempool provides a policy-enforced pool of unmined kaspa transactions.
+
+A key responsbility of the kaspa network is mining user-generated transactions
+into blocks. In order to facilitate this, the mining process relies on having a
+readily-available source of transactions to include in a block that is being
+solved.
+
+At a high level, this package satisfies that requirement by providing an
+in-memory pool of fully validated transactions that can also optionally be
+further filtered based upon a configurable policy.
+
+One of the policy configuration options controls whether or not "standard"
+transactions are accepted. In essence, a "standard" transaction is one that
+satisfies a fairly strict set of requirements that are largely intended to help
+provide fair use of the system to all users. It is important to note that what
+is considered a "standard" transaction changes over time. For some insight, at
+the time of this writing, an example of SOME of the criteria that are required
+for a transaction to be considered standard are that it is of the most-recently
+supported version, finalized, does not exceed a specific size, and only consists
+of specific script forms.
+
+Since this package does not deal with other kaspa specifics such as network
+communication and transaction relay, it returns a list of transactions that were
+accepted which gives the caller a high level of flexibility in how they want to
+proceed. Typically, this will involve things such as relaying the transactions
+to other peers on the network and notifying the mining process that new
+transactions are available.
+
+Feature Overview
+
+The following is a quick overview of the major features. It is not intended to
+be an exhaustive list.
+
+ - Maintain a pool of fully validated transactions
+   - Reject non-fully-spent duplicate transactions
+   - Reject coinbase transactions
+   - Reject double spends (both from the DAG and other transactions in pool)
+   - Reject invalid transactions according to the network consensus rules
+   - Full script execution and validation with signature cache support
+   - Individual transaction query support
+ - Orphan transaction support (transactions that spend from unknown outputs)
+   - Configurable limits (see transaction acceptance policy)
+   - Automatic addition of orphan transactions that are no longer orphans as new
+     transactions are added to the pool
+   - Individual orphan transaction query support
+ - Configurable transaction acceptance policy
+   - Option to accept or reject standard transactions
+   - Option to accept or reject transactions based on priority calculations
+   - Max signature operations per transaction
+   - Max number of orphan transactions allowed
+ - Additional metadata tracking for each transaction
+   - Timestamp when the transaction was added to the pool
+   - The fee the transaction pays
+   - The starting priority for the transaction
+ - Manual control of transaction removal
+   - Recursive removal of all dependent transactions
+
+Errors
+
+Errors returned by this package are either the raw errors provided by underlying
+calls or of type mempool.RuleError. Since there are two classes of rules
+(mempool acceptance rules and blockDAG (consensus) acceptance rules), the
+mempool.RuleError type contains a single Err field which will, in turn, either
+be a mempool.TxRuleError or a blockdag.RuleError. The first indicates a
+violation of mempool acceptance rules while the latter indicates a violation of
+consensus acceptance rules. This allows the caller to easily differentiate
+between unexpected errors, such as database errors, versus errors due to rule
+violations through type assertions. In addition, callers can programmatically
+determine the specific rule violation by type asserting the Err field to one of
+the aforementioned types and examining their underlying ErrorCode field.
+*/
+package mempool
diff --git a/domain/miningmanager/mempool/error.go b/domain/miningmanager/mempool/error.go
new file mode 100644
index 00000000..2a09d464
--- /dev/null
+++ b/domain/miningmanager/mempool/error.go
@@ -0,0 +1,150 @@
+// Copyright (c) 2014-2016 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package mempool
+
+import (
+	"fmt"
+	"github.com/kaspanet/kaspad/domain/blockdag"
+	"github.com/pkg/errors"
+)
+
+// RuleError identifies a rule violation. It is used to indicate that
+// processing of a transaction failed due to one of the many validation
+// rules. The caller can use type assertions to determine if a failure was
+// specifically due to a rule violation and use the Err field to access the
+// underlying error, which will be either a TxRuleError or a
+// blockdag.RuleError.
+type RuleError struct {
+	Err error
+}
+
+// Error satisfies the error interface and prints human-readable errors.
+func (e RuleError) Error() string {
+	if e.Err == nil {
+		return "<nil>"
+	}
+	return e.Err.Error()
+}
+
+// RejectCode represents a numeric value by which a remote peer indicates
+// why a message was rejected.
+type RejectCode uint8
+
+// These constants define the various supported reject codes.
+const (
+	RejectMalformed       RejectCode = 0x01
+	RejectInvalid         RejectCode = 0x10
+	RejectObsolete        RejectCode = 0x11
+	RejectDuplicate       RejectCode = 0x12
+	RejectNotRequested    RejectCode = 0x13
+	RejectNonstandard     RejectCode = 0x40
+	RejectDust            RejectCode = 0x41
+	RejectInsufficientFee RejectCode = 0x42
+	RejectFinality        RejectCode = 0x43
+	RejectDifficulty      RejectCode = 0x44
+)
+
+// Map of reject codes back strings for pretty printing.
+var rejectCodeStrings = map[RejectCode]string{
+	RejectMalformed:       "REJECT_MALFORMED",
+	RejectInvalid:         "REJECT_INVALID",
+	RejectObsolete:        "REJECT_OBSOLETE",
+	RejectDuplicate:       "REJECT_DUPLICATE",
+	RejectNonstandard:     "REJECT_NONSTANDARD",
+	RejectDust:            "REJECT_DUST",
+	RejectInsufficientFee: "REJECT_INSUFFICIENTFEE",
+	RejectFinality:        "REJECT_FINALITY",
+	RejectDifficulty:      "REJECT_DIFFICULTY",
+	RejectNotRequested:    "REJECT_NOTREQUESTED",
+}
+
+// String returns the RejectCode in human-readable form.
+func (code RejectCode) String() string {
+	if s, ok := rejectCodeStrings[code]; ok {
+		return s
+	}
+
+	return fmt.Sprintf("Unknown RejectCode (%d)", uint8(code))
+}
+
+// TxRuleError identifies a rule violation. It is used to indicate that
+// processing of a transaction failed due to one of the many validation
+// rules. The caller can use type assertions to determine if a failure was
+// specifically due to a rule violation and access the ErrorCode field to
+// ascertain the specific reason for the rule violation.
+type TxRuleError struct {
+	RejectCode  RejectCode // The code to send with reject messages
+	Description string     // Human readable description of the issue
+}
+
+// Error satisfies the error interface and prints human-readable errors.
+func (e TxRuleError) Error() string {
+	return e.Description
+}
+
+// txRuleError creates an underlying TxRuleError with the given a set of
+// arguments and returns a RuleError that encapsulates it.
+func txRuleError(c RejectCode, desc string) RuleError {
+	return RuleError{
+		Err: TxRuleError{RejectCode: c, Description: desc},
+	}
+}
+
+// dagRuleError returns a RuleError that encapsulates the given
+// blockdag.RuleError.
+func dagRuleError(dagErr blockdag.RuleError) RuleError {
+	return RuleError{
+		Err: dagErr,
+	}
+}
+
+// extractRejectCode attempts to return a relevant reject code for a given error
+// by examining the error for known types. It will return true if a code
+// was successfully extracted.
+func extractRejectCode(err error) (RejectCode, bool) {
+	// Pull the underlying error out of a RuleError.
+	var ruleErr RuleError
+	if ok := errors.As(err, &ruleErr); ok {
+		err = ruleErr.Err
+	}
+
+	var dagRuleErr blockdag.RuleError
+	if errors.As(err, &dagRuleErr) {
+		// Convert the DAG error to a reject code.
+		var code RejectCode
+		switch dagRuleErr.ErrorCode {
+		// Rejected due to duplicate.
+		case blockdag.ErrDuplicateBlock:
+			code = RejectDuplicate
+
+		// Rejected due to obsolete version.
+		case blockdag.ErrBlockVersionTooOld:
+			code = RejectObsolete
+
+		// Rejected due to being earlier than the last finality point.
+		case blockdag.ErrFinalityPointTimeTooOld:
+			code = RejectFinality
+		case blockdag.ErrDifficultyTooLow:
+			code = RejectDifficulty
+
+		// Everything else is due to the block or transaction being invalid.
+		default:
+			code = RejectInvalid
+		}
+
+		return code, true
+	}
+
+	var trErr TxRuleError
+	if errors.As(err, &trErr) {
+		return trErr.RejectCode, true
+	}
+
+	if err == nil {
+		return RejectInvalid, false
+	}
+
+	return RejectInvalid, false
+}
diff --git a/domain/miningmanager/mempool/log.go b/domain/miningmanager/mempool/log.go
new file mode 100644
index 00000000..ce5fd746
--- /dev/null
+++ b/domain/miningmanager/mempool/log.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2013-2016 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package mempool
+
+import (
+	"github.com/kaspanet/kaspad/infrastructure/logger"
+)
+
+var log, _ = logger.Get(logger.SubsystemTags.TXMP)
diff --git a/domain/miningmanager/mempool/mempool.go b/domain/miningmanager/mempool/mempool.go
index 93bc4038..82935759 100644
--- a/domain/miningmanager/mempool/mempool.go
+++ b/domain/miningmanager/mempool/mempool.go
@@ -1,36 +1,895 @@
+// Copyright (c) 2013-2016 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
 package mempool
 
 import (
+	"container/list"
+	"fmt"
 	"github.com/kaspanet/kaspad/domain/consensus"
 	consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/hashserialization"
 	miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model"
+	"github.com/kaspanet/kaspad/infrastructure/logger"
+	"github.com/kaspanet/kaspad/util"
+	"github.com/kaspanet/kaspad/util/mstime"
+	"github.com/pkg/errors"
+	"sync"
+	"time"
+)
+
+const (
+	// orphanTTL is the maximum amount of time an orphan is allowed to
+	// stay in the orphan pool before it expires and is evicted during the
+	// next scan.
+	orphanTTL = time.Minute * 15
+
+	// orphanExpireScanInterval is the minimum amount of time in between
+	// scans of the orphan pool to evict expired transactions.
+	orphanExpireScanInterval = time.Minute * 5
 )
 
-// mempool maintains a set of known transactions that
-// are intended to be mined into new blocks
+// policy houses the policy (configuration parameters) which is used to
+// control the mempool.
+type policy struct {
+	// MaxTxVersion is the transaction version that the mempool should
+	// accept. All transactions above this version are rejected as
+	// non-standard.
+	MaxTxVersion int32
+
+	// AcceptNonStd defines whether to accept non-standard transactions. If
+	// true, non-standard transactions will be accepted into the mempool.
+	// Otherwise, all non-standard transactions will be rejected.
+	AcceptNonStd bool
+
+	// MaxOrphanTxs is the maximum number of orphan transactions
+	// that can be queued.
+	MaxOrphanTxs int
+
+	// MaxOrphanTxSize is the maximum size allowed for orphan transactions.
+	// This helps prevent memory exhaustion attacks from sending a lot of
+	// of big orphans.
+	MaxOrphanTxSize int
+
+	// MinRelayTxFee defines the minimum transaction fee in KAS/kB to be
+	// considered a non-zero fee.
+	MinRelayTxFee util.Amount
+}
+
+// mempool is used as a source of transactions that need to be mined into blocks
+// and relayed to other peers. It is safe for concurrent access from multiple
+// peers.
 type mempool struct {
-	consensus *consensus.Consensus
+	pool map[consensusexternalapi.DomainTransactionID]*txDescriptor
+
+	chainedTransactions                  map[consensusexternalapi.DomainTransactionID]*txDescriptor
+	chainedTransactionByPreviousOutpoint map[consensusexternalapi.DomainOutpoint]*txDescriptor
+
+	orphans       map[consensusexternalapi.DomainTransactionID]*orphanTx
+	orphansByPrev map[consensusexternalapi.DomainOutpoint]map[consensusexternalapi.DomainTransactionID]*consensusexternalapi.DomainTransaction
+
+	mempoolUTXOSet *mempoolUTXOSet
+	consensus      consensus.Consensus
+
+	// nextExpireScan is the time after which the orphan pool will be
+	// scanned in order to evict orphans. This is NOT a hard deadline as
+	// the scan will only run when an orphan is added to the pool as opposed
+	// to on an unconditional timer.
+	nextExpireScan mstime.Time
+
+	mtx    sync.RWMutex
+	policy policy
 }
 
-// New creates a new mempool
-func New(consensus *consensus.Consensus) miningmanagermodel.Mempool {
+// New returns a new memory pool for validating and storing standalone
+// transactions until they are mined into a block.
+func New(consensus consensus.Consensus) miningmanagermodel.Mempool {
+	policy := policy{
+		MaxTxVersion:    0,
+		AcceptNonStd:    false,
+		MaxOrphanTxs:    5,
+		MaxOrphanTxSize: 100000,
+		MinRelayTxFee:   1000, // 1 sompi per byte
+	}
 	return &mempool{
-		consensus: consensus,
+		mtx:                                  sync.RWMutex{},
+		policy:                               policy,
+		pool:                                 make(map[consensusexternalapi.DomainTransactionID]*txDescriptor),
+		chainedTransactions:                  make(map[consensusexternalapi.DomainTransactionID]*txDescriptor),
+		chainedTransactionByPreviousOutpoint: make(map[consensusexternalapi.DomainOutpoint]*txDescriptor),
+		orphans:                              make(map[consensusexternalapi.DomainTransactionID]*orphanTx),
+		orphansByPrev:                        make(map[consensusexternalapi.DomainOutpoint]map[consensusexternalapi.DomainTransactionID]*consensusexternalapi.DomainTransaction),
+		mempoolUTXOSet:                       newMempoolUTXOSet(),
+		consensus:                            consensus,
+		nextExpireScan:                       mstime.Now().Add(orphanExpireScanInterval),
 	}
 }
 
-// HandleNewBlock handles a new block that was just added to the DAG
-func (mp *mempool) HandleNewBlock(block *consensusexternalapi.DomainBlock) {
+// txDescriptor is a descriptor containing a transaction in the mempool along with
+// additional metadata.
+type txDescriptor struct {
+	*consensusexternalapi.DomainTransaction
 
+	// depCount is not 0 for a chained transaction. A chained transaction is
+	// one that is accepted to pool, but cannot be mined in next block because it
+	// depends on outputs of accepted, but still not mined transaction
+	depCount int
 }
 
-// Transactions returns all the transactions in the mempool
-func (mp *mempool) Transactions() []*consensusexternalapi.DomainTransaction {
+// orphanTx is normal transaction that references an ancestor transaction
+// that is not yet available. It also contains additional information related
+// to it such as an expiration time to help prevent caching the orphan forever.
+type orphanTx struct {
+	tx         *consensusexternalapi.DomainTransaction
+	expiration mstime.Time
+}
+
+// removeOrphan removes the passed orphan transaction from the orphan pool and
+// previous orphan index.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) removeOrphan(tx *consensusexternalapi.DomainTransaction, removeRedeemers bool) {
+	// Nothing to do if passed tx is not an orphan.
+	txID := hashserialization.TransactionID(tx)
+	otx, exists := mp.orphans[*txID]
+	if !exists {
+		return
+	}
+
+	// Remove the reference from the previous orphan index.
+	for _, txIn := range otx.tx.Inputs {
+		orphans, exists := mp.orphansByPrev[txIn.PreviousOutpoint]
+		if exists {
+			delete(orphans, *txID)
+
+			// Remove the map entry altogether if there are no
+			// longer any orphans which depend on it.
+			if len(orphans) == 0 {
+				delete(mp.orphansByPrev, txIn.PreviousOutpoint)
+			}
+		}
+	}
+
+	// Remove any orphans that redeem outputs from this one if requested.
+	if removeRedeemers {
+		prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *txID}
+		for txOutIdx := range tx.Outputs {
+			prevOut.Index = uint32(txOutIdx)
+			for _, orphan := range mp.orphansByPrev[prevOut] {
+				mp.removeOrphan(orphan, true)
+			}
+		}
+	}
+
+	// Remove the transaction from the orphan pool.
+	delete(mp.orphans, *txID)
+}
+
+// limitNumOrphans limits the number of orphan transactions by evicting a random
+// orphan if adding a new one would cause it to overflow the max allowed.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) limitNumOrphans() error {
+	// Scan through the orphan pool and remove any expired orphans when it's
+	// time. This is done for efficiency so the scan only happens
+	// periodically instead of on every orphan added to the pool.
+	if now := mstime.Now(); now.After(mp.nextExpireScan) {
+		origNumOrphans := len(mp.orphans)
+		for _, otx := range mp.orphans {
+			if now.After(otx.expiration) {
+				// Remove redeemers too because the missing
+				// parents are very unlikely to ever materialize
+				// since the orphan has already been around more
+				// than long enough for them to be delivered.
+				mp.removeOrphan(otx.tx, true)
+			}
+		}
+
+		// Set next expiration scan to occur after the scan interval.
+		mp.nextExpireScan = now.Add(orphanExpireScanInterval)
+
+		numOrphans := len(mp.orphans)
+		if numExpired := origNumOrphans - numOrphans; numExpired > 0 {
+			log.Debugf("Expired %d %s (remaining: %d)", numExpired,
+				logger.PickNoun(uint64(numExpired), "orphan", "orphans"),
+				numOrphans)
+		}
+	}
+
+	// Nothing to do if adding another orphan will not cause the pool to
+	// exceed the limit.
+	if len(mp.orphans)+1 <= mp.policy.MaxOrphanTxs {
+		return nil
+	}
+
+	// Remove a random entry from the map. For most compilers, Go's
+	// range statement iterates starting at a random item although
+	// that is not 100% guaranteed by the spec. The iteration order
+	// is not important here because an adversary would have to be
+	// able to pull off preimage attacks on the hashing function in
+	// order to target eviction of specific entries anyways.
+	for _, otx := range mp.orphans {
+		// Don't remove redeemers in the case of a random eviction since
+		// it is quite possible it might be needed again shortly.
+		mp.removeOrphan(otx.tx, false)
+		break
+	}
+
 	return nil
 }
 
-// ValidateAndInsertTransaction validates the given transaction, and
-// adds it to the mempool
-func (mp *mempool) ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction) error {
+// addOrphan adds an orphan transaction to the orphan pool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) addOrphan(tx *consensusexternalapi.DomainTransaction) {
+	// Nothing to do if no orphans are allowed.
+	if mp.policy.MaxOrphanTxs <= 0 {
+		return
+	}
+
+	// Limit the number orphan transactions to prevent memory exhaustion.
+	// This will periodically remove any expired orphans and evict a random
+	// orphan if space is still needed.
+	mp.limitNumOrphans()
+	txID := hashserialization.TransactionID(tx)
+	mp.orphans[*txID] = &orphanTx{
+		tx:         tx,
+		expiration: mstime.Now().Add(orphanTTL),
+	}
+	for _, txIn := range tx.Inputs {
+		if _, exists := mp.orphansByPrev[txIn.PreviousOutpoint]; !exists {
+			mp.orphansByPrev[txIn.PreviousOutpoint] =
+				make(map[consensusexternalapi.DomainTransactionID]*consensusexternalapi.DomainTransaction)
+		}
+		mp.orphansByPrev[txIn.PreviousOutpoint][*txID] = tx
+	}
+
+	log.Debugf("Stored orphan transaction %s (total: %d)", hashserialization.TransactionID(tx),
+		len(mp.orphans))
+}
+
+// maybeAddOrphan potentially adds an orphan to the orphan pool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) maybeAddOrphan(tx *consensusexternalapi.DomainTransaction) error {
+	// Ignore orphan transactions that are too large. This helps avoid
+	// a memory exhaustion attack based on sending a lot of really large
+	// orphans. In the case there is a valid transaction larger than this,
+	// it will ultimtely be rebroadcast after the parent transactions
+	// have been mined or otherwise received.
+	//
+	// Note that the number of orphan transactions in the orphan pool is
+	// also limited, so this equates to a maximum memory used of
+	// mp.policy.MaxOrphanTxSize * mp.policy.MaxOrphanTxs (which is ~5MB
+	// using the default values at the time this comment was written).
+	serializedLen := estimatedsize.TransactionEstimatedSerializedSize(tx)
+	if serializedLen > uint64(mp.policy.MaxOrphanTxSize) {
+		str := fmt.Sprintf("orphan transaction size of %d bytes is "+
+			"larger than max allowed size of %d bytes",
+			serializedLen, mp.policy.MaxOrphanTxSize)
+		return txRuleError(RejectNonstandard, str)
+	}
+
+	// Add the orphan if the none of the above disqualified it.
+	mp.addOrphan(tx)
+
+	return nil
+}
+
+// removeOrphanDoubleSpends removes all orphans which spend outputs spent by the
+// passed transaction from the orphan pool. Removing those orphans then leads
+// to removing all orphans which rely on them, recursively. This is necessary
+// when a transaction is added to the main pool because it may spend outputs
+// that orphans also spend.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) removeOrphanDoubleSpends(tx *consensusexternalapi.DomainTransaction) {
+	for _, txIn := range tx.Inputs {
+		for _, orphan := range mp.orphansByPrev[txIn.PreviousOutpoint] {
+			mp.removeOrphan(orphan, true)
+		}
+	}
+}
+
+// isTransactionInPool returns whether or not the passed transaction already
+// exists in the main pool.
+//
+// This function MUST be called with the mempool lock held (for reads).
+func (mp *mempool) isTransactionInPool(txID *consensusexternalapi.DomainTransactionID) bool {
+	if _, exists := mp.pool[*txID]; exists {
+		return true
+	}
+	return mp.isInDependPool(txID)
+}
+
+// isInDependPool returns whether or not the passed transaction already
+// exists in the depend pool.
+//
+// This function MUST be called with the mempool lock held (for reads).
+func (mp *mempool) isInDependPool(hash *consensusexternalapi.DomainTransactionID) bool {
+	if _, exists := mp.chainedTransactions[*hash]; exists {
+		return true
+	}
+
+	return false
+}
+
+// isOrphanInPool returns whether or not the passed transaction already exists
+// in the orphan pool.
+//
+// This function MUST be called with the mempool lock held (for reads).
+func (mp *mempool) isOrphanInPool(txID *consensusexternalapi.DomainTransactionID) bool {
+	if _, exists := mp.orphans[*txID]; exists {
+		return true
+	}
+
+	return false
+}
+
+// haveTransaction returns whether or not the passed transaction already exists
+// in the main pool or in the orphan pool.
+//
+// This function MUST be called with the mempool lock held (for reads).
+func (mp *mempool) haveTransaction(txID *consensusexternalapi.DomainTransactionID) bool {
+	return mp.isTransactionInPool(txID) || mp.isOrphanInPool(txID)
+}
+
+// removeBlockTransactionsFromPool removes the transactions that are found in the block
+// from the mempool, and move their chained mempool transactions (if any) to the main pool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) removeBlockTransactionsFromPool(txs []*consensusexternalapi.DomainTransaction) error {
+	for _, tx := range txs[util.CoinbaseTransactionIndex+1:] {
+		txID := hashserialization.TransactionID(tx)
+
+		if _, exists := mp.fetchTxDesc(txID); !exists {
+			continue
+		}
+
+		err := mp.cleanTransactionFromSets(tx)
+		if err != nil {
+			return err
+		}
+
+		mp.updateBlockTransactionChainedTransactions(tx)
+	}
+	return nil
+}
+
+// removeTransactionAndItsChainedTransactions removes a transaction and all of its chained transaction from the mempool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) removeTransactionAndItsChainedTransactions(tx *consensusexternalapi.DomainTransaction) error {
+	txID := hashserialization.TransactionID(tx)
+	// Remove any transactions which rely on this one.
+	for i := uint32(0); i < uint32(len(tx.Outputs)); i++ {
+		prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *txID, Index: i}
+		if txRedeemer, exists := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(prevOut); exists {
+			err := mp.removeTransactionAndItsChainedTransactions(txRedeemer)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	if _, exists := mp.chainedTransactions[*txID]; exists {
+		mp.removeChainTransaction(tx)
+	}
+
+	err := mp.cleanTransactionFromSets(tx)
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
+
+// cleanTransactionFromSets removes the transaction from all mempool related transaction sets.
+// It assumes that any chained transaction is already cleaned from the mempool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) cleanTransactionFromSets(tx *consensusexternalapi.DomainTransaction) error {
+	err := mp.mempoolUTXOSet.removeTx(tx)
+	if err != nil {
+		return err
+	}
+
+	txID := hashserialization.TransactionID(tx)
+	delete(mp.pool, *txID)
+	delete(mp.chainedTransactions, *txID)
+
+	return nil
+}
+
+// updateBlockTransactionChainedTransactions processes the dependencies of a
+// transaction that was included in a block and was just now removed from the mempool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+
+func (mp *mempool) updateBlockTransactionChainedTransactions(tx *consensusexternalapi.DomainTransaction) {
+	prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *hashserialization.TransactionID(tx)}
+	for txOutIdx := range tx.Outputs {
+		// Skip to the next available output if there are none.
+		prevOut.Index = uint32(txOutIdx)
+		txDesc, exists := mp.chainedTransactionByPreviousOutpoint[prevOut]
+		if !exists {
+			continue
+		}
+
+		txDesc.depCount--
+		// If the transaction is not chained anymore, move it into the main pool
+		if txDesc.depCount == 0 {
+			// Transaction may be already removed by recursive calls, if removeRedeemers is true.
+			// So avoid moving it into main pool
+			txDescID := hashserialization.TransactionID(txDesc.DomainTransaction)
+			if _, ok := mp.chainedTransactions[*txDescID]; ok {
+				delete(mp.chainedTransactions, *txDescID)
+				mp.pool[*txDescID] = txDesc
+			}
+		}
+		delete(mp.chainedTransactionByPreviousOutpoint, prevOut)
+	}
+}
+
+// removeChainTransaction removes a chain transaction and all of its relation as a result of double spend.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) removeChainTransaction(tx *consensusexternalapi.DomainTransaction) {
+	delete(mp.chainedTransactions, *hashserialization.TransactionID(tx))
+	for _, txIn := range tx.Inputs {
+		delete(mp.chainedTransactionByPreviousOutpoint, txIn.PreviousOutpoint)
+	}
+}
+
+// removeDoubleSpends removes all transactions which spend outputs spent by the
+// passed transaction from the memory pool. Removing those transactions then
+// leads to removing all transactions which rely on them, recursively. This is
+// necessary when a block is connected to the DAG because the block may
+// contain transactions which were previously unknown to the memory pool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) removeDoubleSpends(tx *consensusexternalapi.DomainTransaction) error {
+	txID := *hashserialization.TransactionID(tx)
+	for _, txIn := range tx.Inputs {
+		if txRedeemer, ok := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(txIn.PreviousOutpoint); ok {
+			if !(*hashserialization.TransactionID(txRedeemer) == txID) {
+				err := mp.removeTransactionAndItsChainedTransactions(txRedeemer)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// addTransaction adds the passed transaction to the memory pool. It should
+// not be called directly as it doesn't perform any validation. This is a
+// helper for maybeAcceptTransaction.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) addTransaction(tx *consensusexternalapi.DomainTransaction, mass uint64, fee uint64, parentsInPool []consensusexternalapi.DomainOutpoint) (*txDescriptor, error) {
+	// Add the transaction to the pool and mark the referenced outpoints
+	// as spent by the pool.
+	txDescriptor := &txDescriptor{
+		DomainTransaction: tx,
+		depCount:          len(parentsInPool),
+	}
+	txID := *hashserialization.TransactionID(tx)
+
+	if len(parentsInPool) == 0 {
+		mp.pool[txID] = txDescriptor
+	} else {
+		mp.chainedTransactions[txID] = txDescriptor
+		for _, previousOutpoint := range parentsInPool {
+			mp.chainedTransactionByPreviousOutpoint[previousOutpoint] = txDescriptor
+		}
+	}
+
+	err := mp.mempoolUTXOSet.addTx(tx)
+	if err != nil {
+		return nil, err
+	}
+
+	return txDescriptor, nil
+}
+
+// checkPoolDoubleSpend checks whether or not the passed transaction is
+// attempting to spend coins already spent by other transactions in the pool.
+// Note it does not check for double spends against transactions already in the
+// DAG.
+//
+// This function MUST be called with the mempool lock held (for reads).
+func (mp *mempool) checkPoolDoubleSpend(tx *consensusexternalapi.DomainTransaction) error {
+	for _, txIn := range tx.Inputs {
+		if txR, exists := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(txIn.PreviousOutpoint); exists {
+			str := fmt.Sprintf("output %s already spent by "+
+				"transaction %s in the memory pool",
+				txIn.PreviousOutpoint, hashserialization.TransactionID(txR))
+			return txRuleError(RejectDuplicate, str)
+		}
+	}
+
+	return nil
+}
+
+// This function MUST be called with the mempool lock held (for reads).
+// This only fetches from the main transaction pool and does not include
+// orphans.
+// returns false in the second return parameter if transaction was not found
+func (mp *mempool) fetchTxDesc(txID *consensusexternalapi.DomainTransactionID) (*txDescriptor, bool) {
+	txDesc, exists := mp.pool[*txID]
+	if !exists {
+		txDesc, exists = mp.chainedTransactions[*txID]
+	}
+	return txDesc, exists
+}
+
+// maybeAcceptTransaction is the main workhorse for handling insertion of new
+// free-standing transactions into a memory pool. It includes functionality
+// such as rejecting duplicate transactions, ensuring transactions follow all
+// rules, detecting orphan transactions, and insertion into the memory pool.
+//
+// If the transaction is an orphan (missing parent transactions), the
+// transaction is NOT added to the orphan pool, but each unknown referenced
+// parent is returned. Use ProcessTransaction instead if new orphans should
+// be added to the orphan pool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) maybeAcceptTransaction(tx *consensusexternalapi.DomainTransaction, rejectDupOrphans bool) ([]consensusexternalapi.DomainOutpoint, *txDescriptor, error) {
+	txID := hashserialization.TransactionID(tx)
+
+	// Don't accept the transaction if it already exists in the pool. This
+	// applies to orphan transactions as well when the reject duplicate
+	// orphans flag is set. This check is intended to be a quick check to
+	// weed out duplicates.
+	if mp.isTransactionInPool(txID) || (rejectDupOrphans &&
+		mp.isOrphanInPool(txID)) {
+
+		str := fmt.Sprintf("already have transaction %s", txID)
+		return nil, nil, txRuleError(RejectDuplicate, str)
+	}
+
+	// Don't allow non-standard transactions if the network parameters
+	// forbid their acceptance.
+	if !mp.policy.AcceptNonStd {
+		err := checkTransactionStandard(tx, &mp.policy)
+		if err != nil {
+			// Attempt to extract a reject code from the error so
+			// it can be retained. When not possible, fall back to
+			// a non standard error.
+			rejectCode, found := extractRejectCode(err)
+			if !found {
+				rejectCode = RejectNonstandard
+			}
+			str := fmt.Sprintf("transaction %s is not standard: %s",
+				txID, err)
+			return nil, nil, txRuleError(rejectCode, str)
+		}
+	}
+
+	// The transaction may not use any of the same outputs as other
+	// transactions already in the pool as that would ultimately result in a
+	// double spend. This check is intended to be quick and therefore only
+	// detects double spends within the transaction pool itself. The
+	// transaction could still be double spending coins from the DAG
+	// at this point. There is a more in-depth check that happens later
+	// after fetching the referenced transaction inputs from the DAG
+	// which examines the actual spend data and prevents double spends.
+	err := mp.checkPoolDoubleSpend(tx)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Don't allow the transaction if it exists in the DAG and is
+	// not already fully spent.
+	if mp.mempoolUTXOSet.checkExists(tx) {
+		return nil, nil, txRuleError(RejectDuplicate, "transaction already exists")
+	}
+
+	// Transaction is an orphan if any of the referenced transaction outputs
+	// don't exist or are already spent. Adding orphans to the orphan pool
+	// is not handled by this function, and the caller should use
+	// maybeAddOrphan if this behavior is desired.
+	parentsInPool := mp.mempoolUTXOSet.populateUTXOEntries(tx)
+
+	// This will populate the missing UTXOEntries.
+	err = mp.consensus.ValidateTransactionAndPopulateWithConsensusData(tx)
+	missingOutpoints := ruleerrors.ErrMissingTxOut{}
+	if errors.As(err, &missingOutpoints) {
+		return missingOutpoints.MissingOutpoints, nil, nil
+	}
+
+	// Don't allow transactions with non-standard inputs if the network
+	// parameters forbid their acceptance.
+	if !mp.policy.AcceptNonStd {
+		err := checkInputsStandard(tx)
+		if err != nil {
+			// Attempt to extract a reject code from the error so
+			// it can be retained. When not possible, fall back to
+			// a non standard error.
+			rejectCode, found := extractRejectCode(err)
+			if !found {
+				rejectCode = RejectNonstandard
+			}
+			str := fmt.Sprintf("transaction %s has a non-standard "+
+				"input: %s", txID, err)
+			return nil, nil, txRuleError(rejectCode, str)
+		}
+	}
+
+	//// NOTE: if you modify this code to accept non-standard transactions,
+	//// you should add code here to check that the transaction does a
+	//// reasonable number of ECDSA signature verifications.
+	//
+
+	// Don't allow transactions with fees too low to get into a mined block.
+	//
+	// Most miners allow a free transaction area in blocks they mine to go
+	// alongside the area used for high-priority transactions as well as
+	// transactions with fees. A transaction size of up to 1000 bytes is
+	// considered safe to go into this section. Further, the minimum fee
+	// calculated below on its own would encourage several small
+	// transactions to avoid fees rather than one single larger transaction
+	// which is more desirable. Therefore, as long as the size of the
+	// transaction does not exceeed 1000 less than the reserved space for
+	// high-priority transactions, don't require a fee for it.
+	serializedSize := int64(estimatedsize.TransactionEstimatedSerializedSize(tx))
+	minFee := uint64(calcMinRequiredTxRelayFee(serializedSize,
+		mp.policy.MinRelayTxFee))
+	if tx.Fee < minFee {
+		str := fmt.Sprintf("transaction %s has %d fees which is under "+
+			"the required amount of %d", txID, tx.Fee,
+			minFee)
+		return nil, nil, txRuleError(RejectInsufficientFee, str)
+	}
+	// Add to transaction pool.
+	txDesc, err := mp.addTransaction(tx, tx.Mass, tx.Fee, parentsInPool)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	log.Debugf("Accepted transaction %s (pool size: %d)", txID,
+		len(mp.pool))
+
+	return nil, txDesc, nil
+}
+
+// processOrphans determines if there are any orphans which depend on the passed
+// transaction hash (it is possible that they are no longer orphans) and
+// potentially accepts them to the memory pool. It repeats the process for the
+// newly accepted transactions (to detect further orphans which may no longer be
+// orphans) until there are no more.
+//
+// It returns a slice of transactions added to the mempool. A nil slice means
+// no transactions were moved from the orphan pool to the mempool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *mempool) processOrphans(acceptedTx *consensusexternalapi.DomainTransaction) []*txDescriptor {
+	var acceptedTxns []*txDescriptor
+
+	// Start with processing at least the passed transaction.
+	processList := list.New()
+	processList.PushBack(acceptedTx)
+	for processList.Len() > 0 {
+		// Pop the transaction to process from the front of the list.
+		firstElement := processList.Remove(processList.Front())
+		processItem := firstElement.(*consensusexternalapi.DomainTransaction)
+
+		prevOut := consensusexternalapi.DomainOutpoint{TransactionID: *hashserialization.TransactionID(processItem)}
+		for txOutIdx := range processItem.Outputs {
+			// Look up all orphans that redeem the output that is
+			// now available. This will typically only be one, but
+			// it could be multiple if the orphan pool contains
+			// double spends. While it may seem odd that the orphan
+			// pool would allow this since there can only possibly
+			// ultimately be a single redeemer, it's important to
+			// track it this way to prevent malicious actors from
+			// being able to purposely constructing orphans that
+			// would otherwise make outputs unspendable.
+			//
+			// Skip to the next available output if there are none.
+			prevOut.Index = uint32(txOutIdx)
+			orphans, exists := mp.orphansByPrev[prevOut]
+			if !exists {
+				continue
+			}
+
+			// Potentially accept an orphan into the tx pool.
+			for _, tx := range orphans {
+				missing, txD, err := mp.maybeAcceptTransaction(
+					tx, false)
+				if err != nil {
+					// The orphan is now invalid, so there
+					// is no way any other orphans which
+					// redeem any of its outputs can be
+					// accepted. Remove them.
+					mp.removeOrphan(tx, true)
+					break
+				}
+
+				// Transaction is still an orphan. Try the next
+				// orphan which redeems this output.
+				if len(missing) > 0 {
+					continue
+				}
+
+				// Transaction was accepted into the main pool.
+				//
+				// Add it to the list of accepted transactions
+				// that are no longer orphans, remove it from
+				// the orphan pool, and add it to the list of
+				// transactions to process so any orphans that
+				// depend on it are handled too.
+				acceptedTxns = append(acceptedTxns, txD)
+				mp.removeOrphan(tx, false)
+				processList.PushBack(tx)
+
+				// Only one transaction for this outpoint can be
+				// accepted, so the rest are now double spends
+				// and are removed later.
+				break
+			}
+		}
+	}
+
+	// Recursively remove any orphans that also redeem any outputs redeemed
+	// by the accepted transactions since those are now definitive double
+	// spends.
+	mp.removeOrphanDoubleSpends(acceptedTx)
+	for _, txDescriptor := range acceptedTxns {
+		mp.removeOrphanDoubleSpends(txDescriptor.DomainTransaction)
+	}
+
+	return acceptedTxns
+}
+
+// ProcessTransaction is the main workhorse for handling insertion of new
+// free-standing transactions into the memory pool. It includes functionality
+// such as rejecting duplicate transactions, ensuring transactions follow all
+// rules, orphan transaction handling, and insertion into the memory pool.
+//
+// It returns a slice of transactions added to the mempool. When the
+// error is nil, the list will include the passed transaction itself along
+// with any additional orphan transaactions that were added as a result of
+// the passed one being accepted.
+//
+// This function is safe for concurrent access.
+func (mp *mempool) ValidateAndInsertTransaction(tx *consensusexternalapi.DomainTransaction, allowOrphan bool) error {
+	log.Tracef("Processing transaction %s", hashserialization.TransactionID(tx))
+
+	// Protect concurrent access.
+	mp.mtx.Lock()
+	defer mp.mtx.Unlock()
+
+	// Potentially accept the transaction to the memory pool.
+	missingParents, txD, err := mp.maybeAcceptTransaction(tx, true)
+	if err != nil {
+		return err
+	}
+
+	if len(missingParents) == 0 {
+		// Accept any orphan transactions that depend on this
+		// transaction (they may no longer be orphans if all inputs
+		// are now available) and repeat for those accepted
+		// transactions until there are no more.
+		newTxs := mp.processOrphans(tx)
+		acceptedTxs := make([]*txDescriptor, len(newTxs)+1)
+
+		// Add the parent transaction first so remote nodes
+		// do not add orphans.
+		acceptedTxs[0] = txD
+		copy(acceptedTxs[1:], newTxs)
+
+		return nil
+	}
+
+	// The transaction is an orphan (has inputs missing). Reject
+	// it if the flag to allow orphans is not set.
+	if !allowOrphan {
+		// Only use the first missing parent transaction in
+		// the error message.
+		//
+		// NOTE: RejectDuplicate is really not an accurate
+		// reject code here, but it matches the reference
+		// implementation and there isn't a better choice due
+		// to the limited number of reject codes. Missing
+		// inputs is assumed to mean they are already spent
+		// which is not really always the case.
+		str := fmt.Sprintf("orphan transaction %s references "+
+			"outputs of unknown or fully-spent "+
+			"transaction %s", hashserialization.TransactionID(tx), missingParents[0])
+		return txRuleError(RejectDuplicate, str)
+	}
+
+	// Potentially add the orphan transaction to the orphan pool.
+	return mp.maybeAddOrphan(tx)
+}
+
+// Count returns the number of transactions in the main pool. It does not
+// include the orphan pool.
+//
+// This function is safe for concurrent access.
+func (mp *mempool) Count() int {
+	mp.mtx.RLock()
+	defer mp.mtx.RUnlock()
+	count := len(mp.pool)
+
+	return count
+}
+
+// ChainedCount returns the number of chained transactions in the mempool. It does not
+// include the orphan pool.
+//
+// This function is safe for concurrent access.
+func (mp *mempool) ChainedCount() int {
+	mp.mtx.RLock()
+	defer mp.mtx.RUnlock()
+	return len(mp.chainedTransactions)
+}
+
+// Transactions returns a slice of all the transactions in the block
+// This is safe for concurrent use
+func (mp *mempool) Transactions() []*consensusexternalapi.DomainTransaction {
+	mp.mtx.RLock()
+	defer mp.mtx.RUnlock()
+	descs := make([]*consensusexternalapi.DomainTransaction, len(mp.pool))
+	i := 0
+	for _, desc := range mp.pool {
+		descs[i] = desc.DomainTransaction
+		i++
+	}
+
+	return descs
+}
+
+// HandleNewBlockTransactions removes all the transactions in the new block
+// from the mempool and the orphan pool, and it also removes
+// from the mempool transactions that double spend a
+// transaction that is already in the DAG
+func (mp *mempool) HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction) {
+	// Protect concurrent access.
+	mp.mtx.Lock()
+	defer mp.mtx.Unlock()
+
+	// Remove all of the transactions (except the coinbase) in the
+	// connected block from the transaction pool. Secondly, remove any
+	// transactions which are now double spends as a result of these
+	// new transactions. Finally, remove any transaction that is
+	// no longer an orphan. Transactions which depend on a confirmed
+	// transaction are NOT removed recursively because they are still
+	// valid.
+	err := mp.removeBlockTransactionsFromPool(txs)
+	if err != nil {
+		log.Errorf("Failed removing txs from pool: '%s'", err)
+	}
+	acceptedTxs := make([]*consensusexternalapi.DomainTransaction, 0)
+	for _, tx := range txs[util.CoinbaseTransactionIndex+1:] {
+		err := mp.removeDoubleSpends(tx)
+		if err != nil {
+			log.Infof("Failed removing tx from mempool: %s, '%s'", hashserialization.TransactionID(tx), err)
+		}
+		mp.removeOrphan(tx, false)
+		acceptedOrphans := mp.processOrphans(tx)
+		for _, acceptedOrphan := range acceptedOrphans {
+			acceptedTxs = append(acceptedTxs, acceptedOrphan.DomainTransaction)
+		}
+	}
+}
+
+func (mp *mempool) RemoveTransactions(txs []*consensusexternalapi.DomainTransaction) {
+	// Protect concurrent access.
+	mp.mtx.Lock()
+	defer mp.mtx.Unlock()
+
+	for _, tx := range txs {
+		err := mp.removeDoubleSpends(tx)
+		if err != nil {
+			log.Infof("Failed removing tx from mempool: %s, '%s'", hashserialization.TransactionID(tx), err)
+		}
+		mp.removeOrphan(tx, true)
+	}
+}
diff --git a/domain/miningmanager/mempool/mempool_utxoset.go b/domain/miningmanager/mempool/mempool_utxoset.go
new file mode 100644
index 00000000..29b5f1e0
--- /dev/null
+++ b/domain/miningmanager/mempool/mempool_utxoset.go
@@ -0,0 +1,102 @@
+package mempool
+
+import (
+	"github.com/kaspanet/kaspad/domain/blockdag"
+	consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/hashserialization"
+	"github.com/pkg/errors"
+)
+
+func newMempoolUTXOSet() *mempoolUTXOSet {
+	return &mempoolUTXOSet{
+		transactionByPreviousOutpoint: make(map[consensusexternalapi.DomainOutpoint]*consensusexternalapi.DomainTransaction),
+		poolUnspentOutputs:            make(map[consensusexternalapi.DomainOutpoint]*consensusexternalapi.UTXOEntry),
+	}
+}
+
+type mempoolUTXOSet struct {
+	transactionByPreviousOutpoint map[consensusexternalapi.DomainOutpoint]*consensusexternalapi.DomainTransaction
+	poolUnspentOutputs            map[consensusexternalapi.DomainOutpoint]*consensusexternalapi.UTXOEntry
+}
+
+// Populate UTXO Entries in the transaction, to allow chained txs.
+func (mpus *mempoolUTXOSet) populateUTXOEntries(tx *consensusexternalapi.DomainTransaction) (parentsInPool []consensusexternalapi.DomainOutpoint) {
+	for _, txIn := range tx.Inputs {
+		if utxoEntry, exists := mpus.poolUnspentOutputs[txIn.PreviousOutpoint]; exists {
+			txIn.UTXOEntry = utxoEntry
+			parentsInPool = append(parentsInPool, txIn.PreviousOutpoint)
+		}
+	}
+	return parentsInPool
+}
+
+func (mpus *mempoolUTXOSet) checkExists(tx *consensusexternalapi.DomainTransaction) bool {
+	// Check if it was already spent.
+	for _, txIn := range tx.Inputs {
+		if _, exists := mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint]; exists {
+			return true
+		}
+	}
+
+	// Check if it creates an already existing UTXO
+	outpoint := consensusexternalapi.DomainOutpoint{TransactionID: *hashserialization.TransactionID(tx)}
+	for i := range tx.Outputs {
+		outpoint.Index = uint32(i)
+		if _, exists := mpus.poolUnspentOutputs[outpoint]; exists {
+			return true
+		}
+	}
+	return false
+}
+
+// addTx adds a transaction to the mempool UTXO set. It assumes that it doesn't double spend another transaction
+// in the mempool, and that its outputs doesn't exist in the mempool UTXO set, and returns error otherwise.
+func (mpus *mempoolUTXOSet) addTx(tx *consensusexternalapi.DomainTransaction) error {
+	for _, txIn := range tx.Inputs {
+		if existingTx, exists := mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint]; exists {
+			return errors.Errorf("outpoint %s is already used by %s", txIn.PreviousOutpoint, hashserialization.TransactionID(existingTx))
+		}
+		mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint] = tx
+	}
+
+	for i, txOut := range tx.Outputs {
+		outpoint := consensusexternalapi.DomainOutpoint{TransactionID: *hashserialization.TransactionID(tx), Index: uint32(i)}
+		if _, exists := mpus.poolUnspentOutputs[outpoint]; exists {
+			return errors.Errorf("outpoint %s already exists", outpoint)
+		}
+		mpus.poolUnspentOutputs[outpoint] = &consensusexternalapi.UTXOEntry{
+			Amount:          txOut.Value,
+			ScriptPublicKey: txOut.ScriptPublicKey,
+			BlockBlueScore:  blockdag.UnacceptedBlueScore,
+			IsCoinbase:      false,
+		}
+	}
+	return nil
+}
+
+// removeTx removes a transaction to the mempool UTXO set.
+// Note: it doesn't re-add its previous outputs to the mempool UTXO set.
+func (mpus *mempoolUTXOSet) removeTx(tx *consensusexternalapi.DomainTransaction) error {
+	for _, txIn := range tx.Inputs {
+		if _, exists := mpus.transactionByPreviousOutpoint[txIn.PreviousOutpoint]; !exists {
+			return errors.Errorf("outpoint %s doesn't exist", txIn.PreviousOutpoint)
+		}
+		delete(mpus.transactionByPreviousOutpoint, txIn.PreviousOutpoint)
+	}
+
+	outpoint := consensusexternalapi.DomainOutpoint{TransactionID: *hashserialization.TransactionID(tx)}
+	for i := range tx.Outputs {
+		outpoint.Index = uint32(i)
+		if _, exists := mpus.poolUnspentOutputs[outpoint]; !exists {
+			return errors.Errorf("outpoint %s doesn't exist", outpoint)
+		}
+		delete(mpus.poolUnspentOutputs, outpoint)
+	}
+
+	return nil
+}
+
+func (mpus *mempoolUTXOSet) poolTransactionBySpendingOutpoint(outpoint consensusexternalapi.DomainOutpoint) (*consensusexternalapi.DomainTransaction, bool) {
+	tx, exists := mpus.transactionByPreviousOutpoint[outpoint]
+	return tx, exists
+}
diff --git a/domain/miningmanager/mempool/policy.go b/domain/miningmanager/mempool/policy.go
new file mode 100644
index 00000000..f2793b6d
--- /dev/null
+++ b/domain/miningmanager/mempool/policy.go
@@ -0,0 +1,265 @@
+// Copyright (c) 2013-2016 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package mempool
+
+import (
+	"fmt"
+	consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize"
+	"github.com/kaspanet/kaspad/domain/txscript"
+	"github.com/kaspanet/kaspad/util"
+)
+
+const (
+	// maxStandardP2SHSigOps is the maximum number of signature operations
+	// that are considered standard in a pay-to-script-hash script.
+	maxStandardP2SHSigOps = 15
+
+	// maxStandardSigScriptSize is the maximum size allowed for a
+	// transaction input signature script to be considered standard. This
+	// value allows for a 15-of-15 CHECKMULTISIG pay-to-script-hash with
+	// compressed keys.
+	//
+	// The form of the overall script is: OP_0 <15 signatures> OP_PUSHDATA2
+	// <2 bytes len> [OP_15 <15 pubkeys> OP_15 OP_CHECKMULTISIG]
+	//
+	// For the p2sh script portion, each of the 15 compressed pubkeys are
+	// 33 bytes (plus one for the OP_DATA_33 opcode), and the thus it totals
+	// to (15*34)+3 = 513 bytes. Next, each of the 15 signatures is a max
+	// of 73 bytes (plus one for the OP_DATA_73 opcode). Also, there is one
+	// extra byte for the initial extra OP_0 push and 3 bytes for the
+	// OP_PUSHDATA2 needed to specify the 513 bytes for the script push.
+	// That brings the total to 1+(15*74)+3+513 = 1627. This value also
+	// adds a few extra bytes to provide a little buffer.
+	// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
+	maxStandardSigScriptSize = 1650
+
+	// MaxStandardTxSize is the maximum size allowed for transactions that
+	// are considered standard and will therefore be relayed and considered
+	// for mining.
+	MaxStandardTxSize = 100000
+
+	// DefaultMinRelayTxFee is the minimum fee in sompi that is required
+	// for a transaction to be treated as free for relay and mining
+	// purposes. It is also used to help determine if a transaction is
+	// considered dust and as a base for calculating minimum required fees
+	// for larger transactions. This value is in sompi/1000 bytes.
+	DefaultMinRelayTxFee = util.Amount(1000)
+)
+
+// calcMinRequiredTxRelayFee returns the minimum transaction fee required for a
+// transaction with the passed serialized size to be accepted into the memory
+// pool and relayed.
+func calcMinRequiredTxRelayFee(serializedSize int64, minRelayTxFee util.Amount) int64 {
+	// Calculate the minimum fee for a transaction to be allowed into the
+	// mempool and relayed by scaling the base fee (which is the minimum
+	// free transaction relay fee). minTxRelayFee is in sompi/kB so
+	// multiply by serializedSize (which is in bytes) and divide by 1000 to
+	// get minimum sompis.
+	minFee := (serializedSize * int64(minRelayTxFee)) / 1000
+
+	if minFee == 0 && minRelayTxFee > 0 {
+		minFee = int64(minRelayTxFee)
+	}
+
+	// Set the minimum fee to the maximum possible value if the calculated
+	// fee is not in the valid range for monetary amounts.
+	if minFee < 0 || minFee > util.MaxSompi {
+		minFee = util.MaxSompi
+	}
+
+	return minFee
+}
+
+// checkInputsStandard performs a series of checks on a transaction's inputs
+// to ensure they are "standard". A standard transaction input within the
+// context of this function is one whose referenced public key script is of a
+// standard form and, for pay-to-script-hash, does not have more than
+// maxStandardP2SHSigOps signature operations.
+func checkInputsStandard(tx *consensusexternalapi.DomainTransaction) error {
+	// NOTE: The reference implementation also does a coinbase check here,
+	// but coinbases have already been rejected prior to calling this
+	// function so no need to recheck.
+
+	for i, txIn := range tx.Inputs {
+		// It is safe to elide existence and index checks here since
+		// they have already been checked prior to calling this
+		// function.
+		entry := txIn.UTXOEntry
+		originScriptPubKey := entry.ScriptPublicKey
+		switch txscript.GetScriptClass(originScriptPubKey) {
+		case txscript.ScriptHashTy:
+			numSigOps := txscript.GetPreciseSigOpCount(
+				txIn.SignatureScript, originScriptPubKey, true)
+			if numSigOps > maxStandardP2SHSigOps {
+				str := fmt.Sprintf("transaction input #%d has "+
+					"%d signature operations which is more "+
+					"than the allowed max amount of %d",
+					i, numSigOps, maxStandardP2SHSigOps)
+				return txRuleError(RejectNonstandard, str)
+			}
+
+		case txscript.NonStandardTy:
+			str := fmt.Sprintf("transaction input #%d has a "+
+				"non-standard script form", i)
+			return txRuleError(RejectNonstandard, str)
+		}
+	}
+
+	return nil
+}
+
+// isDust returns whether or not the passed transaction output amount is
+// considered dust or not based on the passed minimum transaction relay fee.
+// Dust is defined in terms of the minimum transaction relay fee. In
+// particular, if the cost to the network to spend coins is more than 1/3 of the
+// minimum transaction relay fee, it is considered dust.
+func isDust(txOut *consensusexternalapi.DomainTransactionOutput, minRelayTxFee util.Amount) bool {
+	// Unspendable outputs are considered dust.
+	if txscript.IsUnspendable(txOut.ScriptPublicKey) {
+		return true
+	}
+
+	// The total serialized size consists of the output and the associated
+	// input script to redeem it. Since there is no input script
+	// to redeem it yet, use the minimum size of a typical input script.
+	//
+	// Pay-to-pubkey-hash bytes breakdown:
+	//
+	//  Output to hash (34 bytes):
+	//   8 value, 1 script len, 25 script [1 OP_DUP, 1 OP_HASH_160,
+	//   1 OP_DATA_20, 20 hash, 1 OP_EQUALVERIFY, 1 OP_CHECKSIG]
+	//
+	//  Input with compressed pubkey (148 bytes):
+	//   36 prev outpoint, 1 script len, 107 script [1 OP_DATA_72, 72 sig,
+	//   1 OP_DATA_33, 33 compressed pubkey], 4 sequence
+	//
+	//  Input with uncompressed pubkey (180 bytes):
+	//   36 prev outpoint, 1 script len, 139 script [1 OP_DATA_72, 72 sig,
+	//   1 OP_DATA_65, 65 compressed pubkey], 4 sequence
+	//
+	// Pay-to-pubkey bytes breakdown:
+	//
+	//  Output to compressed pubkey (44 bytes):
+	//   8 value, 1 script len, 35 script [1 OP_DATA_33,
+	//   33 compressed pubkey, 1 OP_CHECKSIG]
+	//
+	//  Output to uncompressed pubkey (76 bytes):
+	//   8 value, 1 script len, 67 script [1 OP_DATA_65, 65 pubkey,
+	//   1 OP_CHECKSIG]
+	//
+	//  Input (114 bytes):
+	//   36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72,
+	//   72 sig], 4 sequence
+	//
+	// Theoretically this could examine the script type of the output script
+	// and use a different size for the typical input script size for
+	// pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns,
+	// but the only combination which is less than the value chosen is
+	// a pay-to-pubkey script with a compressed pubkey, which is not very
+	// common.
+	//
+	// The most common scripts are pay-to-pubkey-hash, and as per the above
+	// breakdown, the minimum size of a p2pkh input script is 148 bytes. So
+	// that figure is used.
+	totalSize := estimatedsize.TransactionOutputEstimatedSerializedSize(txOut) + 148
+
+	// The output is considered dust if the cost to the network to spend the
+	// coins is more than 1/3 of the minimum free transaction relay fee.
+	// minFreeTxRelayFee is in sompi/KB, so multiply by 1000 to
+	// convert to bytes.
+	//
+	// Using the typical values for a pay-to-pubkey-hash transaction from
+	// the breakdown above and the default minimum free transaction relay
+	// fee of 1000, this equates to values less than 546 sompi being
+	// considered dust.
+	//
+	// The following is equivalent to (value/totalSize) * (1/3) * 1000
+	// without needing to do floating point math.
+	return txOut.Value*1000/(3*totalSize) < uint64(minRelayTxFee)
+}
+
+// checkTransactionStandard performs a series of checks on a transaction to
+// ensure it is a "standard" transaction. A standard transaction is one that
+// conforms to several additional limiting cases over what is considered a
+// "sane" transaction such as having a version in the supported range, being
+// finalized, conforming to more stringent size constraints, having scripts
+// of recognized forms, and not containing "dust" outputs (those that are
+// so small it costs more to process them than they are worth).
+func checkTransactionStandard(tx *consensusexternalapi.DomainTransaction, policy *policy) error {
+
+	// The transaction must be a currently supported version.
+	if tx.Version > policy.MaxTxVersion || tx.Version < 1 {
+		str := fmt.Sprintf("transaction version %d is not in the "+
+			"valid range of %d-%d", tx.Version, 1,
+			policy.MaxTxVersion)
+		return txRuleError(RejectNonstandard, str)
+	}
+
+	// IsFinalizedTransaction is a consensus check, no need to also check it in policy.
+
+	// The transaction must be finalized to be standard and therefore
+	// considered for inclusion in a block.
+	//if !blockdag.IsFinalizedTransaction(tx, blueScore, medianTimePast) {
+	//	return txRuleError(RejectNonstandard,
+	//		"transaction is not finalized")
+	//}
+
+	// Since extremely large transactions with a lot of inputs can cost
+	// almost as much to process as the sender fees, limit the maximum
+	// size of a transaction. This also helps mitigate CPU exhaustion
+	// attacks.
+	serializedLen := estimatedsize.TransactionEstimatedSerializedSize(tx)
+	if serializedLen > MaxStandardTxSize {
+		str := fmt.Sprintf("transaction size of %d is larger than max "+
+			"allowed size of %d", serializedLen, MaxStandardTxSize)
+		return txRuleError(RejectNonstandard, str)
+	}
+
+	for i, txIn := range tx.Inputs {
+		// Each transaction input signature script must not exceed the
+		// maximum size allowed for a standard transaction. See
+		// the comment on maxStandardSigScriptSize for more details.
+		sigScriptLen := len(txIn.SignatureScript)
+		if sigScriptLen > maxStandardSigScriptSize {
+			str := fmt.Sprintf("transaction input %d: signature "+
+				"script size of %d bytes is large than max "+
+				"allowed size of %d bytes", i, sigScriptLen,
+				maxStandardSigScriptSize)
+			return txRuleError(RejectNonstandard, str)
+		}
+
+		// Each transaction input signature script must only contain
+		// opcodes which push data onto the stack.
+		isPushOnly, err := txscript.IsPushOnlyScript(txIn.SignatureScript)
+		if err != nil {
+			str := fmt.Sprintf("transaction input %d: IsPushOnlyScript: %t. Error %s", i, isPushOnly, err)
+			return txRuleError(RejectNonstandard, str)
+		}
+		if !isPushOnly {
+			str := fmt.Sprintf("transaction input %d: signature "+
+				"script is not push only", i)
+			return txRuleError(RejectNonstandard, str)
+		}
+	}
+
+	// None of the output public key scripts can be a non-standard script or
+	// be "dust".
+	for i, txOut := range tx.Outputs {
+		scriptClass := txscript.GetScriptClass(txOut.ScriptPublicKey)
+		if scriptClass == txscript.NonStandardTy {
+			str := fmt.Sprintf("transaction output %d: non-standard script form", i)
+			return txRuleError(RejectNonstandard, str)
+		}
+
+		if isDust(txOut, policy.MinRelayTxFee) {
+			str := fmt.Sprintf("transaction output %d: payment "+
+				"of %d is dust", i, txOut.Value)
+			return txRuleError(RejectDust, str)
+		}
+	}
+
+	return nil
+}
diff --git a/domain/miningmanager/miningmanager.go b/domain/miningmanager/miningmanager.go
index bdc3368b..f615a55a 100644
--- a/domain/miningmanager/miningmanager.go
+++ b/domain/miningmanager/miningmanager.go
@@ -9,8 +9,8 @@ import (
 // known transactions that have no yet been added to any block
 type MiningManager interface {
 	GetBlockTemplate(coinbaseData *consensusexternalapi.DomainCoinbaseData) *consensusexternalapi.DomainBlock
-	HandleNewBlock(block *consensusexternalapi.DomainBlock)
-	ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction) error
+	HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction)
+	ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction, allowOrphan bool) error
 }
 
 type miningManager struct {
@@ -23,14 +23,14 @@ func (mm *miningManager) GetBlockTemplate(coinbaseData *consensusexternalapi.Dom
 	return mm.blockTemplateBuilder.GetBlockTemplate(coinbaseData)
 }
 
-// HandleNewBlock handles a new block that was just added to the DAG
-func (mm *miningManager) HandleNewBlock(block *consensusexternalapi.DomainBlock) {
-	mm.mempool.HandleNewBlock(block)
+// HandleNewBlock handles the transactions for a new block that was just added to the DAG
+func (mm *miningManager) HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction) {
+	mm.mempool.HandleNewBlockTransactions(txs)
 }
 
 // ValidateAndInsertTransaction validates the given transaction, and
 // adds it to the set of known transactions that have not yet been
 // added to any block
-func (mm *miningManager) ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction) error {
-	return mm.mempool.ValidateAndInsertTransaction(transaction)
+func (mm *miningManager) ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction, allowOrphan bool) error {
+	return mm.mempool.ValidateAndInsertTransaction(transaction, allowOrphan)
 }
diff --git a/domain/miningmanager/model/interface_mempool.go b/domain/miningmanager/model/interface_mempool.go
index 74cdff2e..9293ec74 100644
--- a/domain/miningmanager/model/interface_mempool.go
+++ b/domain/miningmanager/model/interface_mempool.go
@@ -7,7 +7,8 @@ import (
 // Mempool maintains a set of known transactions that
 // are intended to be mined into new blocks
 type Mempool interface {
-	HandleNewBlock(block *consensusexternalapi.DomainBlock)
+	HandleNewBlockTransactions(txs []*consensusexternalapi.DomainTransaction)
 	Transactions() []*consensusexternalapi.DomainTransaction
-	ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction) error
+	ValidateAndInsertTransaction(transaction *consensusexternalapi.DomainTransaction, allowOrphan bool) error
+	RemoveTransactions(txs []*consensusexternalapi.DomainTransaction)
 }


Test patch:
diff --git a/domain/miningmanager/mempool/policy_test.go b/domain/miningmanager/mempool/policy_test.go
new file mode 100644
index 00000000..eae4ff37
--- /dev/null
+++ b/domain/miningmanager/mempool/policy_test.go
@@ -0,0 +1,331 @@
+// Copyright (c) 2013-2016 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package mempool
+
+import (
+	"bytes"
+	"github.com/kaspanet/kaspad/app/appmessage"
+	consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
+	"github.com/kaspanet/kaspad/domain/txscript"
+	"github.com/kaspanet/kaspad/util"
+	"github.com/pkg/errors"
+	"testing"
+)
+
+// TestCalcMinRequiredTxRelayFee tests the calcMinRequiredTxRelayFee API.
+func TestCalcMinRequiredTxRelayFee(t *testing.T) {
+	tests := []struct {
+		name     string      // test description.
+		size     int64       // Transaction size in bytes.
+		relayFee util.Amount // minimum relay transaction fee.
+		want     int64       // Expected fee.
+	}{
+		{
+			// Ensure combination of size and fee that are less than 1000
+			// produce a non-zero fee.
+			"250 bytes with relay fee of 3",
+			250,
+			3,
+			3,
+		},
+		{
+			"100 bytes with default minimum relay fee",
+			100,
+			DefaultMinRelayTxFee,
+			100,
+		},
+		{
+			"max standard tx size with default minimum relay fee",
+			MaxStandardTxSize,
+			DefaultMinRelayTxFee,
+			100000,
+		},
+		{
+			"max standard tx size with max sompi relay fee",
+			MaxStandardTxSize,
+			util.MaxSompi,
+			util.MaxSompi,
+		},
+		{
+			"1500 bytes with 5000 relay fee",
+			1500,
+			5000,
+			7500,
+		},
+		{
+			"1500 bytes with 3000 relay fee",
+			1500,
+			3000,
+			4500,
+		},
+		{
+			"782 bytes with 5000 relay fee",
+			782,
+			5000,
+			3910,
+		},
+		{
+			"782 bytes with 3000 relay fee",
+			782,
+			3000,
+			2346,
+		},
+		{
+			"782 bytes with 2550 relay fee",
+			782,
+			2550,
+			1994,
+		},
+	}
+
+	for _, test := range tests {
+		got := calcMinRequiredTxRelayFee(test.size, test.relayFee)
+		if got != test.want {
+			t.Errorf("TestCalcMinRequiredTxRelayFee test '%s' "+
+				"failed: got %v want %v", test.name, got,
+				test.want)
+			continue
+		}
+	}
+}
+
+// TestDust tests the isDust API.
+func TestDust(t *testing.T) {
+	ScriptPublicKey := []byte{0x76, 0xa9, 0x21, 0x03, 0x2f, 0x7e, 0x43,
+		0x0a, 0xa4, 0xc9, 0xd1, 0x59, 0x43, 0x7e, 0x84, 0xb9,
+		0x75, 0xdc, 0x76, 0xd9, 0x00, 0x3b, 0xf0, 0x92, 0x2c,
+		0xf3, 0xaa, 0x45, 0x28, 0x46, 0x4b, 0xab, 0x78, 0x0d,
+		0xba, 0x5e, 0x88, 0xac}
+
+	tests := []struct {
+		name     string // test description
+		txOut    consensusexternalapi.DomainTransactionOutput
+		relayFee util.Amount // minimum relay transaction fee.
+		isDust   bool
+	}{
+		{
+			// Any value is allowed with a zero relay fee.
+			"zero value with zero relay fee",
+			consensusexternalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: ScriptPublicKey},
+			0,
+			false,
+		},
+		{
+			// Zero value is dust with any relay fee"
+			"zero value with very small tx fee",
+			consensusexternalapi.DomainTransactionOutput{Value: 0, ScriptPublicKey: ScriptPublicKey},
+			1,
+			true,
+		},
+		{
+			"38 byte public key script with value 605",
+			consensusexternalapi.DomainTransactionOutput{Value: 605, ScriptPublicKey: ScriptPublicKey},
+			1000,
+			true,
+		},
+		{
+			"38 byte public key script with value 606",
+			consensusexternalapi.DomainTransactionOutput{Value: 606, ScriptPublicKey: ScriptPublicKey},
+			1000,
+			false,
+		},
+		{
+			// Maximum allowed value is never dust.
+			"max sompi amount is never dust",
+			consensusexternalapi.DomainTransactionOutput{Value: util.MaxSompi, ScriptPublicKey: ScriptPublicKey},
+			util.MaxSompi,
+			false,
+		},
+		{
+			// Maximum int64 value causes overflow.
+			"maximum int64 value",
+			consensusexternalapi.DomainTransactionOutput{Value: 1<<63 - 1, ScriptPublicKey: ScriptPublicKey},
+			1<<63 - 1,
+			true,
+		},
+		{
+			// Unspendable ScriptPublicKey due to an invalid public key
+			// script.
+			"unspendable ScriptPublicKey",
+			consensusexternalapi.DomainTransactionOutput{Value: 5000, ScriptPublicKey: []byte{0x01}},
+			0, // no relay fee
+			true,
+		},
+	}
+	for _, test := range tests {
+		res := isDust(&test.txOut, test.relayFee)
+		if res != test.isDust {
+			t.Fatalf("Dust test '%s' failed: want %v got %v",
+				test.name, test.isDust, res)
+			continue
+		}
+	}
+}
+
+// TestCheckTransactionStandard tests the checkTransactionStandard API.
+func TestCheckTransactionStandard(t *testing.T) {
+	// Create some dummy, but otherwise standard, data for transactions.
+	prevOutTxID := &consensusexternalapi.DomainTransactionID{}
+	dummyPrevOut := consensusexternalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
+	dummySigScript := bytes.Repeat([]byte{0x00}, 65)
+	dummyTxIn := consensusexternalapi.DomainTransactionInput{
+		PreviousOutpoint: dummyPrevOut,
+		SignatureScript:  dummySigScript,
+		Sequence:         appmessage.MaxTxInSequenceNum,
+	}
+	addrHash := [20]byte{0x01}
+	addr, err := util.NewAddressPubKeyHash(addrHash[:], util.Bech32PrefixKaspaTest)
+	if err != nil {
+		t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err)
+	}
+	dummyScriptPublicKey, err := txscript.PayToAddrScript(addr)
+	if err != nil {
+		t.Fatalf("PayToAddrScript: unexpected error: %v", err)
+	}
+	dummyTxOut := consensusexternalapi.DomainTransactionOutput{
+		Value:           100000000, // 1 KAS
+		ScriptPublicKey: dummyScriptPublicKey,
+	}
+
+	tests := []struct {
+		name       string
+		tx         consensusexternalapi.DomainTransaction
+		height     uint64
+		isStandard bool
+		code       RejectCode
+	}{
+		{
+			name:       "Typical pay-to-pubkey-hash transaction",
+			tx:         consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
+			height:     300000,
+			isStandard: true,
+		},
+		{
+			name:       "Transaction version too high",
+			tx:         consensusexternalapi.DomainTransaction{Version: appmessage.TxVersion + 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
+			height:     300000,
+			isStandard: false,
+			code:       RejectNonstandard,
+		},
+		// This is commented out, because transaction finaliation is a consensus check, not a policy check.
+		//{
+		//	name: "Transaction is not finalized",
+		//	tx: consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{{
+		//		PreviousOutpoint: dummyPrevOut,
+		//		SignatureScript:  dummySigScript,
+		//		Sequence:         0,
+		//	}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}, LockTime: 300001},
+		//	height:     300000,
+		//	isStandard: false,
+		//	code:       RejectNonstandard,
+		//},
+		{
+			name: "Transaction size is too large",
+			tx: consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{
+				Value:           0,
+				ScriptPublicKey: bytes.Repeat([]byte{0x00}, MaxStandardTxSize+1),
+			}}},
+			height:     300000,
+			isStandard: false,
+			code:       RejectNonstandard,
+		},
+		{
+			name: "Signature script size is too large",
+			tx: consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{{
+				PreviousOutpoint: dummyPrevOut,
+				SignatureScript: bytes.Repeat([]byte{0x00},
+					maxStandardSigScriptSize+1),
+				Sequence: appmessage.MaxTxInSequenceNum,
+			}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
+			height:     300000,
+			isStandard: false,
+			code:       RejectNonstandard,
+		},
+		{
+			name: "Signature script that does more than push data",
+			tx: consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{{
+				PreviousOutpoint: dummyPrevOut,
+				SignatureScript: []byte{
+					txscript.OpCheckSigVerify},
+				Sequence: appmessage.MaxTxInSequenceNum,
+			}}, Outputs: []*consensusexternalapi.DomainTransactionOutput{&dummyTxOut}},
+			height:     300000,
+			isStandard: false,
+			code:       RejectNonstandard,
+		},
+		{
+			name: "Valid but non standard public key script",
+			tx: consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{
+				Value:           100000000,
+				ScriptPublicKey: []byte{txscript.OpTrue},
+			}}},
+			height:     300000,
+			isStandard: false,
+			code:       RejectNonstandard,
+		},
+		{
+			name: "Dust output",
+			tx: consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{
+				Value:           0,
+				ScriptPublicKey: dummyScriptPublicKey,
+			}}},
+			height:     300000,
+			isStandard: false,
+			code:       RejectDust,
+		},
+		{
+			name: "Nulldata transaction",
+			tx: consensusexternalapi.DomainTransaction{Version: 1, Inputs: []*consensusexternalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*consensusexternalapi.DomainTransactionOutput{{
+				Value:           0,
+				ScriptPublicKey: []byte{txscript.OpReturn},
+			}}},
+			height:     300000,
+			isStandard: false,
+			code:       RejectNonstandard,
+		},
+	}
+
+	for _, test := range tests {
+		// Ensure standardness is as expected.
+		err := checkTransactionStandard(&test.tx, &policy{MinRelayTxFee: DefaultMinRelayTxFee, MaxTxVersion: 1})
+		if err == nil && test.isStandard {
+			// Test passes since function returned standard for a
+			// transaction which is intended to be standard.
+			continue
+		}
+		if err == nil && !test.isStandard {
+			t.Errorf("checkTransactionStandard (%s): standard when "+
+				"it should not be", test.name)
+			continue
+		}
+		if err != nil && test.isStandard {
+			t.Errorf("checkTransactionStandard (%s): nonstandard "+
+				"when it should not be: %v", test.name, err)
+			continue
+		}
+
+		// Ensure error type is a TxRuleError inside of a RuleError.
+		var ruleErr RuleError
+		if !errors.As(err, &ruleErr) {
+			t.Errorf("checkTransactionStandard (%s): unexpected "+
+				"error type - got %T", test.name, err)
+			continue
+		}
+		txRuleErr, ok := ruleErr.Err.(TxRuleError)
+		if !ok {
+			t.Errorf("checkTransactionStandard (%s): unexpected "+
+				"error type - got %T", test.name, ruleErr.Err)
+			continue
+		}
+
+		// Ensure the reject code is the expected one.
+		if txRuleErr.RejectCode != test.code {
+			t.Errorf("checkTransactionStandard (%s): unexpected "+
+				"error code - got %v, want %v", test.name,
+				txRuleErr.RejectCode, test.code)
+			continue
+		}
+	}
+}