mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-05-21 06:16:45 +00:00

* Add the mempool size to getInfo RPC command * Add mempool.Len() * Rename mempool.Len() to mempool.TransactionCount() Co-authored-by: Svarog <feanorr@gmail.com>
1019 lines
36 KiB
Go
1019 lines
36 KiB
Go
// 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"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
|
|
|
|
consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize"
|
|
miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model"
|
|
"github.com/kaspanet/kaspad/util"
|
|
"github.com/kaspanet/kaspad/util/mstime"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
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
|
|
)
|
|
|
|
// 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 uint16
|
|
|
|
// 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 {
|
|
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 consensusexternalapi.Consensus
|
|
|
|
orderedTransactionsByFeeRate []*consensusexternalapi.DomainTransaction
|
|
|
|
// 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 returns a new memory pool for validating and storing standalone
|
|
// transactions until they are mined into a block.
|
|
func New(consensus consensusexternalapi.Consensus, acceptNonStd bool) miningmanagermodel.Mempool {
|
|
policy := policy{
|
|
MaxTxVersion: constants.MaxTransactionVersion,
|
|
AcceptNonStd: acceptNonStd,
|
|
MaxOrphanTxs: 5,
|
|
MaxOrphanTxSize: 100000,
|
|
MinRelayTxFee: 1000, // 1 sompi per byte
|
|
}
|
|
return &mempool{
|
|
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),
|
|
}
|
|
}
|
|
|
|
func (mp *mempool) GetTransaction(
|
|
transactionID *consensusexternalapi.DomainTransactionID) (*consensusexternalapi.DomainTransaction, bool) {
|
|
mp.mtx.RLock()
|
|
defer mp.mtx.RUnlock()
|
|
|
|
txDesc, exists := mp.fetchTxDesc(transactionID)
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
return txDesc.DomainTransaction, true
|
|
}
|
|
|
|
func (mp *mempool) AllTransactions() []*consensusexternalapi.DomainTransaction {
|
|
mp.mtx.RLock()
|
|
defer mp.mtx.RUnlock()
|
|
|
|
transactions := make([]*consensusexternalapi.DomainTransaction, 0, len(mp.pool)+len(mp.chainedTransactions))
|
|
for _, txDesc := range mp.pool {
|
|
transactions = append(transactions, txDesc.DomainTransaction)
|
|
}
|
|
|
|
for _, txDesc := range mp.chainedTransactions {
|
|
transactions = append(transactions, txDesc.DomainTransaction)
|
|
}
|
|
|
|
return transactions
|
|
}
|
|
|
|
func (mp *mempool) TransactionCount() int {
|
|
mp.mtx.RLock()
|
|
defer mp.mtx.RUnlock()
|
|
|
|
return len(mp.pool) + len(mp.chainedTransactions)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 := consensushashing.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 orphans (remaining: %d)", numExpired, 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
|
|
}
|
|
|
|
// 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 := consensushashing.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)", consensushashing.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)
|
|
}
|
|
|
|
// removeTransactionsFromPool removes given transactions 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) removeTransactionsFromPool(txs []*consensusexternalapi.DomainTransaction) error {
|
|
for _, tx := range txs[transactionhelper.CoinbaseTransactionIndex+1:] {
|
|
txID := consensushashing.TransactionID(tx)
|
|
|
|
// We use the mempool transaction, because it has populated fee and mass
|
|
mempoolTx, exists := mp.fetchTxDesc(txID)
|
|
if !exists {
|
|
continue
|
|
}
|
|
|
|
err := mp.cleanTransactionFromSets(mempoolTx.DomainTransaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mp.updateBlockTransactionChainedTransactions(mempoolTx.DomainTransaction)
|
|
}
|
|
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 := consensushashing.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 := consensushashing.TransactionID(tx)
|
|
delete(mp.pool, *txID)
|
|
delete(mp.chainedTransactions, *txID)
|
|
|
|
return mp.removeTransactionFromOrderedTransactionsByFeeRate(tx)
|
|
}
|
|
|
|
// 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: *consensushashing.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 := consensushashing.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, *consensushashing.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 := consensushashing.TransactionID(tx)
|
|
for _, txIn := range tx.Inputs {
|
|
if txRedeemer, ok := mp.mempoolUTXOSet.poolTransactionBySpendingOutpoint(txIn.PreviousOutpoint); ok {
|
|
if !consensushashing.TransactionID(txRedeemer).Equal(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, 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 := *consensushashing.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
|
|
}
|
|
|
|
err = mp.addTransactionToOrderedTransactionsByFeeRate(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return txDescriptor, nil
|
|
}
|
|
|
|
func (mp *mempool) findTxIndexInOrderedTransactionsByFeeRate(tx *consensusexternalapi.DomainTransaction) (int, error) {
|
|
if tx.Fee == 0 || tx.Mass == 0 {
|
|
return 0, errors.Errorf("findTxIndexInOrderedTransactionsByFeeRate expects a transaction with " +
|
|
"populated fee and mass")
|
|
}
|
|
txID := consensushashing.TransactionID(tx)
|
|
txFeeRate := float64(tx.Fee) / float64(tx.Mass)
|
|
|
|
return sort.Search(len(mp.orderedTransactionsByFeeRate), func(i int) bool {
|
|
elementFeeRate := float64(mp.orderedTransactionsByFeeRate[i].Fee) / float64(mp.orderedTransactionsByFeeRate[i].Mass)
|
|
if elementFeeRate > txFeeRate {
|
|
return true
|
|
}
|
|
|
|
if elementFeeRate == txFeeRate && txID.LessOrEqual(consensushashing.TransactionID(mp.orderedTransactionsByFeeRate[i])) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}), nil
|
|
}
|
|
|
|
func (mp *mempool) addTransactionToOrderedTransactionsByFeeRate(tx *consensusexternalapi.DomainTransaction) error {
|
|
index, err := mp.findTxIndexInOrderedTransactionsByFeeRate(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mp.orderedTransactionsByFeeRate = append(mp.orderedTransactionsByFeeRate[:index],
|
|
append([]*consensusexternalapi.DomainTransaction{tx}, mp.orderedTransactionsByFeeRate[index:]...)...)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mp *mempool) removeTransactionFromOrderedTransactionsByFeeRate(tx *consensusexternalapi.DomainTransaction) error {
|
|
index, err := mp.findTxIndexInOrderedTransactionsByFeeRate(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
txID := consensushashing.TransactionID(tx)
|
|
if !consensushashing.TransactionID(mp.orderedTransactionsByFeeRate[index]).Equal(txID) {
|
|
return errors.Errorf("Couldn't find %s in mp.orderedTransactionsByFeeRate", txID)
|
|
}
|
|
|
|
mp.orderedTransactionsByFeeRate = append(mp.orderedTransactionsByFeeRate[:index], mp.orderedTransactionsByFeeRate[index+1:]...)
|
|
return nil
|
|
}
|
|
|
|
func (mp *mempool) enforceTransactionLimit() error {
|
|
const limit = 1_000_000
|
|
if len(mp.pool)+len(mp.chainedTransactions) > limit {
|
|
// mp.orderedTransactionsByFeeRate[0] is the least profitable transaction
|
|
txToRemove := mp.orderedTransactionsByFeeRate[0]
|
|
log.Debugf("Mempool size is over the limit of %d transactions. Removing %s",
|
|
limit,
|
|
consensushashing.TransactionID(txToRemove),
|
|
)
|
|
return mp.removeTransactionAndItsChainedTransactions(txToRemove)
|
|
}
|
|
return 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, consensushashing.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 := consensushashing.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 err != nil {
|
|
if errors.As(err, &missingOutpoints) {
|
|
return missingOutpoints.MissingOutpoints, nil, nil
|
|
}
|
|
if errors.As(err, &ruleerrors.RuleError{}) {
|
|
return nil, nil, newRuleError(err)
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// Don't allow transactions with fees too low to get into a mined block
|
|
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, parentsInPool)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
log.Debugf("Accepted transaction %s (pool size: %d)", txID,
|
|
len(mp.pool))
|
|
|
|
err = mp.enforceTransactionLimit()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
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, error) {
|
|
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: *consensushashing.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 {
|
|
if !errors.As(err, &RuleError{}) {
|
|
return nil, err
|
|
}
|
|
|
|
log.Warnf("Invalid orphan transaction: %s", err)
|
|
// 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, nil
|
|
}
|
|
|
|
// 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", consensushashing.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, err := mp.processOrphans(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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", consensushashing.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)
|
|
}
|
|
|
|
// BlockCandidateTransactions returns a slice of all the candidate transactions for the next block
|
|
// This is safe for concurrent use
|
|
func (mp *mempool) BlockCandidateTransactions() []*consensusexternalapi.DomainTransaction {
|
|
mp.mtx.RLock()
|
|
defer mp.mtx.RUnlock()
|
|
|
|
onEnd := logger.LogAndMeasureExecutionTime(log, "BlockCandidateTransactions")
|
|
defer onEnd()
|
|
|
|
descs := make([]*consensusexternalapi.DomainTransaction, len(mp.pool))
|
|
i := 0
|
|
for _, desc := range mp.pool {
|
|
descs[i] = desc.DomainTransaction.Clone() // Clone the transaction to prevent data races. A shallow-copy might do as well
|
|
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) ([]*consensusexternalapi.DomainTransaction, error) {
|
|
// 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.removeTransactionsFromPool(txs)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Failed removing txs from pool")
|
|
}
|
|
acceptedTxs := make([]*consensusexternalapi.DomainTransaction, 0)
|
|
for _, tx := range txs[transactionhelper.CoinbaseTransactionIndex+1:] {
|
|
err := mp.removeDoubleSpends(tx)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Failed removing tx from mempool: %s", consensushashing.TransactionID(tx))
|
|
}
|
|
mp.removeOrphan(tx, false)
|
|
acceptedOrphans, err := mp.processOrphans(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, acceptedOrphan := range acceptedOrphans {
|
|
acceptedTxs = append(acceptedTxs, acceptedOrphan.DomainTransaction)
|
|
}
|
|
}
|
|
|
|
return acceptedTxs, nil
|
|
}
|
|
|
|
func (mp *mempool) RemoveTransactions(txs []*consensusexternalapi.DomainTransaction) error {
|
|
// Protect concurrent access.
|
|
mp.mtx.Lock()
|
|
defer mp.mtx.Unlock()
|
|
|
|
return mp.removeTransactionsFromPool(txs)
|
|
}
|