wtxmgr: add new method to allow callers to remove conflicted unmined txns

In this commit, we add a new method to the Store object that allows
callers to *manually* remove any conflicting transactions. At times,
it’s the case that while we were offline another transaction was
broadcast that double spends our own, or with the existence of RBF,
another replacement transaction was generated. In this case, when we
come back online, the tx will be rejected. Currently, we have no way of
removing such transaction sot avoid the retransmit-then-reject-dance.
This commit fixes that by adding RemoveUnminedTx.
This commit is contained in:
Olaoluwa Osuntokun 2018-02-21 16:08:54 -08:00
parent 8bcd56fc27
commit 50acc9cdf5
2 changed files with 82 additions and 0 deletions

View file

@ -317,6 +317,19 @@ func (s *Store) InsertTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *Bloc
return s.insertMinedTx(ns, rec, block)
}
// RemoveUnminedTx attempts to remove an unmined transaction from the
// transaction store. This is to be used in the scenario that a transaction
// that we attempt to rebroadcast, turns out to double spend one of our
// existing inputs. This function we remove the conflicting transaction
// identified by the tx record, and also recursively remove all transactions
// that depend on it.
func (s *Store) RemoveUnminedTx(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
// As we already have a tx record, we can directly call the
// removeConflict method. This will do the job of recursively removing
// this unmined transaction, and any transactions that depend on it.
return s.removeConflict(ns, rec)
}
// insertMinedTx inserts a new transaction record for a mined transaction into
// the database. It is expected that the exact transation does not already
// exist in the unmined buckets, but unmined double spends (including mutations)

View file

@ -19,6 +19,7 @@ import (
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcwallet/walletdb"
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
"github.com/roasbeef/btcwallet/wtxmgr"
. "github.com/roasbeef/btcwallet/wtxmgr"
)
@ -1295,3 +1296,71 @@ func TestInsertUnserializedTx(t *testing.T) {
t.Fatal("Serialized txs for coinbase spender do not match")
}
}
// TestRemoveUnminedTx tests that if we add an umined transaction, then we're
// able to remove that unmined transaction later along with any of its
// descendants. Any balance modifications due to the unmined transaction should
// be revered.
func TestRemoveUnminedTx(t *testing.T) {
t.Parallel()
store, db, teardown, err := testStore()
defer teardown()
if err != nil {
t.Fatal(err)
}
dbtx, err := db.BeginReadWriteTx()
if err != nil {
t.Fatal(err)
}
defer dbtx.Commit()
ns := dbtx.ReadWriteBucket(namespaceKey)
// We'll start off by adding an unconfirmed transaction to the
// transaction store.
tx := TstRecvTx
txRec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx(), time.Now())
if err != nil {
t.Fatalf("unable to create unmined txns: %v", err)
}
if err := store.InsertTx(ns, txRec, nil); err != nil {
t.Fatalf("unable to insert transaction: %v", err)
}
// With the transaction inserted, ensure that it's reflected in the set
// of unmined transactions.
unminedTxns, err := store.UnminedTxs(ns)
if err != nil {
t.Fatalf("unable to query for unmined txns: %v", err)
}
if len(unminedTxns) != 1 {
t.Fatalf("expected 1 mined tx, instead got %v",
len(unminedTxns))
}
unminedTxHash := unminedTxns[0].TxHash()
txHash := tx.MsgTx().TxHash()
if !unminedTxHash.IsEqual(&txHash) {
t.Fatalf("mismatch tx hashes: expected %v, got %v",
tx.MsgTx().TxHash(), unminedTxHash)
}
// Next, we'll delete the unmined transaction in order to simulate an
// encountered conflict.
if err := store.RemoveUnminedTx(ns, txRec); err != nil {
t.Fatalf("unable to remove unmined txns: %v", err)
}
// If we query again for the set of unconfirmed transactions, then we
// should get an empty slice.
// With the transaction inserted, ensure that it's reflected in the set
// of unmined transactions.
unminedTxns, err = store.UnminedTxs(ns)
if err != nil {
t.Fatalf("unable to query for unmined txns: %v", err)
}
if len(unminedTxns) != 0 {
t.Fatalf("expected zero unmined txns, instead have %v",
len(unminedTxns))
}
}