diff --git a/wallet/wallet.go b/wallet/wallet.go index bd16d71..1a11602 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -3444,24 +3444,39 @@ func (w *Wallet) publishTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { case err == nil: return txid, nil - // The following are errors returned from btcd's mempool. - case strings.Contains(err.Error(), "spent"): - fallthrough - case strings.Contains(err.Error(), "orphan"): - fallthrough - case strings.Contains(err.Error(), "conflict"): + // Since we have different backends that can be used with the wallet, + // we'll need to check specific errors for each one. + // + // If the transaction is already in the mempool, we can just return now. + // + // This error is returned when broadcasting/sending a transaction to a + // btcd node that already has it in their mempool. + case strings.Contains(err.Error(), "already have transaction"): fallthrough - // The following errors are returned from bitcoind's mempool. - case strings.Contains(err.Error(), "fee not met"): + // This error is returned when broadcasting a transaction to a bitcoind + // node that already has it in their mempool. + case strings.Contains(err.Error(), "txn-already-in-mempool"): + return txid, nil + + // If the transaction has already confirmed, we can safely remove it + // from the unconfirmed store as it should already exist within the + // confirmed store. We'll avoid returning an error as the broadcast was + // in a sense successful. + // + // This error is returned when broadcasting/sending a transaction that + // has already confirmed to a btcd node. + case strings.Contains(err.Error(), "transaction already exists"): fallthrough - case strings.Contains(err.Error(), "Missing inputs"): + + // This error is returned when broadcasting a transaction that has + // already confirmed to a bitcoind node. + case strings.Contains(err.Error(), "txn-already-known"): fallthrough - case strings.Contains(err.Error(), "already in block chain"): - // If the transaction was rejected, then we'll remove it from - // the txstore, as otherwise, we'll attempt to continually - // re-broadcast it, and the utxo state of the wallet won't be - // accurate. + + // This error is returned when sending a transaction that has already + // confirmed to a bitcoind node over RPC. + case strings.Contains(err.Error(), "transaction already in block chain"): dbErr := walletdb.Update(w.db, func(dbTx walletdb.ReadWriteTx) error { txmgrNs := dbTx.ReadWriteBucket(wtxmgrNamespaceKey) txRec, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now()) @@ -3471,13 +3486,33 @@ func (w *Wallet) publishTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { return w.TxStore.RemoveUnminedTx(txmgrNs, txRec) }) if dbErr != nil { - return nil, fmt.Errorf("unable to broadcast tx: %v, "+ - "unable to remove invalid tx: %v", err, dbErr) + log.Warnf("Unable to remove confirmed transaction %v "+ + "from unconfirmed store: %v", tx.TxHash(), dbErr) } - return nil, err + return txid, nil + // If the transaction was rejected for whatever other reason, then we'll + // remove it from the transaction store, as otherwise, we'll attempt to + // continually re-broadcast it, and the UTXO state of the wallet won't + // be accurate. default: + dbErr := walletdb.Update(w.db, func(dbTx walletdb.ReadWriteTx) error { + txmgrNs := dbTx.ReadWriteBucket(wtxmgrNamespaceKey) + txRec, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now()) + if err != nil { + return err + } + return w.TxStore.RemoveUnminedTx(txmgrNs, txRec) + }) + if dbErr != nil { + log.Warnf("Unable to remove invalid transaction %v: %v", + tx.TxHash(), dbErr) + } else { + log.Infof("Removed invalid transaction: %v", + spew.Sdump(tx)) + } + return nil, err } }