diff --git a/wallet/history.go b/wallet/history.go new file mode 100644 index 0000000..a33e0e1 --- /dev/null +++ b/wallet/history.go @@ -0,0 +1,161 @@ +// Copyright (c) 2015-2020 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package wallet + +import ( + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" +) + +var ( + // bucketTxLabels is the name of the label sub bucket of the wtxmgr + // top level bucket that stores the mapping between a txid and a + // user-defined transaction label. + bucketTxLabels = []byte("l") +) + +// DropTransactionHistory completely removes and re-creates the transaction +// manager namespace from the given wallet database. This can be used to force +// a full chain rescan of all wallet transaction and UTXO data. User-defined +// transaction labels can optionally be kept by setting keepLabels to true. +func DropTransactionHistory(db walletdb.DB, keepLabels bool) error { + log.Infof("Dropping btcwallet transaction history") + + err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + // If we want to keep our tx labels, we read them out so we + // can re-add them after we have deleted our wtxmgr. + var ( + labels map[chainhash.Hash]string + err error + ) + if keepLabels { + labels, err = fetchAllLabels(tx) + if err != nil { + return err + } + } + + err = tx.DeleteTopLevelBucket(wtxmgrNamespaceKey) + if err != nil && err != walletdb.ErrBucketNotFound { + return err + } + ns, err := tx.CreateTopLevelBucket(wtxmgrNamespaceKey) + if err != nil { + return err + } + err = wtxmgr.Create(ns) + if err != nil { + return err + } + + // If we want to re-add our labels, we do so now. + if keepLabels { + if err := putTxLabels(ns, labels); err != nil { + return err + } + } + + ns = tx.ReadWriteBucket(waddrmgrNamespaceKey) + birthdayBlock, err := waddrmgr.FetchBirthdayBlock(ns) + if err != nil { + log.Warnf("Wallet does not have a birthday block " + + "set, falling back to rescan from genesis") + + startBlock, err := waddrmgr.FetchStartBlock(ns) + if err != nil { + return err + } + return waddrmgr.PutSyncedTo(ns, startBlock) + } + + // We'll need to remove our birthday block first because it + // serves as a barrier when updating our state to detect reorgs + // due to the wallet not storing all block hashes of the chain. + if err := waddrmgr.DeleteBirthdayBlock(ns); err != nil { + return err + } + + if err := waddrmgr.PutSyncedTo(ns, &birthdayBlock); err != nil { + return err + } + return waddrmgr.PutBirthdayBlock(ns, birthdayBlock) + }) + if err != nil { + return fmt.Errorf("failed to drop and re-create namespace: %v", + err) + } + + return nil +} + +// fetchAllLabels returns a map of hex-encoded txid to label. +func fetchAllLabels(tx walletdb.ReadWriteTx) (map[chainhash.Hash]string, + error) { + + // Get our top level bucket, if it does not exist we just exit. + txBucket := tx.ReadBucket(wtxmgrNamespaceKey) + if txBucket == nil { + return nil, nil + } + + // If we do not have a labels bucket, there are no labels so we exit. + labelsBucket := txBucket.NestedReadBucket(bucketTxLabels) + if labelsBucket == nil { + return nil, nil + } + + labels := make(map[chainhash.Hash]string) + if err := labelsBucket.ForEach(func(k, v []byte) error { + txid, err := chainhash.NewHash(k) + if err != nil { + return err + } + + label, err := wtxmgr.DeserializeLabel(v) + if err != nil { + return err + } + + // Add an entry to our map of labels. + labels[*txid] = label + + return nil + }); err != nil { + return nil, err + } + + return labels, nil +} + +// putTxLabels re-adds a nested labels bucket and entries to the bucket provided +// if there are any labels present. +func putTxLabels(ns walletdb.ReadWriteBucket, + labels map[chainhash.Hash]string) error { + + // If there are no labels, exit early. + if len(labels) == 0 { + return nil + } + + // First, we create a labels bucket which we will add all labels to. + labelBucket, err := ns.CreateBucketIfNotExists(bucketTxLabels) + if err != nil { + return err + } + + // Next, we re-add every label to the bucket. + for txid, label := range labels { + err := wtxmgr.PutTxLabel(labelBucket, txid, label) + if err != nil { + return err + } + } + + return nil +}