mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
1797 lines
60 KiB
Go
1797 lines
60 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"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"runtime"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"bou.ke/monkey"
|
|
"github.com/daglabs/btcd/blockdag"
|
|
"github.com/daglabs/btcd/blockdag/indexers"
|
|
"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, blockdag.Config{
|
|
DAGParams: &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)
|
|
}
|
|
|
|
count := tc.harness.txPool.Count()
|
|
txHashes := tc.harness.txPool.TxHashes()
|
|
txDescs := tc.harness.txPool.TxDescs()
|
|
txMiningDescs := tc.harness.txPool.MiningDescs()
|
|
if count != len(txHashes) || count != len(txDescs) || count != len(txMiningDescs) {
|
|
tc.t.Error("mempool.TxHashes(), mempool.TxDescs() and mempool.MiningDescs() have different length")
|
|
}
|
|
if inTxPool {
|
|
wasFound := false
|
|
for _, txh := range txHashes {
|
|
if *txHash == *txh {
|
|
wasFound = true
|
|
break
|
|
}
|
|
}
|
|
if !wasFound {
|
|
tc.t.Error("Can not find transaction in mempool.TxHashes")
|
|
}
|
|
|
|
wasFound = false
|
|
for _, txd := range txDescs {
|
|
if *txHash == *txd.Tx.Hash() {
|
|
wasFound = true
|
|
break
|
|
}
|
|
}
|
|
if !wasFound {
|
|
tc.t.Error("Can not find transaction in mempool.TxDescs")
|
|
}
|
|
|
|
wasFound = false
|
|
for _, txd := range txMiningDescs {
|
|
if *txHash == *txd.Tx.Hash() {
|
|
wasFound = true
|
|
break
|
|
}
|
|
}
|
|
if !wasFound {
|
|
tc.t.Error("Can not find transaction in mempool.MiningDescs")
|
|
}
|
|
}
|
|
}
|
|
|
|
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{{
|
|
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{{
|
|
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{{
|
|
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 TestAddrIndex(t *testing.T) {
|
|
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestAddrIndex")
|
|
if err != nil {
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
}
|
|
harness.txPool.cfg.AddrIndex = &indexers.AddrIndex{}
|
|
enteredAddUnconfirmedTx := false
|
|
guard := monkey.Patch((*indexers.AddrIndex).AddUnconfirmedTx, func(idx *indexers.AddrIndex, tx *util.Tx, utxoSet blockdag.UTXOSet) {
|
|
enteredAddUnconfirmedTx = true
|
|
})
|
|
defer guard.Unpatch()
|
|
enteredRemoveUnconfirmedTx := false
|
|
guard = monkey.Patch((*indexers.AddrIndex).RemoveUnconfirmedTx, func(idx *indexers.AddrIndex, hash *daghash.Hash) {
|
|
enteredRemoveUnconfirmedTx = true
|
|
})
|
|
defer guard.Unpatch()
|
|
|
|
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)
|
|
}
|
|
|
|
if !enteredAddUnconfirmedTx {
|
|
t.Errorf("TestAddrIndex: (*indexers.AddrIndex).AddUnconfirmedTx was not called")
|
|
}
|
|
|
|
err = harness.txPool.RemoveTransaction(tx, false, false)
|
|
if err != nil {
|
|
t.Errorf("TestAddrIndex: unexpected error: %v", err)
|
|
}
|
|
|
|
if !enteredRemoveUnconfirmedTx {
|
|
t.Errorf("TestAddrIndex: (*indexers.AddrIndex).RemoveUnconfirmedTx was not called")
|
|
}
|
|
}
|
|
|
|
func TestFeeEstimatorCfg(t *testing.T) {
|
|
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestFeeEstimatorCfg")
|
|
if err != nil {
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
}
|
|
harness.txPool.cfg.FeeEstimator = &FeeEstimator{}
|
|
enteredObserveTransaction := false
|
|
guard := monkey.Patch((*FeeEstimator).ObserveTransaction, func(ef *FeeEstimator, t *TxDesc) {
|
|
enteredObserveTransaction = true
|
|
})
|
|
defer guard.Unpatch()
|
|
|
|
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)
|
|
}
|
|
|
|
if !enteredObserveTransaction {
|
|
t.Errorf("TestFeeEstimatorCfg: (*FeeEstimator).ObserveTransaction was not called")
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
fakeWithDiffErr := "error from WithDiff"
|
|
guard := monkey.Patch((*blockdag.DiffUTXOSet).WithDiff, func(_ *blockdag.DiffUTXOSet, _ *blockdag.UTXODiff) (blockdag.UTXOSet, error) {
|
|
return nil, errors.New(fakeWithDiffErr)
|
|
})
|
|
defer guard.Unpatch()
|
|
err = harness.txPool.RemoveTransaction(chainedTxns[0], false, false)
|
|
if err == nil || err.Error() != fakeWithDiffErr {
|
|
t.Errorf("RemoveTransaction: expected error %v but got %v", fakeWithDiffErr, err)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
func TestCount(t *testing.T) {
|
|
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestCount")
|
|
if err != nil {
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
}
|
|
if harness.txPool.Count() != 0 {
|
|
t.Errorf("TestCount: txPool should be initialized with 0 transactions")
|
|
}
|
|
|
|
chainedTxns, err := harness.CreateTxChain(outputs[0], 3)
|
|
if err != nil {
|
|
t.Fatalf("harness.CreateTxChain: unexpected error: %v", err)
|
|
}
|
|
|
|
for i, tx := range chainedTxns {
|
|
_, err = harness.txPool.ProcessTransaction(tx, true, false, 0)
|
|
if err != nil {
|
|
t.Errorf("ProcessTransaction: unexpected error: %v", err)
|
|
}
|
|
if harness.txPool.Count() != i+1 {
|
|
t.Errorf("TestCount: txPool expected to have %v transactions but got %v", i+1, harness.txPool.Count())
|
|
}
|
|
}
|
|
|
|
err = harness.txPool.RemoveTransaction(chainedTxns[0], false, false)
|
|
if err != nil {
|
|
t.Fatalf("harness.CreateTxChain: unexpected error: %v", err)
|
|
}
|
|
if harness.txPool.Count() != 2 {
|
|
t.Errorf("TestCount: txPool expected to have 2 transactions but got %v", harness.txPool.Count())
|
|
}
|
|
}
|
|
|
|
func TestExtractRejectCode(t *testing.T) {
|
|
tests := []struct {
|
|
blockdagRuleErrorCode blockdag.ErrorCode
|
|
wireRejectCode wire.RejectCode
|
|
}{
|
|
{
|
|
blockdagRuleErrorCode: blockdag.ErrDuplicateBlock,
|
|
wireRejectCode: wire.RejectDuplicate,
|
|
},
|
|
{
|
|
blockdagRuleErrorCode: blockdag.ErrBlockVersionTooOld,
|
|
wireRejectCode: wire.RejectObsolete,
|
|
},
|
|
{
|
|
blockdagRuleErrorCode: blockdag.ErrCheckpointTimeTooOld,
|
|
wireRejectCode: wire.RejectCheckpoint,
|
|
},
|
|
{
|
|
blockdagRuleErrorCode: blockdag.ErrDifficultyTooLow,
|
|
wireRejectCode: wire.RejectCheckpoint,
|
|
},
|
|
{
|
|
blockdagRuleErrorCode: blockdag.ErrBadCheckpoint,
|
|
wireRejectCode: wire.RejectCheckpoint,
|
|
},
|
|
{
|
|
blockdagRuleErrorCode: blockdag.ErrForkTooOld,
|
|
wireRejectCode: wire.RejectCheckpoint,
|
|
},
|
|
{
|
|
blockdagRuleErrorCode: math.MaxUint32,
|
|
wireRejectCode: wire.RejectInvalid,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
err := blockdag.RuleError{ErrorCode: test.blockdagRuleErrorCode}
|
|
code, ok := extractRejectCode(err)
|
|
if !ok {
|
|
t.Errorf("TestExtractRejectCode: %v could not be extracted", test.blockdagRuleErrorCode)
|
|
}
|
|
if test.wireRejectCode != code {
|
|
t.Errorf("TestExtractRejectCode: expected %v to extract %v but got %v", test.blockdagRuleErrorCode, test.wireRejectCode, code)
|
|
}
|
|
}
|
|
|
|
txRuleError := TxRuleError{RejectCode: wire.RejectDust}
|
|
txExtractedCode, ok := extractRejectCode(txRuleError)
|
|
if !ok {
|
|
t.Errorf("TestExtractRejectCode: %v could not be extracted", txRuleError)
|
|
}
|
|
if txExtractedCode != wire.RejectDust {
|
|
t.Errorf("TestExtractRejectCode: expected %v to extract %v but got %v", wire.RejectDust, wire.RejectDust, txExtractedCode)
|
|
}
|
|
|
|
var nilErr error
|
|
nilErrExtractedCode, ok := extractRejectCode(nilErr)
|
|
if nilErrExtractedCode != wire.RejectInvalid {
|
|
t.Errorf("TestExtractRejectCode: expected %v to extract %v but got %v", wire.RejectInvalid, wire.RejectInvalid, nilErrExtractedCode)
|
|
}
|
|
if ok {
|
|
t.Errorf("TestExtractRejectCode: a nil error is expected to return false but got %v", ok)
|
|
}
|
|
|
|
nonRuleError := errors.New("nonRuleError")
|
|
|
|
fErrExtractedCode, ok := extractRejectCode(nonRuleError)
|
|
if fErrExtractedCode != wire.RejectInvalid {
|
|
t.Errorf("TestExtractRejectCode: expected %v to extract %v but got %v", wire.RejectInvalid, wire.RejectInvalid, nilErrExtractedCode)
|
|
}
|
|
if ok {
|
|
t.Errorf("TestExtractRejectCode: a nonRuleError is expected to return false but got %v", ok)
|
|
}
|
|
}
|
|
|
|
// TestHandleNewBlock
|
|
func TestHandleNewBlock(t *testing.T) {
|
|
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 2, "TestHandleNewBlock")
|
|
if err != nil {
|
|
t.Fatalf("unable to create test pool: %v", err)
|
|
}
|
|
tc := &testContext{t, harness}
|
|
|
|
// Create parent transaction for orphan transaction below
|
|
blockTx1, err := harness.CreateSignedTx(spendableOuts[:1], 1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
|
|
// Create orphan transaction and add it to UTXO set
|
|
hash := blockTx1.Hash()
|
|
orphanTx, err := harness.CreateSignedTx([]spendableOutpoint{{
|
|
amount: util.Amount(2500000000),
|
|
outPoint: wire.OutPoint{Hash: *hash, Index: 0},
|
|
}}, 1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create signed tx: %v", err)
|
|
}
|
|
_, err = harness.txPool.ProcessTransaction(orphanTx, true, false, 0)
|
|
if err != nil {
|
|
t.Fatalf("ProcessTransaction: unexpected error: %v", err)
|
|
}
|
|
// ensure that transaction added to orphan pool
|
|
testPoolMembership(tc, orphanTx, true, false)
|
|
|
|
// Add one more transaction to block
|
|
blockTx2, err := harness.CreateSignedTx(spendableOuts[1:], 1)
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction 1: %v", err)
|
|
}
|
|
dummyBlock.Transactions = append(dummyBlock.Transactions, blockTx1.MsgTx(), blockTx2.MsgTx())
|
|
|
|
// Create block and add its transactions to UTXO set
|
|
block := util.NewBlock(&dummyBlock)
|
|
for i, tx := range block.Transactions() {
|
|
if !harness.txPool.mpUTXOSet.AddTx(tx.MsgTx(), 1) {
|
|
t.Fatalf("Failed to add transaction %v to UTXO set: %v", i, tx.Hash())
|
|
}
|
|
}
|
|
|
|
// Handle new block by pool
|
|
ch := make(chan NewBlockMsg)
|
|
go func() {
|
|
err = harness.txPool.HandleNewBlock(block, ch)
|
|
close(ch)
|
|
}()
|
|
|
|
// process messages pushed by HandleNewBlock
|
|
blockTransnactions := make(map[daghash.Hash]int)
|
|
for msg := range ch {
|
|
blockTransnactions[*msg.Tx.Hash()] = 1
|
|
if *msg.Tx.Hash() != *blockTx1.Hash() {
|
|
if len(msg.AcceptedTxs) != 0 {
|
|
t.Fatalf("Expected amount of accepted transactions 0. Got: %v", len(msg.AcceptedTxs))
|
|
}
|
|
} else {
|
|
if len(msg.AcceptedTxs) != 1 {
|
|
t.Fatalf("Wrong accepted transactions length")
|
|
}
|
|
if *msg.AcceptedTxs[0].Tx.Hash() != *orphanTx.Hash() {
|
|
t.Fatalf("Wrong accepted transaction hash")
|
|
}
|
|
}
|
|
}
|
|
// ensure that HandleNewBlock has not failed
|
|
if err != nil {
|
|
t.Fatalf("HandleNewBlock failed to handle block %v", err)
|
|
}
|
|
|
|
// Validate messages pushed by HandleNewBlock into the channel
|
|
if len(blockTransnactions) != 2 {
|
|
t.Fatalf("Wrong size of blockTransnactions after new block handling")
|
|
}
|
|
|
|
if _, ok := blockTransnactions[*blockTx1.Hash()]; !ok {
|
|
t.Fatalf("Transaction 1 of new block is not handled")
|
|
}
|
|
|
|
if _, ok := blockTransnactions[*blockTx2.Hash()]; !ok {
|
|
t.Fatalf("Transaction 2 of new block is not handled")
|
|
}
|
|
|
|
// ensure that orphan transaction moved to main pool
|
|
testPoolMembership(tc, orphanTx, false, true)
|
|
}
|
|
|
|
// dummyBlock defines a block on the block DAG. It is used to test block operations.
|
|
var dummyBlock = wire.MsgBlock{
|
|
Header: wire.BlockHeader{
|
|
Version: 1,
|
|
ParentHashes: []daghash.Hash{
|
|
[32]byte{ // Make go vet happy.
|
|
0x82, 0xdc, 0xbd, 0xe6, 0x88, 0x37, 0x74, 0x5b,
|
|
0x78, 0x6b, 0x03, 0x1d, 0xa3, 0x48, 0x3c, 0x45,
|
|
0x3f, 0xc3, 0x2e, 0xd4, 0x53, 0x5b, 0x6f, 0x26,
|
|
0x26, 0xb0, 0x48, 0x4f, 0x09, 0x00, 0x00, 0x00,
|
|
}, // MainNet genesis
|
|
[32]byte{ // Make go vet happy.
|
|
0xc1, 0x5b, 0x71, 0xfe, 0x20, 0x70, 0x0f, 0xd0,
|
|
0x08, 0x49, 0x88, 0x1b, 0x32, 0xb5, 0xbd, 0x13,
|
|
0x17, 0xbe, 0x75, 0xe7, 0x29, 0x46, 0xdd, 0x03,
|
|
0x01, 0x92, 0x90, 0xf1, 0xca, 0x8a, 0x88, 0x11,
|
|
}}, // SimNet genesis
|
|
MerkleRoot: daghash.Hash([32]byte{ // Make go vet happy.
|
|
0x66, 0x57, 0xa9, 0x25, 0x2a, 0xac, 0xd5, 0xc0,
|
|
0xb2, 0x94, 0x09, 0x96, 0xec, 0xff, 0x95, 0x22,
|
|
0x28, 0xc3, 0x06, 0x7c, 0xc3, 0x8d, 0x48, 0x85,
|
|
0xef, 0xb5, 0xa4, 0xac, 0x42, 0x47, 0xe9, 0xf3,
|
|
}), // f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766
|
|
Timestamp: time.Unix(1529483563, 0), // 2018-06-20 08:32:43 +0000 UTC
|
|
Bits: 0x1e00ffff, // 503382015
|
|
Nonce: 0x000ae53f, // 714047
|
|
},
|
|
Transactions: []*wire.MsgTx{
|
|
{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: wire.OutPoint{
|
|
Hash: daghash.Hash{},
|
|
Index: 0xffffffff,
|
|
},
|
|
SignatureScript: []byte{
|
|
0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02,
|
|
},
|
|
Sequence: math.MaxUint64,
|
|
},
|
|
},
|
|
TxOut: []*wire.TxOut{
|
|
{
|
|
Value: 0x12a05f200, // 5000000000
|
|
PkScript: []byte{
|
|
0x41, // OP_DATA_65
|
|
0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25,
|
|
0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73,
|
|
0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7,
|
|
0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16,
|
|
0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24,
|
|
0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed,
|
|
0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28,
|
|
0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf,
|
|
0x84, // 65-byte signature
|
|
0xac, // OP_CHECKSIG
|
|
},
|
|
},
|
|
},
|
|
LockTime: 0,
|
|
},
|
|
},
|
|
}
|