lbcd/mempool.go

1061 lines
36 KiB
Go
Raw Normal View History

2014-01-01 17:16:15 +01:00
// Copyright (c) 2013-2014 Conformal Systems LLC.
// 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"
"github.com/conformal/btcchain"
"github.com/conformal/btcdb"
"github.com/conformal/btcscript"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
"math"
"math/big"
"sync"
"time"
)
// TxRuleError identifies a rule violation. It is used to indicate that
// processing of a transaction failed due to one of the many validation
// rules. The caller can use type assertions to determine if a failure was
// specifically due to a rule violation.
type TxRuleError string
// Error satisfies the error interface to print human-readable errors.
func (e TxRuleError) Error() string {
return string(e)
}
const (
// mempoolHeight is the height used for the "block" height field of the
// contextual transaction information provided in a transaction store.
mempoolHeight = 0x7fffffff
// maxOrphanTransactions is the maximum number of orphan transactions
// that can be queued. At the time this comment was written, this
// equates to 10,000 transactions, but will increase if the max allowed
// block payload increases.
maxOrphanTransactions = btcwire.MaxBlockPayload / 100
// maxOrphanTxSize is the maximum size allowed for orphan transactions.
// This helps prevent memory exhaustion attacks from sending a lot of
// of big orphans.
maxOrphanTxSize = 5000
// maxStandardTxSize is the maximum size allowed for transactions that
// are considered standard and will therefore be relayed and considered
// for mining.
maxStandardTxSize = btcwire.MaxBlockPayload / 10
// maxStandardSigScriptSize is the maximum size allowed for a
// transaction input signature script to be considered standard. This
// value allows for a CHECKMULTISIG pay-to-sript-hash with 3 signatures
// since each signature is about 80-bytes, the 3 corresponding public
// keys are 65-bytes each if uncompressed, and the script opcodes take
// a few extra bytes. This value also adds a few extra bytes for
// prosperity. 3*80 + 3*65 + 65 = 500
maxStandardSigScriptSize = 500
// maxStandardMultiSigKeys is the maximum number of public keys allowed
// in a multi-signature transaction output script for it to be
// considered standard.
maxStandardMultiSigKeys = 3
// minTxRelayFee is the minimum fee in satoshi that is required for a
// transaction to be treated as free for relay purposes. It is also
// used to help determine if a transaction is considered dust and as a
// base for calculating minimum required fees for larger transactions.
// This value is in Satoshi/KB (kilobyte, not kibibyte).
minTxRelayFee = 10000
// blockPrioritySize is the number of bytes reserved in a block for
// high-priority transactions. It is mainly used to help determine the
// minimum required fee for a transaction.
blockPrioritySize = 27000
)
// TxDesc is a descriptor containing a transaction in the mempool and the
// metadata we store about it.
type TxDesc struct {
Tx *btcutil.Tx // Transaction.
2013-12-17 15:02:35 +01:00
Added time.Time // Time when added to pool.
Height int64 // Blockheight when added to pool.
Fee int64 // Transaction fees.
}
// 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 {
sync.RWMutex
server *server
pool map[btcwire.ShaHash]*TxDesc
orphans map[btcwire.ShaHash]*btcutil.Tx
orphansByPrev map[btcwire.ShaHash]*list.List
outpoints map[btcwire.OutPoint]*btcutil.Tx
}
// isDust returns whether or not the passed transaction output amount is
// considered dust or not. Dust is defined in terms of the minimum transaction
// relay fee. In particular, if the cost to the network to spend coins is more
// than 1/3 of the minimum transaction relay fee, it is considered dust.
func isDust(txOut *btcwire.TxOut) bool {
// The total serialized size consists of the output and the associated
// input script to redeem it. Since there is no input script
// to redeem it yet, use the minimum size of a typical input script.
//
// Pay-to-pubkey-hash bytes breakdown:
//
// Output to hash (34 bytes):
// 8 value, 1 script len, 25 script [1 OP_DUP, 1 OP_HASH_160,
// 1 OP_DATA_20, 20 hash, 1 OP_EQUALVERIFY, 1 OP_CHECKSIG]
//
// Input with compressed pubkey (148 bytes):
// 36 prev outpoint, 1 script len, 107 script [1 OP_DATA_72, 72 sig,
// 1 OP_DATA_33, 33 compressed pubkey], 4 sequence
//
// Input with uncompressed pubkey (180 bytes):
// 36 prev outpoint, 1 script len, 139 script [1 OP_DATA_72, 72 sig,
// 1 OP_DATA_65, 65 compressed pubkey], 4 sequence
//
// Pay-to-pubkey bytes breakdown:
//
// Output to compressed pubkey (44 bytes):
// 8 value, 1 script len, 35 script [1 OP_DATA_33,
// 33 compressed pubkey, 1 OP_CHECKSIG]
//
// Output to uncompressed pubkey (76 bytes):
// 8 value, 1 script len, 67 script [1 OP_DATA_65, 65 pubkey,
// 1 OP_CHECKSIG]
//
// Input (114 bytes):
// 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72,
// 72 sig], 4 sequence
//
// Theoretically this could examine the script type of the output script
// and use a different size for the typical input script size for
// pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns,
// but the only combinination which is less than the value chosen is
// a pay-to-pubkey script with a compressed pubkey, which is not very
// common.
//
// The most common scripts are pay-to-pubkey-hash, and as per the above
// breakdown, the minimum size of a p2pkh input script is 148 bytes. So
// that figure is used.
totalSize := txOut.SerializeSize() + 148
// The output is considered dust if the cost to the network to spend the
// coins is more than 1/3 of the minimum free transaction relay fee.
// minFreeTxRelayFee is in Satoshi/KB (kilobyte, not kibibyte), so
// multiply by 1000 to convert bytes.
//
// Using the typical values for a pay-to-pubkey-hash transaction from
// the breakdown above and the default minimum free transaction relay
// fee of 10000, this equates to values less than 5460 satoshi being
// considered dust.
//
// The following is equivalent to (value/totalSize) * (1/3) * 1000
// without needing to do floating point math.
return txOut.Value*1000/(3*int64(totalSize)) < minTxRelayFee
}
// checkPkScriptStandard performs a series of checks on a transaction ouput
// script (public key script) to ensure it is a "standard" public key script.
// A standard public key script is one that is a recognized form, and for
// multi-signature scripts, only contains from 1 to maxStandardMultiSigKeys
// public keys.
func checkPkScriptStandard(pkScript []byte, scriptClass btcscript.ScriptClass) error {
switch scriptClass {
case btcscript.MultiSigTy:
numPubKeys, numSigs, err := btcscript.CalcMultiSigStats(pkScript)
if err != nil {
return err
}
// A standard multi-signature public key script must contain
// from 1 to maxStandardMultiSigKeys public keys.
if numPubKeys < 1 {
str := fmt.Sprintf("multi-signature script with no " +
"pubkeys")
return TxRuleError(str)
}
if numPubKeys > maxStandardMultiSigKeys {
str := fmt.Sprintf("multi-signature script with %d "+
"public keys which is more than the allowed "+
"max of %d", numPubKeys, maxStandardMultiSigKeys)
return TxRuleError(str)
}
// A standard multi-signature public key script must have at
// least 1 signature and no more signatures than available
// public keys.
if numSigs < 1 {
str := fmt.Sprintf("multi-signature script with no " +
"signatures")
return TxRuleError(str)
}
if numSigs > numPubKeys {
str := fmt.Sprintf("multi-signature script with %d "+
"signatures which is more than the available "+
"%d public keys", numSigs, numPubKeys)
return TxRuleError(str)
}
case btcscript.NonStandardTy:
return TxRuleError(fmt.Sprintf("non-standard script form"))
}
return nil
}
// checkTransactionStandard performs a series of checks on a transaction to
// ensure it is a "standard" transaction. A standard transaction is one that
// conforms to several additional limiting cases over what is considered a
// "sane" transaction such as having a version in the supported range, being
// finalized, conforming to more stringent size constraints, having scripts
// of recognized forms, and not containing "dust" outputs (those that are
// so small it costs more to process them than they are worth).
func checkTransactionStandard(tx *btcutil.Tx, height int64) error {
msgTx := tx.MsgTx()
// The transaction must be a currently supported version.
if msgTx.Version > btcwire.TxVersion || msgTx.Version < 1 {
str := fmt.Sprintf("transaction version %d is not in the "+
"valid range of %d-%d", msgTx.Version, 1,
btcwire.TxVersion)
return TxRuleError(str)
}
// The transaction must be finalized to be standard and therefore
// considered for inclusion in a block.
if !btcchain.IsFinalizedTransaction(tx, height, time.Now()) {
str := fmt.Sprintf("transaction is not finalized")
return TxRuleError(str)
}
// Since extremely large transactions with a lot of inputs can cost
// almost as much to process as the sender fees, limit the maximum
// size of a transaction. This also helps mitigate CPU exhaustion
// attacks.
serializedLen := msgTx.SerializeSize()
if serializedLen > maxStandardTxSize {
str := fmt.Sprintf("transaction size of %v is larger than max "+
"allowed size of %v", serializedLen, maxStandardTxSize)
return TxRuleError(str)
}
for i, txIn := range msgTx.TxIn {
// Each transaction input signature script must not exceed the
// maximum size allowed for a standard transaction. See
// the comment on maxStandardSigScriptSize for more details.
sigScriptLen := len(txIn.SignatureScript)
if sigScriptLen > maxStandardSigScriptSize {
str := fmt.Sprintf("transaction input %d: signature "+
"script size of %d bytes is large than max "+
"allowed size of %d bytes", i, sigScriptLen,
maxStandardSigScriptSize)
return TxRuleError(str)
}
// Each transaction input signature script must only contain
// opcodes which push data onto the stack.
if !btcscript.IsPushOnlyScript(txIn.SignatureScript) {
str := fmt.Sprintf("transaction input %d: signature "+
"script is not push only", i)
return TxRuleError(str)
}
}
// None of the output public key scripts can be a non-standard script or
// be "dust".
numNullDataOutputs := 0
for i, txOut := range msgTx.TxOut {
scriptClass := btcscript.GetScriptClass(txOut.PkScript)
err := checkPkScriptStandard(txOut.PkScript, scriptClass)
if err != nil {
str := fmt.Sprintf("transaction output %d: %v", i, err)
return TxRuleError(str)
}
// Accumulate the number of outputs which only carry data.
if scriptClass == btcscript.NullDataTy {
numNullDataOutputs++
}
if isDust(txOut) {
str := fmt.Sprintf("transaction output %d: payment "+
"of %d is dust", i, txOut.Value)
return TxRuleError(str)
}
}
// A standard transaction must not have more than one output script that
// only carries data.
if numNullDataOutputs > 1 {
return TxRuleError("more than one transaction output is a " +
"nulldata script")
}
return nil
}
// checkInputsStandard performs a series of checks on a transaction's inputs
// to ensure they are "standard". A standard transaction input is one that
// that consumes the expected number of elements from the stack and that number
// is the same as the output script pushes. This help prevent resource
// exhaustion attacks by "creative" use of scripts that are super expensive to
// process like OP_DUP OP_CHECKSIG OP_DROP repeated a large number of times
// followed by a final OP_TRUE.
func checkInputsStandard(tx *btcutil.Tx, txStore btcchain.TxStore) error {
// NOTE: The reference implementation also does a coinbase check here,
// but coinbases have already been rejected prior to calling this
// function so no need to recheck.
for i, txIn := range tx.MsgTx().TxIn {
// It is safe to elide existence and index checks here since
// they have already been checked prior to calling this
// function.
prevOut := txIn.PreviousOutpoint
originTx := txStore[prevOut.Hash].Tx.MsgTx()
originPkScript := originTx.TxOut[prevOut.Index].PkScript
// Calculate stats for the script pair.
scriptInfo, err := btcscript.CalcScriptInfo(txIn.SignatureScript,
originPkScript, true)
if err != nil {
return err
}
// A negative value for expected inputs indicates the script is
// non-standard in some way.
if scriptInfo.ExpectedInputs < 0 {
str := fmt.Sprintf("transaction input #%d expects %d "+
"inputs", i, scriptInfo.ExpectedInputs)
return TxRuleError(str)
}
// The script pair is non-standard if the number of available
// inputs does not match the number of expected inputs.
if scriptInfo.NumInputs != scriptInfo.ExpectedInputs {
str := fmt.Sprintf("transaction input #%d expects %d "+
"inputs, but referenced output script only "+
"provides %d", i, scriptInfo.ExpectedInputs,
scriptInfo.NumInputs)
return TxRuleError(str)
}
}
return nil
}
// calcMinRelayFee retuns the minimum transaction fee required for the passed
// transaction to be accepted into the memory pool and relayed.
func calcMinRelayFee(tx *btcutil.Tx) int64 {
// 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.
serializedLen := int64(tx.MsgTx().SerializeSize())
if serializedLen < (blockPrioritySize - 1000) {
return 0
}
// Calculate the minimum fee for a transaction to be allowed into the
// mempool and relayed by scaling the base fee (which is the minimum
// free transaction relay fee). minTxRelayFee is in Satoshi/KB
// (kilobyte, not kibibyte), so divide the transaction size by 1000 to
// convert to kilobytes. Also, integer division is used so fees only
// increase on full kilobyte boundaries.
minFee := (1 + serializedLen/1000) * minTxRelayFee
// Set the minimum fee to the maximum possible value if the calculated
// fee is not in the valid range for monetary amounts.
if minFee < 0 || minFee > btcutil.MaxSatoshi {
minFee = btcutil.MaxSatoshi
}
return minFee
}
// removeOrphan removes the passed orphan transaction from the orphan pool and
// previous orphan index.
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *txMemPool) removeOrphan(txHash *btcwire.ShaHash) {
// 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 {
for e := orphans.Front(); e != nil; e = e.Next() {
if e.Value.(*btcutil.Tx) == tx {
orphans.Remove(e)
break
}
}
// Remove the map entry altogether if there are no
// longer any orphans which depend on it.
if orphans.Len() == 0 {
delete(mp.orphansByPrev, originTxHash)
}
}
}
// Remove the transaction from the orphan pool.
delete(mp.orphans, *txHash)
}
// 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 > maxOrphanTransactions {
// Generate a cryptographically random hash.
randHashBytes := make([]byte, btcwire.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 *btcwire.ShaHash
for txHash := range mp.orphans {
if foundHash == nil {
foundHash = &txHash
}
txHashNum := btcchain.ShaHashToBig(&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 *btcutil.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.Sha()] = tx
for _, txIn := range tx.MsgTx().TxIn {
originTxHash := txIn.PreviousOutpoint.Hash
if mp.orphansByPrev[originTxHash] == nil {
mp.orphansByPrev[originTxHash] = list.New()
}
mp.orphansByPrev[originTxHash].PushBack(tx)
}
txmpLog.Debugf("Stored orphan transaction %v (total: %d)", tx.Sha(),
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 *btcutil.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
// maxOrphanTxSize * maxOrphanTransactions (which is 500MB as of the
// time this comment was written).
serializedLen := tx.MsgTx().SerializeSize()
if serializedLen > maxOrphanTxSize {
str := fmt.Sprintf("orphan transaction size of %d bytes is "+
"larger than max allowed size of %d bytes",
serializedLen, maxOrphanTxSize)
return TxRuleError(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 *btcwire.ShaHash) 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 *btcwire.ShaHash) 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 *btcwire.ShaHash) 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 *btcwire.ShaHash) 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 *btcwire.ShaHash) 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 *btcwire.ShaHash) bool {
// Protect concurrent access.
mp.RLock()
defer mp.RUnlock()
return mp.haveTransaction(hash)
}
// 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 *btcutil.Tx) {
// Remove any transactions which rely on this one.
txHash := tx.Sha()
for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ {
outpoint := btcwire.NewOutPoint(txHash, i)
if txRedeemer, exists := mp.outpoints[*outpoint]; exists {
mp.removeTransaction(txRedeemer)
}
}
// Remove the transaction and mark the referenced outpoints as unspent
// by the pool.
if txDesc, exists := mp.pool[*txHash]; exists {
for _, txIn := range txDesc.Tx.MsgTx().TxIn {
delete(mp.outpoints, txIn.PreviousOutpoint)
}
delete(mp.pool, *txHash)
}
}
// RemoveTransaction removes the passed transaction and any transactions which
// depend on it from the memory pool.
//
// This function is safe for concurrent access.
func (mp *txMemPool) RemoveTransaction(tx *btcutil.Tx) {
// Protect concurrent access.
mp.Lock()
defer mp.Unlock()
mp.removeTransaction(tx)
}
// 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 *btcutil.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.Sha().IsEqual(tx.Sha()) {
mp.removeTransaction(txRedeemer)
}
}
}
}
// 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(tx *btcutil.Tx, height, fee int64) {
// Add the transaction to the pool and mark the referenced outpoints
// as spent by the pool.
mp.pool[*tx.Sha()] = &TxDesc{
2013-12-17 15:02:35 +01:00
Tx: tx,
Added: time.Now(),
Height: height,
2013-12-17 15:02:35 +01:00
Fee: fee,
}
for _, txIn := range tx.MsgTx().TxIn {
mp.outpoints[txIn.PreviousOutpoint] = tx
}
}
// 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 *btcutil.Tx) error {
for _, txIn := range tx.MsgTx().TxIn {
if txR, exists := mp.outpoints[txIn.PreviousOutpoint]; exists {
str := fmt.Sprintf("transaction %v in the pool "+
"already spends the same coins", txR.Sha())
return TxRuleError(str)
}
}
return nil
}
// fetchInputTransactions fetches the input transactions referenced by the
// passed transaction. First, it fetches from the main chain, then it tries to
// fetch any missing inputs from the transaction pool.
//
// This function MUST be called with the mempool lock held (for reads).
func (mp *txMemPool) fetchInputTransactions(tx *btcutil.Tx) (btcchain.TxStore, error) {
txStore, err := mp.server.blockManager.blockChain.FetchTransactionStore(tx)
if err != nil {
return nil, err
}
// Attempt to populate any missing inputs from the transaction pool.
for _, txD := range txStore {
if txD.Err == btcdb.TxShaMissing || txD.Tx == nil {
if poolTxDesc, exists := mp.pool[*txD.Hash]; exists {
poolTx := poolTxDesc.Tx
txD.Tx = poolTx
txD.BlockHeight = mempoolHeight
txD.Spent = make([]bool, len(poolTx.MsgTx().TxOut))
txD.Err = nil
}
}
}
return txStore, 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 *btcwire.ShaHash) (*btcutil.Tx, error) {
// Protect concurrent access.
mp.RLock()
defer mp.RUnlock()
if txDesc, exists := mp.pool[*txHash]; 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).
func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNew bool) error {
if isOrphan != nil {
*isOrphan = false
}
txHash := tx.Sha()
// 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 TxRuleError(str)
}
// Perform preliminary sanity checks on the transaction. This makes
// use of btcchain which contains the invariant rules for what
// transactions are allowed into blocks.
err := btcchain.CheckTransactionSanity(tx)
if err != nil {
if _, ok := err.(btcchain.RuleError); ok {
return TxRuleError(err.Error())
}
return err
}
// A standalone transaction must not be a coinbase transaction.
if btcchain.IsCoinBase(tx) {
str := fmt.Sprintf("transaction %v is an individual coinbase",
txHash)
return TxRuleError(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 tx.MsgTx().LockTime > math.MaxInt32 {
str := fmt.Sprintf("transaction %v is has a lock time after "+
"2038 which is not accepted yet", txHash)
return TxRuleError(str)
}
// Get the current height of the main chain. A standalone transaction
// will be mined into the next block at best, so it's height is at least
// one more than the current height.
_, curHeight, err := mp.server.db.NewestSha()
if err != nil {
return err
}
nextBlockHeight := curHeight + 1
// Don't allow non-standard transactions on the main network.
if activeNetParams.btcnet == btcwire.MainNet {
err := checkTransactionStandard(tx, nextBlockHeight)
if err != nil {
str := fmt.Sprintf("transaction %v is not a standard "+
"transaction: %v", txHash, err)
return TxRuleError(str)
}
}
// The transaction may not use any of the same outputs as other
// transactions already in the pool as that would ultimately result in a
// double spend. This check is intended to be quick and therefore only
// detects double spends within the transaction pool itself. The
// transaction could still be double spending coins from the 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)
if err != nil {
return err
}
// Fetch all of the transactions 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.
txStore, err := mp.fetchInputTransactions(tx)
if err != nil {
return err
}
// Don't allow the transaction if it exists in the main chain and is not
// not already fully spent.
if txD, exists := txStore[*txHash]; exists && txD.Err == nil {
for _, isOutputSpent := range txD.Spent {
if !isOutputSpent {
str := fmt.Sprintf("transaction already exists")
return TxRuleError(str)
}
}
}
delete(txStore, *txHash)
// Transaction is an orphan if any of the inputs don't exist.
for _, txD := range txStore {
if txD.Err == btcdb.TxShaMissing {
if isOrphan != nil {
*isOrphan = true
}
return nil
}
}
// Perform several checks on the transaction inputs using the invariant
// rules in btcchain for what transactions are allowed into blocks.
// Also returns the fees associated with the transaction which will be
// used later.
txFee, err := btcchain.CheckTransactionInputs(tx, nextBlockHeight, txStore)
if err != nil {
if _, ok := err.(btcchain.RuleError); ok {
return TxRuleError(err.Error())
}
return err
}
// Don't allow transactions with non-standard inputs on the main
// network.
if activeNetParams.btcnet == btcwire.MainNet {
err := checkInputsStandard(tx, txStore)
if err != nil {
str := fmt.Sprintf("transaction %v has a non-standard "+
"input: %v", txHash, err)
return TxRuleError(str)
}
}
// NOTE: if you modify this code to accept non-standard transactions,
// you should add code here to check that the transaction does a
// reasonable number of ECDSA signature verifications.
// Don't allow transactions with fees too low to get into a mined block.
minRequiredFee := calcMinRelayFee(tx)
if txFee < minRequiredFee {
str := fmt.Sprintf("transaction %v has %d fees which is under "+
"the required amount of %d", txHash, txFee,
minRequiredFee)
return TxRuleError(str)
}
// TODO(davec): Rate-limit 'free' transactions. That is to say
// transactions which are less than the minimum relay fee and are
// therefore considered free.
// Verify crypto signatures for each input and reject the transaction if
// any don't verify.
flags := btcscript.ScriptBip16 | btcscript.ScriptCanonicalSignatures
err = btcchain.ValidateTransactionScripts(tx, txStore, flags)
if err != nil {
return err
}
// Add to transaction pool.
mp.addTransaction(tx, curHeight, txFee)
txmpLog.Debugf("Accepted transaction %v (pool size: %v)", txHash,
len(mp.pool))
Rework and improve websocket notification system. This commit refactors the entire websocket client code to resolve several issues with the previous implementation. Note that this commit does not change the public API for websockets. It only consists of internal improvements. The following is the major issues which have been addressed: - A slow websocket client could impede notifications to all clients - Long-running operations such as rescans would block all other requests until it had completed - The above two points taken together could lead to apparant hangs since the client doing the rescan would eventually run out of channel buffer and block the entire group of clients until the rescan completed - Disconnecting a websocket during certain operations could lead to a hang - Stopping the rpc server with operations under way could lead to a hang - There were no limits to the number of websocket clients that could connect The following is a summary of the major changes: - The websocket code has been split into two entities: a connection/notification manager and a websocket client - The new connection/notification manager acts as the entry point from the rest of the subsystems to feed data which potentially needs to notify clients - Each websocket client now has its own instance of the new websocket client type which controls its own lifecycle - The data flow has been completely redesigned to closely resemble the peer data flow - Each websocket now has its own long-lived goroutines for input, output, and queuing of notifications - Notifications use the new notification queue goroutine along with queueing to ensure they dont't block on stalled or slow peers - There is a new infrastructure for asynchronously executing long-running commands such as a rescan while still allowing the faster operations to continue to be serviced by the same client - Since long-running operations now run asynchronously, they have been limited to one at a time - Added a limit of 10 websocket clients. This is hard coded for now, but will be made configurable in the future Taken together these changes make the code far easier to reason about and update as well solve the aforementioned issues. Further optimizations to improve performance are possible in regards to the way the connection/notification manager works, however this commit already contains a ton of changes, so they are being left for another time.
2014-02-19 00:23:33 +01:00
// Notify websocket clients about mempool transactions.
if mp.server.rpcServer != nil {
Rework and improve websocket notification system. This commit refactors the entire websocket client code to resolve several issues with the previous implementation. Note that this commit does not change the public API for websockets. It only consists of internal improvements. The following is the major issues which have been addressed: - A slow websocket client could impede notifications to all clients - Long-running operations such as rescans would block all other requests until it had completed - The above two points taken together could lead to apparant hangs since the client doing the rescan would eventually run out of channel buffer and block the entire group of clients until the rescan completed - Disconnecting a websocket during certain operations could lead to a hang - Stopping the rpc server with operations under way could lead to a hang - There were no limits to the number of websocket clients that could connect The following is a summary of the major changes: - The websocket code has been split into two entities: a connection/notification manager and a websocket client - The new connection/notification manager acts as the entry point from the rest of the subsystems to feed data which potentially needs to notify clients - Each websocket client now has its own instance of the new websocket client type which controls its own lifecycle - The data flow has been completely redesigned to closely resemble the peer data flow - Each websocket now has its own long-lived goroutines for input, output, and queuing of notifications - Notifications use the new notification queue goroutine along with queueing to ensure they dont't block on stalled or slow peers - There is a new infrastructure for asynchronously executing long-running commands such as a rescan while still allowing the faster operations to continue to be serviced by the same client - Since long-running operations now run asynchronously, they have been limited to one at a time - Added a limit of 10 websocket clients. This is hard coded for now, but will be made configurable in the future Taken together these changes make the code far easier to reason about and update as well solve the aforementioned issues. Further optimizations to improve performance are possible in regards to the way the connection/notification manager works, however this commit already contains a ton of changes, so they are being left for another time.
2014-02-19 00:23:33 +01:00
go func() {
mp.server.rpcServer.ntfnMgr.NotifyForTxOuts(tx, nil)
Rework and improve websocket notification system. This commit refactors the entire websocket client code to resolve several issues with the previous implementation. Note that this commit does not change the public API for websockets. It only consists of internal improvements. The following is the major issues which have been addressed: - A slow websocket client could impede notifications to all clients - Long-running operations such as rescans would block all other requests until it had completed - The above two points taken together could lead to apparant hangs since the client doing the rescan would eventually run out of channel buffer and block the entire group of clients until the rescan completed - Disconnecting a websocket during certain operations could lead to a hang - Stopping the rpc server with operations under way could lead to a hang - There were no limits to the number of websocket clients that could connect The following is a summary of the major changes: - The websocket code has been split into two entities: a connection/notification manager and a websocket client - The new connection/notification manager acts as the entry point from the rest of the subsystems to feed data which potentially needs to notify clients - Each websocket client now has its own instance of the new websocket client type which controls its own lifecycle - The data flow has been completely redesigned to closely resemble the peer data flow - Each websocket now has its own long-lived goroutines for input, output, and queuing of notifications - Notifications use the new notification queue goroutine along with queueing to ensure they dont't block on stalled or slow peers - There is a new infrastructure for asynchronously executing long-running commands such as a rescan while still allowing the faster operations to continue to be serviced by the same client - Since long-running operations now run asynchronously, they have been limited to one at a time - Added a limit of 10 websocket clients. This is hard coded for now, but will be made configurable in the future Taken together these changes make the code far easier to reason about and update as well solve the aforementioned issues. Further optimizations to improve performance are possible in regards to the way the connection/notification manager works, however this commit already contains a ton of changes, so they are being left for another time.
2014-02-19 00:23:33 +01:00
if isNew {
mp.server.rpcServer.ntfnMgr.NotifyForNewTx(tx)
}
}()
}
return 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 *btcutil.Tx, isOrphan *bool, isNew bool) error {
// Protect concurrent access.
mp.Lock()
defer mp.Unlock()
return mp.maybeAcceptTransaction(tx, isOrphan, isNew)
}
// processOrphans determines if there are any orphans which depend on the passed
// transaction hash (they are no longer orphans if true) and potentially accepts
// them. It repeats the process for the newly accepted transactions (to detect
// further orphans which may no longer be orphans) until there are no more.
//
// This function MUST be called with the mempool lock held (for writes).
func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error {
// 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.(*btcwire.ShaHash)
// 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
}
var enext *list.Element
for e := orphans.Front(); e != nil; e = enext {
enext = e.Next()
tx := e.Value.(*btcutil.Tx)
// Remove the orphan from the orphan pool.
orphanHash := tx.Sha()
mp.removeOrphan(orphanHash)
// Potentially accept the transaction into the
// transaction pool.
var isOrphan bool
err := mp.maybeAcceptTransaction(tx, &isOrphan, true)
if err != nil {
return err
}
if !isOrphan {
// Generate the inventory vector and relay it.
iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha())
mp.server.RelayInventory(iv)
} else {
mp.removeOrphan(orphanHash)
}
// Add this transaction to the list of transactions to
// process so any orphans that depend on this one are
// handled too.
processHashes.PushBack(orphanHash)
}
}
return nil
}
// ProcessTransaction is the main workhorse for handling insertion of new
// free-standing transactions into the memory pool. It includes functionality
// such as rejecting duplicate transactions, ensuring transactions follow all
// rules, orphan transaction handling, and insertion into the memory pool.
//
// This function is safe for concurrent access.
func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx) error {
// Protect concurrent access.
mp.Lock()
defer mp.Unlock()
txmpLog.Tracef("Processing transaction %v", tx.Sha())
// Potentially accept the transaction to the memory pool.
var isOrphan bool
err := mp.maybeAcceptTransaction(tx, &isOrphan, true)
if err != nil {
return err
}
if !isOrphan {
// Generate the inventory vector and relay it.
iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha())
mp.server.RelayInventory(iv)
// Accept any orphan transactions that depend on this
// transaction (they are no longer orphans) and repeat for those
// accepted transactions until there are no more.
err := mp.processOrphans(tx.Sha())
if err != nil {
return err
}
} else {
// When the transaction is an orphan (has inputs missing),
// potentially add it to the orphan pool.
err := mp.maybeAddOrphan(tx)
if err != nil {
return err
}
}
return nil
}
2013-10-08 20:34:04 +02:00
// TxShas returns a slice of hashes for all of the transactions in the memory
// pool.
//
// This function is safe for concurrent access.
2013-10-08 07:04:51 +02:00
func (mp *txMemPool) TxShas() []*btcwire.ShaHash {
mp.RLock()
defer mp.RUnlock()
2013-10-08 07:04:51 +02:00
hashes := make([]*btcwire.ShaHash, 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() []*TxDesc {
mp.RLock()
defer mp.RUnlock()
descs := make([]*TxDesc, len(mp.pool))
i := 0
for _, desc := range mp.pool {
descs[i] = desc
i++
}
return descs
}
// newTxMemPool returns a new memory pool for validating and storing standalone
// transactions until they are mined into a block.
func newTxMemPool(server *server) *txMemPool {
return &txMemPool{
server: server,
pool: make(map[btcwire.ShaHash]*TxDesc),
orphans: make(map[btcwire.ShaHash]*btcutil.Tx),
orphansByPrev: make(map[btcwire.ShaHash]*list.List),
outpoints: make(map[btcwire.OutPoint]*btcutil.Tx),
}
}