mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 09:50:08 +01:00
760c5299c7
The current max orphan transaction size causes problems with dependent transaction relay due to its artificially small size in relation to the max standard transaction size. Consequently, this modifies the orphan transaction policy by increasing the max size of each orphan to the same value allowed for standard non-orphan transactions and reducing the default max allowed number of orphans to 100. From a memory usage standpoint, the worst case max mem usage prior to this change was 5MB plus structure and tracking overhead (1000 max orphans * 5KB max each). With this, that is raised to 10MB (100 max orphans * 100KB max each) in the worst case. It is important to note that the values were originally implemented as a naive means to control the size of the orphan pool before several of the recent enhancements which more aggressively remove orphans from the orphan pool were added, so they needed to be evaluated again. For a very long time prior to recent changes, the orphan pool would quickly reach the max allowed worst-case usage and effectively stay there forever whereas with more recent changes, the actual run-time orphan pool usage is usually much smaller. Finally, as another point in favor of this change, as the network has evolved, nodes have generally become better about orphan management and as such missing ancestors will typically either be broadcast or mined fairly quickly resulting in fewer overall orphans.
513 lines
12 KiB
Go
513 lines
12 KiB
Go
// Copyright (c) 2013-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"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
)
|
|
|
|
// TestCalcMinRequiredTxRelayFee tests the calcMinRequiredTxRelayFee API.
|
|
func TestCalcMinRequiredTxRelayFee(t *testing.T) {
|
|
tests := []struct {
|
|
name string // test description.
|
|
size int64 // Transaction size in bytes.
|
|
relayFee btcutil.Amount // minimum relay transaction fee.
|
|
want int64 // Expected fee.
|
|
}{
|
|
{
|
|
// Ensure combination of size and fee that are less than 1000
|
|
// produce a non-zero fee.
|
|
"250 bytes with relay fee of 3",
|
|
250,
|
|
3,
|
|
3,
|
|
},
|
|
{
|
|
"100 bytes with default minimum relay fee",
|
|
100,
|
|
DefaultMinRelayTxFee,
|
|
100,
|
|
},
|
|
{
|
|
"max standard tx size with default minimum relay fee",
|
|
MaxStandardTxSize,
|
|
DefaultMinRelayTxFee,
|
|
100000,
|
|
},
|
|
{
|
|
"max standard tx size with max satoshi relay fee",
|
|
MaxStandardTxSize,
|
|
btcutil.MaxSatoshi,
|
|
btcutil.MaxSatoshi,
|
|
},
|
|
{
|
|
"1500 bytes with 5000 relay fee",
|
|
1500,
|
|
5000,
|
|
7500,
|
|
},
|
|
{
|
|
"1500 bytes with 3000 relay fee",
|
|
1500,
|
|
3000,
|
|
4500,
|
|
},
|
|
{
|
|
"782 bytes with 5000 relay fee",
|
|
782,
|
|
5000,
|
|
3910,
|
|
},
|
|
{
|
|
"782 bytes with 3000 relay fee",
|
|
782,
|
|
3000,
|
|
2346,
|
|
},
|
|
{
|
|
"782 bytes with 2550 relay fee",
|
|
782,
|
|
2550,
|
|
1994,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
got := calcMinRequiredTxRelayFee(test.size, test.relayFee)
|
|
if got != test.want {
|
|
t.Errorf("TestCalcMinRequiredTxRelayFee test '%s' "+
|
|
"failed: got %v want %v", test.name, got,
|
|
test.want)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCheckPkScriptStandard tests the checkPkScriptStandard API.
|
|
func TestCheckPkScriptStandard(t *testing.T) {
|
|
var pubKeys [][]byte
|
|
for i := 0; i < 4; i++ {
|
|
pk, err := btcec.NewPrivateKey(btcec.S256())
|
|
if err != nil {
|
|
t.Fatalf("TestCheckPkScriptStandard NewPrivateKey failed: %v",
|
|
err)
|
|
return
|
|
}
|
|
pubKeys = append(pubKeys, pk.PubKey().SerializeCompressed())
|
|
}
|
|
|
|
tests := []struct {
|
|
name string // test description.
|
|
script *txscript.ScriptBuilder
|
|
isStandard bool
|
|
}{
|
|
{
|
|
"key1 and key2",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_2).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG),
|
|
true,
|
|
},
|
|
{
|
|
"key1 or key2",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_1).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG),
|
|
true,
|
|
},
|
|
{
|
|
"escrow",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_2).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddData(pubKeys[2]).
|
|
AddOp(txscript.OP_3).AddOp(txscript.OP_CHECKMULTISIG),
|
|
true,
|
|
},
|
|
{
|
|
"one of four",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_1).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddData(pubKeys[2]).AddData(pubKeys[3]).
|
|
AddOp(txscript.OP_4).AddOp(txscript.OP_CHECKMULTISIG),
|
|
false,
|
|
},
|
|
{
|
|
"malformed1",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_3).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG),
|
|
false,
|
|
},
|
|
{
|
|
"malformed2",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_2).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddOp(txscript.OP_3).AddOp(txscript.OP_CHECKMULTISIG),
|
|
false,
|
|
},
|
|
{
|
|
"malformed3",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_0).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddOp(txscript.OP_2).AddOp(txscript.OP_CHECKMULTISIG),
|
|
false,
|
|
},
|
|
{
|
|
"malformed4",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_1).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddOp(txscript.OP_0).AddOp(txscript.OP_CHECKMULTISIG),
|
|
false,
|
|
},
|
|
{
|
|
"malformed5",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_1).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]).
|
|
AddOp(txscript.OP_CHECKMULTISIG),
|
|
false,
|
|
},
|
|
{
|
|
"malformed6",
|
|
txscript.NewScriptBuilder().AddOp(txscript.OP_1).
|
|
AddData(pubKeys[0]).AddData(pubKeys[1]),
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
script, err := test.script.Script()
|
|
if err != nil {
|
|
t.Fatalf("TestCheckPkScriptStandard test '%s' "+
|
|
"failed: %v", test.name, err)
|
|
continue
|
|
}
|
|
scriptClass := txscript.GetScriptClass(script)
|
|
got := checkPkScriptStandard(script, scriptClass)
|
|
if (test.isStandard && got != nil) ||
|
|
(!test.isStandard && got == nil) {
|
|
|
|
t.Fatalf("TestCheckPkScriptStandard test '%s' failed",
|
|
test.name)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDust tests the isDust API.
|
|
func TestDust(t *testing.T) {
|
|
pkScript := []byte{0x76, 0xa9, 0x21, 0x03, 0x2f, 0x7e, 0x43,
|
|
0x0a, 0xa4, 0xc9, 0xd1, 0x59, 0x43, 0x7e, 0x84, 0xb9,
|
|
0x75, 0xdc, 0x76, 0xd9, 0x00, 0x3b, 0xf0, 0x92, 0x2c,
|
|
0xf3, 0xaa, 0x45, 0x28, 0x46, 0x4b, 0xab, 0x78, 0x0d,
|
|
0xba, 0x5e, 0x88, 0xac}
|
|
|
|
tests := []struct {
|
|
name string // test description
|
|
txOut wire.TxOut
|
|
relayFee btcutil.Amount // minimum relay transaction fee.
|
|
isDust bool
|
|
}{
|
|
{
|
|
// Any value is allowed with a zero relay fee.
|
|
"zero value with zero relay fee",
|
|
wire.TxOut{Value: 0, PkScript: pkScript},
|
|
0,
|
|
false,
|
|
},
|
|
{
|
|
// Zero value is dust with any relay fee"
|
|
"zero value with very small tx fee",
|
|
wire.TxOut{Value: 0, PkScript: pkScript},
|
|
1,
|
|
true,
|
|
},
|
|
{
|
|
"38 byte public key script with value 584",
|
|
wire.TxOut{Value: 584, PkScript: pkScript},
|
|
1000,
|
|
true,
|
|
},
|
|
{
|
|
"38 byte public key script with value 585",
|
|
wire.TxOut{Value: 585, PkScript: pkScript},
|
|
1000,
|
|
false,
|
|
},
|
|
{
|
|
// Maximum allowed value is never dust.
|
|
"max satoshi amount is never dust",
|
|
wire.TxOut{Value: btcutil.MaxSatoshi, PkScript: pkScript},
|
|
btcutil.MaxSatoshi,
|
|
false,
|
|
},
|
|
{
|
|
// Maximum int64 value causes overflow.
|
|
"maximum int64 value",
|
|
wire.TxOut{Value: 1<<63 - 1, PkScript: pkScript},
|
|
1<<63 - 1,
|
|
true,
|
|
},
|
|
{
|
|
// Unspendable pkScript due to an invalid public key
|
|
// script.
|
|
"unspendable pkScript",
|
|
wire.TxOut{Value: 5000, PkScript: []byte{0x01}},
|
|
0, // no relay fee
|
|
true,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
res := isDust(&test.txOut, test.relayFee)
|
|
if res != test.isDust {
|
|
t.Fatalf("Dust test '%s' failed: want %v got %v",
|
|
test.name, test.isDust, res)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCheckTransactionStandard tests the checkTransactionStandard API.
|
|
func TestCheckTransactionStandard(t *testing.T) {
|
|
// Create some dummy, but otherwise standard, data for transactions.
|
|
prevOutHash, err := chainhash.NewHashFromStr("01")
|
|
if err != nil {
|
|
t.Fatalf("NewShaHashFromStr: unexpected error: %v", err)
|
|
}
|
|
dummyPrevOut := wire.OutPoint{Hash: *prevOutHash, Index: 1}
|
|
dummySigScript := bytes.Repeat([]byte{0x00}, 65)
|
|
dummyTxIn := wire.TxIn{
|
|
PreviousOutPoint: dummyPrevOut,
|
|
SignatureScript: dummySigScript,
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
}
|
|
addrHash := [20]byte{0x01}
|
|
addr, err := btcutil.NewAddressPubKeyHash(addrHash[:],
|
|
&chaincfg.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)
|
|
}
|
|
dummyTxOut := wire.TxOut{
|
|
Value: 100000000, // 1 BTC
|
|
PkScript: dummyPkScript,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
tx wire.MsgTx
|
|
height int32
|
|
isStandard bool
|
|
code wire.RejectCode
|
|
}{
|
|
{
|
|
name: "Typical pay-to-pubkey-hash transaction",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{&dummyTxIn},
|
|
TxOut: []*wire.TxOut{&dummyTxOut},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: true,
|
|
},
|
|
{
|
|
name: "Transaction version too high",
|
|
tx: wire.MsgTx{
|
|
Version: wire.TxVersion + 1,
|
|
TxIn: []*wire.TxIn{&dummyTxIn},
|
|
TxOut: []*wire.TxOut{&dummyTxOut},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectNonstandard,
|
|
},
|
|
{
|
|
name: "Transaction is not finalized",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{{
|
|
PreviousOutPoint: dummyPrevOut,
|
|
SignatureScript: dummySigScript,
|
|
Sequence: 0,
|
|
}},
|
|
TxOut: []*wire.TxOut{&dummyTxOut},
|
|
LockTime: 300001,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectNonstandard,
|
|
},
|
|
{
|
|
name: "Transaction size is too large",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{&dummyTxIn},
|
|
TxOut: []*wire.TxOut{{
|
|
Value: 0,
|
|
PkScript: bytes.Repeat([]byte{0x00},
|
|
MaxStandardTxSize+1),
|
|
}},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectNonstandard,
|
|
},
|
|
{
|
|
name: "Signature script size is too large",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{{
|
|
PreviousOutPoint: dummyPrevOut,
|
|
SignatureScript: bytes.Repeat([]byte{0x00},
|
|
maxStandardSigScriptSize+1),
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
}},
|
|
TxOut: []*wire.TxOut{&dummyTxOut},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectNonstandard,
|
|
},
|
|
{
|
|
name: "Signature script that does more than push data",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{{
|
|
PreviousOutPoint: dummyPrevOut,
|
|
SignatureScript: []byte{
|
|
txscript.OP_CHECKSIGVERIFY},
|
|
Sequence: wire.MaxTxInSequenceNum,
|
|
}},
|
|
TxOut: []*wire.TxOut{&dummyTxOut},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectNonstandard,
|
|
},
|
|
{
|
|
name: "Valid but non standard public key script",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{&dummyTxIn},
|
|
TxOut: []*wire.TxOut{{
|
|
Value: 100000000,
|
|
PkScript: []byte{txscript.OP_TRUE},
|
|
}},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectNonstandard,
|
|
},
|
|
{
|
|
name: "More than one nulldata output",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{&dummyTxIn},
|
|
TxOut: []*wire.TxOut{{
|
|
Value: 0,
|
|
PkScript: []byte{txscript.OP_RETURN},
|
|
}, {
|
|
Value: 0,
|
|
PkScript: []byte{txscript.OP_RETURN},
|
|
}},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectNonstandard,
|
|
},
|
|
{
|
|
name: "Dust output",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{&dummyTxIn},
|
|
TxOut: []*wire.TxOut{{
|
|
Value: 0,
|
|
PkScript: dummyPkScript,
|
|
}},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: false,
|
|
code: wire.RejectDust,
|
|
},
|
|
{
|
|
name: "One nulldata output with 0 amount (standard)",
|
|
tx: wire.MsgTx{
|
|
Version: 1,
|
|
TxIn: []*wire.TxIn{&dummyTxIn},
|
|
TxOut: []*wire.TxOut{{
|
|
Value: 0,
|
|
PkScript: []byte{txscript.OP_RETURN},
|
|
}},
|
|
LockTime: 0,
|
|
},
|
|
height: 300000,
|
|
isStandard: true,
|
|
},
|
|
}
|
|
|
|
pastMedianTime := time.Now()
|
|
for _, test := range tests {
|
|
// Ensure standardness is as expected.
|
|
err := checkTransactionStandard(btcutil.NewTx(&test.tx),
|
|
test.height, pastMedianTime, DefaultMinRelayTxFee, 1)
|
|
if err == nil && test.isStandard {
|
|
// Test passes since function returned standard for a
|
|
// transaction which is intended to be standard.
|
|
continue
|
|
}
|
|
if err == nil && !test.isStandard {
|
|
t.Errorf("checkTransactionStandard (%s): standard when "+
|
|
"it should not be", test.name)
|
|
continue
|
|
}
|
|
if err != nil && test.isStandard {
|
|
t.Errorf("checkTransactionStandard (%s): nonstandard "+
|
|
"when it should not be: %v", test.name, err)
|
|
continue
|
|
}
|
|
|
|
// Ensure error type is a TxRuleError inside of a RuleError.
|
|
rerr, ok := err.(RuleError)
|
|
if !ok {
|
|
t.Errorf("checkTransactionStandard (%s): unexpected "+
|
|
"error type - got %T", test.name, err)
|
|
continue
|
|
}
|
|
txrerr, ok := rerr.Err.(TxRuleError)
|
|
if !ok {
|
|
t.Errorf("checkTransactionStandard (%s): unexpected "+
|
|
"error type - got %T", test.name, rerr.Err)
|
|
continue
|
|
}
|
|
|
|
// Ensure the reject code is the expected one.
|
|
if txrerr.RejectCode != test.code {
|
|
t.Errorf("checkTransactionStandard (%s): unexpected "+
|
|
"error code - got %v, want %v", test.name,
|
|
txrerr.RejectCode, test.code)
|
|
continue
|
|
}
|
|
}
|
|
}
|