// 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 / 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 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 := int64(totalInput) / int64(numOutputs) remainder := int64(totalInput) - amountPerOutput*int64(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: 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, 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 int64, numOutputs int64) (*util.Tx, error) { tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn(&wire.TxIn{ PreviousOutPoint: outpoint.outPoint, SignatureScript: nil, Sequence: wire.MaxTxInSequenceNum, }) amountPerOutput := (int64(outpoint.amount) - fee) / 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) } }