mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
1713 lines
57 KiB
Go
1713 lines
57 KiB
Go
// Copyright (c) 2013-2016 The btcsuite developers
|
|
// Copyright (c) 2015-2019 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mempool
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain/stake/v2"
|
|
"github.com/decred/dcrd/blockchain/standalone"
|
|
"github.com/decred/dcrd/blockchain/v2"
|
|
"github.com/decred/dcrd/blockchain/v2/indexers"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/chaincfg/v2"
|
|
"github.com/decred/dcrd/dcrutil/v2"
|
|
"github.com/decred/dcrd/mining/v2"
|
|
"github.com/decred/dcrd/txscript/v2"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
const (
|
|
// DefaultBlockPrioritySize is the default size in bytes for high-
|
|
// priority / low-fee transactions. It is used to help determine which
|
|
// are allowed into the mempool and consequently affects their relay and
|
|
// inclusion when generating block templates.
|
|
DefaultBlockPrioritySize = 20000
|
|
|
|
// maxRelayFeeMultiplier is the factor that we disallow fees / kB above the
|
|
// minimum tx fee. At the current default minimum relay fee of 0.0001
|
|
// DCR/kB, this results in a maximum allowed high fee of 1 DCR/kB.
|
|
maxRelayFeeMultiplier = 1e4
|
|
|
|
// maxVoteDoubleSpends is the maximum number of vote double spends allowed
|
|
// in the pool.
|
|
maxVoteDoubleSpends = 5
|
|
|
|
// heightDiffToPruneTicket is the number of blocks to pass by in terms
|
|
// of height before old tickets are pruned.
|
|
// TODO Set this based up the stake difficulty retargeting interval?
|
|
heightDiffToPruneTicket = 288
|
|
|
|
// heightDiffToPruneVotes is the number of blocks to pass by in terms
|
|
// of height before SSGen relating to that block are pruned.
|
|
heightDiffToPruneVotes = 10
|
|
|
|
// If a vote is on a block whose height is before tip minus this
|
|
// amount, reject it from being added to the mempool.
|
|
maximumVoteAgeDelta = 1440
|
|
|
|
// maxNullDataOutputs is the maximum number of OP_RETURN null data
|
|
// pushes in a transaction, after which it is considered non-standard.
|
|
maxNullDataOutputs = 4
|
|
)
|
|
|
|
// Config is a descriptor containing the memory pool configuration.
|
|
type Config struct {
|
|
// Policy defines the various mempool configuration options related
|
|
// to policy.
|
|
Policy Policy
|
|
|
|
// ChainParams identifies which chain parameters the txpool is
|
|
// associated with.
|
|
ChainParams *chaincfg.Params
|
|
|
|
// NextStakeDifficulty defines the function to retrieve the stake
|
|
// difficulty for the block after the current best block.
|
|
//
|
|
// This function must be safe for concurrent access.
|
|
NextStakeDifficulty func() (int64, error)
|
|
|
|
// FetchUtxoView defines the function to use to fetch unspent
|
|
// transaction output information.
|
|
FetchUtxoView func(*dcrutil.Tx, bool) (*blockchain.UtxoViewpoint, error)
|
|
|
|
// BlockByHash defines the function use to fetch the block identified
|
|
// by the given hash.
|
|
BlockByHash func(*chainhash.Hash) (*dcrutil.Block, error)
|
|
|
|
// BestHash defines the function to use to access the block hash of
|
|
// the current best chain.
|
|
BestHash func() *chainhash.Hash
|
|
|
|
// BestHeight defines the function to use to access the block height of
|
|
// the current best chain.
|
|
BestHeight func() int64
|
|
|
|
// PastMedianTime defines the function to use in order to access the
|
|
// median time calculated from the point-of-view of the current chain
|
|
// tip within the best chain.
|
|
PastMedianTime func() time.Time
|
|
|
|
// CalcSequenceLock defines the function to use in order to generate
|
|
// the current sequence lock for the given transaction using the passed
|
|
// utxo view.
|
|
CalcSequenceLock func(*dcrutil.Tx, *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error)
|
|
|
|
// SubsidyCache defines a subsidy cache to use.
|
|
SubsidyCache *standalone.SubsidyCache
|
|
|
|
// SigCache defines a signature cache to use.
|
|
SigCache *txscript.SigCache
|
|
|
|
// AddrIndex defines the optional address index instance to use for
|
|
// indexing the unconfirmed transactions in the memory pool.
|
|
// This can be nil if the address index is not enabled.
|
|
AddrIndex *indexers.AddrIndex
|
|
|
|
// ExistsAddrIndex defines the optional exists address index instance
|
|
// to use for indexing the unconfirmed transactions in the memory pool.
|
|
// This can be nil if the address index is not enabled.
|
|
ExistsAddrIndex *indexers.ExistsAddrIndex
|
|
|
|
// AddTxToFeeEstimation defines an optional function to be called whenever a
|
|
// new transaction is added to the mempool, which can be used to track fees
|
|
// for the purposes of smart fee estimation.
|
|
AddTxToFeeEstimation func(txHash *chainhash.Hash, fee, size int64, txType stake.TxType)
|
|
|
|
// RemoveTxFromFeeEstimation defines an optional function to be called
|
|
// whenever a transaction is removed from the mempool in order to track fee
|
|
// estimation.
|
|
RemoveTxFromFeeEstimation func(txHash *chainhash.Hash)
|
|
|
|
// OnVoteReceived defines the function used to signal receiving a new
|
|
// vote in the mempool.
|
|
OnVoteReceived func(voteTx *dcrutil.Tx)
|
|
}
|
|
|
|
// Policy houses the policy (configuration parameters) which is used to
|
|
// control the mempool.
|
|
type Policy struct {
|
|
// MaxTxVersion is the max transaction version that the mempool should
|
|
// accept. All transactions above this version are rejected as
|
|
// non-standard.
|
|
MaxTxVersion uint16
|
|
|
|
// DisableRelayPriority defines whether to relay free or low-fee
|
|
// transactions that do not have enough priority to be relayed.
|
|
DisableRelayPriority bool
|
|
|
|
// AcceptNonStd defines whether to accept and relay non-standard
|
|
// transactions to the network. If true, non-standard transactions
|
|
// will be accepted into the mempool and relayed to the rest of the
|
|
// network. Otherwise, all non-standard transactions will be rejected.
|
|
AcceptNonStd bool
|
|
|
|
// FreeTxRelayLimit defines the given amount in thousands of bytes
|
|
// per minute that transactions with no fee are rate limited to.
|
|
FreeTxRelayLimit float64
|
|
|
|
// 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
|
|
|
|
// MaxSigOpsPerTx is the maximum number of signature operations
|
|
// in a single transaction we will relay or mine. It is a fraction
|
|
// of the max signature operations for a block.
|
|
MaxSigOpsPerTx int
|
|
|
|
// MinRelayTxFee defines the minimum transaction fee in DCR/kB to be
|
|
// considered a non-zero fee.
|
|
MinRelayTxFee dcrutil.Amount
|
|
|
|
// AllowOldVotes defines whether or not votes on old blocks will be
|
|
// admitted and relayed.
|
|
AllowOldVotes bool
|
|
|
|
// StandardVerifyFlags defines the function to retrieve the flags to
|
|
// use for verifying scripts for the block after the current best block.
|
|
// It must set the verification flags properly depending on the result
|
|
// of any agendas that affect them.
|
|
//
|
|
// This function must be safe for concurrent access.
|
|
StandardVerifyFlags func() (txscript.ScriptFlags, error)
|
|
|
|
// AcceptSequenceLocks defines the function to determine whether or not
|
|
// to accept transactions with sequence locks. Typically this will be
|
|
// set depending on the result of the fix sequence locks agenda vote.
|
|
//
|
|
// This function must be safe for concurrent access.
|
|
AcceptSequenceLocks func() (bool, error)
|
|
}
|
|
|
|
// TxDesc is a descriptor containing a transaction in the mempool along with
|
|
// additional metadata.
|
|
type TxDesc struct {
|
|
mining.TxDesc
|
|
|
|
// StartingPriority is the priority of the transaction when it was added
|
|
// to the pool.
|
|
StartingPriority float64
|
|
}
|
|
|
|
// VerboseTxDesc is a descriptor containing a transaction in the mempool along
|
|
// with additional more expensive to calculate metadata. Callers should prefer
|
|
// working with the more efficient TxDesc unless they specifically need access
|
|
// to the additional details provided.
|
|
type VerboseTxDesc struct {
|
|
TxDesc
|
|
|
|
// CurrentPriority is the current priority of the transaction within the
|
|
// pool.
|
|
CurrentPriority float64
|
|
|
|
// Depends enumerates any unconfirmed transactions in the pool used as
|
|
// inputs for the transaction.
|
|
Depends []*TxDesc
|
|
}
|
|
|
|
// TxPool 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 TxPool struct {
|
|
// The following variables must only be used atomically.
|
|
lastUpdated int64 // last time pool was updated.
|
|
|
|
mtx sync.RWMutex
|
|
cfg Config
|
|
pool map[chainhash.Hash]*TxDesc
|
|
orphans map[chainhash.Hash]*dcrutil.Tx
|
|
orphansByPrev map[wire.OutPoint]map[chainhash.Hash]*dcrutil.Tx
|
|
outpoints map[wire.OutPoint]*dcrutil.Tx
|
|
|
|
// Votes on blocks.
|
|
votesMtx sync.RWMutex
|
|
votes map[chainhash.Hash][]mining.VoteDesc
|
|
|
|
pennyTotal float64 // exponentially decaying total for penny spends.
|
|
lastPennyUnix int64 // unix time of last ``penny spend''
|
|
}
|
|
|
|
// insertVote inserts a vote into the map of block votes.
|
|
//
|
|
// This function MUST be called with the vote mutex locked (for writes).
|
|
func (mp *TxPool) insertVote(ssgen *dcrutil.Tx) {
|
|
// Get the block it is voting on; here we're agnostic of height.
|
|
msgTx := ssgen.MsgTx()
|
|
blockHash, blockHeight := stake.SSGenBlockVotedOn(msgTx)
|
|
|
|
// If there are currently no votes for this block,
|
|
// start a new buffered slice and store it.
|
|
vts, exists := mp.votes[blockHash]
|
|
if !exists {
|
|
vts = make([]mining.VoteDesc, 0, mp.cfg.ChainParams.TicketsPerBlock)
|
|
}
|
|
|
|
// Nothing to do if a vote for the ticket is already known.
|
|
ticketHash := &msgTx.TxIn[1].PreviousOutPoint.Hash
|
|
for _, vt := range vts {
|
|
if vt.TicketHash.IsEqual(ticketHash) {
|
|
return
|
|
}
|
|
}
|
|
|
|
voteHash := ssgen.Hash()
|
|
voteBits := stake.SSGenVoteBits(msgTx)
|
|
vote := dcrutil.IsFlagSet16(voteBits, dcrutil.BlockValid)
|
|
voteTx := mining.VoteDesc{
|
|
VoteHash: *voteHash,
|
|
TicketHash: *ticketHash,
|
|
ApprovesParent: vote,
|
|
}
|
|
|
|
// Append the new vote.
|
|
mp.votes[blockHash] = append(vts, voteTx)
|
|
|
|
log.Debugf("Accepted vote %v for block hash %v (height %v), voting "+
|
|
"%v on the transaction tree", voteHash, blockHash, blockHeight,
|
|
vote)
|
|
}
|
|
|
|
// VoteHashesForBlock returns the hashes for all votes on the provided block
|
|
// hash that are currently available in the mempool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) VoteHashesForBlock(blockHash *chainhash.Hash) []chainhash.Hash {
|
|
mp.votesMtx.RLock()
|
|
vts, exists := mp.votes[*blockHash]
|
|
mp.votesMtx.RUnlock()
|
|
|
|
// Lookup the vote metadata for the block.
|
|
if !exists || len(vts) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Copy the vote hashes from the vote metadata.
|
|
hashes := make([]chainhash.Hash, 0, len(vts))
|
|
for _, vt := range vts {
|
|
hashes = append(hashes, vt.VoteHash)
|
|
}
|
|
|
|
return hashes
|
|
}
|
|
|
|
// VotesForBlocks returns the vote metadata for all votes on the provided
|
|
// block hashes that are currently available in the mempool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) VotesForBlocks(hashes []chainhash.Hash) [][]mining.VoteDesc {
|
|
result := make([][]mining.VoteDesc, 0, len(hashes))
|
|
|
|
mp.votesMtx.RLock()
|
|
for _, hash := range hashes {
|
|
votes := mp.votes[hash]
|
|
result = append(result, votes)
|
|
}
|
|
mp.votesMtx.RUnlock()
|
|
|
|
return result
|
|
}
|
|
|
|
// TODO Pruning of the votes map DECRED
|
|
|
|
// Ensure the TxPool type implements the mining.TxSource interface.
|
|
var _ mining.TxSource = (*TxPool)(nil)
|
|
|
|
// removeOrphan is the internal function which implements the public
|
|
// RemoveOrphan. See the comment for RemoveOrphan for more details.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for writes).
|
|
func (mp *TxPool) removeOrphan(tx *dcrutil.Tx, removeRedeemers bool) {
|
|
txHash := tx.Hash()
|
|
|
|
// Nothing to do if passed tx is not an orphan.
|
|
tx, exists := mp.orphans[*txHash]
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
log.Tracef("Removing orphan transaction %v", txHash)
|
|
|
|
// Remove the reference from the previous orphan index.
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
orphans, exists := mp.orphansByPrev[txIn.PreviousOutPoint]
|
|
if exists {
|
|
delete(orphans, *txHash)
|
|
|
|
// 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 {
|
|
txType := stake.DetermineTxType(tx.MsgTx())
|
|
tree := wire.TxTreeRegular
|
|
if txType != stake.TxTypeRegular {
|
|
tree = wire.TxTreeStake
|
|
}
|
|
|
|
prevOut := wire.OutPoint{Hash: *txHash, Tree: tree}
|
|
for txOutIdx := range tx.MsgTx().TxOut {
|
|
prevOut.Index = uint32(txOutIdx)
|
|
for _, orphan := range mp.orphansByPrev[prevOut] {
|
|
mp.removeOrphan(orphan, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the transaction from the orphan pool.
|
|
delete(mp.orphans, *txHash)
|
|
}
|
|
|
|
// RemoveOrphan removes the passed orphan transaction from the orphan pool and
|
|
// previous orphan index.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) RemoveOrphan(tx *dcrutil.Tx) {
|
|
mp.mtx.Lock()
|
|
mp.removeOrphan(tx, false)
|
|
mp.mtx.Unlock()
|
|
}
|
|
|
|
// 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 *TxPool) limitNumOrphans() {
|
|
if len(mp.orphans)+1 <= mp.cfg.Policy.MaxOrphanTxs {
|
|
return
|
|
}
|
|
|
|
// 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 _, tx := range mp.orphans {
|
|
mp.removeOrphan(tx, false)
|
|
break
|
|
}
|
|
}
|
|
|
|
// addOrphan adds an orphan transaction to the orphan pool.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for writes).
|
|
func (mp *TxPool) addOrphan(tx *dcrutil.Tx) {
|
|
// Nothing to do if no orphans are allowed.
|
|
if mp.cfg.Policy.MaxOrphanTxs <= 0 {
|
|
return
|
|
}
|
|
|
|
// Limit the number orphan transactions to prevent memory exhaustion. A
|
|
// random orphan is evicted to make room if needed.
|
|
mp.limitNumOrphans()
|
|
|
|
mp.orphans[*tx.Hash()] = tx
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
if _, exists := mp.orphansByPrev[txIn.PreviousOutPoint]; !exists {
|
|
mp.orphansByPrev[txIn.PreviousOutPoint] =
|
|
make(map[chainhash.Hash]*dcrutil.Tx)
|
|
}
|
|
mp.orphansByPrev[txIn.PreviousOutPoint][*tx.Hash()] = tx
|
|
}
|
|
|
|
log.Debugf("Stored orphan transaction %v (total: %d)", tx.Hash(),
|
|
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 *TxPool) maybeAddOrphan(tx *dcrutil.Tx) 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 ultimately 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.cfg.Policy.MaxOrphanTxSize * mp.cfg.Policy.MaxOrphanTxs (which is ~5MB
|
|
// using the default values at the time this comment was written).
|
|
serializedLen := tx.MsgTx().SerializeSize()
|
|
if serializedLen > mp.cfg.Policy.MaxOrphanTxSize {
|
|
str := fmt.Sprintf("orphan transaction size of %d bytes is "+
|
|
"larger than max allowed size of %d bytes",
|
|
serializedLen, mp.cfg.Policy.MaxOrphanTxSize)
|
|
return txRuleError(wire.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 *TxPool) removeOrphanDoubleSpends(tx *dcrutil.Tx) {
|
|
msgTx := tx.MsgTx()
|
|
for _, txIn := range msgTx.TxIn {
|
|
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 *TxPool) isTransactionInPool(hash *chainhash.Hash) bool {
|
|
if _, exists := mp.pool[*hash]; exists {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// IsTransactionInPool returns whether or not the passed transaction already
|
|
// exists in the main pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) IsTransactionInPool(hash *chainhash.Hash) bool {
|
|
// Protect concurrent access.
|
|
mp.mtx.RLock()
|
|
inPool := mp.isTransactionInPool(hash)
|
|
mp.mtx.RUnlock()
|
|
|
|
return inPool
|
|
}
|
|
|
|
// 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 *TxPool) isOrphanInPool(hash *chainhash.Hash) bool {
|
|
if _, exists := mp.orphans[*hash]; exists {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// IsOrphanInPool returns whether or not the passed transaction already exists
|
|
// in the orphan pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) IsOrphanInPool(hash *chainhash.Hash) bool {
|
|
// Protect concurrent access.
|
|
mp.mtx.RLock()
|
|
inPool := mp.isOrphanInPool(hash)
|
|
mp.mtx.RUnlock()
|
|
|
|
return inPool
|
|
}
|
|
|
|
// 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 *TxPool) haveTransaction(hash *chainhash.Hash) bool {
|
|
return mp.isTransactionInPool(hash) || mp.isOrphanInPool(hash)
|
|
}
|
|
|
|
// HaveTransaction returns whether or not the passed transaction already exists
|
|
// in the main pool or in the orphan pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) HaveTransaction(hash *chainhash.Hash) bool {
|
|
// Protect concurrent access.
|
|
mp.mtx.RLock()
|
|
haveTx := mp.haveTransaction(hash)
|
|
mp.mtx.RUnlock()
|
|
|
|
return haveTx
|
|
}
|
|
|
|
// haveTransactions returns whether or not the passed transactions already exist
|
|
// in the main pool or in the orphan pool.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for reads).
|
|
func (mp *TxPool) haveTransactions(hashes []*chainhash.Hash) []bool {
|
|
have := make([]bool, len(hashes))
|
|
for i := range hashes {
|
|
have[i] = mp.haveTransaction(hashes[i])
|
|
}
|
|
return have
|
|
}
|
|
|
|
// HaveTransactions returns whether or not the passed transactions already exist
|
|
// in the main pool or in the orphan pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) HaveTransactions(hashes []*chainhash.Hash) []bool {
|
|
// Protect concurrent access.
|
|
mp.mtx.RLock()
|
|
haveTxns := mp.haveTransactions(hashes)
|
|
mp.mtx.RUnlock()
|
|
return haveTxns
|
|
}
|
|
|
|
// HaveAllTransactions returns whether or not all of the passed transaction
|
|
// hashes exist in the mempool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) HaveAllTransactions(hashes []chainhash.Hash) bool {
|
|
mp.mtx.RLock()
|
|
inPool := true
|
|
for _, h := range hashes {
|
|
if _, exists := mp.pool[h]; !exists {
|
|
inPool = false
|
|
break
|
|
}
|
|
}
|
|
mp.mtx.RUnlock()
|
|
return inPool
|
|
}
|
|
|
|
// removeTransaction is the internal function which implements the public
|
|
// RemoveTransaction. See the comment for RemoveTransaction for more details.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for writes).
|
|
func (mp *TxPool) removeTransaction(tx *dcrutil.Tx, removeRedeemers bool) {
|
|
txHash := tx.Hash()
|
|
if removeRedeemers {
|
|
// Remove any transactions which rely on this one.
|
|
txType := stake.DetermineTxType(tx.MsgTx())
|
|
tree := wire.TxTreeRegular
|
|
if txType != stake.TxTypeRegular {
|
|
tree = wire.TxTreeStake
|
|
}
|
|
|
|
prevOut := wire.OutPoint{Hash: *txHash, Tree: tree}
|
|
for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ {
|
|
prevOut.Index = i
|
|
if txRedeemer, exists := mp.outpoints[prevOut]; exists {
|
|
mp.removeTransaction(txRedeemer, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the transaction if needed.
|
|
if txDesc, exists := mp.pool[*txHash]; exists {
|
|
log.Tracef("Removing transaction %v", txHash)
|
|
|
|
// Remove unconfirmed address index entries associated with the
|
|
// transaction if enabled.
|
|
if mp.cfg.AddrIndex != nil {
|
|
mp.cfg.AddrIndex.RemoveUnconfirmedTx(txHash)
|
|
}
|
|
|
|
// Mark the referenced outpoints as unspent by the pool.
|
|
for _, txIn := range txDesc.Tx.MsgTx().TxIn {
|
|
delete(mp.outpoints, txIn.PreviousOutPoint)
|
|
}
|
|
delete(mp.pool, *txHash)
|
|
atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix())
|
|
|
|
// Inform associated fee estimator that the transaction has been removed
|
|
// from the mempool
|
|
if mp.cfg.RemoveTxFromFeeEstimation != nil {
|
|
mp.cfg.RemoveTxFromFeeEstimation(txHash)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RemoveTransaction removes the passed transaction from the mempool. When the
|
|
// removeRedeemers flag is set, any transactions that redeem outputs from the
|
|
// removed transaction will also be removed recursively from the mempool, as
|
|
// they would otherwise become orphans.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) RemoveTransaction(tx *dcrutil.Tx, removeRedeemers bool) {
|
|
// Protect concurrent access.
|
|
mp.mtx.Lock()
|
|
mp.removeTransaction(tx, removeRedeemers)
|
|
mp.mtx.Unlock()
|
|
}
|
|
|
|
// 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 main chain because the block may
|
|
// contain transactions which were previously unknown to the memory pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) RemoveDoubleSpends(tx *dcrutil.Tx) {
|
|
// Protect concurrent access.
|
|
mp.mtx.Lock()
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
if txRedeemer, ok := mp.outpoints[txIn.PreviousOutPoint]; ok {
|
|
if !txRedeemer.Hash().IsEqual(tx.Hash()) {
|
|
mp.removeTransaction(txRedeemer, true)
|
|
}
|
|
}
|
|
}
|
|
mp.mtx.Unlock()
|
|
}
|
|
|
|
// 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 *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint,
|
|
tx *dcrutil.Tx, txType stake.TxType, height int64, fee int64) {
|
|
|
|
// Notify callback about vote if requested.
|
|
if mp.cfg.OnVoteReceived != nil && txType == stake.TxTypeSSGen {
|
|
mp.cfg.OnVoteReceived(tx)
|
|
}
|
|
|
|
// Add the transaction to the pool and mark the referenced outpoints
|
|
// as spent by the pool.
|
|
msgTx := tx.MsgTx()
|
|
mp.pool[*tx.Hash()] = &TxDesc{
|
|
TxDesc: mining.TxDesc{
|
|
Tx: tx,
|
|
Type: txType,
|
|
Added: time.Now(),
|
|
Height: height,
|
|
Fee: fee,
|
|
},
|
|
StartingPriority: mining.CalcPriority(msgTx, utxoView, height),
|
|
}
|
|
for _, txIn := range msgTx.TxIn {
|
|
mp.outpoints[txIn.PreviousOutPoint] = tx
|
|
}
|
|
atomic.StoreInt64(&mp.lastUpdated, time.Now().Unix())
|
|
|
|
// Add unconfirmed address index entries associated with the transaction
|
|
// if enabled.
|
|
if mp.cfg.AddrIndex != nil {
|
|
mp.cfg.AddrIndex.AddUnconfirmedTx(tx, utxoView)
|
|
}
|
|
if mp.cfg.ExistsAddrIndex != nil {
|
|
mp.cfg.ExistsAddrIndex.AddUnconfirmedTx(msgTx)
|
|
}
|
|
|
|
// Inform the associated fee estimator that a new transaction has been added
|
|
// to the mempool
|
|
if mp.cfg.AddTxToFeeEstimation != nil {
|
|
mp.cfg.AddTxToFeeEstimation(tx.Hash(), fee, int64(msgTx.SerializeSize()),
|
|
txType)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
// main chain.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for reads).
|
|
func (mp *TxPool) checkPoolDoubleSpend(tx *dcrutil.Tx, txType stake.TxType) error {
|
|
for i, txIn := range tx.MsgTx().TxIn {
|
|
// We don't care about double spends of stake bases.
|
|
if i == 0 && (txType == stake.TxTypeSSGen || txType == stake.TxTypeSSRtx) {
|
|
continue
|
|
}
|
|
|
|
if txR, exists := mp.outpoints[txIn.PreviousOutPoint]; exists {
|
|
str := fmt.Sprintf("transaction %v in the pool "+
|
|
"already spends the same coins", txR.Hash())
|
|
return txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkVoteDoubleSpend checks whether or not the passed vote is for a block
|
|
// that already has a vote that spends the same ticket available. This is
|
|
// necessary because the same ticket might be selected for blocks on candidate
|
|
// side chains and thus a more generic check to merely reject double spends of
|
|
// tickets is not possible.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for reads).
|
|
// This function MUST NOT be called with the votes mutex held.
|
|
func (mp *TxPool) checkVoteDoubleSpend(vote *dcrutil.Tx) error {
|
|
voteTx := vote.MsgTx()
|
|
ticketSpent := voteTx.TxIn[1].PreviousOutPoint.Hash
|
|
hashVotedOn, heightVotedOn := stake.SSGenBlockVotedOn(voteTx)
|
|
mp.votesMtx.RLock()
|
|
for _, existingVote := range mp.votes[hashVotedOn] {
|
|
if existingVote.TicketHash == ticketSpent {
|
|
// Ensure the vote is still actually in the mempool. This is needed
|
|
// because the votes map is not currently kept in sync with the
|
|
// contents of the pool.
|
|
//
|
|
// TODO(decred): Ideally the votes map and mempool would be kept in
|
|
// sync, which would remove the need for this check, however, there
|
|
// is currently code outside of mempool that relies on being able to
|
|
// look up seen votes by block hash, regardless of their current
|
|
// membership in the pool.
|
|
if _, exists := mp.pool[existingVote.VoteHash]; !exists {
|
|
continue
|
|
}
|
|
|
|
mp.votesMtx.RUnlock()
|
|
str := fmt.Sprintf("vote %v spending ticket %v already votes on "+
|
|
"block %s (height %d)", vote.Hash(), ticketSpent, hashVotedOn,
|
|
heightVotedOn)
|
|
return txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
}
|
|
mp.votesMtx.RUnlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsRegTxTreeKnownDisapproved returns whether or not the regular tree of the
|
|
// block represented by the provided hash is known to be disapproved according
|
|
// to the votes currently in the memory pool.
|
|
//
|
|
// The function is safe for concurrent access.
|
|
func (mp *TxPool) IsRegTxTreeKnownDisapproved(hash *chainhash.Hash) bool {
|
|
mp.votesMtx.RLock()
|
|
vts := mp.votes[*hash]
|
|
mp.votesMtx.RUnlock()
|
|
|
|
// There are not possibly enough votes to tell if the regular transaction
|
|
// tree is approved or not, so assume it's valid.
|
|
if len(vts) <= int(mp.cfg.ChainParams.TicketsPerBlock/2) {
|
|
return false
|
|
}
|
|
|
|
// Otherwise, tally the votes and determine if it's approved or not.
|
|
var yes, no int
|
|
for _, vote := range vts {
|
|
if vote.ApprovesParent {
|
|
yes++
|
|
} else {
|
|
no++
|
|
}
|
|
}
|
|
|
|
return yes <= no
|
|
}
|
|
|
|
// fetchInputUtxos loads utxo details about the input transactions referenced by
|
|
// the passed transaction. First, it loads the details from the viewpoint of
|
|
// the main chain, then it adjusts them based upon the contents of the
|
|
// transaction pool.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for reads).
|
|
func (mp *TxPool) fetchInputUtxos(tx *dcrutil.Tx) (*blockchain.UtxoViewpoint, error) {
|
|
knownDisapproved := mp.IsRegTxTreeKnownDisapproved(mp.cfg.BestHash())
|
|
utxoView, err := mp.cfg.FetchUtxoView(tx, !knownDisapproved)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Attempt to populate any missing inputs from the transaction pool.
|
|
for originHash, entry := range utxoView.Entries() {
|
|
if entry != nil && !entry.IsFullySpent() {
|
|
continue
|
|
}
|
|
|
|
if poolTxDesc, exists := mp.pool[originHash]; exists {
|
|
utxoView.AddTxOuts(poolTxDesc.Tx, mining.UnminedHeight,
|
|
wire.NullBlockIndex)
|
|
}
|
|
}
|
|
|
|
return utxoView, nil
|
|
}
|
|
|
|
// FetchTransaction returns the requested transaction from the transaction pool.
|
|
// This only fetches from the main transaction pool and does not include
|
|
// orphans.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) FetchTransaction(txHash *chainhash.Hash) (*dcrutil.Tx, error) {
|
|
// Protect concurrent access.
|
|
mp.mtx.RLock()
|
|
txDesc, exists := mp.pool[*txHash]
|
|
mp.mtx.RUnlock()
|
|
|
|
if exists {
|
|
return txDesc.Tx, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("transaction is not in the pool")
|
|
}
|
|
|
|
// maybeAcceptTransaction is the internal function which implements the public
|
|
// MaybeAcceptTransaction. See the comment for MaybeAcceptTransaction for
|
|
// more details.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for writes).
|
|
// DECRED - TODO
|
|
// We need to make sure thing also assigns the TxType after it evaluates the tx,
|
|
// so that we can easily pick different stake tx types from the mempool later.
|
|
// This should probably be done at the bottom using "IsSStx" etc functions.
|
|
// It should also set the dcrutil tree type for the tx as well.
|
|
func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allowHighFees, rejectDupOrphans bool) ([]*chainhash.Hash, error) {
|
|
msgTx := tx.MsgTx()
|
|
txHash := tx.Hash()
|
|
// 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(txHash) || (rejectDupOrphans && mp.isOrphanInPool(txHash)) {
|
|
str := fmt.Sprintf("already have transaction %v", txHash)
|
|
return nil, txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
|
|
// Perform preliminary sanity checks on the transaction. This makes
|
|
// use of blockchain which contains the invariant rules for what
|
|
// transactions are allowed into blocks.
|
|
err := blockchain.CheckTransactionSanity(msgTx, mp.cfg.ChainParams)
|
|
if err != nil {
|
|
if cerr, ok := err.(blockchain.RuleError); ok {
|
|
return nil, chainRuleError(cerr)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// A standalone transaction must not be a coinbase transaction.
|
|
if standalone.IsCoinBaseTx(msgTx) {
|
|
str := fmt.Sprintf("transaction %v is an individual coinbase",
|
|
txHash)
|
|
return nil, txRuleError(wire.RejectInvalid, str)
|
|
}
|
|
|
|
// Get the current height of the main chain. A standalone transaction
|
|
// will be mined into the next block at best, so its height is at least
|
|
// one more than the current height.
|
|
bestHeight := mp.cfg.BestHeight()
|
|
nextBlockHeight := bestHeight + 1
|
|
|
|
// Don't accept transactions that will be expired as of the next block.
|
|
if blockchain.IsExpired(tx, nextBlockHeight) {
|
|
str := fmt.Sprintf("transaction %v expired at height %d",
|
|
txHash, msgTx.Expiry)
|
|
return nil, txRuleError(wire.RejectInvalid, str)
|
|
}
|
|
|
|
// Determine what type of transaction we're dealing with (regular or stake).
|
|
// Then, be sure to set the tx tree correctly as it's possible a use submitted
|
|
// it to the network with TxTreeUnknown.
|
|
txType := stake.DetermineTxType(msgTx)
|
|
if txType == stake.TxTypeRegular {
|
|
tx.SetTree(wire.TxTreeRegular)
|
|
} else {
|
|
tx.SetTree(wire.TxTreeStake)
|
|
}
|
|
isVote := txType == stake.TxTypeSSGen
|
|
|
|
// Choose whether or not to accept transactions with sequence locks enabled.
|
|
//
|
|
// Typically, this will be set based on the result of the fix sequence locks
|
|
// agenda vote.
|
|
acceptSeqLocks, err := mp.cfg.Policy.AcceptSequenceLocks()
|
|
if err != nil {
|
|
if cerr, ok := err.(blockchain.RuleError); ok {
|
|
return nil, chainRuleError(cerr)
|
|
}
|
|
return nil, err
|
|
}
|
|
if !acceptSeqLocks {
|
|
if msgTx.Version >= 2 && !isVote {
|
|
for _, txIn := range msgTx.TxIn {
|
|
sequenceNum := txIn.Sequence
|
|
if sequenceNum&wire.SequenceLockTimeDisabled != 0 {
|
|
continue
|
|
}
|
|
|
|
str := "violates sequence lock consensus bug"
|
|
return nil, txRuleError(wire.RejectInvalid, str)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reject votes before stake validation height.
|
|
stakeValidationHeight := mp.cfg.ChainParams.StakeValidationHeight
|
|
if isVote && nextBlockHeight < stakeValidationHeight {
|
|
str := fmt.Sprintf("votes are not valid until block height %d (next "+
|
|
"block height %d)", stakeValidationHeight, nextBlockHeight)
|
|
return nil, txRuleError(wire.RejectInvalid, str)
|
|
}
|
|
|
|
// Reject revocations before they can possibly be valid. A vote must be
|
|
// missed for a revocation to be valid and votes are not allowed until stake
|
|
// validation height, so, a revocations can't possibly be valid until one
|
|
// block later.
|
|
isRevocation := txType == stake.TxTypeSSRtx
|
|
if isRevocation && nextBlockHeight < stakeValidationHeight+1 {
|
|
str := fmt.Sprintf("revocations are not valid until block height %d "+
|
|
"(next block height %d)", stakeValidationHeight+1, nextBlockHeight)
|
|
return nil, txRuleError(wire.RejectInvalid, str)
|
|
}
|
|
|
|
// Don't allow non-standard transactions if the mempool config forbids
|
|
// their acceptance and relaying.
|
|
medianTime := mp.cfg.PastMedianTime()
|
|
if !mp.cfg.Policy.AcceptNonStd {
|
|
err := checkTransactionStandard(tx, txType, nextBlockHeight,
|
|
medianTime, mp.cfg.Policy.MinRelayTxFee,
|
|
mp.cfg.Policy.MaxTxVersion)
|
|
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 = wire.RejectNonstandard
|
|
}
|
|
str := fmt.Sprintf("transaction %v is not standard: %v",
|
|
txHash, err)
|
|
return nil, txRuleError(rejectCode, str)
|
|
}
|
|
}
|
|
|
|
// If the transaction is a ticket, ensure that it meets the next
|
|
// stake difficulty.
|
|
isTicket := txType == stake.TxTypeSStx
|
|
if isTicket {
|
|
sDiff, err := mp.cfg.NextStakeDifficulty()
|
|
if err != nil {
|
|
// This is an unexpected error so don't turn it into a
|
|
// rule error.
|
|
return nil, err
|
|
}
|
|
|
|
if msgTx.TxOut[0].Value < sDiff {
|
|
str := fmt.Sprintf("transaction %v has not enough funds "+
|
|
"to meet stake difficulty (ticket diff %v < next diff %v)",
|
|
txHash, msgTx.TxOut[0].Value, sDiff)
|
|
return nil, txRuleError(wire.RejectInsufficientFee, str)
|
|
}
|
|
}
|
|
|
|
// Aside from a few exceptions for votes and revocations, 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 main chain at this point. There is a more in-depth check
|
|
// that happens later after fetching the referenced transaction inputs from
|
|
// the main chain which examines the actual spend data and prevents double
|
|
// spends.
|
|
if !isVote && !isRevocation {
|
|
err = mp.checkPoolDoubleSpend(tx, txType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
} else if isVote {
|
|
// Reject votes on blocks that already have a vote that spends the same
|
|
// ticket available. This is necessary because the same ticket might be
|
|
// selected for blocks on candidate side chains and thus a more generic
|
|
// check to merely reject double spends of tickets is not possible.
|
|
err := mp.checkVoteDoubleSpend(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
voteAlreadyFound := 0
|
|
for _, mpTx := range mp.pool {
|
|
if mpTx.Type == stake.TxTypeSSGen {
|
|
if mpTx.Tx.MsgTx().TxIn[1].PreviousOutPoint ==
|
|
msgTx.TxIn[1].PreviousOutPoint {
|
|
voteAlreadyFound++
|
|
}
|
|
}
|
|
if voteAlreadyFound >= maxVoteDoubleSpends {
|
|
str := fmt.Sprintf("transaction %v in the pool with more than "+
|
|
"%v votes", msgTx.TxIn[1].PreviousOutPoint,
|
|
maxVoteDoubleSpends)
|
|
return nil, txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
}
|
|
|
|
} else if isRevocation {
|
|
for _, mpTx := range mp.pool {
|
|
if mpTx.Type == stake.TxTypeSSRtx {
|
|
if mpTx.Tx.MsgTx().TxIn[0].PreviousOutPoint ==
|
|
msgTx.TxIn[0].PreviousOutPoint {
|
|
str := fmt.Sprintf("transaction %v in the pool as a "+
|
|
"revocation. Only one revocation is allowed.",
|
|
msgTx.TxIn[0].PreviousOutPoint)
|
|
return nil, txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Votes that are on too old of blocks are rejected.
|
|
if isVote {
|
|
_, voteHeight := stake.SSGenBlockVotedOn(msgTx)
|
|
if (int64(voteHeight) < nextBlockHeight-maximumVoteAgeDelta) &&
|
|
!mp.cfg.Policy.AllowOldVotes {
|
|
|
|
str := fmt.Sprintf("transaction %v votes on old "+
|
|
"block height of %v which is before the "+
|
|
"current cutoff height of %v",
|
|
tx.Hash(), voteHeight, nextBlockHeight-maximumVoteAgeDelta)
|
|
return nil, txRuleError(wire.RejectNonstandard, str)
|
|
}
|
|
}
|
|
|
|
// Fetch all of the unspent transaction outputs referenced by the inputs
|
|
// to this transaction. This function also attempts to fetch the
|
|
// transaction itself to be used for detecting a duplicate transaction
|
|
// without needing to do a separate lookup.
|
|
utxoView, err := mp.fetchInputUtxos(tx)
|
|
if err != nil {
|
|
if cerr, ok := err.(blockchain.RuleError); ok {
|
|
return nil, chainRuleError(cerr)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Don't allow the transaction if it exists in the main chain and is not
|
|
// already fully spent.
|
|
txEntry := utxoView.LookupEntry(txHash)
|
|
if txEntry != nil && !txEntry.IsFullySpent() {
|
|
return nil, txRuleError(wire.RejectDuplicate,
|
|
"transaction already exists")
|
|
}
|
|
delete(utxoView.Entries(), *txHash)
|
|
|
|
// Transaction is an orphan if any of the inputs don't exist.
|
|
var missingParents []*chainhash.Hash
|
|
for i, txIn := range msgTx.TxIn {
|
|
if i == 0 && isVote {
|
|
continue
|
|
}
|
|
|
|
entry := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash)
|
|
if entry == nil || entry.IsFullySpent() {
|
|
// Must make a copy of the hash here since the iterator
|
|
// is replaced and taking its address directly would
|
|
// result in all of the entries pointing to the same
|
|
// memory location and thus all be the final hash.
|
|
hashCopy := txIn.PreviousOutPoint.Hash
|
|
missingParents = append(missingParents, &hashCopy)
|
|
|
|
// Prevent a panic in the logger by continuing here if the
|
|
// transaction input is nil.
|
|
if entry == nil {
|
|
log.Tracef("Transaction %v uses unknown input %v "+
|
|
"and will be considered an orphan", txHash,
|
|
txIn.PreviousOutPoint.Hash)
|
|
continue
|
|
}
|
|
if entry.IsFullySpent() {
|
|
log.Tracef("Transaction %v uses full spent input %v "+
|
|
"and will be considered an orphan", txHash,
|
|
txIn.PreviousOutPoint.Hash)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(missingParents) > 0 {
|
|
return missingParents, nil
|
|
}
|
|
|
|
// Don't allow the transaction into the mempool unless its sequence
|
|
// lock is active, meaning that it'll be allowed into the next block
|
|
// with respect to its defined relative lock times.
|
|
seqLock, err := mp.cfg.CalcSequenceLock(tx, utxoView)
|
|
if err != nil {
|
|
if cerr, ok := err.(blockchain.RuleError); ok {
|
|
return nil, chainRuleError(cerr)
|
|
}
|
|
return nil, err
|
|
}
|
|
if !blockchain.SequenceLockActive(seqLock, nextBlockHeight, medianTime) {
|
|
return nil, txRuleError(wire.RejectNonstandard,
|
|
"transaction sequence locks on inputs not met")
|
|
}
|
|
|
|
// Perform several checks on the transaction inputs using the invariant
|
|
// rules in blockchain for what transactions are allowed into blocks.
|
|
// Also returns the fees associated with the transaction which will be
|
|
// used later. The fraud proof is not checked because it will be
|
|
// filled in by the miner.
|
|
txFee, err := blockchain.CheckTransactionInputs(mp.cfg.SubsidyCache,
|
|
tx, nextBlockHeight, utxoView, false, mp.cfg.ChainParams)
|
|
if err != nil {
|
|
if cerr, ok := err.(blockchain.RuleError); ok {
|
|
return nil, chainRuleError(cerr)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Don't allow transactions with non-standard inputs if the mempool config
|
|
// forbids their acceptance and relaying.
|
|
if !mp.cfg.Policy.AcceptNonStd {
|
|
err := checkInputsStandard(tx, txType, utxoView)
|
|
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 = wire.RejectNonstandard
|
|
}
|
|
str := fmt.Sprintf("transaction %v has a non-standard "+
|
|
"input: %v", txHash, err)
|
|
return 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 an excessive number of signature
|
|
// operations which would result in making it impossible to mine. Since
|
|
// the coinbase address itself can contain signature operations, the
|
|
// maximum allowed signature operations per transaction is less than
|
|
// the maximum allowed signature operations per block.
|
|
numSigOps, err := blockchain.CountP2SHSigOps(tx, false,
|
|
(txType == stake.TxTypeSSGen), utxoView)
|
|
if err != nil {
|
|
if cerr, ok := err.(blockchain.RuleError); ok {
|
|
return nil, chainRuleError(cerr)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
numSigOps += blockchain.CountSigOps(tx, false, isVote)
|
|
if numSigOps > mp.cfg.Policy.MaxSigOpsPerTx {
|
|
str := fmt.Sprintf("transaction %v has too many sigops: %d > %d",
|
|
txHash, numSigOps, mp.cfg.Policy.MaxSigOpsPerTx)
|
|
return nil, txRuleError(wire.RejectNonstandard, str)
|
|
}
|
|
|
|
// 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 exceed 1000 less than the reserved space for
|
|
// high-priority transactions, don't require a fee for it.
|
|
// This applies to non-stake transactions only.
|
|
serializedSize := int64(msgTx.SerializeSize())
|
|
minFee := calcMinRequiredTxRelayFee(serializedSize,
|
|
mp.cfg.Policy.MinRelayTxFee)
|
|
if txType == stake.TxTypeRegular { // Non-stake only
|
|
if serializedSize >= (DefaultBlockPrioritySize-1000) &&
|
|
txFee < minFee {
|
|
|
|
str := fmt.Sprintf("transaction %v has %v fees which "+
|
|
"is under the required amount of %v", txHash,
|
|
txFee, minFee)
|
|
return nil, txRuleError(wire.RejectInsufficientFee, str)
|
|
}
|
|
}
|
|
|
|
// Require that free transactions have sufficient priority to be mined
|
|
// in the next block. Transactions which are being added back to the
|
|
// memory pool from blocks that have been disconnected during a reorg
|
|
// are exempted.
|
|
//
|
|
// This applies to non-stake transactions only.
|
|
if isNew && !mp.cfg.Policy.DisableRelayPriority && txFee < minFee &&
|
|
txType == stake.TxTypeRegular {
|
|
|
|
currentPriority := mining.CalcPriority(msgTx, utxoView,
|
|
nextBlockHeight)
|
|
if currentPriority <= mining.MinHighPriority {
|
|
str := fmt.Sprintf("transaction %v has insufficient "+
|
|
"priority (%g <= %g)", txHash,
|
|
currentPriority, mining.MinHighPriority)
|
|
return nil, txRuleError(wire.RejectInsufficientFee, str)
|
|
}
|
|
}
|
|
|
|
// Free-to-relay transactions are rate limited here to prevent
|
|
// penny-flooding with tiny transactions as a form of attack.
|
|
// This applies to non-stake transactions only.
|
|
if rateLimit && txFee < minFee && txType == stake.TxTypeRegular {
|
|
nowUnix := time.Now().Unix()
|
|
// Decay passed data with an exponentially decaying ~10 minute
|
|
// window.
|
|
mp.pennyTotal *= math.Pow(1.0-1.0/600.0,
|
|
float64(nowUnix-mp.lastPennyUnix))
|
|
mp.lastPennyUnix = nowUnix
|
|
|
|
// Are we still over the limit?
|
|
if mp.pennyTotal >= mp.cfg.Policy.FreeTxRelayLimit*10*1000 {
|
|
str := fmt.Sprintf("transaction %v has been rejected "+
|
|
"by the rate limiter due to low fees", txHash)
|
|
return nil, txRuleError(wire.RejectInsufficientFee, str)
|
|
}
|
|
oldTotal := mp.pennyTotal
|
|
|
|
mp.pennyTotal += float64(serializedSize)
|
|
log.Tracef("rate limit: curTotal %v, nextTotal: %v, "+
|
|
"limit %v", oldTotal, mp.pennyTotal,
|
|
mp.cfg.Policy.FreeTxRelayLimit*10*1000)
|
|
}
|
|
|
|
// Check that tickets also pay the minimum of the relay fee. This fee is
|
|
// also performed on regular transactions above, but fees lower than the
|
|
// minimum may be allowed when there is sufficient priority, and these
|
|
// checks aren't desired for ticket purchases.
|
|
if isTicket {
|
|
minTicketFee := calcMinRequiredTxRelayFee(serializedSize,
|
|
mp.cfg.Policy.MinRelayTxFee)
|
|
if txFee < minTicketFee {
|
|
str := fmt.Sprintf("ticket purchase transaction %v has a %v "+
|
|
"fee which is under the required threshold amount of %d",
|
|
txHash, txFee, minTicketFee)
|
|
return nil, txRuleError(wire.RejectInsufficientFee, str)
|
|
}
|
|
}
|
|
|
|
// Check whether allowHighFees is set to false (default), if so, then make
|
|
// sure the current fee is sensible. If people would like to avoid this
|
|
// check then they can AllowHighFees = true
|
|
if !allowHighFees {
|
|
maxFee := calcMinRequiredTxRelayFee(serializedSize*maxRelayFeeMultiplier,
|
|
mp.cfg.Policy.MinRelayTxFee)
|
|
if txFee > maxFee {
|
|
err = fmt.Errorf("transaction %v has %v fee which is above the "+
|
|
"allowHighFee check threshold amount of %v", txHash,
|
|
txFee, maxFee)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Verify crypto signatures for each input and reject the transaction if
|
|
// any don't verify.
|
|
flags, err := mp.cfg.Policy.StandardVerifyFlags()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = blockchain.ValidateTransactionScripts(tx, utxoView, flags,
|
|
mp.cfg.SigCache)
|
|
if err != nil {
|
|
if cerr, ok := err.(blockchain.RuleError); ok {
|
|
return nil, chainRuleError(cerr)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Add to transaction pool.
|
|
mp.addTransaction(utxoView, tx, txType, bestHeight, txFee)
|
|
|
|
// Keep track of votes separately.
|
|
if isVote {
|
|
mp.votesMtx.Lock()
|
|
mp.insertVote(tx)
|
|
mp.votesMtx.Unlock()
|
|
}
|
|
|
|
log.Debugf("Accepted transaction %v (pool size: %v)", txHash,
|
|
len(mp.pool))
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// 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, orphan transaction handling, and insertion into the memory pool. The
|
|
// isOrphan parameter can be nil if the caller does not need to know whether
|
|
// or not the transaction is an orphan.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) MaybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit bool) ([]*chainhash.Hash, error) {
|
|
// Protect concurrent access.
|
|
mp.mtx.Lock()
|
|
hashes, err := mp.maybeAcceptTransaction(tx, isNew, rateLimit, true, true)
|
|
mp.mtx.Unlock()
|
|
|
|
return hashes, err
|
|
}
|
|
|
|
// processOrphans is the internal function which implements the public
|
|
// ProcessOrphans. See the comment for ProcessOrphans for more details.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for writes).
|
|
func (mp *TxPool) processOrphans(acceptedTx *dcrutil.Tx) []*dcrutil.Tx {
|
|
var acceptedTxns []*dcrutil.Tx
|
|
|
|
// Start with processing at least the passed transaction.
|
|
processList := []*dcrutil.Tx{acceptedTx}
|
|
for len(processList) > 0 {
|
|
// Pop the transaction to process from the front of the list.
|
|
processItem := processList[0]
|
|
processList[0] = nil
|
|
processList = processList[1:]
|
|
|
|
txType := stake.DetermineTxType(processItem.MsgTx())
|
|
tree := wire.TxTreeRegular
|
|
if txType != stake.TxTypeRegular {
|
|
tree = wire.TxTreeStake
|
|
}
|
|
|
|
prevOut := wire.OutPoint{Hash: *processItem.Hash(), Tree: tree}
|
|
for txOutIdx := range processItem.MsgTx().TxOut {
|
|
// 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, err := mp.maybeAcceptTransaction(
|
|
tx, true, true, true, 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, tx)
|
|
mp.removeOrphan(tx, false)
|
|
processList = append(processList, 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 _, tx := range acceptedTxns {
|
|
mp.removeOrphanDoubleSpends(tx)
|
|
}
|
|
|
|
return acceptedTxns
|
|
}
|
|
|
|
// PruneStakeTx is the function which is called every time a new block is
|
|
// processed. The idea is any outstanding SStx that hasn't been mined in a
|
|
// certain period of time (CoinbaseMaturity) and the submitted SStx's
|
|
// stake difficulty is below the current required stake difficulty should be
|
|
// pruned from mempool since they will never be mined. The same idea stands
|
|
// for SSGen and SSRtx
|
|
func (mp *TxPool) PruneStakeTx(requiredStakeDifficulty, height int64) {
|
|
// Protect concurrent access.
|
|
mp.mtx.Lock()
|
|
mp.pruneStakeTx(requiredStakeDifficulty, height)
|
|
mp.mtx.Unlock()
|
|
}
|
|
|
|
func (mp *TxPool) pruneStakeTx(requiredStakeDifficulty, height int64) {
|
|
for _, tx := range mp.pool {
|
|
txType := stake.DetermineTxType(tx.Tx.MsgTx())
|
|
if txType == stake.TxTypeSStx &&
|
|
tx.Height+int64(heightDiffToPruneTicket) < height {
|
|
mp.removeTransaction(tx.Tx, true)
|
|
}
|
|
if txType == stake.TxTypeSStx &&
|
|
tx.Tx.MsgTx().TxOut[0].Value < requiredStakeDifficulty {
|
|
mp.removeTransaction(tx.Tx, true)
|
|
}
|
|
if (txType == stake.TxTypeSSRtx || txType == stake.TxTypeSSGen) &&
|
|
tx.Height+int64(heightDiffToPruneVotes) < height {
|
|
mp.removeTransaction(tx.Tx, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// pruneExpiredTx prunes expired transactions from the mempool that are no
|
|
// longer able to be included into a block.
|
|
//
|
|
// This function MUST be called with the mempool lock held (for writes).
|
|
func (mp *TxPool) pruneExpiredTx() {
|
|
nextBlockHeight := mp.cfg.BestHeight() + 1
|
|
|
|
for _, tx := range mp.pool {
|
|
if blockchain.IsExpired(tx.Tx, nextBlockHeight) {
|
|
log.Debugf("Pruning expired transaction %v from the mempool",
|
|
tx.Tx.Hash())
|
|
mp.removeTransaction(tx.Tx, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// PruneExpiredTx prunes expired transactions from the mempool that may no longer
|
|
// be able to be included into a block.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) PruneExpiredTx() {
|
|
// Protect concurrent access.
|
|
mp.mtx.Lock()
|
|
mp.pruneExpiredTx()
|
|
mp.mtx.Unlock()
|
|
}
|
|
|
|
// 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 is safe for concurrent access.
|
|
func (mp *TxPool) ProcessOrphans(acceptedTx *dcrutil.Tx) []*dcrutil.Tx {
|
|
mp.mtx.Lock()
|
|
acceptedTxns := mp.processOrphans(acceptedTx)
|
|
mp.mtx.Unlock()
|
|
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 transactions that were added as a result of the
|
|
// passed one being accepted.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) ProcessTransaction(tx *dcrutil.Tx, allowOrphan, rateLimit, allowHighFees bool) ([]*dcrutil.Tx, error) {
|
|
// Protect concurrent access.
|
|
mp.mtx.Lock()
|
|
defer mp.mtx.Unlock()
|
|
var err error
|
|
defer func() {
|
|
if err != nil {
|
|
log.Tracef("Failed to process transaction %v: %s",
|
|
tx.Hash(), err.Error())
|
|
}
|
|
}()
|
|
|
|
// Potentially accept the transaction to the memory pool.
|
|
missingParents, err := mp.maybeAcceptTransaction(tx, true, rateLimit,
|
|
allowHighFees, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If len(missingParents) == 0 then we know the tx is NOT an orphan.
|
|
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([]*dcrutil.Tx, len(newTxs)+1)
|
|
|
|
// Add the parent transaction first so remote nodes
|
|
// do not add orphans.
|
|
acceptedTxs[0] = tx
|
|
copy(acceptedTxs[1:], newTxs)
|
|
|
|
return acceptedTxs, 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 %v references "+
|
|
"outputs of unknown or fully-spent "+
|
|
"transaction %v", tx.Hash(), missingParents[0])
|
|
return nil, txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
|
|
// Potentially add the orphan transaction to the orphan pool.
|
|
err = mp.maybeAddOrphan(tx)
|
|
return nil, err
|
|
}
|
|
|
|
// 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 *TxPool) Count() int {
|
|
mp.mtx.RLock()
|
|
count := len(mp.pool)
|
|
mp.mtx.RUnlock()
|
|
|
|
return count
|
|
}
|
|
|
|
// TxHashes returns a slice of hashes for all of the transactions in the memory
|
|
// pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) TxHashes() []*chainhash.Hash {
|
|
mp.mtx.RLock()
|
|
hashes := make([]*chainhash.Hash, len(mp.pool))
|
|
i := 0
|
|
for hash := range mp.pool {
|
|
hashCopy := hash
|
|
hashes[i] = &hashCopy
|
|
i++
|
|
}
|
|
mp.mtx.RUnlock()
|
|
|
|
return hashes
|
|
}
|
|
|
|
// TxDescs returns a slice of descriptors for all the transactions in the pool.
|
|
// The descriptors must be treated as read only.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) TxDescs() []*TxDesc {
|
|
mp.mtx.RLock()
|
|
descs := make([]*TxDesc, len(mp.pool))
|
|
i := 0
|
|
for _, desc := range mp.pool {
|
|
descs[i] = desc
|
|
i++
|
|
}
|
|
mp.mtx.RUnlock()
|
|
|
|
return descs
|
|
}
|
|
|
|
// VerboseTxDescs returns a slice of verbose descriptors for all the
|
|
// transactions in the pool. The descriptors must be treated as read only.
|
|
//
|
|
// Callers should prefer working with the more efficient TxDescs unless they
|
|
// specifically need access to the additional details provided.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) VerboseTxDescs() []*VerboseTxDesc {
|
|
mp.mtx.RLock()
|
|
defer mp.mtx.RUnlock()
|
|
|
|
result := make([]*VerboseTxDesc, 0, len(mp.pool))
|
|
bestHeight := mp.cfg.BestHeight()
|
|
|
|
for _, desc := range mp.pool {
|
|
// Calculate the current priority based on inputs to the transaction.
|
|
// Use zero if one or more of the input transactions can't be found for
|
|
// some reason.
|
|
tx := desc.Tx
|
|
var currentPriority float64
|
|
utxos, err := mp.fetchInputUtxos(tx)
|
|
if err == nil {
|
|
currentPriority = mining.CalcPriority(tx.MsgTx(), utxos,
|
|
bestHeight+1)
|
|
}
|
|
|
|
// Create the descriptor and add dependencies as needed.
|
|
vtxd := &VerboseTxDesc{
|
|
TxDesc: *desc,
|
|
CurrentPriority: currentPriority,
|
|
}
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
hash := &txIn.PreviousOutPoint.Hash
|
|
if depDesc, ok := mp.pool[*hash]; ok {
|
|
vtxd.Depends = append(vtxd.Depends, depDesc)
|
|
}
|
|
}
|
|
|
|
result = append(result, vtxd)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// MiningDescs returns a slice of mining descriptors for all the transactions
|
|
// in the pool.
|
|
//
|
|
// This is part of the mining.TxSource interface implementation and is safe for
|
|
// concurrent access as required by the interface contract.
|
|
func (mp *TxPool) MiningDescs() []*mining.TxDesc {
|
|
mp.mtx.RLock()
|
|
descs := make([]*mining.TxDesc, len(mp.pool))
|
|
i := 0
|
|
for _, desc := range mp.pool {
|
|
descs[i] = &desc.TxDesc
|
|
i++
|
|
}
|
|
mp.mtx.RUnlock()
|
|
|
|
return descs
|
|
}
|
|
|
|
// LastUpdated returns the last time a transaction was added to or removed from
|
|
// the main pool. It does not include the orphan pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *TxPool) LastUpdated() time.Time {
|
|
return time.Unix(atomic.LoadInt64(&mp.lastUpdated), 0)
|
|
}
|
|
|
|
// New returns a new memory pool for validating and storing standalone
|
|
// transactions until they are mined into a block.
|
|
func New(cfg *Config) *TxPool {
|
|
return &TxPool{
|
|
cfg: *cfg,
|
|
pool: make(map[chainhash.Hash]*TxDesc),
|
|
orphans: make(map[chainhash.Hash]*dcrutil.Tx),
|
|
orphansByPrev: make(map[wire.OutPoint]map[chainhash.Hash]*dcrutil.Tx),
|
|
outpoints: make(map[wire.OutPoint]*dcrutil.Tx),
|
|
votes: make(map[chainhash.Hash][]mining.VoteDesc),
|
|
}
|
|
}
|