[DEV-91] Ensure 100% coverage in mempool package (#60)

* [DEV-91] add tests for RemoveOrphansByTag

* [DEV-91] Add tests for orphan tx expiration

* [DEV-91] add TestDoubleSpends

* [DEV-91] add TestFetchTransaction

* [DEV-91] added a test for MinHighPriority

* [DEV-91] test unparsable scripts

* [DEV-91] test MaxOrphanTxs=0

* [DEV-91] add TestRemoveTransaction

* [DEV-105] use utxodiff in mempool instead of utxoview

* [DEV-105] get rid of utxoview

* [DEV-105] fix tests to use utxoset

* [DEV-105] remove utxoview type

* [DEV-105] move DagSetup to test_utils.go

* [DEV-105] add comments and add blockdag/test_utils_test.go

* [DEV-105] change checkPoolDoubleSpend to check utxodiff

* [DEV-105] add restoreInputs arg to removeTransaction

* [DEV-91] add restoreInputs arg to removeTransaction in unit tests

* [DEV-105] add restoreInputs arg to removeTransaction

* [DEV-105] give more descriptive names to vars

* [DEV-115] close txChan outside of HandleNewBlock

* [DEV-105] rename DagSetup -> DAGSetup

* [DEV-91] remove IsSpentInDiff

* [DEV-91] fix comment

* [DEV-91] Make IsPushOnlyScript return an error if the script cannot be parsed

* [DEV-91] add more tests for IsPushOnlyScript

* [DEV-91] fix comments
This commit is contained in:
Ori Newman 2018-09-27 12:09:19 +03:00 committed by stasatdaglabs
parent 81453b221d
commit 1cd82fcd3b
10 changed files with 671 additions and 81 deletions

View File

@ -670,7 +670,7 @@ func loadConfig() (*Config, []string, error) {
return nil, nil, err
}
// Limit the max orphan count to a sane vlue.
// Limit the max orphan count to a sane value.
if cfg.MaxOrphanTxs < 0 {
str := "%s: The maxorphantx option may not be less than 0 " +
"-- parsed [%d]"

View File

@ -50,11 +50,11 @@ func txRuleError(c wire.RejectCode, desc string) RuleError {
}
}
// chainRuleError returns a RuleError that encapsulates the given
// blockchain.RuleError.
func chainRuleError(chainErr blockdag.RuleError) RuleError {
// dagRuleError returns a RuleError that encapsulates the given
// blockdag.RuleError.
func dagRuleError(dagErr blockdag.RuleError) RuleError {
return RuleError{
Err: chainErr,
Err: dagErr,
}
}

View File

@ -17,12 +17,3 @@ var log btclog.Logger
func init() {
log, _ = logger.Get(logger.SubsystemTags.TXMP)
}
// pickNoun returns the singular or plural form of a noun depending
// on the count n.
func pickNoun(n int, singular, plural string) string {
if n == 1 {
return singular
}
return plural
}

View File

@ -17,6 +17,7 @@ import (
"github.com/daglabs/btcd/btcjson"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/logger"
"github.com/daglabs/btcd/mining"
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util"
@ -279,7 +280,7 @@ func (mp *TxPool) limitNumOrphans() error {
numOrphans := len(mp.orphans)
if numExpired := origNumOrphans - numOrphans; numExpired > 0 {
log.Debugf("Expired %d %s (remaining: %d)", numExpired,
pickNoun(numExpired, "orphan", "orphans"),
logger.PickNoun(uint64(numExpired), "orphan", "orphans"),
numOrphans)
}
}
@ -651,7 +652,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
err := blockdag.CheckTransactionSanity(tx)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
return nil, nil, dagRuleError(cerr)
}
return nil, nil, err
}
@ -703,7 +704,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
return nil, nil, err
}
// Don't allow the transaction if it exists in the DAG and is not
// Don't allow the transaction if it exists in the DAG and is
// not already fully spent.
prevOut := wire.OutPoint{Hash: *txHash}
for txOutIdx := range tx.MsgTx().TxOut {
@ -740,7 +741,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
sequenceLock, err := mp.cfg.CalcSequenceLock(tx, mp.mpUTXOSet)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
return nil, nil, dagRuleError(cerr)
}
return nil, nil, err
}
@ -758,7 +759,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
mp.mpUTXOSet, mp.cfg.DAGParams)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
return nil, nil, dagRuleError(cerr)
}
return nil, nil, err
}
@ -793,7 +794,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
sigOpCount, err := blockdag.CountP2SHSigOps(tx, false, mp.mpUTXOSet)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
return nil, nil, dagRuleError(cerr)
}
return nil, nil, err
}
@ -869,7 +870,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
txscript.StandardVerifyFlags, mp.cfg.SigCache)
if err != nil {
if cerr, ok := err.(blockdag.RuleError); ok {
return nil, nil, chainRuleError(cerr)
return nil, nil, dagRuleError(cerr)
}
return nil, nil, err
}

View File

@ -5,8 +5,10 @@
package mempool
import (
"bytes"
"encoding/hex"
"fmt"
"math"
"reflect"
"runtime"
"sync"
@ -64,9 +66,7 @@ func (s *fakeChain) SetMedianTimePast(mtp time.Time) {
s.Unlock()
}
// CalcSequenceLock returns the current sequence lock for the passed
// transaction associated with the fake chain instance.
func (s *fakeChain) CalcSequenceLock(tx *util.Tx,
func calcSequenceLock(tx *util.Tx,
utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
return &blockdag.SequenceLock{
@ -75,18 +75,18 @@ func (s *fakeChain) CalcSequenceLock(tx *util.Tx,
}, nil
}
// spendableOutput is a convenience type that houses a particular utxo and the
// spendableOutpoint is a convenience type that houses a particular utxo and the
// amount associated with it.
type spendableOutput struct {
type spendableOutpoint struct {
outPoint wire.OutPoint
amount util.Amount
}
// txOutToSpendableOut returns a spendable output given a transaction and index
// 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 txOutToSpendableOut(tx *util.Tx, outputNum uint32) spendableOutput {
return spendableOutput{
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),
}
@ -156,7 +156,7 @@ func (p *poolHarness) CreateCoinbaseTx(blockHeight int32, numOutputs uint32) (*u
// 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 []spendableOutput, numOutputs uint32) (*util.Tx, error) {
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
@ -204,7 +204,7 @@ func (p *poolHarness) CreateSignedTx(inputs []spendableOutput, numOutputs uint32
// transaction spends the entire amount from the previous one) with the first
// one spending the provided outpoint. Each transaction spends the entire
// amount of the previous one and as such does not include any fees.
func (p *poolHarness) CreateTxChain(firstOutput spendableOutput, numTxns uint32) ([]*util.Tx, error) {
func (p *poolHarness) CreateTxChain(firstOutput spendableOutpoint, numTxns uint32) ([]*util.Tx, error) {
txChain := make([]*util.Tx, 0, numTxns)
prevOutPoint := firstOutput.outPoint
spendableAmount := firstOutput.amount
@ -245,7 +245,7 @@ func (p *poolHarness) CreateTxChain(firstOutput spendableOutput, numTxns uint32)
// 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, dbName string) (*poolHarness, []spendableOutput, error) {
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")
@ -298,7 +298,7 @@ func newPoolHarness(dagParams *dagconfig.Params, dbName string) (*poolHarness, [
DAGParams: dagParams,
BestHeight: chain.BestHeight,
MedianTimePast: chain.MedianTimePast,
CalcSequenceLock: chain.CalcSequenceLock,
CalcSequenceLock: calcSequenceLock,
SigCache: nil,
AddrIndex: nil,
}),
@ -309,8 +309,7 @@ func newPoolHarness(dagParams *dagconfig.Params, dbName string) (*poolHarness, [
// coinbase will mature in the next block. This ensures the txpool
// accepts transactions which spend immature coinbases that will become
// mature in the next block.
numOutputs := uint32(1)
outputs := make([]spendableOutput, 0, numOutputs)
outpoints := make([]spendableOutpoint, 0, numOutputs)
curHeight := harness.chain.BestHeight()
coinbase, err := harness.CreateCoinbaseTx(curHeight+1, numOutputs)
if err != nil {
@ -318,12 +317,12 @@ func newPoolHarness(dagParams *dagconfig.Params, dbName string) (*poolHarness, [
}
harness.txPool.mpUTXOSet.AddTx(coinbase.MsgTx(), curHeight+1)
for i := uint32(0); i < numOutputs; i++ {
outputs = append(outputs, txOutToSpendableOut(coinbase, i))
outpoints = append(outpoints, txOutToSpendableOutpoint(coinbase, i))
}
harness.chain.SetHeight(int32(dagParams.CoinbaseMaturity) + curHeight)
harness.chain.SetMedianTimePast(time.Now())
return &harness, outputs, nil
return &harness, outpoints, nil
}
// testContext houses a test-related state that is useful to pass to helper
@ -363,15 +362,432 @@ func testPoolMembership(tc *testContext, tx *util.Tx, inOrphanPool, inTxPool boo
}
}
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[:],
&dagconfig.TestNet3Params)
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) {
t.Parallel()
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, "TestSimpleOrphanChain")
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestSimpleOrphanChain")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -432,9 +848,7 @@ func TestSimpleOrphanChain(t *testing.T) {
// TestOrphanReject ensures that orphans are properly rejected when the allow
// orphans flag is not set on ProcessTransaction.
func TestOrphanReject(t *testing.T) {
t.Parallel()
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestOrphanReject")
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanReject")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -484,12 +898,117 @@ func TestOrphanReject(t *testing.T) {
}
}
//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) {
t.Parallel()
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestOrphanEviction")
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanEviction")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -547,14 +1066,66 @@ func TestOrphanEviction(t *testing.T) {
}
}
// 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) {
t.Parallel()
const maxOrphans = 4
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, "TestBasicOrphanRemoval")
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestBasicOrphanRemoval")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -592,7 +1163,7 @@ func TestBasicOrphanRemoval(t *testing.T) {
// Attempt to remove an orphan that has no redeemers and is not present,
// and ensure the state of all other orphans are unaffected.
nonChainedOrphanTx, err := harness.CreateSignedTx([]spendableOutput{{
nonChainedOrphanTx, err := harness.CreateSignedTx([]spendableOutpoint{{
amount: util.Amount(5000000000),
outPoint: wire.OutPoint{Hash: daghash.Hash{}, Index: 0},
}}, 1)
@ -626,10 +1197,8 @@ func TestBasicOrphanRemoval(t *testing.T) {
// TestOrphanChainRemoval ensure that orphan chains (orphans that spend outputs
// from other orphans) are removed as expected.
func TestOrphanChainRemoval(t *testing.T) {
t.Parallel()
const maxOrphans = 10
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, "TestOrphanChainRemoval")
harness, spendableOuts, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestOrphanChainRemoval")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -689,10 +1258,8 @@ func TestOrphanChainRemoval(t *testing.T) {
// TestMultiInputOrphanDoubleSpend ensures that orphans that spend from an
// output that is spend by another transaction entering the pool are removed.
func TestMultiInputOrphanDoubleSpend(t *testing.T) {
t.Parallel()
const maxOrphans = 4
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestMultiInputOrphanDoubleSpend")
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestMultiInputOrphanDoubleSpend")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}
@ -728,9 +1295,9 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
// orphan pool) is accepted to the orphan pool. This must be allowed
// since it would otherwise be possible for a malicious actor to disrupt
// tx chains.
doubleSpendTx, err := harness.CreateSignedTx([]spendableOutput{
txOutToSpendableOut(chainedTxns[1], 0),
txOutToSpendableOut(chainedTxns[maxOrphans], 0),
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)
@ -778,9 +1345,7 @@ func TestMultiInputOrphanDoubleSpend(t *testing.T) {
// TestCheckSpend tests that CheckSpend returns the expected spends found in
// the mempool.
func TestCheckSpend(t *testing.T) {
t.Parallel()
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, "TestCheckSpend")
harness, outputs, err := newPoolHarness(&dagconfig.MainNetParams, 1, "TestCheckSpend")
if err != nil {
t.Fatalf("unable to create test pool: %v", err)
}

View File

@ -290,7 +290,12 @@ func checkTransactionStandard(tx *util.Tx, height int32,
// Each transaction input signature script must only contain
// opcodes which push data onto the stack.
if !txscript.IsPushOnlyScript(txIn.SignatureScript) {
isPushOnly, err := txscript.IsPushOnlyScript(txIn.SignatureScript)
if err != nil {
str := fmt.Sprintf("transaction input %d: IsPushOnlyScript: %v", i, err)
return txRuleError(wire.RejectNonstandard, str)
}
if !isPushOnly {
str := fmt.Sprintf("transaction input %d: signature "+
"script is not push only", i)
return txRuleError(wire.RejectNonstandard, str)

View File

@ -85,12 +85,12 @@ func isPushOnly(pops []parsedOpcode) bool {
// IsPushOnlyScript returns whether or not the passed script only pushes data.
//
// False will be returned when the script does not parse.
func IsPushOnlyScript(script []byte) bool {
func IsPushOnlyScript(script []byte) (bool, error) {
pops, err := parseScript(script)
if err != nil {
return false
return false, err
}
return isPushOnly(pops)
return isPushOnly(pops), nil
}
// parseScriptTemplate is the same as parseScript but allows the passing of the

View File

@ -3733,7 +3733,7 @@ func TestHasCanonicalPush(t *testing.T) {
err)
continue
}
if result := IsPushOnlyScript(script); !result {
if result, _ := IsPushOnlyScript(script); !result {
t.Errorf("IsPushOnlyScript: test #%d failed: %x\n", i,
script)
continue
@ -3759,7 +3759,7 @@ func TestHasCanonicalPush(t *testing.T) {
t.Errorf("StandardPushesTests test #%d unexpected error: %v\n", i, err)
continue
}
if result := IsPushOnlyScript(script); !result {
if result, _ := IsPushOnlyScript(script); !result {
t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, script)
continue
}
@ -3892,20 +3892,48 @@ func TestHasCanonicalPushes(t *testing.T) {
func TestIsPushOnlyScript(t *testing.T) {
t.Parallel()
test := struct {
name string
script []byte
expected bool
tests := []struct {
name string
script []byte
expectedResult bool
shouldFail bool
}{
name: "does not parse",
script: mustParseShortForm("0x046708afdb0fe5548271967f1a67130" +
"b7105cd6a828e03909a67962e0ea1f61d"),
expected: false,
{
name: "does not parse",
script: mustParseShortForm("0x046708afdb0fe5548271967f1a67130" +
"b7105cd6a828e03909a67962e0ea1f61d"),
expectedResult: false,
shouldFail: true,
},
{
name: "non push only script",
script: mustParseShortForm("0x515293"), //OP_1 OP_2 OP_ADD
expectedResult: false,
shouldFail: false,
},
{
name: "push only script",
script: mustParseShortForm("0x5152"), //OP_1 OP_2
expectedResult: true,
shouldFail: false,
},
}
if IsPushOnlyScript(test.script) != test.expected {
t.Errorf("IsPushOnlyScript (%s) wrong result\ngot: %v\nwant: "+
"%v", test.name, true, test.expected)
for _, test := range tests {
isPushOnly, err := IsPushOnlyScript(test.script)
if isPushOnly != test.expectedResult {
t.Errorf("IsPushOnlyScript (%s) wrong result\ngot: %v\nwant: "+
"%v", test.name, isPushOnly, test.expectedResult)
}
if test.shouldFail && err == nil {
t.Errorf("IsPushOnlyScript (%s) expected an error but got <nil>", test.name)
}
if !test.shouldFail && err != nil {
t.Errorf("IsPushOnlyScript (%s) expected no error but got: %v", test.name, err)
}
}
}

View File

@ -10,8 +10,8 @@ import (
"github.com/daglabs/btcd/btcec"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/wire"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
// RawTxInSignature returns the serialized ECDSA signature for the input idx of

View File

@ -32,7 +32,7 @@ const (
// ScriptClass is an enumeration for the list of standard types of script.
type ScriptClass byte
// Classes of script payment known about in the blockchain.
// Classes of script payment known about in the blockDAG.
const (
NonStandardTy ScriptClass = iota // None of the recognized forms.
PubKeyTy // Pay pubkey.