mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
Upstream commit a7b35d9f9e.
Also, the merge commit contains necessary decred-specific alterations.
It also has the side effect of correcting a couple of min reduction bugs
even though they can't currently ever be hit due to the min reduction
interval flag being disabled for all networks.
1663 lines
53 KiB
Go
1663 lines
53 KiB
Go
// Copyright (c) 2013-2016 The btcsuite developers
|
|
// Copyright (c) 2015-2016 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"container/list"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"sort"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain"
|
|
"github.com/decred/dcrd/blockchain/indexers"
|
|
"github.com/decred/dcrd/blockchain/stake"
|
|
"github.com/decred/dcrd/chaincfg"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/mining"
|
|
"github.com/decred/dcrd/txscript"
|
|
"github.com/decred/dcrd/wire"
|
|
"github.com/decred/dcrutil"
|
|
)
|
|
|
|
const (
|
|
// mempoolHeight is the height used for the "block" height field of the
|
|
// contextual transaction information provided in a transaction view.
|
|
mempoolHeight = 0x7fffffff
|
|
|
|
// minTicketFee is the minimum fee per KB in atoms that is required for
|
|
// a ticket to enter the mempool.
|
|
minTicketFee = 1e6
|
|
|
|
// maxRelayFeeMultiplier is the factor that we disallow fees / kb above
|
|
// the minimum tx fee.
|
|
maxRelayFeeMultiplier = 100
|
|
|
|
// maxSSGensDoubleSpends is the maximum number of SSGen double spends
|
|
// allowed in the pool.
|
|
maxSSGensDoubleSpends = 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
|
|
)
|
|
|
|
// VoteTx is a struct describing a block vote (SSGen).
|
|
type VoteTx struct {
|
|
SsgenHash chainhash.Hash // Vote
|
|
SstxHash chainhash.Hash // Ticket
|
|
Vote bool
|
|
}
|
|
|
|
// mempoolTxDesc is a descriptor containing a transaction in the mempool along
|
|
// with additional metadata.
|
|
type mempoolTxDesc struct {
|
|
mining.TxDesc
|
|
|
|
// StartingPriority is the priority of the transaction when it was added
|
|
// to the pool.
|
|
StartingPriority float64
|
|
}
|
|
|
|
// mempoolConfig is a descriptor containing the memory pool configuration.
|
|
type mempoolConfig struct {
|
|
// ChainParams identifies which chain parameters the mempool is
|
|
// associated with.
|
|
ChainParams *chaincfg.Params
|
|
|
|
// NewestHash defines the function to retrieve the newest sha.
|
|
NewestHash func() (*chainhash.Hash, int64, error)
|
|
|
|
// 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)
|
|
|
|
// Policy defines the various mempool configuration options related
|
|
// to policy.
|
|
Policy mempoolPolicy
|
|
|
|
// FetchUtxoView defines the function to use to fetch unspent
|
|
// transaction output information.
|
|
FetchUtxoView func(*dcrutil.Tx, bool) (*blockchain.UtxoViewpoint, error)
|
|
|
|
// Chain defines the concurrent safe block chain instance which houses
|
|
// the current best chain.
|
|
Chain *blockchain.BlockChain
|
|
|
|
// SigCache defines a signature cache to use.
|
|
SigCache *txscript.SigCache
|
|
|
|
// TimeSource defines the timesource to use.
|
|
TimeSource blockchain.MedianTimeSource
|
|
|
|
// 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
|
|
}
|
|
|
|
// mempoolPolicy houses the policy (configuration parameters) which is used to
|
|
// control the mempool.
|
|
type mempoolPolicy struct {
|
|
// DisableRelayPriority defines whether to relay free or low-fee
|
|
// transactions that do not have enough priority to be relayed.
|
|
DisableRelayPriority 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 BTC/kB to be
|
|
// considered a non-zero fee.
|
|
MinRelayTxFee dcrutil.Amount
|
|
}
|
|
|
|
// txMemPool 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 txMemPool struct {
|
|
// The following variables must only be used atomically.
|
|
lastUpdated int64 // last time pool was updated.
|
|
|
|
sync.RWMutex
|
|
cfg mempoolConfig
|
|
pool map[chainhash.Hash]*mempoolTxDesc
|
|
orphans map[chainhash.Hash]*dcrutil.Tx
|
|
orphansByPrev map[chainhash.Hash]map[chainhash.Hash]*dcrutil.Tx
|
|
addrindex map[string]map[chainhash.Hash]struct{} // maps address to txs
|
|
outpoints map[wire.OutPoint]*dcrutil.Tx
|
|
|
|
// Votes on blocks.
|
|
votes map[chainhash.Hash][]*VoteTx
|
|
votesMtx sync.Mutex
|
|
|
|
// A declared subsidy cache as passed from the blockchain.
|
|
subsidyCache *blockchain.SubsidyCache
|
|
|
|
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 is safe for concurrent access.
|
|
func (mp *txMemPool) insertVote(ssgen *dcrutil.Tx) error {
|
|
voteHash := ssgen.Hash()
|
|
msgTx := ssgen.MsgTx()
|
|
ticketHash := &msgTx.TxIn[1].PreviousOutPoint.Hash
|
|
|
|
// Get the block it is voting on; here we're agnostic of height.
|
|
blockHash, blockHeight, err := stake.SSGenBlockVotedOn(msgTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
voteBits := stake.SSGenVoteBits(msgTx)
|
|
vote := dcrutil.IsFlagSet16(voteBits, dcrutil.BlockValid)
|
|
|
|
voteTx := &VoteTx{*voteHash, *ticketHash, vote}
|
|
vts, exists := mp.votes[blockHash]
|
|
|
|
// If there are currently no votes for this block,
|
|
// start a new buffered slice and store it.
|
|
if !exists {
|
|
minrLog.Debugf("Accepted vote %v for block hash %v (height %v), "+
|
|
"voting %v on the transaction tree",
|
|
voteHash, blockHash, blockHeight, vote)
|
|
|
|
slice := make([]*VoteTx, int(mp.cfg.ChainParams.TicketsPerBlock),
|
|
int(mp.cfg.ChainParams.TicketsPerBlock))
|
|
slice[0] = voteTx
|
|
mp.votes[blockHash] = slice
|
|
return nil
|
|
}
|
|
|
|
// We already have a vote for this ticket; break.
|
|
for _, vt := range vts {
|
|
// At the end.
|
|
if vt == nil {
|
|
break
|
|
}
|
|
|
|
if vt.SstxHash.IsEqual(ticketHash) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Add the new vote in. Find where the first empty
|
|
// slot is and insert it.
|
|
for i, vt := range vts {
|
|
// At the end.
|
|
if vt == nil {
|
|
mp.votes[blockHash][i] = voteTx
|
|
break
|
|
}
|
|
}
|
|
|
|
minrLog.Debugf("Accepted vote %v for block hash %v (height %v), "+
|
|
"voting %v on the transaction tree",
|
|
voteHash, blockHash, blockHeight, vote)
|
|
|
|
return nil
|
|
}
|
|
|
|
// InsertVote calls insertVote, but makes it safe for concurrent access.
|
|
func (mp *txMemPool) InsertVote(ssgen *dcrutil.Tx) error {
|
|
mp.votesMtx.Lock()
|
|
defer mp.votesMtx.Unlock()
|
|
|
|
err := mp.insertVote(ssgen)
|
|
|
|
return err
|
|
}
|
|
|
|
// getVoteHashesForBlock gets the transaction hashes of all the known votes for
|
|
// some block on the blockchain.
|
|
func (mp *txMemPool) getVoteHashesForBlock(block chainhash.Hash) ([]chainhash.Hash,
|
|
error) {
|
|
var hashes []chainhash.Hash
|
|
vts, exists := mp.votes[block]
|
|
if !exists {
|
|
return nil, fmt.Errorf("couldn't find block requested in mp.votes")
|
|
}
|
|
|
|
if len(vts) == 0 {
|
|
return nil, fmt.Errorf("block found in mp.votes, but contains no votes")
|
|
}
|
|
|
|
zeroHash := &chainhash.Hash{}
|
|
for _, vt := range vts {
|
|
if vt == nil {
|
|
break
|
|
}
|
|
|
|
if vt.SsgenHash.IsEqual(zeroHash) {
|
|
return nil, fmt.Errorf("unset vote hash in vote info")
|
|
}
|
|
hashes = append(hashes, vt.SsgenHash)
|
|
}
|
|
|
|
return hashes, nil
|
|
}
|
|
|
|
// GetVoteHashesForBlock calls getVoteHashesForBlock, but makes it safe for
|
|
// concurrent access.
|
|
func (mp *txMemPool) GetVoteHashesForBlock(block chainhash.Hash) ([]chainhash.Hash,
|
|
error) {
|
|
mp.votesMtx.Lock()
|
|
defer mp.votesMtx.Unlock()
|
|
|
|
hashes, err := mp.getVoteHashesForBlock(block)
|
|
|
|
return hashes, err
|
|
}
|
|
|
|
// TODO Pruning of the votes map DECRED
|
|
|
|
func getNumberOfVotesOnBlock(blockVoteTxs []*VoteTx) int {
|
|
numVotes := 0
|
|
for _, vt := range blockVoteTxs {
|
|
if vt == nil {
|
|
break
|
|
}
|
|
|
|
numVotes++
|
|
}
|
|
|
|
return numVotes
|
|
}
|
|
|
|
// blockWithLenVotes is a block with the number of votes currently present
|
|
// for that block. Just used for sorting.
|
|
type blockWithLenVotes struct {
|
|
Block chainhash.Hash
|
|
Votes uint16
|
|
}
|
|
|
|
// ByNumberOfVotes defines the methods needed to satisify sort.Interface to
|
|
// sort a slice of Blocks by their number of votes.
|
|
type ByNumberOfVotes []*blockWithLenVotes
|
|
|
|
func (b ByNumberOfVotes) Len() int { return len(b) }
|
|
func (b ByNumberOfVotes) Less(i, j int) bool { return b[i].Votes < b[j].Votes }
|
|
func (b ByNumberOfVotes) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
|
|
// sortParentsByVotes takes a list of block header hashes and sorts them
|
|
// by the number of votes currently available for them in the votes map of
|
|
// mempool. It then returns all blocks that are eligible to be used (have
|
|
// at least a majority number of votes) sorted by number of votes, descending.
|
|
func (mp *txMemPool) sortParentsByVotes(currentTopBlock chainhash.Hash,
|
|
blocks []chainhash.Hash) ([]chainhash.Hash, error) {
|
|
lenBlocks := len(blocks)
|
|
if lenBlocks == 0 {
|
|
return nil, fmt.Errorf("no blocks to sort")
|
|
}
|
|
|
|
bwlvs := make([]*blockWithLenVotes, lenBlocks, lenBlocks)
|
|
|
|
for i, blockHash := range blocks {
|
|
votes, exists := mp.votes[blockHash]
|
|
if exists {
|
|
bwlv := &blockWithLenVotes{
|
|
blockHash,
|
|
uint16(getNumberOfVotesOnBlock(votes)),
|
|
}
|
|
bwlvs[i] = bwlv
|
|
} else {
|
|
bwlv := &blockWithLenVotes{
|
|
blockHash,
|
|
uint16(0),
|
|
}
|
|
bwlvs[i] = bwlv
|
|
}
|
|
}
|
|
|
|
// Blocks with the most votes appear at the top of the list.
|
|
sort.Sort(sort.Reverse(ByNumberOfVotes(bwlvs)))
|
|
|
|
var sortedUsefulBlocks []chainhash.Hash
|
|
minimumVotesRequired := uint16((mp.cfg.ChainParams.TicketsPerBlock / 2) + 1)
|
|
for _, bwlv := range bwlvs {
|
|
if bwlv.Votes >= minimumVotesRequired {
|
|
sortedUsefulBlocks = append(sortedUsefulBlocks, bwlv.Block)
|
|
}
|
|
}
|
|
|
|
if sortedUsefulBlocks == nil {
|
|
return nil, miningRuleError(ErrNotEnoughVoters,
|
|
"no block had enough votes to build on top of")
|
|
}
|
|
|
|
// Make sure we don't reorganize the chain needlessly if the top block has
|
|
// the same amount of votes as the current leader after the sort. After this
|
|
// point, all blocks listed in sortedUsefulBlocks definitely also have the
|
|
// minimum number of votes required.
|
|
topBlockVotes, exists := mp.votes[currentTopBlock]
|
|
topBlockVotesLen := 0
|
|
if exists {
|
|
topBlockVotesLen = getNumberOfVotesOnBlock(topBlockVotes)
|
|
}
|
|
if bwlvs[0].Votes == uint16(topBlockVotesLen) {
|
|
if !bwlvs[0].Block.IsEqual(¤tTopBlock) {
|
|
// Find our block in the list.
|
|
pos := 0
|
|
for i, bwlv := range bwlvs {
|
|
if bwlv.Block.IsEqual(¤tTopBlock) {
|
|
pos = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if pos == 0 { // Should never happen...
|
|
return nil, fmt.Errorf("couldn't find top block in list")
|
|
}
|
|
|
|
// Swap the top block into the first position. We directly access
|
|
// sortedUsefulBlocks useful blocks here with the assumption that
|
|
// since the values were accumulated from blvs, they should be
|
|
// in the same positions and we shouldn't be able to access anything
|
|
// out of bounds.
|
|
sortedUsefulBlocks[0], sortedUsefulBlocks[pos] =
|
|
sortedUsefulBlocks[pos], sortedUsefulBlocks[0]
|
|
}
|
|
}
|
|
|
|
return sortedUsefulBlocks, nil
|
|
}
|
|
|
|
// SortParentsByVotes is the concurrency safe exported version of
|
|
// sortParentsByVotes.
|
|
func (mp *txMemPool) SortParentsByVotes(currentTopBlock chainhash.Hash,
|
|
blocks []chainhash.Hash) ([]chainhash.Hash, error) {
|
|
mp.votesMtx.Lock()
|
|
defer mp.votesMtx.Unlock()
|
|
|
|
sortedBlocks, err := mp.sortParentsByVotes(currentTopBlock, blocks)
|
|
|
|
return sortedBlocks, err
|
|
}
|
|
|
|
// Ensure the txMemPool type implements the mining.TxSource interface.
|
|
var _ mining.TxSource = (*txMemPool)(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 *txMemPool) removeOrphan(txHash *chainhash.Hash) {
|
|
txmpLog.Tracef("Removing orphan transaction %v", txHash)
|
|
|
|
// Nothing to do if passed tx is not an orphan.
|
|
tx, exists := mp.orphans[*txHash]
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
// Remove the reference from the previous orphan index.
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
originTxHash := txIn.PreviousOutPoint.Hash
|
|
if orphans, exists := mp.orphansByPrev[originTxHash]; exists {
|
|
delete(orphans, *tx.Hash())
|
|
|
|
// Remove the map entry altogether if there are no
|
|
// longer any orphans which depend on it.
|
|
if len(orphans) == 0 {
|
|
delete(mp.orphansByPrev, originTxHash)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 *txMemPool) RemoveOrphan(txHash *chainhash.Hash) {
|
|
mp.Lock()
|
|
mp.removeOrphan(txHash)
|
|
mp.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 *txMemPool) limitNumOrphans() error {
|
|
if len(mp.orphans)+1 > mp.cfg.Policy.MaxOrphanTxs &&
|
|
mp.cfg.Policy.MaxOrphanTxs > 0 {
|
|
|
|
// Generate a cryptographically random hash.
|
|
randHashBytes := make([]byte, chainhash.HashSize)
|
|
_, err := rand.Read(randHashBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
randHashNum := new(big.Int).SetBytes(randHashBytes)
|
|
|
|
// Try to find the first entry that is greater than the random
|
|
// hash. Use the first entry (which is already pseudorandom due
|
|
// to Go's range statement over maps) as a fallback if none of
|
|
// the hashes in the orphan pool are larger than the random
|
|
// hash.
|
|
var foundHash *chainhash.Hash
|
|
for txHash := range mp.orphans {
|
|
if foundHash == nil {
|
|
foundHash = &txHash
|
|
}
|
|
txHashNum := blockchain.HashToBig(&txHash)
|
|
if txHashNum.Cmp(randHashNum) > 0 {
|
|
foundHash = &txHash
|
|
break
|
|
}
|
|
}
|
|
|
|
mp.removeOrphan(foundHash)
|
|
}
|
|
|
|
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 *txMemPool) addOrphan(tx *dcrutil.Tx) {
|
|
// 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 {
|
|
originTxHash := txIn.PreviousOutPoint.Hash
|
|
if _, exists := mp.orphansByPrev[originTxHash]; !exists {
|
|
mp.orphansByPrev[originTxHash] =
|
|
make(map[chainhash.Hash]*dcrutil.Tx)
|
|
}
|
|
mp.orphansByPrev[originTxHash][*tx.Hash()] = tx
|
|
}
|
|
|
|
txmpLog.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 *txMemPool) 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 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.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
|
|
}
|
|
|
|
// 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 *txMemPool) 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 *txMemPool) IsTransactionInPool(hash *chainhash.Hash) bool {
|
|
// Protect concurrent access.
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
return mp.isTransactionInPool(hash)
|
|
}
|
|
|
|
// 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 *txMemPool) 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 *txMemPool) IsOrphanInPool(hash *chainhash.Hash) bool {
|
|
// Protect concurrent access.
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
return mp.isOrphanInPool(hash)
|
|
}
|
|
|
|
// 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 *txMemPool) 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 *txMemPool) HaveTransaction(hash *chainhash.Hash) bool {
|
|
// Protect concurrent access.
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
return mp.haveTransaction(hash)
|
|
}
|
|
|
|
// 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 *txMemPool) 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 *txMemPool) HaveTransactions(hashes []*chainhash.Hash) []bool {
|
|
// Protect concurrent access.
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
return mp.haveTransactions(hashes)
|
|
}
|
|
|
|
// 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 *txMemPool) removeTransaction(tx *dcrutil.Tx, removeRedeemers bool) {
|
|
txmpLog.Tracef("Removing transaction %v", tx.Hash())
|
|
|
|
msgTx := tx.MsgTx()
|
|
txHash := tx.Hash()
|
|
var txType stake.TxType
|
|
if removeRedeemers {
|
|
// Remove any transactions which rely on this one.
|
|
txType = stake.DetermineTxType(msgTx)
|
|
tree := wire.TxTreeRegular
|
|
if txType != stake.TxTypeRegular {
|
|
tree = wire.TxTreeStake
|
|
}
|
|
for i := uint32(0); i < uint32(len(msgTx.TxOut)); i++ {
|
|
outpoint := wire.NewOutPoint(txHash, i, tree)
|
|
if txRedeemer, exists := mp.outpoints[*outpoint]; exists {
|
|
mp.removeTransaction(txRedeemer, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the transaction if needed.
|
|
if txDesc, exists := mp.pool[*txHash]; exists {
|
|
// 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())
|
|
}
|
|
}
|
|
|
|
// 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 *txMemPool) RemoveTransaction(tx *dcrutil.Tx, removeRedeemers bool) {
|
|
// Protect concurrent access.
|
|
mp.Lock()
|
|
defer mp.Unlock()
|
|
|
|
mp.removeTransaction(tx, removeRedeemers)
|
|
}
|
|
|
|
// 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 *txMemPool) RemoveDoubleSpends(tx *dcrutil.Tx) {
|
|
// Protect concurrent access.
|
|
mp.Lock()
|
|
defer mp.Unlock()
|
|
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
|
if txRedeemer, ok := mp.outpoints[txIn.PreviousOutPoint]; ok {
|
|
if !txRedeemer.Hash().IsEqual(tx.Hash()) {
|
|
mp.removeTransaction(txRedeemer, true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 *txMemPool) addTransaction(utxoView *blockchain.UtxoViewpoint,
|
|
tx *dcrutil.Tx, txType stake.TxType, height int64, fee int64) {
|
|
msgTx := tx.MsgTx()
|
|
// Add the transaction to the pool and mark the referenced outpoints
|
|
// as spent by the pool.
|
|
mp.pool[*tx.Hash()] = &mempoolTxDesc{
|
|
TxDesc: mining.TxDesc{
|
|
Tx: tx,
|
|
Type: txType,
|
|
Added: time.Now(),
|
|
Height: height,
|
|
Fee: fee,
|
|
},
|
|
StartingPriority: 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)
|
|
}
|
|
}
|
|
|
|
// 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 *txMemPool) 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 (txType == stake.TxTypeSSGen || txType == stake.TxTypeSSRtx) &&
|
|
(i == 0) {
|
|
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
|
|
}
|
|
|
|
// isTxTreeValid checks the map of votes for a block to see if the tx
|
|
// tree regular for the block at HEAD is valid.
|
|
func (mp *txMemPool) isTxTreeValid(newestHash *chainhash.Hash) bool {
|
|
// There are no votes on the block currently; assume it's valid.
|
|
if mp.votes[*newestHash] == nil {
|
|
return true
|
|
}
|
|
|
|
// There are not possibly enough votes to tell if the txTree is valid;
|
|
// assume it's valid.
|
|
if len(mp.votes[*newestHash]) <=
|
|
int(mp.cfg.ChainParams.TicketsPerBlock/2) {
|
|
return true
|
|
}
|
|
|
|
// Otherwise, tally the votes and determine if it's valid or not.
|
|
yea := 0
|
|
nay := 0
|
|
|
|
for _, vote := range mp.votes[*newestHash] {
|
|
// End of list, break.
|
|
if vote == nil {
|
|
break
|
|
}
|
|
|
|
if vote.Vote == true {
|
|
yea++
|
|
} else {
|
|
nay++
|
|
}
|
|
}
|
|
|
|
if yea > nay {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsTxTreeValid calls isTxTreeValid, but makes it safe for concurrent access.
|
|
func (mp *txMemPool) IsTxTreeValid(best *chainhash.Hash) bool {
|
|
mp.votesMtx.Lock()
|
|
defer mp.votesMtx.Unlock()
|
|
isValid := mp.isTxTreeValid(best)
|
|
|
|
return isValid
|
|
}
|
|
|
|
// fetchInputUtxos loads utxo details about the input transactions referenced by
|
|
// the passed transaction. First, it loads the details form 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 *txMemPool) fetchInputUtxos(tx *dcrutil.Tx) (*blockchain.UtxoViewpoint, error) {
|
|
best := mp.cfg.Chain.BestSnapshot()
|
|
tv := mp.IsTxTreeValid(best.Hash)
|
|
utxoView, err := mp.cfg.FetchUtxoView(tx, tv)
|
|
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, mempoolHeight,
|
|
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 *txMemPool) FetchTransaction(txHash *chainhash.Hash, includeRecentBlock bool) (*dcrutil.Tx, error) {
|
|
// Protect concurrent access.
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
if txDesc, exists := mp.pool[*txHash]; exists {
|
|
return txDesc.Tx, nil
|
|
}
|
|
|
|
// For Decred, the latest block is considered "unconfirmed"
|
|
// for the regular transaction tree. Search that if the
|
|
// user indicates too, as well.
|
|
if includeRecentBlock {
|
|
best := mp.cfg.Chain.BestSnapshot()
|
|
bl, err := mp.cfg.Chain.BlockByHash(best.Hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, tx := range bl.Transactions() {
|
|
if tx.Hash().IsEqual(txHash) {
|
|
return 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 *txMemPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew,
|
|
rateLimit, allowHighFees 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. This check is intended to
|
|
// be a quick check to weed out duplicates.
|
|
if mp.haveTransaction(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 chain 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 blockchain.IsCoinBase(tx) {
|
|
str := fmt.Sprintf("transaction %v is an individual coinbase",
|
|
txHash)
|
|
return nil, txRuleError(wire.RejectInvalid, str)
|
|
}
|
|
|
|
// Don't accept transactions with a lock time after the maximum int32
|
|
// value for now. This is an artifact of older bitcoind clients which
|
|
// treated this field as an int32 and would treat anything larger
|
|
// incorrectly (as negative).
|
|
if msgTx.LockTime > math.MaxInt32 {
|
|
str := fmt.Sprintf("transaction %v has a lock time after "+
|
|
"2038 which is not accepted yet", txHash)
|
|
return nil, txRuleError(wire.RejectNonstandard, 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.
|
|
best := mp.cfg.Chain.BestSnapshot()
|
|
nextBlockHeight := best.Height + 1
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Don't allow non-standard transactions if the network parameters
|
|
// forbid their relaying.
|
|
if !activeNetParams.RelayNonStdTxs {
|
|
err := checkTransactionStandard(tx, txType, nextBlockHeight,
|
|
mp.cfg.TimeSource, mp.cfg.Policy.MinRelayTxFee)
|
|
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.
|
|
if txType == stake.TxTypeSStx {
|
|
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 difficuly (ticket diff %v < next diff %v)",
|
|
txHash, msgTx.TxOut[0].Value, sDiff)
|
|
return nil, txRuleError(wire.RejectInsufficientFee, str)
|
|
}
|
|
}
|
|
|
|
// Handle stake transaction double spending exceptions.
|
|
if (txType == stake.TxTypeSSGen) || (txType == stake.TxTypeSSRtx) {
|
|
if txType == stake.TxTypeSSGen {
|
|
ssGenAlreadyFound := 0
|
|
for _, mpTx := range mp.pool {
|
|
if mpTx.Type == stake.TxTypeSSGen {
|
|
if mpTx.Tx.MsgTx().TxIn[1].PreviousOutPoint ==
|
|
msgTx.TxIn[1].PreviousOutPoint {
|
|
ssGenAlreadyFound++
|
|
}
|
|
}
|
|
if ssGenAlreadyFound > maxSSGensDoubleSpends {
|
|
str := fmt.Sprintf("transaction %v in the pool "+
|
|
"with more than %v ssgens",
|
|
msgTx.TxIn[1].PreviousOutPoint,
|
|
maxSSGensDoubleSpends)
|
|
return nil, txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
}
|
|
}
|
|
|
|
if txType == stake.TxTypeSSRtx {
|
|
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 ssrtx. Only one ssrtx allowed.",
|
|
msgTx.TxIn[0].PreviousOutPoint)
|
|
return nil, txRuleError(wire.RejectDuplicate, str)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// 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.
|
|
err = mp.checkPoolDoubleSpend(tx, txType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Votes that are on too old of blocks are rejected.
|
|
if txType == stake.TxTypeSSGen {
|
|
_, voteHeight, err := stake.SSGenBlockVotedOn(msgTx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if (int64(voteHeight) < nextBlockHeight-maximumVoteAgeDelta) &&
|
|
!cfg.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
|
|
// 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 && txType == stake.TxTypeSSGen {
|
|
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 {
|
|
txmpLog.Tracef("Transaction %v uses unknown input %v "+
|
|
"and will be considered an orphan", txHash,
|
|
txIn.PreviousOutPoint.Hash)
|
|
continue
|
|
}
|
|
if entry.IsFullySpent() {
|
|
txmpLog.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
|
|
}
|
|
|
|
// Perform several checks on the transaction inputs using the invariant
|
|
// rules in chain 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.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 network
|
|
// parameters forbid their relaying.
|
|
if !activeNetParams.RelayNonStdTxs {
|
|
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, (txType == stake.TxTypeSSGen))
|
|
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 exceeed 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 := calcPriority(msgTx, utxoView,
|
|
nextBlockHeight)
|
|
if currentPriority <= minHighPriority {
|
|
str := fmt.Sprintf("transaction %v has insufficient "+
|
|
"priority (%g <= %g)", txHash,
|
|
currentPriority, 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()
|
|
// we decay passed data with an exponentially decaying ~10
|
|
// minutes window - matches bitcoind handling.
|
|
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)
|
|
txmpLog.Tracef("rate limit: curTotal %v, nextTotal: %v, "+
|
|
"limit %v", oldTotal, mp.pennyTotal,
|
|
mp.cfg.Policy.FreeTxRelayLimit*10*1000)
|
|
}
|
|
|
|
// Set an absolute threshold for ticket rejection and obey it. Tickets
|
|
// are treated differently in the mempool because they have a set
|
|
// difficulty and generally a window in which they expire.
|
|
//
|
|
// This applies to tickets transactions only.
|
|
minTicketFee := calcMinRequiredTxRelayFee(serializedSize, minTicketFee)
|
|
if (txFee < minTicketFee) && txType == stake.TxTypeSStx {
|
|
str := fmt.Sprintf("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. 100 * above the minimum fee/kb seems to be a
|
|
// reasonable amount to check. If people would like to avoid this check
|
|
// then they can AllowHighFees = true
|
|
if !allowHighFees {
|
|
maxFee := calcMinRequiredTxRelayFee(serializedSize*maxRelayFeeMultiplier,
|
|
cfg.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.
|
|
err = blockchain.ValidateTransactionScripts(tx, utxoView,
|
|
txscript.StandardVerifyFlags, 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, best.Height, txFee)
|
|
|
|
// If it's an SSGen (vote), insert it into the list of
|
|
// votes.
|
|
if txType == stake.TxTypeSSGen {
|
|
err := mp.InsertVote(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
txmpLog.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 *txMemPool) MaybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit bool) ([]*chainhash.Hash, error) {
|
|
// Protect concurrent access.
|
|
mp.Lock()
|
|
defer mp.Unlock()
|
|
|
|
return mp.maybeAcceptTransaction(tx, isNew, rateLimit, true)
|
|
}
|
|
|
|
// 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 *txMemPool) processOrphans(hash *chainhash.Hash) []*dcrutil.Tx {
|
|
var acceptedTxns []*dcrutil.Tx
|
|
|
|
// Start with processing at least the passed hash.
|
|
processHashes := list.New()
|
|
processHashes.PushBack(hash)
|
|
for processHashes.Len() > 0 {
|
|
// Pop the first hash to process.
|
|
firstElement := processHashes.Remove(processHashes.Front())
|
|
processHash := firstElement.(*chainhash.Hash)
|
|
|
|
// Look up all orphans that are referenced by the transaction we
|
|
// just accepted. This will typically only be one, but it could
|
|
// be multiple if the referenced transaction contains multiple
|
|
// outputs. Skip to the next item on the list of hashes to
|
|
// process if there are none.
|
|
orphans, exists := mp.orphansByPrev[*processHash]
|
|
if !exists || orphans == nil {
|
|
continue
|
|
}
|
|
|
|
for _, tx := range orphans {
|
|
// Remove the orphan from the orphan pool. Current
|
|
// behavior requires that all saved orphans with
|
|
// a newly accepted parent are removed from the orphan
|
|
// pool and potentially added to the memory pool, but
|
|
// transactions which cannot be added to memory pool
|
|
// (including due to still being orphans) are expunged
|
|
// from the orphan pool.
|
|
//
|
|
// TODO(jrick): The above described behavior sounds
|
|
// like a bug, and I think we should investigate
|
|
// potentially moving orphans to the memory pool, but
|
|
// leaving them in the orphan pool if not all parent
|
|
// transactions are known yet.
|
|
orphanHash := tx.Hash()
|
|
mp.removeOrphan(orphanHash)
|
|
|
|
// Potentially accept the transaction into the
|
|
// transaction pool.
|
|
missingParents, err := mp.maybeAcceptTransaction(tx,
|
|
true, true, true)
|
|
if err != nil {
|
|
// TODO: Remove orphans that depend on this
|
|
// failed transaction.
|
|
txmpLog.Debugf("Unable to move "+
|
|
"orphan transaction %v to mempool: %v",
|
|
tx.Hash(), err)
|
|
continue
|
|
}
|
|
|
|
if len(missingParents) > 0 {
|
|
// Transaction is still an orphan, so add it
|
|
// back.
|
|
mp.addOrphan(tx)
|
|
continue
|
|
}
|
|
|
|
// Add this transaction to the list of transactions
|
|
// that are no longer orphans.
|
|
acceptedTxns = append(acceptedTxns, tx)
|
|
|
|
// Add this transaction to the list of transactions to
|
|
// process so any orphans that depend on this one are
|
|
// handled too.
|
|
//
|
|
// TODO(jrick): In the case that this is still an orphan,
|
|
// we know that any other transactions in the orphan
|
|
// pool with this orphan as their parent are still
|
|
// orphans as well, and should be removed. While
|
|
// recursively calling removeOrphan and
|
|
// maybeAcceptTransaction on these transactions is not
|
|
// wrong per se, it is overkill if all we care about is
|
|
// recursively removing child transactions of this
|
|
// orphan.
|
|
processHashes.PushBack(orphanHash)
|
|
}
|
|
}
|
|
|
|
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 *txMemPool) PruneStakeTx(requiredStakeDifficulty, height int64) {
|
|
// Protect concurrent access.
|
|
mp.Lock()
|
|
defer mp.Unlock()
|
|
|
|
mp.pruneStakeTx(requiredStakeDifficulty, height)
|
|
}
|
|
|
|
func (mp *txMemPool) 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 may no longer
|
|
// be able to be included into a block.
|
|
func (mp *txMemPool) PruneExpiredTx(height int64) {
|
|
// Protect concurrent access.
|
|
mp.Lock()
|
|
defer mp.Unlock()
|
|
|
|
mp.pruneExpiredTx(height)
|
|
}
|
|
|
|
func (mp *txMemPool) pruneExpiredTx(height int64) {
|
|
for _, tx := range mp.pool {
|
|
if tx.Tx.MsgTx().Expiry != 0 {
|
|
if height >= int64(tx.Tx.MsgTx().Expiry) {
|
|
txmpLog.Debugf("Pruning expired transaction %v from the "+
|
|
"mempool", tx.Tx.Hash())
|
|
mp.removeTransaction(tx.Tx, true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 *txMemPool) ProcessOrphans(hash *chainhash.Hash) []*dcrutil.Tx {
|
|
mp.Lock()
|
|
acceptedTxns := mp.processOrphans(hash)
|
|
mp.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 transaactions that were added as a result of
|
|
// the passed one being accepted.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *txMemPool) ProcessTransaction(tx *dcrutil.Tx, allowOrphan,
|
|
rateLimit, allowHighFees bool) ([]*dcrutil.Tx, error) {
|
|
// Protect concurrent access.
|
|
mp.Lock()
|
|
defer mp.Unlock()
|
|
var err error
|
|
defer func() {
|
|
if err != nil {
|
|
txmpLog.Tracef("Failed to process transaction %v: %s",
|
|
tx.Hash(), err.Error())
|
|
}
|
|
}()
|
|
|
|
txmpLog.Tracef("Processing transaction %v", tx.Hash())
|
|
|
|
// Potentially accept the transaction to the memory pool.
|
|
var missingParents []*chainhash.Hash
|
|
missingParents, err = mp.maybeAcceptTransaction(tx, true, rateLimit,
|
|
allowHighFees)
|
|
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 are no longer orphans if all inputs are
|
|
// now available) and repeat for those accepted transactions
|
|
// until there are no more.
|
|
newTxs := mp.processOrphans(tx.Hash())
|
|
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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// 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 *txMemPool) Count() int {
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
return len(mp.pool)
|
|
}
|
|
|
|
// TxHashes returns a slice of hashes for all of the transactions in the memory
|
|
// pool.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *txMemPool) TxHashes() []*chainhash.Hash {
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
hashes := make([]*chainhash.Hash, len(mp.pool))
|
|
i := 0
|
|
for hash := range mp.pool {
|
|
hashCopy := hash
|
|
hashes[i] = &hashCopy
|
|
i++
|
|
}
|
|
|
|
return hashes
|
|
}
|
|
|
|
// TxDescs returns a slice of descriptors for all the transactions in the pool.
|
|
// The descriptors are to be treated as read only.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *txMemPool) TxDescs() []*mempoolTxDesc {
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
descs := make([]*mempoolTxDesc, len(mp.pool))
|
|
i := 0
|
|
for _, desc := range mp.pool {
|
|
descs[i] = desc
|
|
i++
|
|
}
|
|
|
|
return descs
|
|
}
|
|
|
|
// 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 *txMemPool) MiningDescs() []*mining.TxDesc {
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
descs := make([]*mining.TxDesc, len(mp.pool))
|
|
i := 0
|
|
for _, desc := range mp.pool {
|
|
descs[i] = &desc.TxDesc
|
|
i++
|
|
}
|
|
|
|
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 *txMemPool) LastUpdated() time.Time {
|
|
return time.Unix(atomic.LoadInt64(&mp.lastUpdated), 0)
|
|
}
|
|
|
|
// CheckIfTxsExist checks a list of transaction hashes against the mempool
|
|
// and returns true if they all exist in the mempool, otherwise false.
|
|
//
|
|
// This function is safe for concurrent access.
|
|
func (mp *txMemPool) CheckIfTxsExist(hashes []chainhash.Hash) bool {
|
|
mp.RLock()
|
|
defer mp.RUnlock()
|
|
|
|
inPool := true
|
|
for _, h := range hashes {
|
|
if _, exists := mp.pool[h]; !exists {
|
|
inPool = false
|
|
break
|
|
}
|
|
}
|
|
|
|
return inPool
|
|
}
|
|
|
|
// newTxMemPool returns a new memory pool for validating and storing standalone
|
|
// transactions until they are mined into a block.
|
|
func newTxMemPool(cfg *mempoolConfig) *txMemPool {
|
|
memPool := &txMemPool{
|
|
cfg: *cfg,
|
|
pool: make(map[chainhash.Hash]*mempoolTxDesc),
|
|
orphans: make(map[chainhash.Hash]*dcrutil.Tx),
|
|
orphansByPrev: make(map[chainhash.Hash]map[chainhash.Hash]*dcrutil.Tx),
|
|
outpoints: make(map[wire.OutPoint]*dcrutil.Tx),
|
|
votes: make(map[chainhash.Hash][]*VoteTx),
|
|
subsidyCache: cfg.Chain.FetchSubsidyCache(),
|
|
}
|
|
|
|
return memPool
|
|
}
|