wtxmgr: prevent adding existing confirmed transactions as unconfirmed

We do this as a sanity check for users of the wallet to ensure the
wallet maintains a correct representation of the chain.
This commit is contained in:
Wilmer Paulino 2019-07-10 15:54:28 -07:00
parent cd66e82bc8
commit 44d818d813
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
2 changed files with 70 additions and 5 deletions

View file

@ -1460,6 +1460,68 @@ func TestRemoveUnminedTx(t *testing.T) {
checkBalance(btcutil.Amount(initialBalance), true) checkBalance(btcutil.Amount(initialBalance), true)
} }
// TestInsertMempoolTxAlreadyConfirmed ensures that transactions that already
// exist within the store as confirmed cannot be added as unconfirmed.
func TestInsertMempoolTxAlreadyConfirmed(t *testing.T) {
t.Parallel()
store, db, teardown, err := testStore()
if err != nil {
t.Fatal(err)
}
defer teardown()
// In order to reproduce real-world scenarios, we'll use a new database
// transaction for each interaction with the wallet.
//
// We'll start off the test by creating a new coinbase output at height
// 100 and inserting it into the store.
b100 := &BlockMeta{
Block: Block{Height: 100},
Time: time.Now(),
}
tx := newCoinBase(1e8)
txRec, err := NewTxRecordFromMsgTx(tx, b100.Time)
if err != nil {
t.Fatal(err)
}
commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
if err := store.InsertTx(ns, txRec, b100); err != nil {
t.Fatal(err)
}
})
// checkStore is a helper we'll use to ensure the transaction only
// exists within the store's confirmed bucket.
checkStore := func() {
t.Helper()
commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
if existsRawUnmined(ns, txRec.Hash[:]) != nil {
t.Fatalf("expected transaction to not exist " +
"in unconfirmed bucket")
}
_, v := existsTxRecord(ns, &txRec.Hash, &b100.Block)
if v == nil {
t.Fatalf("expected transaction to exist in " +
"confirmed bucket")
}
})
}
checkStore()
// Inserting the transaction again as unconfirmed should result in a
// NOP, i.e., no error should be returned and no disk modifications are
// needed.
commitDBTx(t, store, db, func(ns walletdb.ReadWriteBucket) {
if err := store.InsertTx(ns, txRec, nil); err != nil {
t.Fatal(err)
}
})
checkStore()
}
// TestOutputsAfterRemoveDoubleSpend ensures that when we remove a transaction // TestOutputsAfterRemoveDoubleSpend ensures that when we remove a transaction
// that double spends an existing output within the wallet, it doesn't remove // that double spends an existing output within the wallet, it doesn't remove
// any other spending transactions of the same output. // any other spending transactions of the same output.

View file

@ -14,11 +14,14 @@ import (
// insertMemPoolTx inserts the unmined transaction record. It also marks // insertMemPoolTx inserts the unmined transaction record. It also marks
// previous outputs referenced by the inputs as spent. // previous outputs referenced by the inputs as spent.
func (s *Store) insertMemPoolTx(ns walletdb.ReadWriteBucket, rec *TxRecord) error { func (s *Store) insertMemPoolTx(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
// Check whether the transaction has already been added to the // Check whether the transaction has already been added to the store,
// unconfirmed bucket. // regardless of whether is has confirmed or not. This ensures that we
if existsRawUnmined(ns, rec.Hash[:]) != nil { // don't add it to the unconfirmed bucket again if it has already
// confirmed.
//
// TODO: compare serialized txs to ensure this isn't a hash // TODO: compare serialized txs to ensure this isn't a hash
// collision? // collision?
if txDetails, _ := s.TxDetails(ns, &rec.Hash); txDetails != nil {
return nil return nil
} }