forked from cerc-io/ipld-eth-server
* Add vendor dir so builds dont require dep * Pin specific version go-eth version
This commit is contained in:
+83
@@ -0,0 +1,83 @@
|
||||
mempool
|
||||
=======
|
||||
|
||||
[](https://travis-ci.org/btcsuite/btcd)
|
||||
[](http://copyfree.org)
|
||||
[](http://godoc.org/github.com/btcsuite/btcd/mempool)
|
||||
|
||||
Package mempool provides a policy-enforced pool of unmined bitcoin transactions.
|
||||
|
||||
A key responsbility of the bitcoin network is mining user-generated transactions
|
||||
into blocks. In order to facilitate this, the mining process relies on having a
|
||||
readily-available source of transactions to include in a block that is being
|
||||
solved.
|
||||
|
||||
At a high level, this package satisfies that requirement by providing an
|
||||
in-memory pool of fully validated transactions that can also optionally be
|
||||
further filtered based upon a configurable policy.
|
||||
|
||||
One of the policy configuration options controls whether or not "standard"
|
||||
transactions are accepted. In essence, a "standard" transaction is one that
|
||||
satisfies a fairly strict set of requirements that are largley intended to help
|
||||
provide fair use of the system to all users. It is important to note that what
|
||||
is considered a "standard" transaction changes over time. For some insight, at
|
||||
the time of this writing, an example of _some_ of the criteria that are required
|
||||
for a transaction to be considered standard are that it is of the most-recently
|
||||
supported version, finalized, does not exceed a specific size, and only consists
|
||||
of specific script forms.
|
||||
|
||||
Since this package does not deal with other bitcoin specifics such as network
|
||||
communication and transaction relay, it returns a list of transactions that were
|
||||
accepted which gives the caller a high level of flexibility in how they want to
|
||||
proceed. Typically, this will involve things such as relaying the transactions
|
||||
to other peers on the network and notifying the mining process that new
|
||||
transactions are available.
|
||||
|
||||
This package has intentionally been designed so it can be used as a standalone
|
||||
package for any projects needing the ability create an in-memory pool of bitcoin
|
||||
transactions that are not only valid by consensus rules, but also adhere to a
|
||||
configurable policy.
|
||||
|
||||
## Feature Overview
|
||||
|
||||
The following is a quick overview of the major features. It is not intended to
|
||||
be an exhaustive list.
|
||||
|
||||
- Maintain a pool of fully validated transactions
|
||||
- Reject non-fully-spent duplicate transactions
|
||||
- Reject coinbase transactions
|
||||
- Reject double spends (both from the chain and other transactions in pool)
|
||||
- Reject invalid transactions according to the network consensus rules
|
||||
- Full script execution and validation with signature cache support
|
||||
- Individual transaction query support
|
||||
- Orphan transaction support (transactions that spend from unknown outputs)
|
||||
- Configurable limits (see transaction acceptance policy)
|
||||
- Automatic addition of orphan transactions that are no longer orphans as new
|
||||
transactions are added to the pool
|
||||
- Individual orphan transaction query support
|
||||
- Configurable transaction acceptance policy
|
||||
- Option to accept or reject standard transactions
|
||||
- Option to accept or reject transactions based on priority calculations
|
||||
- Rate limiting of low-fee and free transactions
|
||||
- Non-zero fee threshold
|
||||
- Max signature operations per transaction
|
||||
- Max orphan transaction size
|
||||
- Max number of orphan transactions allowed
|
||||
- Additional metadata tracking for each transaction
|
||||
- Timestamp when the transaction was added to the pool
|
||||
- Most recent block height when the transaction was added to the pool
|
||||
- The fee the transaction pays
|
||||
- The starting priority for the transaction
|
||||
- Manual control of transaction removal
|
||||
- Recursive removal of all dependent transactions
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/btcsuite/btcd/mempool
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Package mempool is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// 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 provides a policy-enforced pool of unmined bitcoin transactions.
|
||||
|
||||
A key responsbility of the bitcoin network is mining user-generated transactions
|
||||
into blocks. In order to facilitate this, the mining process relies on having a
|
||||
readily-available source of transactions to include in a block that is being
|
||||
solved.
|
||||
|
||||
At a high level, this package satisfies that requirement by providing an
|
||||
in-memory pool of fully validated transactions that can also optionally be
|
||||
further filtered based upon a configurable policy.
|
||||
|
||||
One of the policy configuration options controls whether or not "standard"
|
||||
transactions are accepted. In essence, a "standard" transaction is one that
|
||||
satisfies a fairly strict set of requirements that are largley intended to help
|
||||
provide fair use of the system to all users. It is important to note that what
|
||||
is considered a "standard" transaction changes over time. For some insight, at
|
||||
the time of this writing, an example of SOME of the criteria that are required
|
||||
for a transaction to be considered standard are that it is of the most-recently
|
||||
supported version, finalized, does not exceed a specific size, and only consists
|
||||
of specific script forms.
|
||||
|
||||
Since this package does not deal with other bitcoin specifics such as network
|
||||
communication and transaction relay, it returns a list of transactions that were
|
||||
accepted which gives the caller a high level of flexibility in how they want to
|
||||
proceed. Typically, this will involve things such as relaying the transactions
|
||||
to other peers on the network and notifying the mining process that new
|
||||
transactions are available.
|
||||
|
||||
Feature Overview
|
||||
|
||||
The following is a quick overview of the major features. It is not intended to
|
||||
be an exhaustive list.
|
||||
|
||||
- Maintain a pool of fully validated transactions
|
||||
- Reject non-fully-spent duplicate transactions
|
||||
- Reject coinbase transactions
|
||||
- Reject double spends (both from the chain and other transactions in pool)
|
||||
- Reject invalid transactions according to the network consensus rules
|
||||
- Full script execution and validation with signature cache support
|
||||
- Individual transaction query support
|
||||
- Orphan transaction support (transactions that spend from unknown outputs)
|
||||
- Configurable limits (see transaction acceptance policy)
|
||||
- Automatic addition of orphan transactions that are no longer orphans as new
|
||||
transactions are added to the pool
|
||||
- Individual orphan transaction query support
|
||||
- Configurable transaction acceptance policy
|
||||
- Option to accept or reject standard transactions
|
||||
- Option to accept or reject transactions based on priority calculations
|
||||
- Rate limiting of low-fee and free transactions
|
||||
- Non-zero fee threshold
|
||||
- Max signature operations per transaction
|
||||
- Max orphan transaction size
|
||||
- Max number of orphan transactions allowed
|
||||
- Additional metadata tracking for each transaction
|
||||
- Timestamp when the transaction was added to the pool
|
||||
- Most recent block height when the transaction was added to the pool
|
||||
- The fee the transaction pays
|
||||
- The starting priority for the transaction
|
||||
- Manual control of transaction removal
|
||||
- Recursive removal of all dependent transactions
|
||||
|
||||
Errors
|
||||
|
||||
Errors returned by this package are either the raw errors provided by underlying
|
||||
calls or of type mempool.RuleError. Since there are two classes of rules
|
||||
(mempool acceptance rules and blockchain (consensus) acceptance rules), the
|
||||
mempool.RuleError type contains a single Err field which will, in turn, either
|
||||
be a mempool.TxRuleError or a blockchain.RuleError. The first indicates a
|
||||
violation of mempool acceptance rules while the latter indicates a violation of
|
||||
consensus acceptance rules. This allows the caller to easily differentiate
|
||||
between unexpected errors, such as database errors, versus errors due to rule
|
||||
violations through type assertions. In addition, callers can programmatically
|
||||
determine the specific rule violation by type asserting the Err field to one of
|
||||
the aforementioned types and examining their underlying ErrorCode field.
|
||||
*/
|
||||
package mempool
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2014-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 (
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// RuleError identifies a rule violation. It is used to indicate that
|
||||
// processing of a transaction failed due to one of the many validation
|
||||
// rules. The caller can use type assertions to determine if a failure was
|
||||
// specifically due to a rule violation and use the Err field to access the
|
||||
// underlying error, which will be either a TxRuleError or a
|
||||
// blockchain.RuleError.
|
||||
type RuleError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e RuleError) Error() string {
|
||||
if e.Err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// TxRuleError identifies a rule violation. It is used to indicate that
|
||||
// processing of a transaction failed due to one of the many validation
|
||||
// rules. The caller can use type assertions to determine if a failure was
|
||||
// specifically due to a rule violation and access the ErrorCode field to
|
||||
// ascertain the specific reason for the rule violation.
|
||||
type TxRuleError struct {
|
||||
RejectCode wire.RejectCode // The code to send with reject messages
|
||||
Description string // Human readable description of the issue
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e TxRuleError) Error() string {
|
||||
return e.Description
|
||||
}
|
||||
|
||||
// txRuleError creates an underlying TxRuleError with the given a set of
|
||||
// arguments and returns a RuleError that encapsulates it.
|
||||
func txRuleError(c wire.RejectCode, desc string) RuleError {
|
||||
return RuleError{
|
||||
Err: TxRuleError{RejectCode: c, Description: desc},
|
||||
}
|
||||
}
|
||||
|
||||
// chainRuleError returns a RuleError that encapsulates the given
|
||||
// blockchain.RuleError.
|
||||
func chainRuleError(chainErr blockchain.RuleError) RuleError {
|
||||
return RuleError{
|
||||
Err: chainErr,
|
||||
}
|
||||
}
|
||||
|
||||
// extractRejectCode attempts to return a relevant reject code for a given error
|
||||
// by examining the error for known types. It will return true if a code
|
||||
// was successfully extracted.
|
||||
func extractRejectCode(err error) (wire.RejectCode, bool) {
|
||||
// Pull the underlying error out of a RuleError.
|
||||
if rerr, ok := err.(RuleError); ok {
|
||||
err = rerr.Err
|
||||
}
|
||||
|
||||
switch err := err.(type) {
|
||||
case blockchain.RuleError:
|
||||
// Convert the chain error to a reject code.
|
||||
var code wire.RejectCode
|
||||
switch err.ErrorCode {
|
||||
// Rejected due to duplicate.
|
||||
case blockchain.ErrDuplicateBlock:
|
||||
code = wire.RejectDuplicate
|
||||
|
||||
// Rejected due to obsolete version.
|
||||
case blockchain.ErrBlockVersionTooOld:
|
||||
code = wire.RejectObsolete
|
||||
|
||||
// Rejected due to checkpoint.
|
||||
case blockchain.ErrCheckpointTimeTooOld:
|
||||
fallthrough
|
||||
case blockchain.ErrDifficultyTooLow:
|
||||
fallthrough
|
||||
case blockchain.ErrBadCheckpoint:
|
||||
fallthrough
|
||||
case blockchain.ErrForkTooOld:
|
||||
code = wire.RejectCheckpoint
|
||||
|
||||
// Everything else is due to the block or transaction being invalid.
|
||||
default:
|
||||
code = wire.RejectInvalid
|
||||
}
|
||||
|
||||
return code, true
|
||||
|
||||
case TxRuleError:
|
||||
return err.RejectCode, true
|
||||
|
||||
case nil:
|
||||
return wire.RejectInvalid, false
|
||||
}
|
||||
|
||||
return wire.RejectInvalid, false
|
||||
}
|
||||
|
||||
// ErrToRejectErr examines the underlying type of the error and returns a reject
|
||||
// code and string appropriate to be sent in a wire.MsgReject message.
|
||||
func ErrToRejectErr(err error) (wire.RejectCode, string) {
|
||||
// Return the reject code along with the error text if it can be
|
||||
// extracted from the error.
|
||||
rejectCode, found := extractRejectCode(err)
|
||||
if found {
|
||||
return rejectCode, err.Error()
|
||||
}
|
||||
|
||||
// Return a generic rejected string if there is no error. This really
|
||||
// should not happen unless the code elsewhere is not setting an error
|
||||
// as it should be, but it's best to be safe and simply return a generic
|
||||
// string rather than allowing the following code that dereferences the
|
||||
// err to panic.
|
||||
if err == nil {
|
||||
return wire.RejectInvalid, "rejected"
|
||||
}
|
||||
|
||||
// When the underlying error is not one of the above cases, just return
|
||||
// wire.RejectInvalid with a generic rejected string plus the error
|
||||
// text.
|
||||
return wire.RejectInvalid, "rejected: " + err.Error()
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// 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 (
|
||||
"github.com/btcsuite/btclog"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the caller
|
||||
// requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
DisableLog()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until either UseLogger or SetLogWriter are called.
|
||||
func DisableLog() {
|
||||
log = btclog.Disabled
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
// This should be used in preference to SetLogWriter if the caller is also
|
||||
// using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
+1210
File diff suppressed because it is too large
Load Diff
+796
@@ -0,0 +1,796 @@
|
||||
// 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 (
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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
|
||||
// transations to be appear as though they are spending completely valid utxos.
|
||||
type fakeChain struct {
|
||||
sync.RWMutex
|
||||
utxos *blockchain.UtxoViewpoint
|
||||
currentHeight int32
|
||||
medianTimePast time.Time
|
||||
}
|
||||
|
||||
// FetchUtxoView loads utxo details about the input transactions referenced by
|
||||
// the passed transaction from the point of view of the fake chain.
|
||||
// It also attempts to fetch the utxo details for the transaction itself so the
|
||||
// returned view can be examined for duplicate unspent transaction outputs.
|
||||
//
|
||||
// This function is safe for concurrent access however the returned view is NOT.
|
||||
func (s *fakeChain) FetchUtxoView(tx *btcutil.Tx) (*blockchain.UtxoViewpoint, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
// All entries are cloned to ensure modifications to the returned view
|
||||
// do not affect the fake chain's view.
|
||||
|
||||
// Add an entry for the tx itself to the new view.
|
||||
viewpoint := blockchain.NewUtxoViewpoint()
|
||||
entry := s.utxos.LookupEntry(tx.Hash())
|
||||
viewpoint.Entries()[*tx.Hash()] = entry.Clone()
|
||||
|
||||
// Add entries for all of the inputs to the tx to the new view.
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
originHash := &txIn.PreviousOutPoint.Hash
|
||||
entry := s.utxos.LookupEntry(originHash)
|
||||
viewpoint.Entries()[*originHash] = entry.Clone()
|
||||
}
|
||||
|
||||
return viewpoint, nil
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// CalcSequenceLock returns the current sequence lock for the passed
|
||||
// transaction associated with the fake chain instance.
|
||||
func (s *fakeChain) CalcSequenceLock(tx *btcutil.Tx,
|
||||
view *blockchain.UtxoViewpoint) (*blockchain.SequenceLock, error) {
|
||||
|
||||
return &blockchain.SequenceLock{
|
||||
Seconds: -1,
|
||||
BlockHeight: -1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// spendableOutput is a convenience type that houses a particular utxo and the
|
||||
// amount associated with it.
|
||||
type spendableOutput struct {
|
||||
outPoint wire.OutPoint
|
||||
amount btcutil.Amount
|
||||
}
|
||||
|
||||
// txOutToSpendableOut returns a spendable output given a transaction and index
|
||||
// of the output to use. This is useful as a convenience when creating test
|
||||
// transactions.
|
||||
func txOutToSpendableOut(tx *btcutil.Tx, outputNum uint32) spendableOutput {
|
||||
return spendableOutput{
|
||||
outPoint: wire.OutPoint{Hash: *tx.Hash(), Index: outputNum},
|
||||
amount: btcutil.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 btcutil.Address
|
||||
payScript []byte
|
||||
chainParams *chaincfg.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) (*btcutil.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(&chainhash.Hash{},
|
||||
wire.MaxPrevOutIndex),
|
||||
SignatureScript: coinbaseScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
totalInput := blockchain.CalcBlockSubsidy(blockHeight, p.chainParams)
|
||||
amountPerOutput := totalInput / int64(numOutputs)
|
||||
remainder := totalInput - amountPerOutput*int64(numOutputs)
|
||||
for i := uint32(0); i < numOutputs; i++ {
|
||||
// Ensure the final output accounts for any remainder that might
|
||||
// be left from splitting the input amount.
|
||||
amount := amountPerOutput
|
||||
if i == numOutputs-1 {
|
||||
amount = amountPerOutput + remainder
|
||||
}
|
||||
tx.AddTxOut(&wire.TxOut{
|
||||
PkScript: p.payScript,
|
||||
Value: amount,
|
||||
})
|
||||
}
|
||||
|
||||
return btcutil.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 []spendableOutput, numOutputs uint32) (*btcutil.Tx, error) {
|
||||
// Calculate the total input amount and split it amongst the requested
|
||||
// number of outputs.
|
||||
var totalInput btcutil.Amount
|
||||
for _, input := range inputs {
|
||||
totalInput += input.amount
|
||||
}
|
||||
amountPerOutput := int64(totalInput) / int64(numOutputs)
|
||||
remainder := int64(totalInput) - amountPerOutput*int64(numOutputs)
|
||||
|
||||
tx := wire.NewMsgTx(wire.TxVersion)
|
||||
for _, input := range inputs {
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: input.outPoint,
|
||||
SignatureScript: nil,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
}
|
||||
for i := uint32(0); i < numOutputs; i++ {
|
||||
// Ensure the final output accounts for any remainder that might
|
||||
// be left from splitting the input amount.
|
||||
amount := amountPerOutput
|
||||
if i == numOutputs-1 {
|
||||
amount = amountPerOutput + remainder
|
||||
}
|
||||
tx.AddTxOut(&wire.TxOut{
|
||||
PkScript: p.payScript,
|
||||
Value: amount,
|
||||
})
|
||||
}
|
||||
|
||||
// Sign the new transaction.
|
||||
for i := range tx.TxIn {
|
||||
sigScript, err := txscript.SignatureScript(tx, i, p.payScript,
|
||||
txscript.SigHashAll, p.signKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx.TxIn[i].SignatureScript = sigScript
|
||||
}
|
||||
|
||||
return btcutil.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 spendableOutput, numTxns uint32) ([]*btcutil.Tx, error) {
|
||||
txChain := make([]*btcutil.Tx, 0, numTxns)
|
||||
prevOutPoint := firstOutput.outPoint
|
||||
spendableAmount := firstOutput.amount
|
||||
for i := uint32(0); i < numTxns; i++ {
|
||||
// Create the transaction using the previous transaction output
|
||||
// and paying the full amount to the payment address associated
|
||||
// with the harness.
|
||||
tx := wire.NewMsgTx(wire.TxVersion)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: prevOutPoint,
|
||||
SignatureScript: nil,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
})
|
||||
tx.AddTxOut(&wire.TxOut{
|
||||
PkScript: p.payScript,
|
||||
Value: int64(spendableAmount),
|
||||
})
|
||||
|
||||
// Sign the new transaction.
|
||||
sigScript, err := txscript.SignatureScript(tx, 0, p.payScript,
|
||||
txscript.SigHashAll, p.signKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx.TxIn[0].SignatureScript = sigScript
|
||||
|
||||
txChain = append(txChain, btcutil.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(chainParams *chaincfg.Params) (*poolHarness, []spendableOutput, 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 := btcutil.NewAddressPubKey(pubKeyBytes, chainParams)
|
||||
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 fake chain and harness bound to it.
|
||||
chain := &fakeChain{utxos: blockchain.NewUtxoViewpoint()}
|
||||
harness := poolHarness{
|
||||
signKey: signKey,
|
||||
payAddr: payAddr,
|
||||
payScript: pkScript,
|
||||
chainParams: chainParams,
|
||||
|
||||
chain: chain,
|
||||
txPool: New(&Config{
|
||||
Policy: Policy{
|
||||
DisableRelayPriority: true,
|
||||
FreeTxRelayLimit: 15.0,
|
||||
MaxOrphanTxs: 5,
|
||||
MaxOrphanTxSize: 1000,
|
||||
MaxSigOpCostPerTx: blockchain.MaxBlockSigOpsCost / 4,
|
||||
MinRelayTxFee: 1000, // 1 Satoshi per byte
|
||||
MaxTxVersion: 1,
|
||||
},
|
||||
ChainParams: chainParams,
|
||||
FetchUtxoView: chain.FetchUtxoView,
|
||||
BestHeight: chain.BestHeight,
|
||||
MedianTimePast: chain.MedianTimePast,
|
||||
CalcSequenceLock: chain.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.
|
||||
numOutputs := uint32(1)
|
||||
outputs := make([]spendableOutput, 0, numOutputs)
|
||||
curHeight := harness.chain.BestHeight()
|
||||
coinbase, err := harness.CreateCoinbaseTx(curHeight+1, numOutputs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
harness.chain.utxos.AddTxOuts(coinbase, curHeight+1)
|
||||
for i := uint32(0); i < numOutputs; i++ {
|
||||
outputs = append(outputs, txOutToSpendableOut(coinbase, i))
|
||||
}
|
||||
harness.chain.SetHeight(int32(chainParams.CoinbaseMaturity) + curHeight)
|
||||
harness.chain.SetMedianTimePast(time.Now())
|
||||
|
||||
return &harness, outputs, 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 *btcutil.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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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(&chaincfg.MainNetParams)
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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(&chaincfg.MainNetParams)
|
||||
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 []*btcutil.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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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(&chaincfg.MainNetParams)
|
||||
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([]spendableOutput{{
|
||||
amount: btcutil.Amount(5000000000),
|
||||
outPoint: wire.OutPoint{Hash: chainhash.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) {
|
||||
t.Parallel()
|
||||
|
||||
const maxOrphans = 10
|
||||
harness, spendableOuts, err := newPoolHarness(&chaincfg.MainNetParams)
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
const maxOrphans = 4
|
||||
harness, outputs, err := newPoolHarness(&chaincfg.MainNetParams)
|
||||
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([]spendableOutput{
|
||||
txOutToSpendableOut(chainedTxns[1], 0),
|
||||
txOutToSpendableOut(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)
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxStandardP2SHSigOps is the maximum number of signature operations
|
||||
// that are considered standard in a pay-to-script-hash script.
|
||||
maxStandardP2SHSigOps = 15
|
||||
|
||||
// maxStandardTxCost is the max weight permitted by any transaction
|
||||
// according to the current default policy.
|
||||
maxStandardTxWeight = 400000
|
||||
|
||||
// maxStandardSigScriptSize is the maximum size allowed for a
|
||||
// transaction input signature script to be considered standard. This
|
||||
// value allows for a 15-of-15 CHECKMULTISIG pay-to-script-hash with
|
||||
// compressed keys.
|
||||
//
|
||||
// The form of the overall script is: OP_0 <15 signatures> OP_PUSHDATA2
|
||||
// <2 bytes len> [OP_15 <15 pubkeys> OP_15 OP_CHECKMULTISIG]
|
||||
//
|
||||
// For the p2sh script portion, each of the 15 compressed pubkeys are
|
||||
// 33 bytes (plus one for the OP_DATA_33 opcode), and the thus it totals
|
||||
// to (15*34)+3 = 513 bytes. Next, each of the 15 signatures is a max
|
||||
// of 73 bytes (plus one for the OP_DATA_73 opcode). Also, there is one
|
||||
// extra byte for the initial extra OP_0 push and 3 bytes for the
|
||||
// OP_PUSHDATA2 needed to specify the 513 bytes for the script push.
|
||||
// That brings the total to 1+(15*74)+3+513 = 1627. This value also
|
||||
// adds a few extra bytes to provide a little buffer.
|
||||
// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
|
||||
maxStandardSigScriptSize = 1650
|
||||
|
||||
// DefaultMinRelayTxFee is the minimum fee in satoshi that is required
|
||||
// for a transaction to be treated as free for relay and mining
|
||||
// purposes. It is also used to help determine if a transaction is
|
||||
// considered dust and as a base for calculating minimum required fees
|
||||
// for larger transactions. This value is in Satoshi/1000 bytes.
|
||||
DefaultMinRelayTxFee = btcutil.Amount(1000)
|
||||
|
||||
// maxStandardMultiSigKeys is the maximum number of public keys allowed
|
||||
// in a multi-signature transaction output script for it to be
|
||||
// considered standard.
|
||||
maxStandardMultiSigKeys = 3
|
||||
)
|
||||
|
||||
// calcMinRequiredTxRelayFee returns the minimum transaction fee required for a
|
||||
// transaction with the passed serialized size to be accepted into the memory
|
||||
// pool and relayed.
|
||||
func calcMinRequiredTxRelayFee(serializedSize int64, minRelayTxFee btcutil.Amount) int64 {
|
||||
// Calculate the minimum fee for a transaction to be allowed into the
|
||||
// mempool and relayed by scaling the base fee (which is the minimum
|
||||
// free transaction relay fee). minTxRelayFee is in Satoshi/kB so
|
||||
// multiply by serializedSize (which is in bytes) and divide by 1000 to
|
||||
// get minimum Satoshis.
|
||||
minFee := (serializedSize * int64(minRelayTxFee)) / 1000
|
||||
|
||||
if minFee == 0 && minRelayTxFee > 0 {
|
||||
minFee = int64(minRelayTxFee)
|
||||
}
|
||||
|
||||
// Set the minimum fee to the maximum possible value if the calculated
|
||||
// fee is not in the valid range for monetary amounts.
|
||||
if minFee < 0 || minFee > btcutil.MaxSatoshi {
|
||||
minFee = btcutil.MaxSatoshi
|
||||
}
|
||||
|
||||
return minFee
|
||||
}
|
||||
|
||||
// checkInputsStandard performs a series of checks on a transaction's inputs
|
||||
// to ensure they are "standard". A standard transaction input within the
|
||||
// context of this function is one whose referenced public key script is of a
|
||||
// standard form and, for pay-to-script-hash, does not have more than
|
||||
// maxStandardP2SHSigOps signature operations. However, it should also be noted
|
||||
// that standard inputs also are those which have a clean stack after execution
|
||||
// and only contain pushed data in their signature scripts. This function does
|
||||
// not perform those checks because the script engine already does this more
|
||||
// accurately and concisely via the txscript.ScriptVerifyCleanStack and
|
||||
// txscript.ScriptVerifySigPushOnly flags.
|
||||
func checkInputsStandard(tx *btcutil.Tx, utxoView *blockchain.UtxoViewpoint) error {
|
||||
// NOTE: The reference implementation also does a coinbase check here,
|
||||
// but coinbases have already been rejected prior to calling this
|
||||
// function so no need to recheck.
|
||||
|
||||
for i, txIn := range tx.MsgTx().TxIn {
|
||||
// It is safe to elide existence and index checks here since
|
||||
// they have already been checked prior to calling this
|
||||
// function.
|
||||
prevOut := txIn.PreviousOutPoint
|
||||
entry := utxoView.LookupEntry(&prevOut.Hash)
|
||||
originPkScript := entry.PkScriptByIndex(prevOut.Index)
|
||||
switch txscript.GetScriptClass(originPkScript) {
|
||||
case txscript.ScriptHashTy:
|
||||
numSigOps := txscript.GetPreciseSigOpCount(
|
||||
txIn.SignatureScript, originPkScript, true)
|
||||
if numSigOps > maxStandardP2SHSigOps {
|
||||
str := fmt.Sprintf("transaction input #%d has "+
|
||||
"%d signature operations which is more "+
|
||||
"than the allowed max amount of %d",
|
||||
i, numSigOps, maxStandardP2SHSigOps)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
case txscript.NonStandardTy:
|
||||
str := fmt.Sprintf("transaction input #%d has a "+
|
||||
"non-standard script form", i)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkPkScriptStandard performs a series of checks on a transaction output
|
||||
// script (public key script) to ensure it is a "standard" public key script.
|
||||
// A standard public key script is one that is a recognized form, and for
|
||||
// multi-signature scripts, only contains from 1 to maxStandardMultiSigKeys
|
||||
// public keys.
|
||||
func checkPkScriptStandard(pkScript []byte, scriptClass txscript.ScriptClass) error {
|
||||
switch scriptClass {
|
||||
case txscript.MultiSigTy:
|
||||
numPubKeys, numSigs, err := txscript.CalcMultiSigStats(pkScript)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("multi-signature script parse "+
|
||||
"failure: %v", err)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
// A standard multi-signature public key script must contain
|
||||
// from 1 to maxStandardMultiSigKeys public keys.
|
||||
if numPubKeys < 1 {
|
||||
str := "multi-signature script with no pubkeys"
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
if numPubKeys > maxStandardMultiSigKeys {
|
||||
str := fmt.Sprintf("multi-signature script with %d "+
|
||||
"public keys which is more than the allowed "+
|
||||
"max of %d", numPubKeys, maxStandardMultiSigKeys)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
// A standard multi-signature public key script must have at
|
||||
// least 1 signature and no more signatures than available
|
||||
// public keys.
|
||||
if numSigs < 1 {
|
||||
return txRuleError(wire.RejectNonstandard,
|
||||
"multi-signature script with no signatures")
|
||||
}
|
||||
if numSigs > numPubKeys {
|
||||
str := fmt.Sprintf("multi-signature script with %d "+
|
||||
"signatures which is more than the available "+
|
||||
"%d public keys", numSigs, numPubKeys)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
case txscript.NonStandardTy:
|
||||
return txRuleError(wire.RejectNonstandard,
|
||||
"non-standard script form")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isDust returns whether or not the passed transaction output amount is
|
||||
// considered dust or not based on the passed minimum transaction relay fee.
|
||||
// Dust is defined in terms of the minimum transaction relay fee. In
|
||||
// particular, if the cost to the network to spend coins is more than 1/3 of the
|
||||
// minimum transaction relay fee, it is considered dust.
|
||||
func isDust(txOut *wire.TxOut, minRelayTxFee btcutil.Amount) bool {
|
||||
// Unspendable outputs are considered dust.
|
||||
if txscript.IsUnspendable(txOut.PkScript) {
|
||||
return true
|
||||
}
|
||||
|
||||
// The total serialized size consists of the output and the associated
|
||||
// input script to redeem it. Since there is no input script
|
||||
// to redeem it yet, use the minimum size of a typical input script.
|
||||
//
|
||||
// Pay-to-pubkey-hash bytes breakdown:
|
||||
//
|
||||
// Output to hash (34 bytes):
|
||||
// 8 value, 1 script len, 25 script [1 OP_DUP, 1 OP_HASH_160,
|
||||
// 1 OP_DATA_20, 20 hash, 1 OP_EQUALVERIFY, 1 OP_CHECKSIG]
|
||||
//
|
||||
// Input with compressed pubkey (148 bytes):
|
||||
// 36 prev outpoint, 1 script len, 107 script [1 OP_DATA_72, 72 sig,
|
||||
// 1 OP_DATA_33, 33 compressed pubkey], 4 sequence
|
||||
//
|
||||
// Input with uncompressed pubkey (180 bytes):
|
||||
// 36 prev outpoint, 1 script len, 139 script [1 OP_DATA_72, 72 sig,
|
||||
// 1 OP_DATA_65, 65 compressed pubkey], 4 sequence
|
||||
//
|
||||
// Pay-to-pubkey bytes breakdown:
|
||||
//
|
||||
// Output to compressed pubkey (44 bytes):
|
||||
// 8 value, 1 script len, 35 script [1 OP_DATA_33,
|
||||
// 33 compressed pubkey, 1 OP_CHECKSIG]
|
||||
//
|
||||
// Output to uncompressed pubkey (76 bytes):
|
||||
// 8 value, 1 script len, 67 script [1 OP_DATA_65, 65 pubkey,
|
||||
// 1 OP_CHECKSIG]
|
||||
//
|
||||
// Input (114 bytes):
|
||||
// 36 prev outpoint, 1 script len, 73 script [1 OP_DATA_72,
|
||||
// 72 sig], 4 sequence
|
||||
//
|
||||
// Pay-to-witness-pubkey-hash bytes breakdown:
|
||||
//
|
||||
// Output to witness key hash (31 bytes);
|
||||
// 8 value, 1 script len, 22 script [1 OP_0, 1 OP_DATA_20,
|
||||
// 20 bytes hash160]
|
||||
//
|
||||
// Input (67 bytes as the 107 witness stack is discounted):
|
||||
// 36 prev outpoint, 1 script len, 0 script (not sigScript), 107
|
||||
// witness stack bytes [1 element length, 33 compressed pubkey,
|
||||
// element length 72 sig], 4 sequence
|
||||
//
|
||||
//
|
||||
// Theoretically this could examine the script type of the output script
|
||||
// and use a different size for the typical input script size for
|
||||
// pay-to-pubkey vs pay-to-pubkey-hash inputs per the above breakdowns,
|
||||
// but the only combination which is less than the value chosen is
|
||||
// a pay-to-pubkey script with a compressed pubkey, which is not very
|
||||
// common.
|
||||
//
|
||||
// The most common scripts are pay-to-pubkey-hash, and as per the above
|
||||
// breakdown, the minimum size of a p2pkh input script is 148 bytes. So
|
||||
// that figure is used. If the output being spent is a witness program,
|
||||
// then we apply the witness discount to the size of the signature.
|
||||
//
|
||||
// The segwit analogue to p2pkh is a p2wkh output. This is the smallest
|
||||
// output possible using the new segwit features. The 107 bytes of
|
||||
// witness data is discounted by a factor of 4, leading to a computed
|
||||
// value of 67 bytes of witness data.
|
||||
//
|
||||
// Both cases share a 41 byte preamble required to reference the input
|
||||
// being spent and the sequence number of the input.
|
||||
totalSize := txOut.SerializeSize() + 41
|
||||
if txscript.IsWitnessProgram(txOut.PkScript) {
|
||||
totalSize += (107 / blockchain.WitnessScaleFactor)
|
||||
} else {
|
||||
totalSize += 107
|
||||
}
|
||||
|
||||
// The output is considered dust if the cost to the network to spend the
|
||||
// coins is more than 1/3 of the minimum free transaction relay fee.
|
||||
// minFreeTxRelayFee is in Satoshi/KB, so multiply by 1000 to
|
||||
// convert to bytes.
|
||||
//
|
||||
// Using the typical values for a pay-to-pubkey-hash transaction from
|
||||
// the breakdown above and the default minimum free transaction relay
|
||||
// fee of 1000, this equates to values less than 546 satoshi being
|
||||
// considered dust.
|
||||
//
|
||||
// The following is equivalent to (value/totalSize) * (1/3) * 1000
|
||||
// without needing to do floating point math.
|
||||
return txOut.Value*1000/(3*int64(totalSize)) < int64(minRelayTxFee)
|
||||
}
|
||||
|
||||
// checkTransactionStandard performs a series of checks on a transaction to
|
||||
// ensure it is a "standard" transaction. A standard transaction is one that
|
||||
// conforms to several additional limiting cases over what is considered a
|
||||
// "sane" transaction such as having a version in the supported range, being
|
||||
// finalized, conforming to more stringent size constraints, having scripts
|
||||
// of recognized forms, and not containing "dust" outputs (those that are
|
||||
// so small it costs more to process them than they are worth).
|
||||
func checkTransactionStandard(tx *btcutil.Tx, height int32,
|
||||
medianTimePast time.Time, minRelayTxFee btcutil.Amount,
|
||||
maxTxVersion int32) error {
|
||||
|
||||
// The transaction must be a currently supported version.
|
||||
msgTx := tx.MsgTx()
|
||||
if msgTx.Version > maxTxVersion || msgTx.Version < 1 {
|
||||
str := fmt.Sprintf("transaction version %d is not in the "+
|
||||
"valid range of %d-%d", msgTx.Version, 1,
|
||||
maxTxVersion)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
// The transaction must be finalized to be standard and therefore
|
||||
// considered for inclusion in a block.
|
||||
if !blockchain.IsFinalizedTransaction(tx, height, medianTimePast) {
|
||||
return txRuleError(wire.RejectNonstandard,
|
||||
"transaction is not finalized")
|
||||
}
|
||||
|
||||
// Since extremely large transactions with a lot of inputs can cost
|
||||
// almost as much to process as the sender fees, limit the maximum
|
||||
// size of a transaction. This also helps mitigate CPU exhaustion
|
||||
// attacks.
|
||||
txWeight := blockchain.GetTransactionWeight(tx)
|
||||
if txWeight > maxStandardTxWeight {
|
||||
str := fmt.Sprintf("weight of transaction %v is larger than max "+
|
||||
"allowed weight of %v", txWeight, maxStandardTxWeight)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
for i, txIn := range msgTx.TxIn {
|
||||
// Each transaction input signature script must not exceed the
|
||||
// maximum size allowed for a standard transaction. See
|
||||
// the comment on maxStandardSigScriptSize for more details.
|
||||
sigScriptLen := len(txIn.SignatureScript)
|
||||
if sigScriptLen > maxStandardSigScriptSize {
|
||||
str := fmt.Sprintf("transaction input %d: signature "+
|
||||
"script size of %d bytes is large than max "+
|
||||
"allowed size of %d bytes", i, sigScriptLen,
|
||||
maxStandardSigScriptSize)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
// Each transaction input signature script must only contain
|
||||
// opcodes which push data onto the stack.
|
||||
if !txscript.IsPushOnlyScript(txIn.SignatureScript) {
|
||||
str := fmt.Sprintf("transaction input %d: signature "+
|
||||
"script is not push only", i)
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
}
|
||||
|
||||
// None of the output public key scripts can be a non-standard script or
|
||||
// be "dust" (except when the script is a null data script).
|
||||
numNullDataOutputs := 0
|
||||
for i, txOut := range msgTx.TxOut {
|
||||
scriptClass := txscript.GetScriptClass(txOut.PkScript)
|
||||
err := checkPkScriptStandard(txOut.PkScript, scriptClass)
|
||||
if err != nil {
|
||||
// Attempt to extract a reject code from the error so
|
||||
// it can be retained. When not possible, fall back to
|
||||
// a non standard error.
|
||||
rejectCode := wire.RejectNonstandard
|
||||
if rejCode, found := extractRejectCode(err); found {
|
||||
rejectCode = rejCode
|
||||
}
|
||||
str := fmt.Sprintf("transaction output %d: %v", i, err)
|
||||
return txRuleError(rejectCode, str)
|
||||
}
|
||||
|
||||
// Accumulate the number of outputs which only carry data. For
|
||||
// all other script types, ensure the output value is not
|
||||
// "dust".
|
||||
if scriptClass == txscript.NullDataTy {
|
||||
numNullDataOutputs++
|
||||
} else if isDust(txOut, minRelayTxFee) {
|
||||
str := fmt.Sprintf("transaction output %d: payment "+
|
||||
"of %d is dust", i, txOut.Value)
|
||||
return txRuleError(wire.RejectDust, str)
|
||||
}
|
||||
}
|
||||
|
||||
// A standard transaction must not have more than one output script that
|
||||
// only carries data.
|
||||
if numNullDataOutputs > 1 {
|
||||
str := "more than one transaction output in a nulldata script"
|
||||
return txRuleError(wire.RejectNonstandard, str)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTxVirtualSize computes the virtual size of a given transaction. A
|
||||
// transaction's virtual size is based off its weight, creating a discount for
|
||||
// any witness data it contains, proportional to the current
|
||||
// blockchain.WitnessScaleFactor value.
|
||||
func GetTxVirtualSize(tx *btcutil.Tx) int64 {
|
||||
// vSize := (weight(tx) + 3) / 4
|
||||
// := (((baseSize * 3) + totalSize) + 3) / 4
|
||||
// We add 3 here as a way to compute the ceiling of the prior arithmetic
|
||||
// to 4. The division by 4 creates a discount for wit witness data.
|
||||
return (blockchain.GetTransactionWeight(tx) + (blockchain.WitnessScaleFactor - 1)) /
|
||||
blockchain.WitnessScaleFactor
|
||||
}
|
||||
+512
@@ -0,0 +1,512 @@
|
||||
// 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",
|
||||
maxStandardTxWeight / 4,
|
||||
DefaultMinRelayTxFee,
|
||||
100000,
|
||||
},
|
||||
{
|
||||
"max standard tx size with max satoshi relay fee",
|
||||
maxStandardTxWeight / 4,
|
||||
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},
|
||||
(maxStandardTxWeight/4)+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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user