kaspad/mempool/mempool_test.go
Evgeny Khirin 499adbf046 DEV-222: Changed type of TxOut.Value and util.Amount to uin64 (#108)
* [DEV-222] Changed type of TxOut.Value and util.Amount to uin64

* [DEV-222] Replaced math.MaxUint64 with 0

* [DEV-222] Fixed comment to reflect uint64 instead of int64

* [DEV-222] Fixed overflow comment
2018-10-24 16:29:50 +03:00

1411 lines
48 KiB
Go

// 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 (
"bytes"
"encoding/hex"
"fmt"
"math"
"reflect"
"runtime"
"sync"
"testing"
"time"
"github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/btcec"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
// 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
// transactions to appear as though they are spending completely valid utxos.
type fakeChain struct {
sync.RWMutex
currentHeight int32
medianTimePast time.Time
}
// 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()
}
// 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()
}
func calcSequenceLock(tx *util.Tx,
utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
return &blockdag.SequenceLock{
Seconds: -1,
BlockHeight: -1,
}, nil
}
// spendableOutpoint is a convenience type that houses a particular utxo and the
// amount associated with it.
type spendableOutpoint struct {
outPoint wire.OutPoint
amount util.Amount
}
// txOutToSpendableOutpoint returns a spendable outpoint given a transaction and index
// of the output to use. This is useful as a convenience when creating test
// transactions.
func txOutToSpendableOutpoint(tx *util.Tx, outputNum uint32) spendableOutpoint {
return spendableOutpoint{
outPoint: wire.OutPoint{Hash: *tx.Hash(), Index: outputNum},
amount: util.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 util.Address
payScript []byte
chainParams *dagconfig.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) (*util.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
}
tx := wire.NewMsgTx(wire.TxVersion)
tx.AddTxIn(&wire.TxIn{
// Coinbase transactions have no inputs, so previous outpoint is
// zero hash and max index.
PreviousOutPoint: *wire.NewOutPoint(&daghash.Hash{},
wire.MaxPrevOutIndex),
SignatureScript: coinbaseScript,
Sequence: wire.MaxTxInSequenceNum,
})
totalInput := blockdag.CalcBlockSubsidy(blockHeight, p.chainParams)
amountPerOutput := totalInput / uint64(numOutputs)
remainder := totalInput - amountPerOutput*uint64(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 util.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.
func (p *poolHarness) CreateSignedTx(inputs []spendableOutpoint, numOutputs uint32) (*util.Tx, error) {
// Calculate the total input amount and split it amongst the requested
// number of outputs.
var totalInput util.Amount
for _, input := range inputs {
totalInput += input.amount
}
amountPerOutput := uint64(totalInput) / uint64(numOutputs)
remainder := uint64(totalInput) - amountPerOutput*uint64(numOutputs)
tx := wire.NewMsgTx(wire.TxVersion)
for _, input := range inputs {
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: input.outPoint,
SignatureScript: nil,
Sequence: wire.MaxTxInSequenceNum,
})
}
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 util.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 spendableOutpoint, numTxns uint32) ([]*util.Tx, error) {
txChain := make([]*util.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.
tx := wire.NewMsgTx(wire.TxVersion)
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: prevOutPoint,
SignatureScript: nil,
Sequence: wire.MaxTxInSequenceNum,
})
tx.AddTxOut(&wire.TxOut{
PkScript: p.payScript,
Value: uint64(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, util.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(dagParams *dagconfig.Params, numOutputs uint32, dbName string) (*poolHarness, []spendableOutpoint, 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 := util.NewAddressPubKey(pubKeyBytes, dagParams.Prefix)
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 database and chain instance to run tests against.
dag, teardownFunc, err := blockdag.DAGSetup(dbName,
&dagconfig.MainNetParams)
if err != nil {
return nil, nil, fmt.Errorf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
// Create a new fake chain and harness bound to it.
chain := &fakeChain{}
harness := poolHarness{
signKey: signKey,
payAddr: payAddr,
payScript: pkScript,
chainParams: dagParams,
chain: chain,
txPool: New(&Config{
DAG: dag,
Policy: Policy{
DisableRelayPriority: true,
FreeTxRelayLimit: 15.0,
MaxOrphanTxs: 5,
MaxOrphanTxSize: 1000,
MaxSigOpsPerTx: blockdag.MaxSigOpsPerBlock / 5,
MinRelayTxFee: 1000, // 1 Satoshi per byte
MaxTxVersion: 1,
},
DAGParams: dagParams,
BestHeight: chain.BestHeight,
MedianTimePast: chain.MedianTimePast,
CalcSequenceLock: calcSequenceLock,
SigCache: nil,
AddrIndex: nil,
}),
}
// 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.
outpoints := make([]spendableOutpoint, 0, numOutputs)
curHeight := harness.chain.BestHeight()
coinbase, err := harness.CreateCoinbaseTx(curHeight+1, numOutputs)
if err != nil {
return nil, nil, err
}
harness.txPool.mpUTXOSet.AddTx(coinbase.MsgTx(), curHeight+1)
for i := uint32(0); i < numOutputs; i++ {
outpoints = append(outpoints, txOutToSpendableOutpoint(coinbase, i))
}
harness.chain.SetHeight(int32(dagParams.CoinbaseMaturity) + curHeight)
harness.chain.SetMedianTimePast(time.Now())
return &harness, outpoints, nil
}
// 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
}
// 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 *util.Tx, inOrphanPool, inTxPool bool) {
txHash := tx.Hash()
gotOrphanPool := tc.harness.txPool.IsOrphanInPool(txHash)
if inOrphanPool != gotOrphanPool {
_, file, line, _ := runtime.Caller(1)
tc.t.Fatalf("%s:%d -- IsOrphanInPool: want %v, got %v", file,
line, inOrphanPool, gotOrphanPool)
}
gotTxPool := tc.harness.txPool.IsTransactionInPool(txHash)
if inTxPool != gotTxPool {
_, file, line, _ := runtime.Caller(1)
tc.t.Fatalf("%s:%d -- IsTransactionInPool: want %v, got %v",
file, line, inTxPool, gotTxPool)
}
gotHaveTx := tc.harness.txPool.HaveTransaction(txHash)
wantHaveTx := inOrphanPool || inTxPool
if wantHaveTx != gotHaveTx {
_, file, line, _ := runtime.Caller(1)
tc.t.Fatalf("%s:%d -- HaveTransaction: want %v, got %v", file,
line, wantHaveTx, gotHaveTx)
}
}
func (p *poolHarness) createTx(outpoint spendableOutpoint, fee uint64, numOutputs int64) (*util.Tx, error) {
tx := wire.NewMsgTx(wire.TxVersion)
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: outpoint.outPoint,
SignatureScript: nil,
Sequence: wire.MaxTxInSequenceNum,
})
amountPerOutput := (uint64(outpoint.amount) - fee) / uint64(numOutputs)
for i := int64(0); i < numOutputs; i++ {
tx.AddTxOut(&wire.TxOut{
PkScript: p.payScript,
Value: amountPerOutput,
})
}
// 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
return util.NewTx(tx), nil
}
func TestProcessTransaction(t *testing.T) {
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 6, "TestProcessTransaction")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
//Checks that a transaction cannot be added to the transaction pool if it's already there
tx, err := harness.createTx(spendableOuts[0], 0, 1)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
if err != nil {
t.Errorf("ProcessTransaction: unexpected error: %v", err)
}
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectDuplicate {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectDuplicate, code)
}
orphanedTx, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{}, Index: 1},
}}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
//Checks that an orphaned transaction cannot be
//added to the orphan pool if MaxOrphanTxs is 0
harness.txPool.cfg.Policy.MaxOrphanTxs = 0
_, err = harness.txPool.ProcessTransaction(orphanedTx, true, false, 0)
if err != nil {
t.Errorf("ProcessTransaction: unexpected error: %v", err)
}
testPoolMembership(tc, orphanedTx, false, false)
harness.txPool.cfg.Policy.MaxOrphanTxs = 5
_, err = harness.txPool.ProcessTransaction(orphanedTx, true, false, 0)
if err != nil {
t.Errorf("ProcessTransaction: unexpected error: %v", err)
}
//Checks that an orphaned transaction cannot be
//added to the orphan pool if it's already there
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectDuplicate {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectDuplicate, code)
}
//Checks that a coinbase transaction cannot be added to the mempool
curHeight := harness.chain.BestHeight()
coinbase, err := harness.CreateCoinbaseTx(curHeight+1, 1)
if err != nil {
t.Errorf("CreateCoinbaseTx: %v", err)
}
_, err = harness.txPool.ProcessTransaction(coinbase, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectInvalid {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectInvalid, code)
}
//Checks that non standard transactions are rejected from the mempool
nonStdTx, err := harness.createTx(spendableOuts[0], 0, 1)
nonStdTx.MsgTx().Version = wire.TxVersion + 1
_, err = harness.txPool.ProcessTransaction(nonStdTx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectNonstandard {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectNonstandard, code)
}
//Checks that a transaction is rejected from the mempool if its
//size is above 50KB, and its fee is below the minimum relay fee
bigLowFeeTx, err := harness.createTx(spendableOuts[1], 0, 2000) //A transaction with 2000 outputs, in order to make it bigger than 50kb
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
_, err = harness.txPool.ProcessTransaction(bigLowFeeTx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectInsufficientFee {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectInsufficientFee, code)
}
//Checks that if a ps2h sigscript has more sigops then maxStandardP2SHSigOps, it gets rejected
//maxStandardP2SHSigOps is 15, so 16 OpCheckSig will make it a non standard script
nonStdSigScript, err := txscript.NewScriptBuilder().
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
AddOp(txscript.OpCheckSig).
Script()
if err != nil {
t.Fatalf("Script: error creating nonStdSigScript: %v", err)
}
p2shPKScript, err := txscript.NewScriptBuilder().
AddOp(txscript.OpHash160).
AddData(util.Hash160(nonStdSigScript)).
AddOp(txscript.OpEqual).
Script()
if err != nil {
t.Fatalf("Script: error creating p2shPKScript: %v", err)
}
wrappedP2SHNonStdSigScript, err := txscript.NewScriptBuilder().AddData(nonStdSigScript).Script()
if err != nil {
t.Fatalf("Script: error creating wrappedP2shNonSigScript: %v", err)
}
dummyPrevOutHash, err := daghash.NewHashFromStr("01")
if err != nil {
t.Fatalf("NewShaHashFromStr: unexpected error: %v", err)
}
dummyPrevOut := wire.OutPoint{Hash: *dummyPrevOutHash, Index: 1}
dummySigScript := bytes.Repeat([]byte{0x00}, 65)
addrHash := [20]byte{0x01}
addr, err := util.NewAddressPubKeyHash(addrHash[:],
util.Bech32PrefixDAGTest)
if err != nil {
t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err)
}
dummyPkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
}
p2shTx := util.NewTx(&wire.MsgTx{
Version: 1,
TxOut: []*wire.TxOut{{
Value: 5000000000,
PkScript: p2shPKScript,
}},
LockTime: 0,
})
harness.txPool.mpUTXOSet.AddTx(p2shTx.MsgTx(), curHeight+1)
nonStdSigScriptTx := util.NewTx(&wire.MsgTx{
Version: 1,
TxIn: []*wire.TxIn{&wire.TxIn{
PreviousOutPoint: wire.OutPoint{Hash: *p2shTx.Hash(), Index: 0},
SignatureScript: wrappedP2SHNonStdSigScript,
Sequence: wire.MaxTxInSequenceNum,
}},
TxOut: []*wire.TxOut{{
Value: 5000000000,
PkScript: dummyPkScript,
}},
LockTime: 0,
})
_, err = harness.txPool.ProcessTransaction(nonStdSigScriptTx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectNonstandard {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectNonstandard, code)
}
expectedErrStr := fmt.Sprintf("transaction %v has a non-standard input: "+
"transaction input #%d has "+
"%d signature operations which is more "+
"than the allowed max amount of %d",
nonStdSigScriptTx.Hash(), 0, 16, 15)
if expectedErrStr != err.Error() {
t.Errorf("Unexpected error message. Expected \"%s\" but got \"%s\"", expectedErrStr, err.Error())
}
//Checks that even if we accept non standard transactions, we reject by the MaxSigOpsPerTx consensus rule
harness.txPool.cfg.Policy.AcceptNonStd = true
harness.txPool.cfg.Policy.MaxSigOpsPerTx = 15
_, err = harness.txPool.ProcessTransaction(nonStdSigScriptTx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectNonstandard {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectNonstandard, code)
}
expectedErrStr = fmt.Sprintf("transaction %v sigop count is too high: %v > %v",
nonStdSigScriptTx.Hash(), 16, 15)
if expectedErrStr != err.Error() {
t.Errorf("Unexpected error message. Expected \"%s\" but got \"%s\"", expectedErrStr, err.Error())
}
harness.txPool.cfg.Policy.AcceptNonStd = false
//Checks that a transaction with no outputs will get rejected
noOutsTx := util.NewTx(&wire.MsgTx{
Version: 1,
TxIn: []*wire.TxIn{&wire.TxIn{
PreviousOutPoint: dummyPrevOut,
SignatureScript: dummySigScript,
Sequence: wire.MaxTxInSequenceNum,
}},
TxOut: []*wire.TxOut{},
LockTime: 0,
})
_, err = harness.txPool.ProcessTransaction(noOutsTx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectInvalid {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectInvalid, code)
}
expectedErrStr = "transaction has no outputs"
if err.Error() != "transaction has no outputs" {
t.Errorf("Unexpected error message. Expected \"%s\" but got \"%s\"", expectedErrStr, err.Error())
}
//Checks that transactions get rejected from mempool if sequence lock is not active
harness.txPool.cfg.CalcSequenceLock = func(tx *util.Tx,
view blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
return &blockdag.SequenceLock{
Seconds: math.MaxInt64,
BlockHeight: math.MaxInt32,
}, nil
}
tx, err = harness.createTx(spendableOuts[2], 0, 1)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectNonstandard {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectNonstandard, code)
}
expectedErrStr = "transaction's sequence locks on inputs not met"
if err.Error() != expectedErrStr {
t.Errorf("Unexpected error message. Expected \"%s\" but got \"%s\"", expectedErrStr, err.Error())
}
harness.txPool.cfg.CalcSequenceLock = calcSequenceLock
harness.txPool.cfg.Policy.DisableRelayPriority = false
//Transaction should be accepted to mempool although it has low fee, because its priority is above mining.MinHighPriority
tx, err = harness.createTx(spendableOuts[3], 0, 1)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
if err != nil {
t.Errorf("ProcessTransaction: unexpected error: %v", err)
}
//Transaction should be rejected from mempool because it has low fee, and its priority is above mining.MinHighPriority
tx, err = harness.createTx(spendableOuts[4], 0, 100)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectInsufficientFee {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectInsufficientFee, code)
}
harness.txPool.cfg.Policy.DisableRelayPriority = true
tx = util.NewTx(&wire.MsgTx{
Version: 1,
TxIn: []*wire.TxIn{&wire.TxIn{
PreviousOutPoint: spendableOuts[5].outPoint,
SignatureScript: []byte{02, 01}, //Unparsable script
Sequence: wire.MaxTxInSequenceNum,
}},
TxOut: []*wire.TxOut{{
Value: 1,
PkScript: dummyPkScript,
}},
LockTime: 0,
})
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
fmt.Println(err)
if err == nil {
t.Errorf("ProcessTransaction: expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectNonstandard {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectNonstandard, code)
}
}
func TestDoubleSpends(t *testing.T) {
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestDoubleSpends")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
//Add two transactions to the mempool
tx1, err := harness.createTx(spendableOuts[0], 0, 1)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
harness.txPool.ProcessTransaction(tx1, true, false, 0)
tx2, err := harness.createTx(spendableOuts[1], 1, 1)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
harness.txPool.ProcessTransaction(tx2, true, false, 0)
testPoolMembership(tc, tx1, false, true)
testPoolMembership(tc, tx2, false, true)
//Spends the same outpoint as tx2
tx3, err := harness.createTx(spendableOuts[0], 2, 1) //We put here different fee to create different transaction hash
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
//First we try to add it to the mempool and see it rejected
_, err = harness.txPool.ProcessTransaction(tx3, true, false, 0)
if err == nil {
t.Errorf("ProcessTransaction expected an error, not nil")
}
if code, _ := extractRejectCode(err); code != wire.RejectDuplicate {
t.Errorf("Unexpected error code. Expected %v but got %v", wire.RejectDuplicate, code)
}
testPoolMembership(tc, tx3, false, false)
//Then we assume tx3 is already in the DAG, so we need to remove
//transactions that spends the same outpoints from the mempool
harness.txPool.RemoveDoubleSpends(tx3)
//Ensures that only the transaction that double spends the same
//funds as tx3 is removed, and the other one remains unaffected
testPoolMembership(tc, tx1, false, false)
testPoolMembership(tc, tx2, false, true)
}
//TestFetchTransaction checks that FetchTransaction
//returns only transaction from the main pool and not from the orphan pool
func TestFetchTransaction(t *testing.T) {
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestFetchTransaction")
tc := &testContext{t, harness}
orphanedTx, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{1}, Index: 1},
}}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
harness.txPool.ProcessTransaction(orphanedTx, true, false, 0)
testPoolMembership(tc, orphanedTx, true, false)
fetchedorphanedTx, err := harness.txPool.FetchTransaction(orphanedTx.Hash())
if fetchedorphanedTx != nil {
t.Fatalf("FetchTransaction: expected fetchedorphanedTx to be nil")
}
if err == nil {
t.Errorf("FetchTransaction: expected an error, not nil")
}
tx, err := harness.createTx(spendableOuts[0], 0, 1)
if err != nil {
t.Fatalf("unable to create transaction: %v", err)
}
harness.txPool.ProcessTransaction(tx, true, false, 0)
testPoolMembership(tc, tx, false, true)
fetchedTx, err := harness.txPool.FetchTransaction(tx.Hash())
if !reflect.DeepEqual(fetchedTx, tx) {
t.Fatalf("FetchTransaction: returned a transaction, but not the right one")
}
if err != nil {
t.Errorf("FetchTransaction: unexpected error: %v", err)
}
}
// 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) {
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestSimpleOrphanChain")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
// 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,
false, 0)
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, is not in the
// transaction pool, and is reported as available.
testPoolMembership(tc, tx, true, false)
}
// 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],
false, false, 0)
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))
}
for _, txD := range acceptedTxns {
// Ensure the transaction is no longer in the orphan pool, is
// now in the transaction pool, and is reported as available.
testPoolMembership(tc, txD.Tx, false, true)
}
}
// TestOrphanReject ensures that orphans are properly rejected when the allow
// orphans flag is not set on ProcessTransaction.
func TestOrphanReject(t *testing.T) {
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanReject")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
// 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,
false, 0)
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))
}
// Ensure the transaction is not in the orphan pool, not in the
// transaction pool, and not reported as available
testPoolMembership(tc, tx, false, false)
}
}
//TestOrphanExpiration checks every time we add an orphan transaction
// it will check if we are beyond nextExpireScan, and if so, it will remove
// all expired orphan transactions
func TestOrphanExpiration(t *testing.T) {
harness, _, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanExpiration")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
expiredTx, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{}, Index: 0},
}}, 1)
harness.txPool.ProcessTransaction(expiredTx, true,
false, 0)
harness.txPool.orphans[*expiredTx.Hash()].expiration = time.Unix(0, 0)
tx1, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{1}, Index: 0},
}}, 1)
harness.txPool.ProcessTransaction(tx1, true,
false, 0)
//First check that expired orphan transactions are not removed before nextExpireScan
testPoolMembership(tc, tx1, true, false)
testPoolMembership(tc, expiredTx, true, false)
//Force nextExpireScan to be in the past
harness.txPool.nextExpireScan = time.Unix(0, 0)
fmt.Println(harness.txPool.nextExpireScan.Unix())
tx2, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{2}, Index: 0},
}}, 1)
harness.txPool.ProcessTransaction(tx2, true,
false, 0)
//Check that only expired orphan transactions are removed
testPoolMembership(tc, tx1, true, false)
testPoolMembership(tc, tx2, true, false)
testPoolMembership(tc, expiredTx, false, false)
}
//TestMaxOrphanTxSize ensures that a transaction that is
//bigger than MaxOrphanTxSize will get rejected
func TestMaxOrphanTxSize(t *testing.T) {
harness, _, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestMaxOrphanTxSize")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
harness.txPool.cfg.Policy.MaxOrphanTxSize = 1
tx, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{}, Index: 0},
}}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
harness.txPool.ProcessTransaction(tx, true,
false, 0)
testPoolMembership(tc, tx, false, false)
harness.txPool.cfg.Policy.MaxOrphanTxSize = math.MaxInt32
harness.txPool.ProcessTransaction(tx, true,
false, 0)
testPoolMembership(tc, tx, true, false)
}
func TestRemoveTransaction(t *testing.T) {
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestRemoveTransaction")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
chainedTxns, err := harness.CreateTxChain(outputs[0], 5)
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: %v", err)
}
testPoolMembership(tc, tx, false, true)
}
//Checks that when removeRedeemers is false, the specified transaction is the only transaction that gets removed
harness.txPool.RemoveTransaction(chainedTxns[3], false, true)
testPoolMembership(tc, chainedTxns[3], false, false)
testPoolMembership(tc, chainedTxns[4], false, true)
//Checks that when removeRedeemers is true, all of the transaction that are dependent on it get removed
harness.txPool.RemoveTransaction(chainedTxns[1], true, true)
testPoolMembership(tc, chainedTxns[0], false, true)
testPoolMembership(tc, chainedTxns[1], false, false)
testPoolMembership(tc, chainedTxns[2], false, false)
}
// TestOrphanEviction ensures that exceeding the maximum number of orphans
// evicts entries to make room for the new ones.
func TestOrphanEviction(t *testing.T) {
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanEviction")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
// 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,
false, 0)
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, is not in the
// transaction pool, and is reported as available.
testPoolMembership(tc, tx, true, false)
}
// Figure out which transactions were evicted and make sure the number
// evicted matches the expected number.
var evictedTxns []*util.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)
}
// Ensure none of the evicted transactions ended up in the transaction
// pool.
for _, tx := range evictedTxns {
testPoolMembership(tc, tx, false, false)
}
}
// Attempt to remove orphans by tag,
// and ensure the state of all other orphans are unaffected.
func TestRemoveOrphansByTag(t *testing.T) {
harness, _, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestRemoveOrphansByTag")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
tc := &testContext{t, harness}
orphanedTx1, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{1}, Index: 1},
}}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
harness.txPool.ProcessTransaction(orphanedTx1, true,
false, 1)
orphanedTx2, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{2}, Index: 2},
}}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
harness.txPool.ProcessTransaction(orphanedTx2, true,
false, 1)
orphanedTx3, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{3}, Index: 3},
}}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
harness.txPool.ProcessTransaction(orphanedTx3, true,
false, 1)
orphanedTx4, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{4}, Index: 4},
}}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
harness.txPool.ProcessTransaction(orphanedTx4, true,
false, 2)
harness.txPool.RemoveOrphansByTag(1)
testPoolMembership(tc, orphanedTx1, false, false)
testPoolMembership(tc, orphanedTx2, false, false)
testPoolMembership(tc, orphanedTx3, false, false)
testPoolMembership(tc, orphanedTx4, true, false)
}
// 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) {
const maxOrphans = 4
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestBasicOrphanRemoval")
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,
false, 0)
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([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{}, Index: 0},
}}, 1)
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) {
const maxOrphans = 10
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanChainRemoval")
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,
false, 0)
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) {
const maxOrphans = 4
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestMultiInputOrphanDoubleSpend")
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,
false, 0)
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([]spendableOutpoint{
txOutToSpendableOutpoint(chainedTxns[1], 0),
txOutToSpendableOutpoint(chainedTxns[maxOrphans], 0),
}, 1)
if err != nil {
t.Fatalf("unable to create signed tx: %v", err)
}
acceptedTxns, err := harness.txPool.ProcessTransaction(doubleSpendTx,
true, false, 0)
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],
false, false, 0)
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)
}
for _, txD := range acceptedTxns {
// Ensure the transaction is no longer in the orphan pool, is
// in the transaction pool, and is reported as available.
testPoolMembership(tc, txD.Tx, false, true)
}
// 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)
}
// TestCheckSpend tests that CheckSpend returns the expected spends found in
// the mempool.
func TestCheckSpend(t *testing.T) {
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestCheckSpend")
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)
}
}