Improve double spend error strings.

The mempool's MaybeAcceptTransaction methods have also been modified
to return a slice of transaction hashes referenced by the transaction
inputs which are unknown (totally spent or never seen).  While this is
currently used to include the first hash in a ProcessTransaction error
message if inserting orphans is not allowed, it may also be used in
the future to request orphan transactions from peers.
This commit is contained in:
Josh Rickmar 2015-01-06 19:26:26 -05:00
parent a64ffb820a
commit c257da934e
3 changed files with 95 additions and 55 deletions

View file

@ -1176,7 +1176,8 @@ func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) {
// Reinsert all of the transactions (except the coinbase) into // Reinsert all of the transactions (except the coinbase) into
// the transaction pool. // the transaction pool.
for _, tx := range block.Transactions()[1:] { for _, tx := range block.Transactions()[1:] {
err := b.server.txMemPool.MaybeAcceptTransaction(tx, nil, false, true) _, err := b.server.txMemPool.MaybeAcceptTransaction(tx,
false, true)
if err != nil { if err != nil {
// Remove the transaction and all transactions // Remove the transaction and all transactions
// that depend on it if it wasn't accepted into // that depend on it if it wasn't accepted into

2
log.go
View file

@ -30,7 +30,7 @@ const (
// maxRejectReasonLen is the maximum length of a sanitized reject reason // maxRejectReasonLen is the maximum length of a sanitized reject reason
// that will be logged. // that will be logged.
maxRejectReasonLen = 200 maxRejectReasonLen = 250
) )
// Loggers per subsytem. Note that backendLog is a seelog logger that all of // Loggers per subsytem. Note that backendLog is a seelog logger that all of

View file

@ -741,8 +741,9 @@ func (txD *TxDesc) CurrentPriority(txStore btcchain.TxStore, nextBlockHeight int
func (mp *txMemPool) checkPoolDoubleSpend(tx *btcutil.Tx) error { func (mp *txMemPool) checkPoolDoubleSpend(tx *btcutil.Tx) error {
for _, txIn := range tx.MsgTx().TxIn { for _, txIn := range tx.MsgTx().TxIn {
if txR, exists := mp.outpoints[txIn.PreviousOutPoint]; exists { if txR, exists := mp.outpoints[txIn.PreviousOutPoint]; exists {
str := fmt.Sprintf("transaction %v in the pool "+ str := fmt.Sprintf("output %v already spent by "+
"already spends the same coins", txR.Sha()) "transaction %v in the memory pool",
txIn.PreviousOutPoint, txR.Sha())
return txRuleError(btcwire.RejectDuplicate, str) return txRuleError(btcwire.RejectDuplicate, str)
} }
} }
@ -799,10 +800,7 @@ func (mp *txMemPool) FetchTransaction(txHash *btcwire.ShaHash) (*btcutil.Tx, err
// more details. // more details.
// //
// This function MUST be called with the mempool lock held (for writes). // This function MUST be called with the mempool lock held (for writes).
func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNew, rateLimit bool) error { func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit bool) ([]*btcwire.ShaHash, error) {
if isOrphan != nil {
*isOrphan = false
}
txHash := tx.Sha() txHash := tx.Sha()
// Don't accept the transaction if it already exists in the pool. This // Don't accept the transaction if it already exists in the pool. This
@ -810,7 +808,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
// be a quick check to weed out duplicates. // be a quick check to weed out duplicates.
if mp.haveTransaction(txHash) { if mp.haveTransaction(txHash) {
str := fmt.Sprintf("already have transaction %v", txHash) str := fmt.Sprintf("already have transaction %v", txHash)
return txRuleError(btcwire.RejectDuplicate, str) return nil, txRuleError(btcwire.RejectDuplicate, str)
} }
// Perform preliminary sanity checks on the transaction. This makes // Perform preliminary sanity checks on the transaction. This makes
@ -819,16 +817,16 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
err := btcchain.CheckTransactionSanity(tx) err := btcchain.CheckTransactionSanity(tx)
if err != nil { if err != nil {
if cerr, ok := err.(btcchain.RuleError); ok { if cerr, ok := err.(btcchain.RuleError); ok {
return chainRuleError(cerr) return nil, chainRuleError(cerr)
} }
return err return nil, err
} }
// A standalone transaction must not be a coinbase transaction. // A standalone transaction must not be a coinbase transaction.
if btcchain.IsCoinBase(tx) { if btcchain.IsCoinBase(tx) {
str := fmt.Sprintf("transaction %v is an individual coinbase", str := fmt.Sprintf("transaction %v is an individual coinbase",
txHash) txHash)
return txRuleError(btcwire.RejectInvalid, str) return nil, txRuleError(btcwire.RejectInvalid, str)
} }
// Don't accept transactions with a lock time after the maximum int32 // Don't accept transactions with a lock time after the maximum int32
@ -838,7 +836,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
if tx.MsgTx().LockTime > math.MaxInt32 { if tx.MsgTx().LockTime > math.MaxInt32 {
str := fmt.Sprintf("transaction %v has a lock time after "+ str := fmt.Sprintf("transaction %v has a lock time after "+
"2038 which is not accepted yet", txHash) "2038 which is not accepted yet", txHash)
return txRuleError(btcwire.RejectNonstandard, str) return nil, txRuleError(btcwire.RejectNonstandard, str)
} }
// Get the current height of the main chain. A standalone transaction // Get the current height of the main chain. A standalone transaction
@ -848,7 +846,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
if err != nil { if err != nil {
// This is an unexpected error so don't turn it into a rule // This is an unexpected error so don't turn it into a rule
// error. // error.
return err return nil, err
} }
nextBlockHeight := curHeight + 1 nextBlockHeight := curHeight + 1
@ -866,7 +864,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
} }
str := fmt.Sprintf("transaction %v is not standard: %v", str := fmt.Sprintf("transaction %v is not standard: %v",
txHash, err) txHash, err)
return txRuleError(rejectCode, str) return nil, txRuleError(rejectCode, str)
} }
} }
@ -880,7 +878,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
// which examines the actual spend data and prevents double spends. // which examines the actual spend data and prevents double spends.
err = mp.checkPoolDoubleSpend(tx) err = mp.checkPoolDoubleSpend(tx)
if err != nil { if err != nil {
return err return nil, err
} }
// Fetch all of the transactions referenced by the inputs to this // Fetch all of the transactions referenced by the inputs to this
@ -890,9 +888,9 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
txStore, err := mp.fetchInputTransactions(tx) txStore, err := mp.fetchInputTransactions(tx)
if err != nil { if err != nil {
if cerr, ok := err.(btcchain.RuleError); ok { if cerr, ok := err.(btcchain.RuleError); ok {
return chainRuleError(cerr) return nil, chainRuleError(cerr)
} }
return err return nil, err
} }
// Don't allow the transaction if it exists in the main chain and is not // Don't allow the transaction if it exists in the main chain and is not
@ -900,21 +898,25 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
if txD, exists := txStore[*txHash]; exists && txD.Err == nil { if txD, exists := txStore[*txHash]; exists && txD.Err == nil {
for _, isOutputSpent := range txD.Spent { for _, isOutputSpent := range txD.Spent {
if !isOutputSpent { if !isOutputSpent {
return txRuleError(btcwire.RejectDuplicate, return nil, txRuleError(btcwire.RejectDuplicate,
"transaction already exists") "transaction already exists")
} }
} }
} }
delete(txStore, *txHash) delete(txStore, *txHash)
// Transaction is an orphan if any of the inputs don't exist. // Transaction is an orphan if any of the referenced input transactions
// don't exist. Adding orphans to the orphan pool is not handled by
// this function, and the caller should use maybeAddOrphan if this
// behavior is desired.
var missingParents []*btcwire.ShaHash
for _, txD := range txStore { for _, txD := range txStore {
if txD.Err == btcdb.ErrTxShaMissing { if txD.Err == btcdb.ErrTxShaMissing {
if isOrphan != nil { missingParents = append(missingParents, txD.Hash)
*isOrphan = true
} }
return nil
} }
if len(missingParents) != 0 {
return missingParents, nil
} }
// Perform several checks on the transaction inputs using the invariant // Perform several checks on the transaction inputs using the invariant
@ -924,9 +926,9 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
txFee, err := btcchain.CheckTransactionInputs(tx, nextBlockHeight, txStore) txFee, err := btcchain.CheckTransactionInputs(tx, nextBlockHeight, txStore)
if err != nil { if err != nil {
if cerr, ok := err.(btcchain.RuleError); ok { if cerr, ok := err.(btcchain.RuleError); ok {
return chainRuleError(cerr) return nil, chainRuleError(cerr)
} }
return err return nil, err
} }
// Don't allow transactions with non-standard inputs if the network // Don't allow transactions with non-standard inputs if the network
@ -943,7 +945,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
} }
str := fmt.Sprintf("transaction %v has a non-standard "+ str := fmt.Sprintf("transaction %v has a non-standard "+
"input: %v", txHash, err) "input: %v", txHash, err)
return txRuleError(rejectCode, str) return nil, txRuleError(rejectCode, str)
} }
} }
@ -959,15 +961,15 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
numSigOps, err := btcchain.CountP2SHSigOps(tx, false, txStore) numSigOps, err := btcchain.CountP2SHSigOps(tx, false, txStore)
if err != nil { if err != nil {
if cerr, ok := err.(btcchain.RuleError); ok { if cerr, ok := err.(btcchain.RuleError); ok {
return chainRuleError(cerr) return nil, chainRuleError(cerr)
} }
return err return nil, err
} }
numSigOps += btcchain.CountSigOps(tx) numSigOps += btcchain.CountSigOps(tx)
if numSigOps > maxSigOpsPerTx { if numSigOps > maxSigOpsPerTx {
str := fmt.Sprintf("transaction %v has too many sigops: %d > %d", str := fmt.Sprintf("transaction %v has too many sigops: %d > %d",
txHash, numSigOps, maxSigOpsPerTx) txHash, numSigOps, maxSigOpsPerTx)
return txRuleError(btcwire.RejectNonstandard, str) return nil, txRuleError(btcwire.RejectNonstandard, str)
} }
// Don't allow transactions with fees too low to get into a mined block. // Don't allow transactions with fees too low to get into a mined block.
@ -987,7 +989,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
str := fmt.Sprintf("transaction %v has %d fees which is under "+ str := fmt.Sprintf("transaction %v has %d fees which is under "+
"the required amount of %d", txHash, txFee, "the required amount of %d", txHash, txFee,
minFee) minFee)
return txRuleError(btcwire.RejectInsufficientFee, str) return nil, txRuleError(btcwire.RejectInsufficientFee, str)
} }
// Free-to-relay transactions are rate limited here to prevent // Free-to-relay transactions are rate limited here to prevent
@ -1004,7 +1006,7 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
if mp.pennyTotal >= cfg.FreeTxRelayLimit*10*1000 { if mp.pennyTotal >= cfg.FreeTxRelayLimit*10*1000 {
str := fmt.Sprintf("transaction %v has been rejected "+ str := fmt.Sprintf("transaction %v has been rejected "+
"by the rate limiter due to low fees", txHash) "by the rate limiter due to low fees", txHash)
return txRuleError(btcwire.RejectInsufficientFee, str) return nil, txRuleError(btcwire.RejectInsufficientFee, str)
} }
oldTotal := mp.pennyTotal oldTotal := mp.pennyTotal
@ -1020,9 +1022,9 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
standardScriptVerifyFlags) standardScriptVerifyFlags)
if err != nil { if err != nil {
if cerr, ok := err.(btcchain.RuleError); ok { if cerr, ok := err.(btcchain.RuleError); ok {
return chainRuleError(cerr) return nil, chainRuleError(cerr)
} }
return err return nil, err
} }
// Add to transaction pool. // Add to transaction pool.
@ -1040,29 +1042,33 @@ func (mp *txMemPool) maybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNe
mp.server.rpcServer.gbtWorkState.NotifyMempoolTx(mp.lastUpdated) mp.server.rpcServer.gbtWorkState.NotifyMempoolTx(mp.lastUpdated)
} }
return nil return nil, nil
} }
// MaybeAcceptTransaction is the main workhorse for handling insertion of new // MaybeAcceptTransaction is the main workhorse for handling insertion of new
// free-standing transactions into a memory pool. It includes functionality // free-standing transactions into a memory pool. It includes functionality
// such as rejecting duplicate transactions, ensuring transactions follow all // such as rejecting duplicate transactions, ensuring transactions follow all
// rules, orphan transaction handling, and insertion into the memory pool. The // rules, detecting orphan transactions, and insertion into the memory pool.
// isOrphan parameter can be nil if the caller does not need to know whether //
// or not the transaction is an orphan. // If the transaction is an orphan (missing parent transactions), the
// transaction is NOT added to the orphan pool, but each unknown referenced
// parent is returned. Use ProcessTransaction instead if new orphans should
// be added to the orphan pool.
// //
// This function is safe for concurrent access. // This function is safe for concurrent access.
func (mp *txMemPool) MaybeAcceptTransaction(tx *btcutil.Tx, isOrphan *bool, isNew, rateLimit bool) error { func (mp *txMemPool) MaybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit bool) ([]*btcwire.ShaHash, error) {
// Protect concurrent access. // Protect concurrent access.
mp.Lock() mp.Lock()
defer mp.Unlock() defer mp.Unlock()
return mp.maybeAcceptTransaction(tx, isOrphan, isNew, rateLimit) return mp.maybeAcceptTransaction(tx, isNew, rateLimit)
} }
// processOrphans determines if there are any orphans which depend on the passed // processOrphans determines if there are any orphans which depend on the passed
// transaction hash (they are no longer orphans if true) and potentially accepts // transaction hash (it is possible that they are no longer orphans) and
// them. It repeats the process for the newly accepted transactions (to detect // potentially accepts them to the memory pool. It repeats the process for the
// further orphans which may no longer be orphans) until there are no more. // newly accepted transactions (to detect further orphans which may no longer be
// orphans) until there are no more.
// //
// This function MUST be called with the mempool lock held (for writes). // This function MUST be called with the mempool lock held (for writes).
func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error { func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error {
@ -1089,29 +1095,57 @@ func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error {
enext = e.Next() enext = e.Next()
tx := e.Value.(*btcutil.Tx) tx := e.Value.(*btcutil.Tx)
// Remove the orphan from the orphan pool. // Remove the orphan from the orphan pool. Current
// behavior requires that all saved orphans with
// a newly accepted parent are removed from the orphan
// pool and potentially added to the memory pool, but
// transactions which cannot be added to memory pool
// (including due to still being orphans) are expunged
// from the orphan pool.
//
// TODO(jrick): The above described behavior sounds
// like a bug, and I think we should investigate
// potentially moving orphans to the memory pool, but
// leaving them in the orphan pool if not all parent
// transactions are known yet.
orphanHash := tx.Sha() orphanHash := tx.Sha()
mp.removeOrphan(orphanHash) mp.removeOrphan(orphanHash)
// Potentially accept the transaction into the // Potentially accept the transaction into the
// transaction pool. // transaction pool.
var isOrphan bool missingParents, err := mp.maybeAcceptTransaction(tx,
err := mp.maybeAcceptTransaction(tx, &isOrphan, true, true) true, true)
if err != nil { if err != nil {
return err return err
} }
if !isOrphan { if len(missingParents) == 0 {
// Generate the inventory vector and relay it. // Generate and relay the inventory vector for the
// newly accepted transaction.
iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha())
mp.server.RelayInventory(iv) mp.server.RelayInventory(iv)
} else { } else {
// Transaction is still an orphan.
// TODO(jrick): This removeOrphan call is
// likely unnecessary as it was unconditionally
// removed above and maybeAcceptTransaction won't
// add it back.
mp.removeOrphan(orphanHash) mp.removeOrphan(orphanHash)
} }
// Add this transaction to the list of transactions to // Add this transaction to the list of transactions to
// process so any orphans that depend on this one are // process so any orphans that depend on this one are
// handled too. // handled too.
//
// TODO(jrick): In the case that this is still an orphan,
// we know that any other transactions in the orphan
// pool with this orphan as their parent are still
// orphans as well, and should be removed. While
// recursively calling removeOrphan and
// maybeAcceptTransaction on these transactions is not
// wrong per se, it is overkill if all we care about is
// recursively removing child transactions of this
// orphan.
processHashes.PushBack(orphanHash) processHashes.PushBack(orphanHash)
} }
} }
@ -1133,20 +1167,20 @@ func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx, allowOrphan, rateLimit b
txmpLog.Tracef("Processing transaction %v", tx.Sha()) txmpLog.Tracef("Processing transaction %v", tx.Sha())
// Potentially accept the transaction to the memory pool. // Potentially accept the transaction to the memory pool.
var isOrphan bool missingParents, err := mp.maybeAcceptTransaction(tx, true, rateLimit)
err := mp.maybeAcceptTransaction(tx, &isOrphan, true, rateLimit)
if err != nil { if err != nil {
return err return err
} }
if !isOrphan { if len(missingParents) == 0 {
// Generate the inventory vector and relay it. // Generate the inventory vector and relay it.
iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha())
mp.server.RelayInventory(iv) mp.server.RelayInventory(iv)
// Accept any orphan transactions that depend on this // Accept any orphan transactions that depend on this
// transaction (they are no longer orphans) and repeat for those // transaction (they may no longer be orphans if all inputs
// accepted transactions until there are no more. // are now available) and repeat for those accepted
// transactions until there are no more.
err := mp.processOrphans(tx.Sha()) err := mp.processOrphans(tx.Sha())
if err != nil { if err != nil {
return err return err
@ -1155,14 +1189,19 @@ func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx, allowOrphan, rateLimit b
// The transaction is an orphan (has inputs missing). Reject // The transaction is an orphan (has inputs missing). Reject
// it if the flag to allow orphans is not set. // it if the flag to allow orphans is not set.
if !allowOrphan { if !allowOrphan {
// Only use the first missing parent transaction in
// the error message.
//
// NOTE: RejectDuplicate is really not an accurate // NOTE: RejectDuplicate is really not an accurate
// reject code here, but it matches the reference // reject code here, but it matches the reference
// implementation and there isn't a better choice due // implementation and there isn't a better choice due
// to the limited number of reject codes. Missing // to the limited number of reject codes. Missing
// inputs is assumed to mean they are already spent // inputs is assumed to mean they are already spent
// which is not really always the case. // which is not really always the case.
return txRuleError(btcwire.RejectDuplicate, str := fmt.Sprintf("orphan transaction %v references "+
"transaction spends unknown inputs") "outputs of unknown or fully-spent "+
"transaction %v", tx.Sha(), missingParents[0])
return txRuleError(btcwire.RejectDuplicate, str)
} }
// Potentially add the orphan transaction to the orphan pool. // Potentially add the orphan transaction to the orphan pool.