blockchain: Combine ErrDoubleSpend & ErrMissingTx.

This replaces the ErrDoubleSpend and ErrMissingTx error codes with a
single error code named ErrMissingTxOut and updates the relevant errors
and expected test results accordingly.

Once upon a time, the code relied on a transaction index, so it was able
to definitively differentiate between a transaction output that
legitimately did not exist and one that had already been spent.

However, since the code now uses a pruned utxoset, it is no longer
possible to reliably differentiate since once all outputs of a
transaction are spent, it is removed from the utxoset completely.
Consequently, a missing transaction could be either because the
transaction never existed or because it is fully spent.
This commit is contained in:
Dave Collins 2017-08-14 00:22:40 -05:00
parent e736ae125d
commit 19eada0b4b
No known key found for this signature in database
GPG key ID: B8904D9D9C93D1F2
9 changed files with 43 additions and 60 deletions

View file

@ -348,9 +348,7 @@ type SequenceLock struct {
// the candidate transaction to be included in a block. // the candidate transaction to be included in a block.
// //
// This function is safe for concurrent access. // This function is safe for concurrent access.
func (b *BlockChain) CalcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint, func (b *BlockChain) CalcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) {
mempool bool) (*SequenceLock, error) {
b.chainLock.Lock() b.chainLock.Lock()
defer b.chainLock.Unlock() defer b.chainLock.Unlock()
@ -361,9 +359,7 @@ func (b *BlockChain) CalcSequenceLock(tx *btcutil.Tx, utxoView *UtxoViewpoint,
// transaction. See the exported version, CalcSequenceLock for further details. // transaction. See the exported version, CalcSequenceLock for further details.
// //
// This function MUST be called with the chain state lock held (for writes). // This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx, func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx, utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) {
utxoView *UtxoViewpoint, mempool bool) (*SequenceLock, error) {
// A value of -1 for each relative lock type represents a relative time // A value of -1 for each relative lock type represents a relative time
// lock value that will allow a transaction to be included in a block // lock value that will allow a transaction to be included in a block
// at any given height or time. This value is returned as the relative // at any given height or time. This value is returned as the relative
@ -411,10 +407,11 @@ func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx,
for txInIndex, txIn := range mTx.TxIn { for txInIndex, txIn := range mTx.TxIn {
utxo := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash) utxo := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash)
if utxo == nil { if utxo == nil {
str := fmt.Sprintf("unable to find unspent output "+ str := fmt.Sprintf("output %v referenced from "+
"%v referenced from transaction %s:%d", "transaction %s:%d either does not exist or "+
txIn.PreviousOutPoint, tx.Hash(), txInIndex) "has already been spent", txIn.PreviousOutPoint,
return sequenceLock, ruleError(ErrMissingTx, str) tx.Hash(), txInIndex)
return sequenceLock, ruleError(ErrMissingTxOut, str)
} }
// If the input height is set to the mempool height, then we // If the input height is set to the mempool height, then we

View file

@ -128,9 +128,9 @@ const (
// range or not referencing one at all. // range or not referencing one at all.
ErrBadTxInput ErrBadTxInput
// ErrMissingTx indicates a transaction referenced by an input is // ErrMissingTxOut indicates a transaction output referenced by an input
// missing. // either does not exist or has already been spent.
ErrMissingTx ErrMissingTxOut
// ErrUnfinalizedTx indicates a transaction has not been finalized. // ErrUnfinalizedTx indicates a transaction has not been finalized.
// A valid block may only contain finalized transactions. // A valid block may only contain finalized transactions.
@ -150,10 +150,6 @@ const (
// coinbase that has not yet reached the required maturity. // coinbase that has not yet reached the required maturity.
ErrImmatureSpend ErrImmatureSpend
// ErrDoubleSpend indicates a transaction is attempting to spend coins
// that have already been spent.
ErrDoubleSpend
// ErrSpendTooHigh indicates a transaction is attempting to spend more // ErrSpendTooHigh indicates a transaction is attempting to spend more
// value than the sum of all of its inputs. // value than the sum of all of its inputs.
ErrSpendTooHigh ErrSpendTooHigh
@ -242,12 +238,11 @@ var errorCodeStrings = map[ErrorCode]string{
ErrBadTxOutValue: "ErrBadTxOutValue", ErrBadTxOutValue: "ErrBadTxOutValue",
ErrDuplicateTxInputs: "ErrDuplicateTxInputs", ErrDuplicateTxInputs: "ErrDuplicateTxInputs",
ErrBadTxInput: "ErrBadTxInput", ErrBadTxInput: "ErrBadTxInput",
ErrMissingTx: "ErrMissingTx", ErrMissingTxOut: "ErrMissingTxOut",
ErrUnfinalizedTx: "ErrUnfinalizedTx", ErrUnfinalizedTx: "ErrUnfinalizedTx",
ErrDuplicateTx: "ErrDuplicateTx", ErrDuplicateTx: "ErrDuplicateTx",
ErrOverwriteTx: "ErrOverwriteTx", ErrOverwriteTx: "ErrOverwriteTx",
ErrImmatureSpend: "ErrImmatureSpend", ErrImmatureSpend: "ErrImmatureSpend",
ErrDoubleSpend: "ErrDoubleSpend",
ErrSpendTooHigh: "ErrSpendTooHigh", ErrSpendTooHigh: "ErrSpendTooHigh",
ErrBadFees: "ErrBadFees", ErrBadFees: "ErrBadFees",
ErrTooManySigOps: "ErrTooManySigOps", ErrTooManySigOps: "ErrTooManySigOps",

View file

@ -38,12 +38,11 @@ func TestErrorCodeStringer(t *testing.T) {
{blockchain.ErrDuplicateTxInputs, "ErrDuplicateTxInputs"}, {blockchain.ErrDuplicateTxInputs, "ErrDuplicateTxInputs"},
{blockchain.ErrBadTxInput, "ErrBadTxInput"}, {blockchain.ErrBadTxInput, "ErrBadTxInput"},
{blockchain.ErrBadCheckpoint, "ErrBadCheckpoint"}, {blockchain.ErrBadCheckpoint, "ErrBadCheckpoint"},
{blockchain.ErrMissingTx, "ErrMissingTx"}, {blockchain.ErrMissingTxOut, "ErrMissingTxOut"},
{blockchain.ErrUnfinalizedTx, "ErrUnfinalizedTx"}, {blockchain.ErrUnfinalizedTx, "ErrUnfinalizedTx"},
{blockchain.ErrDuplicateTx, "ErrDuplicateTx"}, {blockchain.ErrDuplicateTx, "ErrDuplicateTx"},
{blockchain.ErrOverwriteTx, "ErrOverwriteTx"}, {blockchain.ErrOverwriteTx, "ErrOverwriteTx"},
{blockchain.ErrImmatureSpend, "ErrImmatureSpend"}, {blockchain.ErrImmatureSpend, "ErrImmatureSpend"},
{blockchain.ErrDoubleSpend, "ErrDoubleSpend"},
{blockchain.ErrSpendTooHigh, "ErrSpendTooHigh"}, {blockchain.ErrSpendTooHigh, "ErrSpendTooHigh"},
{blockchain.ErrBadFees, "ErrBadFees"}, {blockchain.ErrBadFees, "ErrBadFees"},
{blockchain.ErrTooManySigOps, "ErrTooManySigOps"}, {blockchain.ErrTooManySigOps, "ErrTooManySigOps"},

View file

@ -997,7 +997,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
acceptedToSideChainWithExpectedTip("b6") acceptedToSideChainWithExpectedTip("b6")
g.nextBlock("b8", outs[4]) g.nextBlock("b8", outs[4])
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Too much proof-of-work coinbase tests. // Too much proof-of-work coinbase tests.
@ -1078,7 +1078,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// \-> b3(1) -> b4(2) // \-> b3(1) -> b4(2)
g.setTip("b15") g.setTip("b15")
g.nextBlock("b17", &b3Tx1Out) g.nextBlock("b17", &b3Tx1Out)
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// Create block that forks and spends a tx created on a third fork. // Create block that forks and spends a tx created on a third fork.
// //
@ -1090,7 +1090,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
acceptedToSideChainWithExpectedTip("b15") acceptedToSideChainWithExpectedTip("b15")
g.nextBlock("b19", outs[6]) g.nextBlock("b19", outs[6])
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Immature coinbase tests. // Immature coinbase tests.
@ -1278,11 +1278,11 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
doubleSpendTx := createSpendTx(outs[11], lowFee) doubleSpendTx := createSpendTx(outs[11], lowFee)
g.nextBlock("b37", outs[11], additionalTx(doubleSpendTx)) g.nextBlock("b37", outs[11], additionalTx(doubleSpendTx))
b37Tx1Out := makeSpendableOut(g.tip, 1, 0) b37Tx1Out := makeSpendableOut(g.tip, 1, 0)
rejected(blockchain.ErrDoubleSpend) rejected(blockchain.ErrMissingTxOut)
g.setTip("b35") g.setTip("b35")
g.nextBlock("b38", &b37Tx1Out) g.nextBlock("b38", &b37Tx1Out)
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Pay-to-script-hash signature operation count tests. // Pay-to-script-hash signature operation count tests.
@ -1536,7 +1536,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
b.Transactions[1].TxIn[0].PreviousOutPoint.Hash = *hash b.Transactions[1].TxIn[0].PreviousOutPoint.Hash = *hash
b.Transactions[1].TxIn[0].PreviousOutPoint.Index = 0 b.Transactions[1].TxIn[0].PreviousOutPoint.Index = 0
}) })
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Block header median time tests. // Block header median time tests.
@ -1688,7 +1688,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
g.nextBlock("b58", outs[17], func(b *wire.MsgBlock) { g.nextBlock("b58", outs[17], func(b *wire.MsgBlock) {
b.Transactions[1].TxIn[0].PreviousOutPoint.Index = 42 b.Transactions[1].TxIn[0].PreviousOutPoint.Index = 42
}) })
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// Create block with transaction that pays more than its inputs. // Create block with transaction that pays more than its inputs.
// //
@ -1816,7 +1816,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
b.AddTransaction(tx3) b.AddTransaction(tx3)
b.AddTransaction(tx2) b.AddTransaction(tx2)
}) })
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// Create block that double spends a transaction created in the same // Create block that double spends a transaction created in the same
// block. // block.
@ -1831,7 +1831,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
b.AddTransaction(tx3) b.AddTransaction(tx3)
b.AddTransaction(tx4) b.AddTransaction(tx4)
}) })
rejected(blockchain.ErrDoubleSpend) rejected(blockchain.ErrMissingTxOut)
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Extra subsidy tests. // Extra subsidy tests.
@ -2022,7 +2022,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// to effective negate that behavior. // to effective negate that behavior.
b75OpReturnOut.amount++ b75OpReturnOut.amount++
g.nextBlock("b80", &b75OpReturnOut) g.nextBlock("b80", &b75OpReturnOut)
rejected(blockchain.ErrMissingTx) rejected(blockchain.ErrMissingTxOut)
// Create a block that has a transaction with multiple OP_RETURNs. Even // Create a block that has a transaction with multiple OP_RETURNs. Even
// though it's not considered a standard transaction, it is still valid // though it's not considered a standard transaction, it is still valid

View file

@ -65,7 +65,7 @@ out:
"transaction %v referenced from "+ "transaction %v referenced from "+
"transaction %v", originTxHash, "transaction %v", originTxHash,
txVI.tx.Hash()) txVI.tx.Hash())
err := ruleError(ErrMissingTx, str) err := ruleError(ErrMissingTxOut, str)
v.sendResult(err) v.sendResult(err)
break out break out
} }

View file

@ -389,10 +389,11 @@ func CountP2SHSigOps(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint)
originTxIndex := txIn.PreviousOutPoint.Index originTxIndex := txIn.PreviousOutPoint.Index
txEntry := utxoView.LookupEntry(originTxHash) txEntry := utxoView.LookupEntry(originTxHash)
if txEntry == nil || txEntry.IsOutputSpent(originTxIndex) { if txEntry == nil || txEntry.IsOutputSpent(originTxIndex) {
str := fmt.Sprintf("unable to find unspent output "+ str := fmt.Sprintf("output %v referenced from "+
"%v referenced from transaction %s:%d", "transaction %s:%d either does not exist or "+
txIn.PreviousOutPoint, tx.Hash(), txInIndex) "has already been spent", txIn.PreviousOutPoint,
return 0, ruleError(ErrMissingTx, str) tx.Hash(), txInIndex)
return 0, ruleError(ErrMissingTxOut, str)
} }
// We're only interested in pay-to-script-hash types, so skip // We're only interested in pay-to-script-hash types, so skip
@ -897,12 +898,14 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo
for txInIndex, txIn := range tx.MsgTx().TxIn { for txInIndex, txIn := range tx.MsgTx().TxIn {
// Ensure the referenced input transaction is available. // Ensure the referenced input transaction is available.
originTxHash := &txIn.PreviousOutPoint.Hash originTxHash := &txIn.PreviousOutPoint.Hash
originTxIndex := txIn.PreviousOutPoint.Index
utxoEntry := utxoView.LookupEntry(originTxHash) utxoEntry := utxoView.LookupEntry(originTxHash)
if utxoEntry == nil { if utxoEntry == nil || utxoEntry.IsOutputSpent(originTxIndex) {
str := fmt.Sprintf("unable to find unspent output "+ str := fmt.Sprintf("output %v referenced from "+
"%v referenced from transaction %s:%d", "transaction %s:%d either does not exist or "+
txIn.PreviousOutPoint, tx.Hash(), txInIndex) "has already been spent", txIn.PreviousOutPoint,
return 0, ruleError(ErrMissingTx, str) tx.Hash(), txInIndex)
return 0, ruleError(ErrMissingTxOut, str)
} }
// Ensure the transaction is not spending coins which have not // Ensure the transaction is not spending coins which have not
@ -922,15 +925,6 @@ func CheckTransactionInputs(tx *btcutil.Tx, txHeight int32, utxoView *UtxoViewpo
} }
} }
// Ensure the transaction is not double spending coins.
originTxIndex := txIn.PreviousOutPoint.Index
if utxoEntry.IsOutputSpent(originTxIndex) {
str := fmt.Sprintf("transaction %s:%d tried to double "+
"spend output %v", txHash, txInIndex,
txIn.PreviousOutPoint)
return 0, ruleError(ErrDoubleSpend, str)
}
// Ensure the transaction amounts are in range. Each of the // Ensure the transaction amounts are in range. Each of the
// output values of the input transactions must not be negative // output values of the input transactions must not be negative
// or more than the max allowed per transaction. All amounts in // or more than the max allowed per transaction. All amounts in
@ -1017,7 +1011,7 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *btcutil.Block, vi
// an error now. // an error now.
if node.hash.IsEqual(b.chainParams.GenesisHash) { if node.hash.IsEqual(b.chainParams.GenesisHash) {
str := "the coinbase for the genesis block is not spendable" str := "the coinbase for the genesis block is not spendable"
return ruleError(ErrMissingTx, str) return ruleError(ErrMissingTxOut, str)
} }
// Ensure the view is for the node being checked. // Ensure the view is for the node being checked.

View file

@ -91,10 +91,12 @@ func GetSigOpCost(tx *btcutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint,
originTxIndex := txIn.PreviousOutPoint.Index originTxIndex := txIn.PreviousOutPoint.Index
txEntry := utxoView.LookupEntry(originTxHash) txEntry := utxoView.LookupEntry(originTxHash)
if txEntry == nil || txEntry.IsOutputSpent(originTxIndex) { if txEntry == nil || txEntry.IsOutputSpent(originTxIndex) {
str := fmt.Sprintf("unable to find unspent output "+ str := fmt.Sprintf("output %v referenced from "+
"%v referenced from transaction %s:%d", "transaction %s:%d either does not "+
txIn.PreviousOutPoint, tx.Hash(), txInIndex) "exist or has already been spent",
return 0, ruleError(ErrMissingTx, str) txIn.PreviousOutPoint, tx.Hash(),
txInIndex)
return 0, ruleError(ErrMissingTxOut, str)
} }
witness := txIn.Witness witness := txIn.Witness

View file

@ -74,8 +74,6 @@ func extractRejectCode(err error) (wire.RejectCode, bool) {
switch err.ErrorCode { switch err.ErrorCode {
// Rejected due to duplicate. // Rejected due to duplicate.
case blockchain.ErrDuplicateBlock: case blockchain.ErrDuplicateBlock:
fallthrough
case blockchain.ErrDoubleSpend:
code = wire.RejectDuplicate code = wire.RejectDuplicate
// Rejected due to obsolete version. // Rejected due to obsolete version.

View file

@ -1995,7 +1995,7 @@ func chainErrToGBTErrString(err error) string {
return "bad-txns-dupinputs" return "bad-txns-dupinputs"
case blockchain.ErrBadTxInput: case blockchain.ErrBadTxInput:
return "bad-txns-badinput" return "bad-txns-badinput"
case blockchain.ErrMissingTx: case blockchain.ErrMissingTxOut:
return "bad-txns-missinginput" return "bad-txns-missinginput"
case blockchain.ErrUnfinalizedTx: case blockchain.ErrUnfinalizedTx:
return "bad-txns-unfinalizedtx" return "bad-txns-unfinalizedtx"
@ -2005,8 +2005,6 @@ func chainErrToGBTErrString(err error) string {
return "bad-txns-overwrite" return "bad-txns-overwrite"
case blockchain.ErrImmatureSpend: case blockchain.ErrImmatureSpend:
return "bad-txns-maturity" return "bad-txns-maturity"
case blockchain.ErrDoubleSpend:
return "bad-txns-dblspend"
case blockchain.ErrSpendTooHigh: case blockchain.ErrSpendTooHigh:
return "bad-txns-highspend" return "bad-txns-highspend"
case blockchain.ErrBadFees: case blockchain.ErrBadFees: