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:
parent
8bcd56fc27
commit
50acc9cdf5
2 changed files with 82 additions and 0 deletions
13
wtxmgr/tx.go
13
wtxmgr/tx.go
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue