2016-08-23 05:53:22 +02:00
|
|
|
// Copyright (c) 2016 The btcsuite developers
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package mempool
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"reflect"
|
2019-05-03 04:44:51 +02:00
|
|
|
"strings"
|
2016-08-23 05:53:22 +02:00
|
|
|
"sync"
|
|
|
|
"testing"
|
2016-08-23 20:48:03 +02:00
|
|
|
"time"
|
2016-08-23 05:53:22 +02:00
|
|
|
|
2021-10-15 07:45:32 +02:00
|
|
|
"github.com/lbryio/lbcd/blockchain"
|
|
|
|
"github.com/lbryio/lbcd/btcec"
|
|
|
|
"github.com/lbryio/lbcd/chaincfg"
|
|
|
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
|
|
|
"github.com/lbryio/lbcd/txscript"
|
|
|
|
"github.com/lbryio/lbcd/wire"
|
|
|
|
btcutil "github.com/lbryio/lbcutil"
|
2016-08-23 05:53:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// fakeChain is used by the pool harness to provide generated test utxos and
|
|
|
|
// a current faked chain height to the pool callbacks. This, in turn, allows
|
2017-10-29 20:56:03 +01:00
|
|
|
// transactions to appear as though they are spending completely valid utxos.
|
2016-08-23 05:53:22 +02:00
|
|
|
type fakeChain struct {
|
|
|
|
sync.RWMutex
|
2016-08-23 20:48:03 +02:00
|
|
|
utxos *blockchain.UtxoViewpoint
|
|
|
|
currentHeight int32
|
|
|
|
medianTimePast time.Time
|
2016-08-23 05:53:22 +02:00
|
|
|
}
|
|
|
|
|
multi: Rework utxoset/view to use outpoints.
This modifies the utxoset in the database and related UtxoViewpoint to
store and work with unspent transaction outputs on a per-output basis
instead of at a transaction level. This was inspired by similar recent
changes in Bitcoin Core.
The primary motivation is to simplify the code, pave the way for a
utxo cache, and generally focus on optimizing runtime performance.
The tradeoff is that this approach does somewhat increase the size of
the serialized utxoset since it means that the transaction hash is
duplicated for each output as a part of the key and some additional
details such as whether the containing transaction is a coinbase and the
block height it was a part of are duplicated in each output.
However, in practice, the size difference isn't all that large, disk
space is relatively cheap, certainly cheaper than memory, and it is much
more important to provide more efficient runtime operation since that is
the ultimate purpose of the daemon.
While performing this conversion, it also simplifies the code to remove
the transaction version information from the utxoset as well as the
spend journal. The logic for only serializing it under certain
circumstances is complicated and it isn't actually used anywhere aside
from the gettxout RPC where it also isn't used by anything important
either. Consequently, this also removes the version field of the
gettxout RPC result.
The utxos in the database are automatically migrated to the new format
with this commit and it is possible to interrupt and resume the
migration process.
Finally, it also updates the tests for the new format and adds a new
function to the tests to convert the old test data to the new format for
convenience. The data has already been converted and updated in the
commit.
An overview of the changes are as follows:
- Remove transaction version from both spent and unspent output entries
- Update utxo serialization format to exclude the version
- Modify the spend journal serialization format
- The old version field is now reserved and always stores zero and
ignores it when reading
- This allows old entries to be used by new code without having to
migrate the entire spend journal
- Remove version field from gettxout RPC result
- Convert UtxoEntry to represent a specific utxo instead of a
transaction with all remaining utxos
- Optimize for memory usage with an eye towards a utxo cache
- Combine details such as whether the txout was contained in a
coinbase, is spent, and is modified into a single packed field of
bit flags
- Align entry fields to eliminate extra padding since ultimately
there will be a lot of these in memory
- Introduce a free list for serializing an outpoint to the database
key format to significantly reduce pressure on the GC
- Update all related functions that previously dealt with transaction
hashes to accept outpoints instead
- Update all callers accordingly
- Only add individually requested outputs from the mempool when
constructing a mempool view
- Modify the spend journal to always store the block height and coinbase
information with every spent txout
- Introduce code to handle fetching the missing information from
another utxo from the same transaction in the event an old style
entry is encountered
- Make use of a database cursor with seek to do this much more
efficiently than testing every possible output
- Always decompress data loaded from the database now that a utxo entry
only consists of a specific output
- Introduce upgrade code to migrate the utxo set to the new format
- Store versions of the utxoset and spend journal buckets
- Allow migration process to be interrupted and resumed
- Update all tests to expect the correct encodings, remove tests that no
longer apply, and add new ones for the new expected behavior
- Convert old tests for the legacy utxo format deserialization code to
test the new function that is used during upgrade
- Update the utxostore test data and add function that was used to
convert it
- Introduce a few new functions on UtxoViewpoint
- AddTxOut for adding an individual txout versus all of them
- addTxOut to handle the common code between the new AddTxOut and
existing AddTxOuts
- RemoveEntry for removing an individual txout
- fetchEntryByHash for fetching any remaining utxo for a given
transaction hash
2017-09-03 09:59:15 +02:00
|
|
|
// FetchUtxoView loads utxo details about the inputs referenced by the passed
|
|
|
|
// transaction from the point of view of the fake chain. It also attempts to
|
|
|
|
// fetch the utxos for the outputs of the transaction itself so the returned
|
|
|
|
// view can be examined for duplicate transactions.
|
2016-08-23 05:53:22 +02:00
|
|
|
//
|
|
|
|
// This function is safe for concurrent access however the returned view is NOT.
|
|
|
|
func (s *fakeChain) FetchUtxoView(tx *btcutil.Tx) (*blockchain.UtxoViewpoint, error) {
|
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
|
|
|
|
|
|
|
// All entries are cloned to ensure modifications to the returned view
|
|
|
|
// do not affect the fake chain's view.
|
|
|
|
|
|
|
|
// Add an entry for the tx itself to the new view.
|
|
|
|
viewpoint := blockchain.NewUtxoViewpoint()
|
multi: Rework utxoset/view to use outpoints.
This modifies the utxoset in the database and related UtxoViewpoint to
store and work with unspent transaction outputs on a per-output basis
instead of at a transaction level. This was inspired by similar recent
changes in Bitcoin Core.
The primary motivation is to simplify the code, pave the way for a
utxo cache, and generally focus on optimizing runtime performance.
The tradeoff is that this approach does somewhat increase the size of
the serialized utxoset since it means that the transaction hash is
duplicated for each output as a part of the key and some additional
details such as whether the containing transaction is a coinbase and the
block height it was a part of are duplicated in each output.
However, in practice, the size difference isn't all that large, disk
space is relatively cheap, certainly cheaper than memory, and it is much
more important to provide more efficient runtime operation since that is
the ultimate purpose of the daemon.
While performing this conversion, it also simplifies the code to remove
the transaction version information from the utxoset as well as the
spend journal. The logic for only serializing it under certain
circumstances is complicated and it isn't actually used anywhere aside
from the gettxout RPC where it also isn't used by anything important
either. Consequently, this also removes the version field of the
gettxout RPC result.
The utxos in the database are automatically migrated to the new format
with this commit and it is possible to interrupt and resume the
migration process.
Finally, it also updates the tests for the new format and adds a new
function to the tests to convert the old test data to the new format for
convenience. The data has already been converted and updated in the
commit.
An overview of the changes are as follows:
- Remove transaction version from both spent and unspent output entries
- Update utxo serialization format to exclude the version
- Modify the spend journal serialization format
- The old version field is now reserved and always stores zero and
ignores it when reading
- This allows old entries to be used by new code without having to
migrate the entire spend journal
- Remove version field from gettxout RPC result
- Convert UtxoEntry to represent a specific utxo instead of a
transaction with all remaining utxos
- Optimize for memory usage with an eye towards a utxo cache
- Combine details such as whether the txout was contained in a
coinbase, is spent, and is modified into a single packed field of
bit flags
- Align entry fields to eliminate extra padding since ultimately
there will be a lot of these in memory
- Introduce a free list for serializing an outpoint to the database
key format to significantly reduce pressure on the GC
- Update all related functions that previously dealt with transaction
hashes to accept outpoints instead
- Update all callers accordingly
- Only add individually requested outputs from the mempool when
constructing a mempool view
- Modify the spend journal to always store the block height and coinbase
information with every spent txout
- Introduce code to handle fetching the missing information from
another utxo from the same transaction in the event an old style
entry is encountered
- Make use of a database cursor with seek to do this much more
efficiently than testing every possible output
- Always decompress data loaded from the database now that a utxo entry
only consists of a specific output
- Introduce upgrade code to migrate the utxo set to the new format
- Store versions of the utxoset and spend journal buckets
- Allow migration process to be interrupted and resumed
- Update all tests to expect the correct encodings, remove tests that no
longer apply, and add new ones for the new expected behavior
- Convert old tests for the legacy utxo format deserialization code to
test the new function that is used during upgrade
- Update the utxostore test data and add function that was used to
convert it
- Introduce a few new functions on UtxoViewpoint
- AddTxOut for adding an individual txout versus all of them
- addTxOut to handle the common code between the new AddTxOut and
existing AddTxOuts
- RemoveEntry for removing an individual txout
- fetchEntryByHash for fetching any remaining utxo for a given
transaction hash
2017-09-03 09:59:15 +02:00
|
|
|
prevOut := wire.OutPoint{Hash: *tx.Hash()}
|
|
|
|
for txOutIdx := range tx.MsgTx().TxOut {
|
|
|
|
prevOut.Index = uint32(txOutIdx)
|
|
|
|
entry := s.utxos.LookupEntry(prevOut)
|
|
|
|
viewpoint.Entries()[prevOut] = entry.Clone()
|
|
|
|
}
|
2016-08-23 05:53:22 +02:00
|
|
|
|
|
|
|
// Add entries for all of the inputs to the tx to the new view.
|
|
|
|
for _, txIn := range tx.MsgTx().TxIn {
|
multi: Rework utxoset/view to use outpoints.
This modifies the utxoset in the database and related UtxoViewpoint to
store and work with unspent transaction outputs on a per-output basis
instead of at a transaction level. This was inspired by similar recent
changes in Bitcoin Core.
The primary motivation is to simplify the code, pave the way for a
utxo cache, and generally focus on optimizing runtime performance.
The tradeoff is that this approach does somewhat increase the size of
the serialized utxoset since it means that the transaction hash is
duplicated for each output as a part of the key and some additional
details such as whether the containing transaction is a coinbase and the
block height it was a part of are duplicated in each output.
However, in practice, the size difference isn't all that large, disk
space is relatively cheap, certainly cheaper than memory, and it is much
more important to provide more efficient runtime operation since that is
the ultimate purpose of the daemon.
While performing this conversion, it also simplifies the code to remove
the transaction version information from the utxoset as well as the
spend journal. The logic for only serializing it under certain
circumstances is complicated and it isn't actually used anywhere aside
from the gettxout RPC where it also isn't used by anything important
either. Consequently, this also removes the version field of the
gettxout RPC result.
The utxos in the database are automatically migrated to the new format
with this commit and it is possible to interrupt and resume the
migration process.
Finally, it also updates the tests for the new format and adds a new
function to the tests to convert the old test data to the new format for
convenience. The data has already been converted and updated in the
commit.
An overview of the changes are as follows:
- Remove transaction version from both spent and unspent output entries
- Update utxo serialization format to exclude the version
- Modify the spend journal serialization format
- The old version field is now reserved and always stores zero and
ignores it when reading
- This allows old entries to be used by new code without having to
migrate the entire spend journal
- Remove version field from gettxout RPC result
- Convert UtxoEntry to represent a specific utxo instead of a
transaction with all remaining utxos
- Optimize for memory usage with an eye towards a utxo cache
- Combine details such as whether the txout was contained in a
coinbase, is spent, and is modified into a single packed field of
bit flags
- Align entry fields to eliminate extra padding since ultimately
there will be a lot of these in memory
- Introduce a free list for serializing an outpoint to the database
key format to significantly reduce pressure on the GC
- Update all related functions that previously dealt with transaction
hashes to accept outpoints instead
- Update all callers accordingly
- Only add individually requested outputs from the mempool when
constructing a mempool view
- Modify the spend journal to always store the block height and coinbase
information with every spent txout
- Introduce code to handle fetching the missing information from
another utxo from the same transaction in the event an old style
entry is encountered
- Make use of a database cursor with seek to do this much more
efficiently than testing every possible output
- Always decompress data loaded from the database now that a utxo entry
only consists of a specific output
- Introduce upgrade code to migrate the utxo set to the new format
- Store versions of the utxoset and spend journal buckets
- Allow migration process to be interrupted and resumed
- Update all tests to expect the correct encodings, remove tests that no
longer apply, and add new ones for the new expected behavior
- Convert old tests for the legacy utxo format deserialization code to
test the new function that is used during upgrade
- Update the utxostore test data and add function that was used to
convert it
- Introduce a few new functions on UtxoViewpoint
- AddTxOut for adding an individual txout versus all of them
- addTxOut to handle the common code between the new AddTxOut and
existing AddTxOuts
- RemoveEntry for removing an individual txout
- fetchEntryByHash for fetching any remaining utxo for a given
transaction hash
2017-09-03 09:59:15 +02:00
|
|
|
entry := s.utxos.LookupEntry(txIn.PreviousOutPoint)
|
|
|
|
viewpoint.Entries()[txIn.PreviousOutPoint] = entry.Clone()
|
2016-08-23 05:53:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return viewpoint, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// BestHeight returns the current height associated with the fake chain
|
|
|
|
// instance.
|
|
|
|
func (s *fakeChain) BestHeight() int32 {
|
|
|
|
s.RLock()
|
|
|
|
height := s.currentHeight
|
|
|
|
s.RUnlock()
|
|
|
|
return height
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetHeight sets the current height associated with the fake chain instance.
|
|
|
|
func (s *fakeChain) SetHeight(height int32) {
|
|
|
|
s.Lock()
|
|
|
|
s.currentHeight = height
|
|
|
|
s.Unlock()
|
|
|
|
}
|
|
|
|
|
2016-08-23 20:48:03 +02:00
|
|
|
// MedianTimePast returns the current median time past associated with the fake
|
|
|
|
// chain instance.
|
|
|
|
func (s *fakeChain) MedianTimePast() time.Time {
|
|
|
|
s.RLock()
|
|
|
|
mtp := s.medianTimePast
|
|
|
|
s.RUnlock()
|
|
|
|
return mtp
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetMedianTimePast sets the current median time past associated with the fake
|
|
|
|
// chain instance.
|
|
|
|
func (s *fakeChain) SetMedianTimePast(mtp time.Time) {
|
|
|
|
s.Lock()
|
|
|
|
s.medianTimePast = mtp
|
|
|
|
s.Unlock()
|
|
|
|
}
|
|
|
|
|
2016-08-27 02:24:39 +02:00
|
|
|
// CalcSequenceLock returns the current sequence lock for the passed
|
|
|
|
// transaction associated with the fake chain instance.
|
|
|
|
func (s *fakeChain) CalcSequenceLock(tx *btcutil.Tx,
|
|
|
|
view *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error) {
|
|
|
|
|
|
|
|
return &blockchain.SequenceLock{
|
|
|
|
Seconds: -1,
|
|
|
|
BlockHeight: -1,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2016-08-23 05:53:22 +02:00
|
|
|
// spendableOutput is a convenience type that houses a particular utxo and the
|
|
|
|
// amount associated with it.
|
|
|
|
type spendableOutput struct {
|
|
|
|
outPoint wire.OutPoint
|
|
|
|
amount btcutil.Amount
|
|
|
|
}
|
|
|
|
|
|
|
|
// txOutToSpendableOut returns a spendable output given a transaction and index
|
|
|
|
// of the output to use. This is useful as a convenience when creating test
|
|
|
|
// transactions.
|
|
|
|
func txOutToSpendableOut(tx *btcutil.Tx, outputNum uint32) spendableOutput {
|
|
|
|
return spendableOutput{
|
|
|
|
outPoint: wire.OutPoint{Hash: *tx.Hash(), Index: outputNum},
|
|
|
|
amount: btcutil.Amount(tx.MsgTx().TxOut[outputNum].Value),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// poolHarness provides a harness that includes functionality for creating and
|
|
|
|
// signing transactions as well as a fake chain that provides utxos for use in
|
|
|
|
// generating valid transactions.
|
|
|
|
type poolHarness struct {
|
|
|
|
// signKey is the signing key used for creating transactions throughout
|
|
|
|
// the tests.
|
|
|
|
//
|
|
|
|
// payAddr is the p2sh address for the signing key and is used for the
|
|
|
|
// payment address throughout the tests.
|
|
|
|
signKey *btcec.PrivateKey
|
|
|
|
payAddr btcutil.Address
|
|
|
|
payScript []byte
|
|
|
|
chainParams *chaincfg.Params
|
|
|
|
|
|
|
|
chain *fakeChain
|
|
|
|
txPool *TxPool
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateCoinbaseTx returns a coinbase transaction with the requested number of
|
|
|
|
// outputs paying an appropriate subsidy based on the passed block height to the
|
|
|
|
// address associated with the harness. It automatically uses a standard
|
|
|
|
// signature script that starts with the block height that is required by
|
|
|
|
// version 2 blocks.
|
|
|
|
func (p *poolHarness) CreateCoinbaseTx(blockHeight int32, numOutputs uint32) (*btcutil.Tx, error) {
|
|
|
|
// Create standard coinbase script.
|
|
|
|
extraNonce := int64(0)
|
|
|
|
coinbaseScript, err := txscript.NewScriptBuilder().
|
|
|
|
AddInt64(int64(blockHeight)).AddInt64(extraNonce).Script()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-10-27 04:09:19 +02:00
|
|
|
tx := wire.NewMsgTx(wire.TxVersion)
|
2016-08-23 05:53:22 +02:00
|
|
|
tx.AddTxIn(&wire.TxIn{
|
|
|
|
// Coinbase transactions have no inputs, so previous outpoint is
|
|
|
|
// zero hash and max index.
|
|
|
|
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
|
|
|
|
wire.MaxPrevOutIndex),
|
|
|
|
SignatureScript: coinbaseScript,
|
|
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
|
|
})
|
|
|
|
totalInput := blockchain.CalcBlockSubsidy(blockHeight, p.chainParams)
|
|
|
|
amountPerOutput := totalInput / int64(numOutputs)
|
|
|
|
remainder := totalInput - amountPerOutput*int64(numOutputs)
|
|
|
|
for i := uint32(0); i < numOutputs; i++ {
|
|
|
|
// Ensure the final output accounts for any remainder that might
|
|
|
|
// be left from splitting the input amount.
|
|
|
|
amount := amountPerOutput
|
|
|
|
if i == numOutputs-1 {
|
|
|
|
amount = amountPerOutput + remainder
|
|
|
|
}
|
|
|
|
tx.AddTxOut(&wire.TxOut{
|
|
|
|
PkScript: p.payScript,
|
|
|
|
Value: amount,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return btcutil.NewTx(tx), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateSignedTx creates a new signed transaction that consumes the provided
|
|
|
|
// inputs and generates the provided number of outputs by evenly splitting the
|
|
|
|
// total input amount. All outputs will be to the payment script associated
|
|
|
|
// with the harness and all inputs are assumed to do the same.
|
2019-05-03 04:44:51 +02:00
|
|
|
func (p *poolHarness) CreateSignedTx(inputs []spendableOutput,
|
|
|
|
numOutputs uint32, fee btcutil.Amount,
|
|
|
|
signalsReplacement bool) (*btcutil.Tx, error) {
|
|
|
|
|
2016-08-23 05:53:22 +02:00
|
|
|
// Calculate the total input amount and split it amongst the requested
|
|
|
|
// number of outputs.
|
|
|
|
var totalInput btcutil.Amount
|
|
|
|
for _, input := range inputs {
|
|
|
|
totalInput += input.amount
|
|
|
|
}
|
2019-05-03 04:44:51 +02:00
|
|
|
totalInput -= fee
|
2016-08-23 05:53:22 +02:00
|
|
|
amountPerOutput := int64(totalInput) / int64(numOutputs)
|
|
|
|
remainder := int64(totalInput) - amountPerOutput*int64(numOutputs)
|
|
|
|
|
2016-10-27 04:09:19 +02:00
|
|
|
tx := wire.NewMsgTx(wire.TxVersion)
|
2019-05-03 04:44:51 +02:00
|
|
|
sequence := wire.MaxTxInSequenceNum
|
|
|
|
if signalsReplacement {
|
|
|
|
sequence = MaxRBFSequence
|
|
|
|
}
|
2016-08-23 05:53:22 +02:00
|
|
|
for _, input := range inputs {
|
|
|
|
tx.AddTxIn(&wire.TxIn{
|
|
|
|
PreviousOutPoint: input.outPoint,
|
|
|
|
SignatureScript: nil,
|
2019-05-03 04:44:51 +02:00
|
|
|
Sequence: sequence,
|
2016-08-23 05:53:22 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
for i := uint32(0); i < numOutputs; i++ {
|
|
|
|
// Ensure the final output accounts for any remainder that might
|
|
|
|
// be left from splitting the input amount.
|
|
|
|
amount := amountPerOutput
|
|
|
|
if i == numOutputs-1 {
|
|
|
|
amount = amountPerOutput + remainder
|
|
|
|
}
|
|
|
|
tx.AddTxOut(&wire.TxOut{
|
|
|
|
PkScript: p.payScript,
|
|
|
|
Value: amount,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sign the new transaction.
|
|
|
|
for i := range tx.TxIn {
|
|
|
|
sigScript, err := txscript.SignatureScript(tx, i, p.payScript,
|
|
|
|
txscript.SigHashAll, p.signKey, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tx.TxIn[i].SignatureScript = sigScript
|
|
|
|
}
|
|
|
|
|
|
|
|
return btcutil.NewTx(tx), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateTxChain creates a chain of zero-fee transactions (each subsequent
|
|
|
|
// transaction spends the entire amount from the previous one) with the first
|
|
|
|
// one spending the provided outpoint. Each transaction spends the entire
|
|
|
|
// amount of the previous one and as such does not include any fees.
|
|
|
|
func (p *poolHarness) CreateTxChain(firstOutput spendableOutput, numTxns uint32) ([]*btcutil.Tx, error) {
|
|
|
|
txChain := make([]*btcutil.Tx, 0, numTxns)
|
|
|
|
prevOutPoint := firstOutput.outPoint
|
|
|
|
spendableAmount := firstOutput.amount
|
|
|
|
for i := uint32(0); i < numTxns; i++ {
|
|
|
|
// Create the transaction using the previous transaction output
|
|
|
|
// and paying the full amount to the payment address associated
|
|
|
|
// with the harness.
|
2016-10-27 04:09:19 +02:00
|
|
|
tx := wire.NewMsgTx(wire.TxVersion)
|
2016-08-23 05:53:22 +02:00
|
|
|
tx.AddTxIn(&wire.TxIn{
|
|
|
|
PreviousOutPoint: prevOutPoint,
|
|
|
|
SignatureScript: nil,
|
|
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
|
|
})
|
|
|
|
tx.AddTxOut(&wire.TxOut{
|
|
|
|
PkScript: p.payScript,
|
|
|
|
Value: int64(spendableAmount),
|
|
|
|
})
|
|
|
|
|
|
|
|
// Sign the new transaction.
|
|
|
|
sigScript, err := txscript.SignatureScript(tx, 0, p.payScript,
|
|
|
|
txscript.SigHashAll, p.signKey, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
tx.TxIn[0].SignatureScript = sigScript
|
|
|
|
|
|
|
|
txChain = append(txChain, btcutil.NewTx(tx))
|
|
|
|
|
|
|
|
// Next transaction uses outputs from this one.
|
|
|
|
prevOutPoint = wire.OutPoint{Hash: tx.TxHash(), Index: 0}
|
|
|
|
}
|
|
|
|
|
|
|
|
return txChain, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// newPoolHarness returns a new instance of a pool harness initialized with a
|
|
|
|
// fake chain and a TxPool bound to it that is configured with a policy suitable
|
|
|
|
// for testing. Also, the fake chain is populated with the returned spendable
|
|
|
|
// outputs so the caller can easily create new valid transactions which build
|
|
|
|
// off of it.
|
|
|
|
func newPoolHarness(chainParams *chaincfg.Params) (*poolHarness, []spendableOutput, error) {
|
|
|
|
// Use a hard coded key pair for deterministic results.
|
|
|
|
keyBytes, err := hex.DecodeString("700868df1838811ffbdf918fb482c1f7e" +
|
|
|
|
"ad62db4b97bd7012c23e726485e577d")
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
signKey, signPub := btcec.PrivKeyFromBytes(btcec.S256(), keyBytes)
|
|
|
|
|
|
|
|
// Generate associated pay-to-script-hash address and resulting payment
|
|
|
|
// script.
|
|
|
|
pubKeyBytes := signPub.SerializeCompressed()
|
|
|
|
payPubKeyAddr, err := btcutil.NewAddressPubKey(pubKeyBytes, chainParams)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
payAddr := payPubKeyAddr.AddressPubKeyHash()
|
|
|
|
pkScript, err := txscript.PayToAddrScript(payAddr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new fake chain and harness bound to it.
|
|
|
|
chain := &fakeChain{utxos: blockchain.NewUtxoViewpoint()}
|
|
|
|
harness := poolHarness{
|
|
|
|
signKey: signKey,
|
|
|
|
payAddr: payAddr,
|
|
|
|
payScript: pkScript,
|
|
|
|
chainParams: chainParams,
|
|
|
|
|
|
|
|
chain: chain,
|
|
|
|
txPool: New(&Config{
|
|
|
|
Policy: Policy{
|
|
|
|
DisableRelayPriority: true,
|
|
|
|
FreeTxRelayLimit: 15.0,
|
|
|
|
MaxOrphanTxs: 5,
|
|
|
|
MaxOrphanTxSize: 1000,
|
2016-10-19 04:21:55 +02:00
|
|
|
MaxSigOpCostPerTx: blockchain.MaxBlockSigOpsCost / 4,
|
2016-08-23 05:53:22 +02:00
|
|
|
MinRelayTxFee: 1000, // 1 Satoshi per byte
|
2016-08-27 03:49:59 +02:00
|
|
|
MaxTxVersion: 1,
|
2016-08-23 05:53:22 +02:00
|
|
|
},
|
2016-08-27 02:24:39 +02:00
|
|
|
ChainParams: chainParams,
|
|
|
|
FetchUtxoView: chain.FetchUtxoView,
|
|
|
|
BestHeight: chain.BestHeight,
|
|
|
|
MedianTimePast: chain.MedianTimePast,
|
|
|
|
CalcSequenceLock: chain.CalcSequenceLock,
|
|
|
|
SigCache: nil,
|
|
|
|
AddrIndex: nil,
|
2016-08-23 05:53:22 +02:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a single coinbase transaction and add it to the harness
|
|
|
|
// chain's utxo set and set the harness chain height such that the
|
|
|
|
// coinbase will mature in the next block. This ensures the txpool
|
|
|
|
// accepts transactions which spend immature coinbases that will become
|
|
|
|
// mature in the next block.
|
|
|
|
numOutputs := uint32(1)
|
|
|
|
outputs := make([]spendableOutput, 0, numOutputs)
|
|
|
|
curHeight := harness.chain.BestHeight()
|
|
|
|
coinbase, err := harness.CreateCoinbaseTx(curHeight+1, numOutputs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
harness.chain.utxos.AddTxOuts(coinbase, curHeight+1)
|
|
|
|
for i := uint32(0); i < numOutputs; i++ {
|
|
|
|
outputs = append(outputs, txOutToSpendableOut(coinbase, i))
|
|
|
|
}
|
|
|
|
harness.chain.SetHeight(int32(chainParams.CoinbaseMaturity) + curHeight)
|
2016-08-27 02:24:39 +02:00
|
|
|
harness.chain.SetMedianTimePast(time.Now())
|
2016-08-23 05:53:22 +02:00
|
|
|
|
|
|
|
return &harness, outputs, nil
|
|
|
|
}
|
|
|
|
|
2016-10-25 00:25:37 +02:00
|
|
|
// testContext houses a test-related state that is useful to pass to helper
|
|
|
|
// functions as a single argument.
|
|
|
|
type testContext struct {
|
|
|
|
t *testing.T
|
|
|
|
harness *poolHarness
|
|
|
|
}
|
|
|
|
|
2019-05-03 04:44:51 +02:00
|
|
|
// addCoinbaseTx adds a spendable coinbase transaction to the test context's
|
|
|
|
// mock chain.
|
|
|
|
func (ctx *testContext) addCoinbaseTx(numOutputs uint32) *btcutil.Tx {
|
|
|
|
ctx.t.Helper()
|
|
|
|
|
|
|
|
coinbaseHeight := ctx.harness.chain.BestHeight() + 1
|
|
|
|
coinbase, err := ctx.harness.CreateCoinbaseTx(coinbaseHeight, numOutputs)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create coinbase: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.harness.chain.utxos.AddTxOuts(coinbase, coinbaseHeight)
|
|
|
|
maturity := int32(ctx.harness.chainParams.CoinbaseMaturity)
|
|
|
|
ctx.harness.chain.SetHeight(coinbaseHeight + maturity)
|
|
|
|
ctx.harness.chain.SetMedianTimePast(time.Now())
|
|
|
|
|
|
|
|
return coinbase
|
|
|
|
}
|
|
|
|
|
|
|
|
// addSignedTx creates a transaction that spends the inputs with the given fee.
|
|
|
|
// It can be added to the test context's mempool or mock chain based on the
|
|
|
|
// confirmed boolean.
|
|
|
|
func (ctx *testContext) addSignedTx(inputs []spendableOutput,
|
|
|
|
numOutputs uint32, fee btcutil.Amount,
|
|
|
|
signalsReplacement, confirmed bool) *btcutil.Tx {
|
|
|
|
|
|
|
|
ctx.t.Helper()
|
|
|
|
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
inputs, numOutputs, fee, signalsReplacement,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if confirmed {
|
|
|
|
newHeight := ctx.harness.chain.BestHeight() + 1
|
|
|
|
ctx.harness.chain.utxos.AddTxOuts(tx, newHeight)
|
|
|
|
ctx.harness.chain.SetHeight(newHeight)
|
|
|
|
ctx.harness.chain.SetMedianTimePast(time.Now())
|
|
|
|
} else {
|
|
|
|
acceptedTxns, err := ctx.harness.txPool.ProcessTransaction(
|
|
|
|
tx, true, false, 0,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to process transaction: %v", err)
|
|
|
|
}
|
|
|
|
if len(acceptedTxns) != 1 {
|
|
|
|
ctx.t.Fatalf("expected one accepted transaction, got %d",
|
|
|
|
len(acceptedTxns))
|
|
|
|
}
|
|
|
|
testPoolMembership(ctx, tx, false, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx
|
|
|
|
}
|
|
|
|
|
2016-10-25 00:25:37 +02:00
|
|
|
// testPoolMembership tests the transaction pool associated with the provided
|
|
|
|
// test context to determine if the passed transaction matches the provided
|
|
|
|
// orphan pool and transaction pool status. It also further determines if it
|
|
|
|
// should be reported as available by the HaveTransaction function based upon
|
|
|
|
// the two flags and tests that condition as well.
|
|
|
|
func testPoolMembership(tc *testContext, tx *btcutil.Tx, inOrphanPool, inTxPool bool) {
|
2019-05-03 04:44:51 +02:00
|
|
|
tc.t.Helper()
|
|
|
|
|
2016-10-25 00:25:37 +02:00
|
|
|
txHash := tx.Hash()
|
|
|
|
gotOrphanPool := tc.harness.txPool.IsOrphanInPool(txHash)
|
|
|
|
if inOrphanPool != gotOrphanPool {
|
2019-05-03 04:44:51 +02:00
|
|
|
tc.t.Fatalf("IsOrphanInPool: want %v, got %v", inOrphanPool,
|
|
|
|
gotOrphanPool)
|
2016-10-25 00:25:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
gotTxPool := tc.harness.txPool.IsTransactionInPool(txHash)
|
|
|
|
if inTxPool != gotTxPool {
|
2019-05-03 04:44:51 +02:00
|
|
|
tc.t.Fatalf("IsTransactionInPool: want %v, got %v", inTxPool,
|
|
|
|
gotTxPool)
|
2016-10-25 00:25:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
gotHaveTx := tc.harness.txPool.HaveTransaction(txHash)
|
|
|
|
wantHaveTx := inOrphanPool || inTxPool
|
|
|
|
if wantHaveTx != gotHaveTx {
|
2019-05-03 04:44:51 +02:00
|
|
|
tc.t.Fatalf("HaveTransaction: want %v, got %v", wantHaveTx,
|
|
|
|
gotHaveTx)
|
2016-10-25 00:25:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-23 05:53:22 +02:00
|
|
|
// TestSimpleOrphanChain ensures that a simple chain of orphans is handled
|
|
|
|
// properly. In particular, it generates a chain of single input, single output
|
|
|
|
// transactions and inserts them while skipping the first linking transaction so
|
|
|
|
// they are all orphans. Finally, it adds the linking transaction and ensures
|
|
|
|
// the entire orphan chain is moved to the transaction pool.
|
|
|
|
func TestSimpleOrphanChain(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
harness, spendableOuts, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
2016-10-25 00:25:37 +02:00
|
|
|
tc := &testContext{t, harness}
|
2016-08-23 05:53:22 +02:00
|
|
|
|
|
|
|
// Create a chain of transactions rooted with the first spendable output
|
|
|
|
// provided by the harness.
|
|
|
|
maxOrphans := uint32(harness.txPool.cfg.Policy.MaxOrphanTxs)
|
|
|
|
chainedTxns, err := harness.CreateTxChain(spendableOuts[0], maxOrphans+1)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create transaction chain: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the orphans are accepted (only up to the maximum allowed so
|
|
|
|
// none are evicted).
|
|
|
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(tx, true,
|
2016-10-28 19:48:54 +02:00
|
|
|
false, 0)
|
2016-08-23 05:53:22 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid "+
|
|
|
|
"orphan %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure no transactions were reported as accepted.
|
|
|
|
if len(acceptedTxns) != 0 {
|
|
|
|
t.Fatalf("ProcessTransaction: reported %d accepted "+
|
|
|
|
"transactions from what should be an orphan",
|
|
|
|
len(acceptedTxns))
|
|
|
|
}
|
|
|
|
|
2016-10-25 00:25:37 +02:00
|
|
|
// Ensure the transaction is in the orphan pool, is not in the
|
|
|
|
// transaction pool, and is reported as available.
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
2016-08-23 05:53:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add the transaction which completes the orphan chain and ensure they
|
|
|
|
// all get accepted. Notice the accept orphans flag is also false here
|
|
|
|
// to ensure it has no bearing on whether or not already existing
|
|
|
|
// orphans in the pool are linked.
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(chainedTxns[0],
|
2016-10-28 19:48:54 +02:00
|
|
|
false, false, 0)
|
2016-08-23 05:53:22 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid "+
|
|
|
|
"orphan %v", err)
|
|
|
|
}
|
|
|
|
if len(acceptedTxns) != len(chainedTxns) {
|
|
|
|
t.Fatalf("ProcessTransaction: reported accepted transactions "+
|
|
|
|
"length does not match expected -- got %d, want %d",
|
|
|
|
len(acceptedTxns), len(chainedTxns))
|
|
|
|
}
|
2016-10-28 20:23:51 +02:00
|
|
|
for _, txD := range acceptedTxns {
|
2016-10-25 00:25:37 +02:00
|
|
|
// Ensure the transaction is no longer in the orphan pool, is
|
|
|
|
// now in the transaction pool, and is reported as available.
|
2016-10-28 20:23:51 +02:00
|
|
|
testPoolMembership(tc, txD.Tx, false, true)
|
2016-08-23 05:53:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestOrphanReject ensures that orphans are properly rejected when the allow
|
|
|
|
// orphans flag is not set on ProcessTransaction.
|
|
|
|
func TestOrphanReject(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
2016-10-25 00:25:37 +02:00
|
|
|
tc := &testContext{t, harness}
|
2016-08-23 05:53:22 +02:00
|
|
|
|
|
|
|
// Create a chain of transactions rooted with the first spendable output
|
|
|
|
// provided by the harness.
|
|
|
|
maxOrphans := uint32(harness.txPool.cfg.Policy.MaxOrphanTxs)
|
|
|
|
chainedTxns, err := harness.CreateTxChain(outputs[0], maxOrphans+1)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create transaction chain: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure orphans are rejected when the allow orphans flag is not set.
|
|
|
|
for _, tx := range chainedTxns[1:] {
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(tx, false,
|
2016-10-28 19:48:54 +02:00
|
|
|
false, 0)
|
2016-08-23 05:53:22 +02:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("ProcessTransaction: did not fail on orphan "+
|
|
|
|
"%v when allow orphans flag is false", tx.Hash())
|
|
|
|
}
|
|
|
|
expectedErr := RuleError{}
|
|
|
|
if reflect.TypeOf(err) != reflect.TypeOf(expectedErr) {
|
|
|
|
t.Fatalf("ProcessTransaction: wrong error got: <%T> %v, "+
|
|
|
|
"want: <%T>", err, err, expectedErr)
|
|
|
|
}
|
|
|
|
code, extracted := extractRejectCode(err)
|
|
|
|
if !extracted {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to extract reject "+
|
|
|
|
"code from error %q", err)
|
|
|
|
}
|
|
|
|
if code != wire.RejectDuplicate {
|
|
|
|
t.Fatalf("ProcessTransaction: unexpected reject code "+
|
|
|
|
"-- got %v, want %v", code, wire.RejectDuplicate)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure no transactions were reported as accepted.
|
|
|
|
if len(acceptedTxns) != 0 {
|
|
|
|
t.Fatal("ProcessTransaction: reported %d accepted "+
|
|
|
|
"transactions from failed orphan attempt",
|
|
|
|
len(acceptedTxns))
|
|
|
|
}
|
|
|
|
|
2016-10-25 00:25:37 +02:00
|
|
|
// Ensure the transaction is not in the orphan pool, not in the
|
|
|
|
// transaction pool, and not reported as available
|
|
|
|
testPoolMembership(tc, tx, false, false)
|
2016-08-23 05:53:22 +02:00
|
|
|
}
|
|
|
|
}
|
2016-08-23 07:18:38 +02:00
|
|
|
|
|
|
|
// TestOrphanEviction ensures that exceeding the maximum number of orphans
|
|
|
|
// evicts entries to make room for the new ones.
|
|
|
|
func TestOrphanEviction(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
2016-10-25 00:25:37 +02:00
|
|
|
tc := &testContext{t, harness}
|
2016-08-23 07:18:38 +02:00
|
|
|
|
|
|
|
// Create a chain of transactions rooted with the first spendable output
|
|
|
|
// provided by the harness that is long enough to be able to force
|
|
|
|
// several orphan evictions.
|
|
|
|
maxOrphans := uint32(harness.txPool.cfg.Policy.MaxOrphanTxs)
|
|
|
|
chainedTxns, err := harness.CreateTxChain(outputs[0], maxOrphans+5)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create transaction chain: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add enough orphans to exceed the max allowed while ensuring they are
|
|
|
|
// all accepted. This will cause an eviction.
|
|
|
|
for _, tx := range chainedTxns[1:] {
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(tx, true,
|
2016-10-28 19:48:54 +02:00
|
|
|
false, 0)
|
2016-08-23 07:18:38 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid "+
|
|
|
|
"orphan %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure no transactions were reported as accepted.
|
|
|
|
if len(acceptedTxns) != 0 {
|
|
|
|
t.Fatalf("ProcessTransaction: reported %d accepted "+
|
|
|
|
"transactions from what should be an orphan",
|
|
|
|
len(acceptedTxns))
|
|
|
|
}
|
|
|
|
|
2016-10-25 00:25:37 +02:00
|
|
|
// Ensure the transaction is in the orphan pool, is not in the
|
|
|
|
// transaction pool, and is reported as available.
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
2016-08-23 07:18:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Figure out which transactions were evicted and make sure the number
|
|
|
|
// evicted matches the expected number.
|
|
|
|
var evictedTxns []*btcutil.Tx
|
|
|
|
for _, tx := range chainedTxns[1:] {
|
|
|
|
if !harness.txPool.IsOrphanInPool(tx.Hash()) {
|
|
|
|
evictedTxns = append(evictedTxns, tx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
expectedEvictions := len(chainedTxns) - 1 - int(maxOrphans)
|
|
|
|
if len(evictedTxns) != expectedEvictions {
|
|
|
|
t.Fatalf("unexpected number of evictions -- got %d, want %d",
|
|
|
|
len(evictedTxns), expectedEvictions)
|
|
|
|
}
|
|
|
|
|
2016-10-25 00:25:37 +02:00
|
|
|
// Ensure none of the evicted transactions ended up in the transaction
|
2016-08-23 07:18:38 +02:00
|
|
|
// pool.
|
|
|
|
for _, tx := range evictedTxns {
|
2016-10-25 00:25:37 +02:00
|
|
|
testPoolMembership(tc, tx, false, false)
|
2016-08-23 07:18:38 +02:00
|
|
|
}
|
|
|
|
}
|
2016-08-23 19:26:26 +02:00
|
|
|
|
|
|
|
// TestBasicOrphanRemoval ensure that orphan removal works as expected when an
|
|
|
|
// orphan that doesn't exist is removed both when there is another orphan that
|
|
|
|
// redeems it and when there is not.
|
|
|
|
func TestBasicOrphanRemoval(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const maxOrphans = 4
|
|
|
|
harness, spendableOuts, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans
|
|
|
|
tc := &testContext{t, harness}
|
|
|
|
|
|
|
|
// Create a chain of transactions rooted with the first spendable output
|
|
|
|
// provided by the harness.
|
|
|
|
chainedTxns, err := harness.CreateTxChain(spendableOuts[0], maxOrphans+1)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create transaction chain: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the orphans are accepted (only up to the maximum allowed so
|
|
|
|
// none are evicted).
|
|
|
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(tx, true,
|
2016-10-28 19:48:54 +02:00
|
|
|
false, 0)
|
2016-08-23 19:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid "+
|
|
|
|
"orphan %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure no transactions were reported as accepted.
|
|
|
|
if len(acceptedTxns) != 0 {
|
|
|
|
t.Fatalf("ProcessTransaction: reported %d accepted "+
|
|
|
|
"transactions from what should be an orphan",
|
|
|
|
len(acceptedTxns))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the transaction is in the orphan pool, not in the
|
|
|
|
// transaction pool, and reported as available.
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to remove an orphan that has no redeemers and is not present,
|
|
|
|
// and ensure the state of all other orphans are unaffected.
|
|
|
|
nonChainedOrphanTx, err := harness.CreateSignedTx([]spendableOutput{{
|
|
|
|
amount: btcutil.Amount(5000000000),
|
|
|
|
outPoint: wire.OutPoint{Hash: chainhash.Hash{}, Index: 0},
|
2019-05-03 04:44:51 +02:00
|
|
|
}}, 1, 0, false)
|
2016-08-23 19:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create signed tx: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
harness.txPool.RemoveOrphan(nonChainedOrphanTx)
|
|
|
|
testPoolMembership(tc, nonChainedOrphanTx, false, false)
|
|
|
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to remove an orphan that has a existing redeemer but itself
|
|
|
|
// is not present and ensure the state of all other orphans (including
|
|
|
|
// the one that redeems it) are unaffected.
|
|
|
|
harness.txPool.RemoveOrphan(chainedTxns[0])
|
|
|
|
testPoolMembership(tc, chainedTxns[0], false, false)
|
|
|
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove each orphan one-by-one and ensure they are removed as
|
|
|
|
// expected.
|
|
|
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
|
|
|
harness.txPool.RemoveOrphan(tx)
|
|
|
|
testPoolMembership(tc, tx, false, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestOrphanChainRemoval ensure that orphan chains (orphans that spend outputs
|
|
|
|
// from other orphans) are removed as expected.
|
|
|
|
func TestOrphanChainRemoval(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const maxOrphans = 10
|
|
|
|
harness, spendableOuts, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans
|
|
|
|
tc := &testContext{t, harness}
|
|
|
|
|
|
|
|
// Create a chain of transactions rooted with the first spendable output
|
|
|
|
// provided by the harness.
|
|
|
|
chainedTxns, err := harness.CreateTxChain(spendableOuts[0], maxOrphans+1)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create transaction chain: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the orphans are accepted (only up to the maximum allowed so
|
|
|
|
// none are evicted).
|
|
|
|
for _, tx := range chainedTxns[1 : maxOrphans+1] {
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(tx, true,
|
2016-10-28 19:48:54 +02:00
|
|
|
false, 0)
|
2016-08-23 19:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid "+
|
|
|
|
"orphan %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure no transactions were reported as accepted.
|
|
|
|
if len(acceptedTxns) != 0 {
|
|
|
|
t.Fatalf("ProcessTransaction: reported %d accepted "+
|
|
|
|
"transactions from what should be an orphan",
|
|
|
|
len(acceptedTxns))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the transaction is in the orphan pool, not in the
|
|
|
|
// transaction pool, and reported as available.
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the first orphan that starts the orphan chain without the
|
|
|
|
// remove redeemer flag set and ensure that only the first orphan was
|
|
|
|
// removed.
|
|
|
|
harness.txPool.mtx.Lock()
|
|
|
|
harness.txPool.removeOrphan(chainedTxns[1], false)
|
|
|
|
harness.txPool.mtx.Unlock()
|
|
|
|
testPoolMembership(tc, chainedTxns[1], false, false)
|
|
|
|
for _, tx := range chainedTxns[2 : maxOrphans+1] {
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the first remaining orphan that starts the orphan chain with
|
|
|
|
// the remove redeemer flag set and ensure they are all removed.
|
|
|
|
harness.txPool.mtx.Lock()
|
|
|
|
harness.txPool.removeOrphan(chainedTxns[2], true)
|
|
|
|
harness.txPool.mtx.Unlock()
|
|
|
|
for _, tx := range chainedTxns[2 : maxOrphans+1] {
|
|
|
|
testPoolMembership(tc, tx, false, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestMultiInputOrphanDoubleSpend ensures that orphans that spend from an
|
|
|
|
// output that is spend by another transaction entering the pool are removed.
|
|
|
|
func TestMultiInputOrphanDoubleSpend(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const maxOrphans = 4
|
|
|
|
harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
harness.txPool.cfg.Policy.MaxOrphanTxs = maxOrphans
|
|
|
|
tc := &testContext{t, harness}
|
|
|
|
|
|
|
|
// Create a chain of transactions rooted with the first spendable output
|
|
|
|
// provided by the harness.
|
|
|
|
chainedTxns, err := harness.CreateTxChain(outputs[0], maxOrphans+1)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create transaction chain: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start by adding the orphan transactions from the generated chain
|
|
|
|
// except the final one.
|
|
|
|
for _, tx := range chainedTxns[1:maxOrphans] {
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(tx, true,
|
2016-10-28 19:48:54 +02:00
|
|
|
false, 0)
|
2016-08-23 19:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid "+
|
|
|
|
"orphan %v", err)
|
|
|
|
}
|
|
|
|
if len(acceptedTxns) != 0 {
|
|
|
|
t.Fatalf("ProcessTransaction: reported %d accepted transactions "+
|
|
|
|
"from what should be an orphan", len(acceptedTxns))
|
|
|
|
}
|
|
|
|
testPoolMembership(tc, tx, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure a transaction that contains a double spend of the same output
|
|
|
|
// as the second orphan that was just added as well as a valid spend
|
|
|
|
// from that last orphan in the chain generated above (and is not in the
|
|
|
|
// orphan pool) is accepted to the orphan pool. This must be allowed
|
|
|
|
// since it would otherwise be possible for a malicious actor to disrupt
|
|
|
|
// tx chains.
|
|
|
|
doubleSpendTx, err := harness.CreateSignedTx([]spendableOutput{
|
|
|
|
txOutToSpendableOut(chainedTxns[1], 0),
|
|
|
|
txOutToSpendableOut(chainedTxns[maxOrphans], 0),
|
2019-05-03 04:44:51 +02:00
|
|
|
}, 1, 0, false)
|
2016-08-23 19:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create signed tx: %v", err)
|
|
|
|
}
|
|
|
|
acceptedTxns, err := harness.txPool.ProcessTransaction(doubleSpendTx,
|
2016-10-28 19:48:54 +02:00
|
|
|
true, false, 0)
|
2016-08-23 19:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid orphan %v",
|
|
|
|
err)
|
|
|
|
}
|
|
|
|
if len(acceptedTxns) != 0 {
|
|
|
|
t.Fatalf("ProcessTransaction: reported %d accepted transactions "+
|
|
|
|
"from what should be an orphan", len(acceptedTxns))
|
|
|
|
}
|
|
|
|
testPoolMembership(tc, doubleSpendTx, true, false)
|
|
|
|
|
|
|
|
// Add the transaction which completes the orphan chain and ensure the
|
|
|
|
// chain gets accepted. Notice the accept orphans flag is also false
|
|
|
|
// here to ensure it has no bearing on whether or not already existing
|
|
|
|
// orphans in the pool are linked.
|
|
|
|
//
|
|
|
|
// This will cause the shared output to become a concrete spend which
|
|
|
|
// will in turn must cause the double spending orphan to be removed.
|
|
|
|
acceptedTxns, err = harness.txPool.ProcessTransaction(chainedTxns[0],
|
2016-10-28 19:48:54 +02:00
|
|
|
false, false, 0)
|
2016-08-23 19:26:26 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept valid tx %v", err)
|
|
|
|
}
|
|
|
|
if len(acceptedTxns) != maxOrphans {
|
|
|
|
t.Fatalf("ProcessTransaction: reported accepted transactions "+
|
|
|
|
"length does not match expected -- got %d, want %d",
|
|
|
|
len(acceptedTxns), maxOrphans)
|
|
|
|
}
|
2016-10-28 20:23:51 +02:00
|
|
|
for _, txD := range acceptedTxns {
|
2016-08-23 19:26:26 +02:00
|
|
|
// Ensure the transaction is no longer in the orphan pool, is
|
|
|
|
// in the transaction pool, and is reported as available.
|
2016-10-28 20:23:51 +02:00
|
|
|
testPoolMembership(tc, txD.Tx, false, true)
|
2016-08-23 19:26:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the double spending orphan is no longer in the orphan pool and
|
|
|
|
// was not moved to the transaction pool.
|
|
|
|
testPoolMembership(tc, doubleSpendTx, false, false)
|
|
|
|
}
|
2018-03-23 15:40:06 +01:00
|
|
|
|
|
|
|
// TestCheckSpend tests that CheckSpend returns the expected spends found in
|
|
|
|
// the mempool.
|
|
|
|
func TestCheckSpend(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The mempool is empty, so none of the spendable outputs should have a
|
|
|
|
// spend there.
|
|
|
|
for _, op := range outputs {
|
|
|
|
spend := harness.txPool.CheckSpend(op.outPoint)
|
|
|
|
if spend != nil {
|
|
|
|
t.Fatalf("Unexpeced spend found in pool: %v", spend)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a chain of transactions rooted with the first spendable
|
|
|
|
// output provided by the harness.
|
|
|
|
const txChainLength = 5
|
|
|
|
chainedTxns, err := harness.CreateTxChain(outputs[0], txChainLength)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create transaction chain: %v", err)
|
|
|
|
}
|
|
|
|
for _, tx := range chainedTxns {
|
|
|
|
_, err := harness.txPool.ProcessTransaction(tx, true,
|
|
|
|
false, 0)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("ProcessTransaction: failed to accept "+
|
|
|
|
"tx: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The first tx in the chain should be the spend of the spendable
|
|
|
|
// output.
|
|
|
|
op := outputs[0].outPoint
|
|
|
|
spend := harness.txPool.CheckSpend(op)
|
|
|
|
if spend != chainedTxns[0] {
|
|
|
|
t.Fatalf("expected %v to be spent by %v, instead "+
|
|
|
|
"got %v", op, chainedTxns[0], spend)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now all but the last tx should be spent by the next.
|
|
|
|
for i := 0; i < len(chainedTxns)-1; i++ {
|
|
|
|
op = wire.OutPoint{
|
|
|
|
Hash: *chainedTxns[i].Hash(),
|
|
|
|
Index: 0,
|
|
|
|
}
|
|
|
|
expSpend := chainedTxns[i+1]
|
|
|
|
spend = harness.txPool.CheckSpend(op)
|
|
|
|
if spend != expSpend {
|
|
|
|
t.Fatalf("expected %v to be spent by %v, instead "+
|
|
|
|
"got %v", op, expSpend, spend)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The last tx should have no spend.
|
|
|
|
op = wire.OutPoint{
|
|
|
|
Hash: *chainedTxns[txChainLength-1].Hash(),
|
|
|
|
Index: 0,
|
|
|
|
}
|
|
|
|
spend = harness.txPool.CheckSpend(op)
|
|
|
|
if spend != nil {
|
|
|
|
t.Fatalf("Unexpeced spend found in pool: %v", spend)
|
|
|
|
}
|
|
|
|
}
|
2019-05-03 04:44:51 +02:00
|
|
|
|
|
|
|
// TestSignalsReplacement tests that transactions properly signal they can be
|
|
|
|
// replaced using RBF.
|
|
|
|
func TestSignalsReplacement(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
setup func(ctx *testContext) *btcutil.Tx
|
|
|
|
signalsReplacement bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
// Transactions can signal replacement through
|
|
|
|
// inheritance if any of its ancestors does.
|
|
|
|
name: "non-signaling with unconfirmed non-signaling parent",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
return ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
},
|
|
|
|
signalsReplacement: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Transactions can signal replacement through
|
|
|
|
// inheritance if any of its ancestors does, but they
|
|
|
|
// must be unconfirmed.
|
|
|
|
name: "non-signaling with confirmed signaling parent",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(outs, 1, 0, true, true)
|
|
|
|
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
return ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
},
|
|
|
|
signalsReplacement: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "inherited signaling",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// We'll create a chain of transactions
|
|
|
|
// A -> B -> C where C is the transaction we'll
|
|
|
|
// be checking for replacement signaling. The
|
|
|
|
// transaction can signal replacement through
|
|
|
|
// any of its ancestors as long as they also
|
|
|
|
// signal replacement.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
a := ctx.addSignedTx(outs, 1, 0, true, false)
|
|
|
|
|
|
|
|
aOut := txOutToSpendableOut(a, 0)
|
|
|
|
outs = []spendableOutput{aOut}
|
|
|
|
b := ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
bOut := txOutToSpendableOut(b, 0)
|
|
|
|
outs = []spendableOutput{bOut}
|
|
|
|
return ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
},
|
|
|
|
signalsReplacement: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "explicit signaling",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
return ctx.addSignedTx(outs, 1, 0, true, false)
|
|
|
|
},
|
|
|
|
signalsReplacement: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testCase := range testCases {
|
|
|
|
success := t.Run(testCase.name, func(t *testing.T) {
|
|
|
|
// We'll start each test by creating our mempool
|
|
|
|
// harness.
|
|
|
|
harness, _, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
ctx := &testContext{t, harness}
|
|
|
|
|
|
|
|
// Each test includes a setup method, which will set up
|
|
|
|
// its required dependencies. The transaction returned
|
|
|
|
// is the one we'll be using to determine if it signals
|
|
|
|
// replacement support.
|
|
|
|
tx := testCase.setup(ctx)
|
|
|
|
|
|
|
|
// Each test should match the expected response.
|
|
|
|
signalsReplacement := ctx.harness.txPool.signalsReplacement(
|
|
|
|
tx, nil,
|
|
|
|
)
|
|
|
|
if signalsReplacement && !testCase.signalsReplacement {
|
|
|
|
ctx.t.Fatalf("expected transaction %v to not "+
|
|
|
|
"signal replacement", tx.Hash())
|
|
|
|
}
|
|
|
|
if !signalsReplacement && testCase.signalsReplacement {
|
|
|
|
ctx.t.Fatalf("expected transaction %v to "+
|
|
|
|
"signal replacement", tx.Hash())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if !success {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestCheckPoolDoubleSpend ensures that the mempool can properly detect
|
|
|
|
// unconfirmed double spends in the case of replacement and non-replacement
|
|
|
|
// transactions.
|
|
|
|
func TestCheckPoolDoubleSpend(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
setup func(ctx *testContext) *btcutil.Tx
|
|
|
|
isReplacement bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
// Transactions that don't double spend any inputs,
|
|
|
|
// regardless of whether they signal replacement or not,
|
|
|
|
// are valid.
|
|
|
|
name: "no double spend",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
return ctx.addSignedTx(outs, 2, 0, false, false)
|
|
|
|
},
|
|
|
|
isReplacement: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Transactions that don't signal replacement and double
|
|
|
|
// spend inputs are invalid.
|
|
|
|
name: "non-replacement double spend",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
coinbase1 := ctx.addCoinbaseTx(1)
|
|
|
|
coinbaseOut1 := txOutToSpendableOut(coinbase1, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut1}
|
|
|
|
ctx.addSignedTx(outs, 1, 0, true, false)
|
|
|
|
|
|
|
|
coinbase2 := ctx.addCoinbaseTx(1)
|
|
|
|
coinbaseOut2 := txOutToSpendableOut(coinbase2, 0)
|
|
|
|
outs = []spendableOutput{coinbaseOut2}
|
|
|
|
ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
// Create a transaction that spends both
|
|
|
|
// coinbase outputs that were spent above. This
|
|
|
|
// should be detected as a double spend as one
|
|
|
|
// of the transactions doesn't signal
|
|
|
|
// replacement.
|
|
|
|
outs = []spendableOutput{coinbaseOut1, coinbaseOut2}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 1, 0, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx
|
|
|
|
},
|
|
|
|
isReplacement: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Transactions that double spend inputs and signal
|
|
|
|
// replacement are invalid if the mempool's policy
|
|
|
|
// rejects replacements.
|
|
|
|
name: "reject replacement policy",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
// Set the mempool's policy to reject
|
|
|
|
// replacements. Even if we have a transaction
|
|
|
|
// that spends inputs that signal replacement,
|
|
|
|
// it should still be rejected.
|
|
|
|
ctx.harness.txPool.cfg.Policy.RejectReplacement = true
|
|
|
|
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create a replaceable parent that spends the
|
|
|
|
// coinbase output.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(outs, 1, 0, true, false)
|
|
|
|
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
// Create another transaction that spends the
|
|
|
|
// same coinbase output. Since the original
|
|
|
|
// spender of this output, all of its spends
|
|
|
|
// should also be conflicts.
|
|
|
|
outs = []spendableOutput{coinbaseOut}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 2, 0, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx
|
|
|
|
},
|
|
|
|
isReplacement: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Transactions that double spend inputs and signal
|
|
|
|
// replacement are valid as long as the mempool's policy
|
|
|
|
// accepts them.
|
|
|
|
name: "replacement double spend",
|
|
|
|
setup: func(ctx *testContext) *btcutil.Tx {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create a replaceable parent that spends the
|
|
|
|
// coinbase output.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(outs, 1, 0, true, false)
|
|
|
|
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
// Create another transaction that spends the
|
|
|
|
// same coinbase output. Since the original
|
|
|
|
// spender of this output, all of its spends
|
|
|
|
// should also be conflicts.
|
|
|
|
outs = []spendableOutput{coinbaseOut}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 2, 0, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx
|
|
|
|
},
|
|
|
|
isReplacement: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testCase := range testCases {
|
|
|
|
success := t.Run(testCase.name, func(t *testing.T) {
|
|
|
|
// We'll start each test by creating our mempool
|
|
|
|
// harness.
|
|
|
|
harness, _, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
ctx := &testContext{t, harness}
|
|
|
|
|
|
|
|
// Each test includes a setup method, which will set up
|
|
|
|
// its required dependencies. The transaction returned
|
|
|
|
// is the one we'll be querying for the expected
|
|
|
|
// conflicts.
|
|
|
|
tx := testCase.setup(ctx)
|
|
|
|
|
|
|
|
// Ensure that the mempool properly detected the double
|
|
|
|
// spend unless this is a replacement transaction.
|
|
|
|
isReplacement, err :=
|
|
|
|
ctx.harness.txPool.checkPoolDoubleSpend(tx)
|
|
|
|
if testCase.isReplacement && err != nil {
|
|
|
|
t.Fatalf("expected no error for replacement "+
|
|
|
|
"transaction, got: %v", err)
|
|
|
|
}
|
|
|
|
if isReplacement && !testCase.isReplacement {
|
|
|
|
t.Fatalf("expected replacement transaction")
|
|
|
|
}
|
|
|
|
if !isReplacement && testCase.isReplacement {
|
|
|
|
t.Fatalf("expected non-replacement transaction")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if !success {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestConflicts ensures that the mempool can properly detect conflicts when
|
|
|
|
// processing new incoming transactions.
|
|
|
|
func TestConflicts(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
|
|
|
|
// setup sets up the required dependencies for each test. It
|
|
|
|
// returns the transaction we'll check for conflicts and its
|
|
|
|
// expected unique conflicts.
|
|
|
|
setup func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx)
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
// Create a transaction that would introduce no
|
|
|
|
// conflicts in the mempool. This is done by not
|
|
|
|
// spending any outputs that are currently being spent
|
|
|
|
// within the mempool.
|
|
|
|
name: "no conflicts",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 2, 0, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Create a transaction that would introduce two
|
|
|
|
// conflicts in the mempool by spending two outputs
|
|
|
|
// which are each already being spent by a different
|
|
|
|
// transaction within the mempool.
|
|
|
|
name: "conflicts",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase1 := ctx.addCoinbaseTx(1)
|
|
|
|
coinbaseOut1 := txOutToSpendableOut(coinbase1, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut1}
|
|
|
|
conflict1 := ctx.addSignedTx(
|
|
|
|
outs, 1, 0, false, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
coinbase2 := ctx.addCoinbaseTx(1)
|
|
|
|
coinbaseOut2 := txOutToSpendableOut(coinbase2, 0)
|
|
|
|
outs = []spendableOutput{coinbaseOut2}
|
|
|
|
conflict2 := ctx.addSignedTx(
|
|
|
|
outs, 1, 0, false, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// Create a transaction that spends both
|
|
|
|
// coinbase outputs that were spent above.
|
|
|
|
outs = []spendableOutput{coinbaseOut1, coinbaseOut2}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 1, 0, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, []*btcutil.Tx{conflict1, conflict2}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Create a transaction that would introduce two
|
|
|
|
// conflicts in the mempool by spending an output
|
|
|
|
// already being spent in the mempool by a different
|
|
|
|
// transaction. The second conflict stems from spending
|
|
|
|
// the transaction that spends the original spender of
|
|
|
|
// the output, i.e., a descendant of the original
|
|
|
|
// spender.
|
|
|
|
name: "descendant conflicts",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create a replaceable parent that spends the
|
|
|
|
// coinbase output.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
child := ctx.addSignedTx(outs, 1, 0, false, false)
|
|
|
|
|
|
|
|
// Create another transaction that spends the
|
|
|
|
// same coinbase output. Since the original
|
|
|
|
// spender of this output has descendants, they
|
|
|
|
// should also be conflicts.
|
|
|
|
outs = []spendableOutput{coinbaseOut}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 2, 0, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, []*btcutil.Tx{parent, child}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testCase := range testCases {
|
|
|
|
success := t.Run(testCase.name, func(t *testing.T) {
|
|
|
|
// We'll start each test by creating our mempool
|
|
|
|
// harness.
|
|
|
|
harness, _, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
ctx := &testContext{t, harness}
|
|
|
|
|
|
|
|
// Each test includes a setup method, which will set up
|
|
|
|
// its required dependencies. The transaction returned
|
|
|
|
// is the one we'll be querying for the expected
|
|
|
|
// conflicts.
|
|
|
|
tx, conflicts := testCase.setup(ctx)
|
|
|
|
|
|
|
|
// Assert the expected conflicts are returned.
|
|
|
|
txConflicts := ctx.harness.txPool.txConflicts(tx)
|
|
|
|
if len(txConflicts) != len(conflicts) {
|
|
|
|
ctx.t.Fatalf("expected %d conflicts, got %d",
|
|
|
|
len(conflicts), len(txConflicts))
|
|
|
|
}
|
|
|
|
for _, conflict := range conflicts {
|
|
|
|
conflictHash := *conflict.Hash()
|
|
|
|
if _, ok := txConflicts[conflictHash]; !ok {
|
|
|
|
ctx.t.Fatalf("expected %v to be found "+
|
|
|
|
"as a conflict", conflictHash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if !success {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestAncestorsDescendants ensures that we can properly retrieve the
|
|
|
|
// unconfirmed ancestors and descendants of a transaction.
|
|
|
|
func TestAncestorsDescendants(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// We'll start the test by initializing our mempool harness.
|
|
|
|
harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
ctx := &testContext{t, harness}
|
|
|
|
|
|
|
|
// We'll be creating the following chain of unconfirmed transactions:
|
|
|
|
//
|
|
|
|
// B ----
|
|
|
|
// / \
|
|
|
|
// A E
|
|
|
|
// \ /
|
|
|
|
// C -- D
|
|
|
|
//
|
|
|
|
// where B and C spend A, D spends C, and E spends B and D. We set up a
|
|
|
|
// chain like so to properly detect ancestors and descendants past a
|
|
|
|
// single parent/child.
|
|
|
|
aInputs := outputs[:1]
|
|
|
|
a := ctx.addSignedTx(aInputs, 2, 0, false, false)
|
|
|
|
|
|
|
|
bInputs := []spendableOutput{txOutToSpendableOut(a, 0)}
|
|
|
|
b := ctx.addSignedTx(bInputs, 1, 0, false, false)
|
|
|
|
|
|
|
|
cInputs := []spendableOutput{txOutToSpendableOut(a, 1)}
|
|
|
|
c := ctx.addSignedTx(cInputs, 1, 0, false, false)
|
|
|
|
|
|
|
|
dInputs := []spendableOutput{txOutToSpendableOut(c, 0)}
|
|
|
|
d := ctx.addSignedTx(dInputs, 1, 0, false, false)
|
|
|
|
|
|
|
|
eInputs := []spendableOutput{
|
|
|
|
txOutToSpendableOut(b, 0), txOutToSpendableOut(d, 0),
|
|
|
|
}
|
|
|
|
e := ctx.addSignedTx(eInputs, 1, 0, false, false)
|
|
|
|
|
|
|
|
// We'll be querying for the ancestors of E. We should expect to see all
|
|
|
|
// of the transactions that it depends on.
|
|
|
|
expectedAncestors := map[chainhash.Hash]struct{}{
|
2020-05-13 14:44:07 +02:00
|
|
|
*a.Hash(): {}, *b.Hash(): {},
|
|
|
|
*c.Hash(): {}, *d.Hash(): {},
|
2019-05-03 04:44:51 +02:00
|
|
|
}
|
|
|
|
ancestors := ctx.harness.txPool.txAncestors(e, nil)
|
|
|
|
if len(ancestors) != len(expectedAncestors) {
|
|
|
|
ctx.t.Fatalf("expected %d ancestors, got %d",
|
|
|
|
len(expectedAncestors), len(ancestors))
|
|
|
|
}
|
|
|
|
for ancestorHash := range ancestors {
|
|
|
|
if _, ok := expectedAncestors[ancestorHash]; !ok {
|
|
|
|
ctx.t.Fatalf("found unexpected ancestor %v",
|
|
|
|
ancestorHash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then, we'll query for the descendants of A. We should expect to see
|
|
|
|
// all of the transactions that depend on it.
|
|
|
|
expectedDescendants := map[chainhash.Hash]struct{}{
|
2020-05-13 14:44:07 +02:00
|
|
|
*b.Hash(): {}, *c.Hash(): {},
|
|
|
|
*d.Hash(): {}, *e.Hash(): {},
|
2019-05-03 04:44:51 +02:00
|
|
|
}
|
|
|
|
descendants := ctx.harness.txPool.txDescendants(a, nil)
|
|
|
|
if len(descendants) != len(expectedDescendants) {
|
|
|
|
ctx.t.Fatalf("expected %d descendants, got %d",
|
|
|
|
len(expectedDescendants), len(descendants))
|
|
|
|
}
|
|
|
|
for descendantHash := range descendants {
|
|
|
|
if _, ok := expectedDescendants[descendantHash]; !ok {
|
|
|
|
ctx.t.Fatalf("found unexpected descendant %v",
|
|
|
|
descendantHash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestRBF tests the different cases required for a transaction to properly
|
|
|
|
// replace its conflicts given that they all signal replacement.
|
|
|
|
func TestRBF(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
2021-08-19 20:41:48 +02:00
|
|
|
const defaultFee = btcutil.SatoshiPerBitcoin / 50
|
2019-05-03 04:44:51 +02:00
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
setup func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx)
|
|
|
|
err string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
// A transaction cannot replace another if it doesn't
|
|
|
|
// signal replacement.
|
|
|
|
name: "non-replaceable parent",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create a transaction that spends the coinbase
|
|
|
|
// output and doesn't signal for replacement.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
ctx.addSignedTx(outs, 1, defaultFee, false, false)
|
|
|
|
|
|
|
|
// Attempting to create another transaction that
|
|
|
|
// spends the same output should fail since the
|
|
|
|
// original transaction spending it doesn't
|
|
|
|
// signal replacement.
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 2, defaultFee, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
err: "already spent by transaction",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// A transaction cannot replace another if we don't
|
|
|
|
// allow accepting replacement transactions.
|
|
|
|
name: "reject replacement policy",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
ctx.harness.txPool.cfg.Policy.RejectReplacement = true
|
|
|
|
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create a transaction that spends the coinbase
|
|
|
|
// output and doesn't signal for replacement.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
ctx.addSignedTx(outs, 1, defaultFee, true, false)
|
|
|
|
|
|
|
|
// Attempting to create another transaction that
|
|
|
|
// spends the same output should fail since the
|
|
|
|
// original transaction spending it doesn't
|
|
|
|
// signal replacement.
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 2, defaultFee, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
err: "already spent by transaction",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// A transaction cannot replace another if doing so
|
|
|
|
// would cause more than 100 transactions being
|
|
|
|
// replaced.
|
|
|
|
name: "exceeds maximum conflicts",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
const numDescendants = 100
|
|
|
|
coinbaseOuts := make(
|
|
|
|
[]spendableOutput, numDescendants,
|
|
|
|
)
|
|
|
|
for i := 0; i < numDescendants; i++ {
|
|
|
|
tx := ctx.addCoinbaseTx(1)
|
|
|
|
coinbaseOuts[i] = txOutToSpendableOut(tx, 0)
|
|
|
|
}
|
|
|
|
parent := ctx.addSignedTx(
|
|
|
|
coinbaseOuts, numDescendants,
|
|
|
|
defaultFee, true, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// We'll then spend each output of the parent
|
|
|
|
// transaction with a distinct transaction.
|
|
|
|
for i := uint32(0); i < numDescendants; i++ {
|
|
|
|
out := txOutToSpendableOut(parent, i)
|
|
|
|
outs := []spendableOutput{out}
|
|
|
|
ctx.addSignedTx(
|
|
|
|
outs, 1, defaultFee, false, false,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll then create a replacement transaction
|
|
|
|
// by spending one of the coinbase outputs.
|
|
|
|
// Replacing the original spender of the
|
|
|
|
// coinbase output would evict the maximum
|
|
|
|
// number of transactions from the mempool,
|
|
|
|
// however, so we should reject it.
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
coinbaseOuts[:1], 1, defaultFee, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
err: "evicts more transactions than permitted",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// A transaction cannot replace another if the
|
|
|
|
// replacement ends up spending an output that belongs
|
|
|
|
// to one of the transactions it replaces.
|
|
|
|
name: "replacement spends parent transaction",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create a transaction that spends the coinbase
|
|
|
|
// output and signals replacement.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(
|
|
|
|
outs, 1, defaultFee, true, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// Attempting to create another transaction that
|
|
|
|
// spends it, but also replaces it, should be
|
|
|
|
// invalid.
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{coinbaseOut, parentOut}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 2, defaultFee, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
err: "spends parent transaction",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// A transaction cannot replace another if it has a
|
|
|
|
// lower fee rate than any of the transactions it
|
|
|
|
// intends to replace.
|
|
|
|
name: "insufficient fee rate",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase1 := ctx.addCoinbaseTx(1)
|
|
|
|
coinbase2 := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// We'll create two transactions that each spend
|
|
|
|
// one of the coinbase outputs. The first will
|
|
|
|
// have a higher fee rate than the second.
|
|
|
|
coinbaseOut1 := txOutToSpendableOut(coinbase1, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut1}
|
|
|
|
ctx.addSignedTx(outs, 1, defaultFee*2, true, false)
|
|
|
|
|
|
|
|
coinbaseOut2 := txOutToSpendableOut(coinbase2, 0)
|
|
|
|
outs = []spendableOutput{coinbaseOut2}
|
|
|
|
ctx.addSignedTx(outs, 1, defaultFee, true, false)
|
|
|
|
|
|
|
|
// We'll then create the replacement transaction
|
|
|
|
// by spending the coinbase outputs. It will be
|
|
|
|
// an invalid one however, since it won't have a
|
|
|
|
// higher fee rate than the first transaction.
|
|
|
|
outs = []spendableOutput{coinbaseOut1, coinbaseOut2}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 1, defaultFee*2, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
err: "insufficient fee rate",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// A transaction cannot replace another if it doesn't
|
|
|
|
// have an absolute greater than the transactions its
|
|
|
|
// replacing _plus_ the replacement transaction's
|
|
|
|
// minimum relay fee.
|
|
|
|
name: "insufficient absolute fee",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// We'll create a transaction with two outputs
|
|
|
|
// and the default fee.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
ctx.addSignedTx(outs, 2, defaultFee, true, false)
|
|
|
|
|
|
|
|
// We'll create a replacement transaction with
|
|
|
|
// one output, which should cause the
|
|
|
|
// transaction's absolute fee to be lower than
|
|
|
|
// the above's, so it'll be invalid.
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 1, defaultFee, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
err: "insufficient absolute fee",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// A transaction cannot replace another if it introduces
|
|
|
|
// a new unconfirmed input that was not already in any
|
|
|
|
// of the transactions it's directly replacing.
|
|
|
|
name: "spends new unconfirmed input",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase1 := ctx.addCoinbaseTx(1)
|
|
|
|
coinbase2 := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// We'll create two unconfirmed transactions
|
|
|
|
// from our coinbase transactions.
|
|
|
|
coinbaseOut1 := txOutToSpendableOut(coinbase1, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut1}
|
|
|
|
ctx.addSignedTx(outs, 1, defaultFee, true, false)
|
|
|
|
|
|
|
|
coinbaseOut2 := txOutToSpendableOut(coinbase2, 0)
|
|
|
|
outs = []spendableOutput{coinbaseOut2}
|
|
|
|
newTx := ctx.addSignedTx(
|
|
|
|
outs, 1, defaultFee, false, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// We should not be able to accept a replacement
|
|
|
|
// transaction that spends an unconfirmed input
|
|
|
|
// that was not previously included.
|
|
|
|
newTxOut := txOutToSpendableOut(newTx, 0)
|
|
|
|
outs = []spendableOutput{coinbaseOut1, newTxOut}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 1, defaultFee*2, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, nil
|
|
|
|
},
|
|
|
|
err: "spends new unconfirmed input",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// A transaction can replace another with a higher fee.
|
|
|
|
name: "higher fee",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create a transaction that we'll directly
|
|
|
|
// replace.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(
|
|
|
|
outs, 1, defaultFee, true, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// Spend the parent transaction to create a
|
|
|
|
// descendant that will be indirectly replaced.
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
outs = []spendableOutput{parentOut}
|
|
|
|
child := ctx.addSignedTx(
|
|
|
|
outs, 1, defaultFee, false, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// The replacement transaction should replace
|
|
|
|
// both transactions above since it has a higher
|
|
|
|
// fee and doesn't violate any other conditions
|
|
|
|
// within the RBF policy.
|
|
|
|
outs = []spendableOutput{coinbaseOut}
|
|
|
|
tx, err := ctx.harness.CreateSignedTx(
|
|
|
|
outs, 1, defaultFee*3, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create "+
|
|
|
|
"transaction: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return tx, []*btcutil.Tx{parent, child}
|
|
|
|
},
|
|
|
|
err: "",
|
|
|
|
},
|
2021-05-13 02:47:26 +02:00
|
|
|
{
|
|
|
|
// A transaction that doesn't signal replacement, can
|
|
|
|
// be replaced if the parent signals replacement.
|
|
|
|
name: "inherited replacement",
|
|
|
|
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
|
|
|
|
coinbase := ctx.addCoinbaseTx(1)
|
|
|
|
|
|
|
|
// Create an initial parent transaction that
|
|
|
|
// marks replacement, we won't be replacing
|
|
|
|
// this directly however.
|
|
|
|
coinbaseOut := txOutToSpendableOut(coinbase, 0)
|
|
|
|
outs := []spendableOutput{coinbaseOut}
|
|
|
|
parent := ctx.addSignedTx(
|
|
|
|
outs, 1, defaultFee, true, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// Now create a transaction that spends that
|
|
|
|
// parent transaction, which is marked as NOT
|
|
|
|
// being RBF-able.
|
|
|
|
parentOut := txOutToSpendableOut(parent, 0)
|
|
|
|
parentOuts := []spendableOutput{parentOut}
|
|
|
|
childNoReplace := ctx.addSignedTx(
|
|
|
|
parentOuts, 1, defaultFee, false, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// Now we'll create another transaction that
|
|
|
|
// replaces the *child* only. This should work
|
|
|
|
// as the parent has been marked for RBF, even
|
|
|
|
// though the child hasn't.
|
|
|
|
respendOuts := []spendableOutput{parentOut}
|
|
|
|
childReplace, err := ctx.harness.CreateSignedTx(
|
|
|
|
respendOuts, 1, defaultFee*3, false,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
ctx.t.Fatalf("unable to create child tx: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return childReplace, []*btcutil.Tx{childNoReplace}
|
|
|
|
},
|
|
|
|
err: "",
|
|
|
|
},
|
2019-05-03 04:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, testCase := range testCases {
|
|
|
|
success := t.Run(testCase.name, func(t *testing.T) {
|
|
|
|
// We'll start each test by creating our mempool
|
|
|
|
// harness.
|
|
|
|
harness, _, err := newPoolHarness(&chaincfg.MainNetParams)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll enable relay priority to ensure we can properly
|
|
|
|
// test fees between replacement transactions and the
|
|
|
|
// transactions it replaces.
|
|
|
|
harness.txPool.cfg.Policy.DisableRelayPriority = false
|
|
|
|
|
|
|
|
// Each test includes a setup method, which will set up
|
|
|
|
// its required dependencies. The transaction returned
|
|
|
|
// is the intended replacement, which should replace the
|
|
|
|
// expected list of transactions.
|
|
|
|
ctx := &testContext{t, harness}
|
|
|
|
replacementTx, replacedTxs := testCase.setup(ctx)
|
|
|
|
|
|
|
|
// Attempt to process the replacement transaction. If
|
|
|
|
// it's not a valid one, we should see the error
|
|
|
|
// expected by the test.
|
|
|
|
_, err = ctx.harness.txPool.ProcessTransaction(
|
|
|
|
replacementTx, false, false, 0,
|
|
|
|
)
|
|
|
|
if testCase.err == "" && err != nil {
|
|
|
|
ctx.t.Fatalf("expected no error when "+
|
|
|
|
"processing replacement transaction, "+
|
|
|
|
"got: %v", err)
|
|
|
|
}
|
|
|
|
if testCase.err != "" && err == nil {
|
|
|
|
ctx.t.Fatalf("expected error when processing "+
|
|
|
|
"replacement transaction: %v",
|
|
|
|
testCase.err)
|
|
|
|
}
|
|
|
|
if testCase.err != "" && err != nil {
|
|
|
|
if !strings.Contains(err.Error(), testCase.err) {
|
|
|
|
ctx.t.Fatalf("expected error: %v\n"+
|
|
|
|
"got: %v", testCase.err, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the replacement transaction is valid, we'll check
|
|
|
|
// that it has been included in the mempool and its
|
|
|
|
// conflicts have been removed. Otherwise, the conflicts
|
|
|
|
// should remain in the mempool.
|
|
|
|
valid := testCase.err == ""
|
|
|
|
for _, tx := range replacedTxs {
|
|
|
|
testPoolMembership(ctx, tx, false, !valid)
|
|
|
|
}
|
|
|
|
testPoolMembership(ctx, replacementTx, false, valid)
|
|
|
|
})
|
|
|
|
if !success {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|