decred.org/dcrwallet/v3@v3.1.0/wallet/rescan.go (about)

     1  // Copyright (c) 2013-2014 The btcsuite developers
     2  // Copyright (c) 2015-2020 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package wallet
     7  
     8  import (
     9  	"context"
    10  	"time"
    11  
    12  	"decred.org/dcrwallet/v3/errors"
    13  	"decred.org/dcrwallet/v3/wallet/udb"
    14  	"decred.org/dcrwallet/v3/wallet/walletdb"
    15  	"github.com/decred/dcrd/chaincfg/chainhash"
    16  	"github.com/decred/dcrd/crypto/ripemd160"
    17  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    18  	"github.com/decred/dcrd/wire"
    19  )
    20  
    21  const maxBlocksPerRescan = 2000
    22  
    23  // RescanFilter implements a precise filter intended to hold all watched wallet
    24  // data in memory such as addresses and unspent outputs.  The zero value is not
    25  // valid, and filters must be created using NewRescanFilter.  RescanFilter is
    26  // not safe for concurrent access.
    27  type RescanFilter struct {
    28  	// Implemented fast paths for address lookup.
    29  	pubKeyHashes      map[[ripemd160.Size]byte]struct{}
    30  	scriptHashes      map[[ripemd160.Size]byte]struct{}
    31  	compressedPubKeys map[[33]byte]struct{}
    32  
    33  	// A fallback address lookup map in case a fast path doesn't exist.
    34  	// Only exists for completeness.  If using this shows up in a profile,
    35  	// there's a good chance a fast path should be added.
    36  	otherAddresses map[string]struct{}
    37  
    38  	// Outpoints of unspent outputs.
    39  	unspent map[wire.OutPoint]struct{}
    40  }
    41  
    42  // NewRescanFilter creates and initializes a RescanFilter containing each passed
    43  // address and outpoint.
    44  func NewRescanFilter(addresses []stdaddr.Address, unspentOutPoints []*wire.OutPoint) *RescanFilter {
    45  	filter := &RescanFilter{
    46  		pubKeyHashes:      map[[ripemd160.Size]byte]struct{}{},
    47  		scriptHashes:      map[[ripemd160.Size]byte]struct{}{},
    48  		compressedPubKeys: map[[33]byte]struct{}{},
    49  		otherAddresses:    map[string]struct{}{},
    50  		unspent:           make(map[wire.OutPoint]struct{}, len(unspentOutPoints)),
    51  	}
    52  
    53  	for _, s := range addresses {
    54  		filter.AddAddress(s)
    55  	}
    56  	for _, op := range unspentOutPoints {
    57  		filter.AddUnspentOutPoint(op)
    58  	}
    59  
    60  	return filter
    61  }
    62  
    63  // AddAddress adds an address to the filter if it does not already exist.
    64  func (f *RescanFilter) AddAddress(a stdaddr.Address) {
    65  	switch a := a.(type) {
    66  	case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0:
    67  		f.pubKeyHashes[*a.Hash160()] = struct{}{}
    68  	case *stdaddr.AddressScriptHashV0:
    69  		f.scriptHashes[*a.Hash160()] = struct{}{}
    70  	case *stdaddr.AddressPubKeyEcdsaSecp256k1V0:
    71  		serializedPubKey := a.SerializedPubKey()
    72  		switch len(serializedPubKey) {
    73  		case 33: // compressed
    74  			var compressedPubKey [33]byte
    75  			copy(compressedPubKey[:], serializedPubKey)
    76  			f.compressedPubKeys[compressedPubKey] = struct{}{}
    77  		default:
    78  			f.otherAddresses[a.String()] = struct{}{}
    79  		}
    80  	default:
    81  		f.otherAddresses[a.String()] = struct{}{}
    82  	}
    83  }
    84  
    85  // ExistsAddress returns whether an address is contained in the filter.
    86  func (f *RescanFilter) ExistsAddress(a stdaddr.Address) (ok bool) {
    87  	switch a := a.(type) {
    88  	case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0:
    89  		_, ok = f.pubKeyHashes[*a.Hash160()]
    90  	case *stdaddr.AddressScriptHashV0:
    91  		_, ok = f.scriptHashes[*a.Hash160()]
    92  	case *stdaddr.AddressPubKeyEcdsaSecp256k1V0:
    93  		serializedPubKey := a.SerializedPubKey()
    94  		switch len(serializedPubKey) {
    95  		case 33: // compressed
    96  			var compressedPubKey [33]byte
    97  			copy(compressedPubKey[:], serializedPubKey)
    98  			_, ok = f.compressedPubKeys[compressedPubKey]
    99  			if !ok {
   100  				a := a.AddressPubKeyHash().(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0)
   101  				_, ok = f.pubKeyHashes[*a.Hash160()]
   102  			}
   103  		default:
   104  			_, ok = f.otherAddresses[a.String()]
   105  		}
   106  	default:
   107  		_, ok = f.otherAddresses[a.String()]
   108  	}
   109  	return
   110  }
   111  
   112  // RemoveAddress removes an address from the filter if it exists.
   113  func (f *RescanFilter) RemoveAddress(a stdaddr.Address) {
   114  	switch a := a.(type) {
   115  	case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0:
   116  		delete(f.pubKeyHashes, *a.Hash160())
   117  	case *stdaddr.AddressScriptHashV0:
   118  		delete(f.scriptHashes, *a.Hash160())
   119  	case *stdaddr.AddressPubKeyEcdsaSecp256k1V0:
   120  		serializedPubKey := a.SerializedPubKey()
   121  		switch len(serializedPubKey) {
   122  		case 33: // compressed
   123  			var compressedPubKey [33]byte
   124  			copy(compressedPubKey[:], serializedPubKey)
   125  			delete(f.compressedPubKeys, compressedPubKey)
   126  		default:
   127  			delete(f.otherAddresses, a.String())
   128  		}
   129  	default:
   130  		delete(f.otherAddresses, a.String())
   131  	}
   132  }
   133  
   134  // AddUnspentOutPoint adds an outpoint to the filter if it does not already
   135  // exist.
   136  func (f *RescanFilter) AddUnspentOutPoint(op *wire.OutPoint) {
   137  	f.unspent[*op] = struct{}{}
   138  }
   139  
   140  // ExistsUnspentOutPoint returns whether an outpoint is contained in the filter.
   141  func (f *RescanFilter) ExistsUnspentOutPoint(op *wire.OutPoint) bool {
   142  	_, ok := f.unspent[*op]
   143  	return ok
   144  }
   145  
   146  // RemoveUnspentOutPoint removes an outpoint from the filter if it exists.
   147  func (f *RescanFilter) RemoveUnspentOutPoint(op *wire.OutPoint) {
   148  	delete(f.unspent, *op)
   149  }
   150  
   151  // saveRescanned records transactions from a rescanned block.  This
   152  // does not update the network backend with data to watch for future
   153  // relevant transactions as the rescanner is assumed to handle this
   154  // task.
   155  func (w *Wallet) saveRescanned(ctx context.Context, hash *chainhash.Hash, txs []*wire.MsgTx) error {
   156  	const op errors.Op = "wallet.saveRescanned"
   157  
   158  	defer w.lockedOutpointMu.Unlock()
   159  	w.lockedOutpointMu.Lock()
   160  
   161  	err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   162  		txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
   163  		blockMeta, err := w.txStore.GetBlockMetaForHash(txmgrNs, hash)
   164  		if err != nil {
   165  			return err
   166  		}
   167  		header, err := w.txStore.GetBlockHeader(dbtx, hash)
   168  		if err != nil {
   169  			return err
   170  		}
   171  
   172  		for _, tx := range txs {
   173  			rec, err := udb.NewTxRecordFromMsgTx(tx, time.Now())
   174  			if err != nil {
   175  				return err
   176  			}
   177  			_, err = w.processTransactionRecord(ctx, dbtx, rec, header, &blockMeta)
   178  			if err != nil {
   179  				return err
   180  			}
   181  		}
   182  		return w.txStore.UpdateProcessedTxsBlockMarker(dbtx, hash)
   183  	})
   184  	if err != nil {
   185  		return errors.E(op, err)
   186  	}
   187  	return nil
   188  }
   189  
   190  // rescan synchronously scans over all blocks on the main chain starting at
   191  // startHash and height up through the recorded main chain tip block.  The
   192  // progress channel, if non-nil, is sent non-error progress notifications with
   193  // the heights the rescan has completed through, starting with the start height.
   194  func (w *Wallet) rescan(ctx context.Context, n NetworkBackend,
   195  	startHash *chainhash.Hash, height int32, p chan<- RescanProgress) error {
   196  
   197  	blockHashStorage := make([]chainhash.Hash, maxBlocksPerRescan)
   198  	rescanFrom := *startHash
   199  	inclusive := true
   200  	for {
   201  		select {
   202  		case <-ctx.Done():
   203  			return ctx.Err()
   204  		default:
   205  		}
   206  
   207  		var rescanBlocks []chainhash.Hash
   208  		err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
   209  			txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
   210  			var err error
   211  			rescanBlocks, err = w.txStore.GetMainChainBlockHashes(txmgrNs,
   212  				&rescanFrom, inclusive, blockHashStorage)
   213  			return err
   214  		})
   215  		if err != nil {
   216  			return err
   217  		}
   218  		if len(rescanBlocks) == 0 {
   219  			break
   220  		}
   221  
   222  		through := height + int32(len(rescanBlocks)) - 1
   223  		// Genesis block is not rescanned
   224  		if height == 0 {
   225  			rescanBlocks = rescanBlocks[1:]
   226  			height = 1
   227  			if len(rescanBlocks) == 0 {
   228  				break
   229  			}
   230  		}
   231  		log.Infof("Rescanning block range [%v, %v]...", height, through)
   232  		saveRescanned := func(block *chainhash.Hash, txs []*wire.MsgTx) error {
   233  			return w.saveRescanned(ctx, block, txs)
   234  		}
   235  		err = n.Rescan(ctx, rescanBlocks, saveRescanned)
   236  		if err != nil {
   237  			return err
   238  		}
   239  		err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
   240  			return w.txStore.UpdateProcessedTxsBlockMarker(dbtx, &rescanBlocks[len(rescanBlocks)-1])
   241  		})
   242  		if err != nil {
   243  			return err
   244  		}
   245  		if p != nil {
   246  			p <- RescanProgress{ScannedThrough: through}
   247  		}
   248  		rescanFrom = rescanBlocks[len(rescanBlocks)-1]
   249  		height += int32(len(rescanBlocks))
   250  		inclusive = false
   251  	}
   252  
   253  	log.Infof("Rescan complete")
   254  	return nil
   255  }
   256  
   257  // Rescan starts a rescan of the wallet for all blocks on the main chain
   258  // beginning at startHash.  This function blocks until the rescan completes.
   259  func (w *Wallet) Rescan(ctx context.Context, n NetworkBackend, startHash *chainhash.Hash) error {
   260  	const op errors.Op = "wallet.Rescan"
   261  
   262  	var startHeight int32
   263  	err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error {
   264  		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
   265  		header, err := w.txStore.GetSerializedBlockHeader(txmgrNs, startHash)
   266  		if err != nil {
   267  			return err
   268  		}
   269  		startHeight = udb.ExtractBlockHeaderHeight(header)
   270  		return nil
   271  	})
   272  	if err != nil {
   273  		return errors.E(op, err)
   274  	}
   275  
   276  	err = w.rescan(ctx, n, startHash, startHeight, nil)
   277  	if err != nil {
   278  		return errors.E(op, err)
   279  	}
   280  	return nil
   281  }
   282  
   283  // RescanFromHeight is an alternative to Rescan that takes a block height
   284  // instead of a hash.  See Rescan for more details.
   285  func (w *Wallet) RescanFromHeight(ctx context.Context, n NetworkBackend, startHeight int32) error {
   286  	const op errors.Op = "wallet.RescanFromHeight"
   287  
   288  	var startHash chainhash.Hash
   289  	err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error {
   290  		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
   291  		var err error
   292  		startHash, err = w.txStore.GetMainChainBlockHashForHeight(
   293  			txmgrNs, startHeight)
   294  		return err
   295  	})
   296  	if err != nil {
   297  		return errors.E(op, err)
   298  	}
   299  
   300  	err = w.rescan(ctx, n, &startHash, startHeight, nil)
   301  	if err != nil {
   302  		return errors.E(op, err)
   303  	}
   304  	return nil
   305  }
   306  
   307  // RescanProgress records the height the rescan has completed through and any
   308  // errors during processing of the rescan.
   309  type RescanProgress struct {
   310  	Err            error
   311  	ScannedThrough int32
   312  }
   313  
   314  // RescanProgressFromHeight rescans for relevant transactions in all blocks in
   315  // the main chain starting at startHeight.  Progress notifications and any
   316  // errors are sent to the channel p.  This function blocks until the rescan
   317  // completes or ends in an error.  p is closed before returning.
   318  func (w *Wallet) RescanProgressFromHeight(ctx context.Context, n NetworkBackend,
   319  	startHeight int32, p chan<- RescanProgress) {
   320  
   321  	const op errors.Op = "wallet.RescanProgressFromHeight"
   322  
   323  	defer close(p)
   324  
   325  	var startHash chainhash.Hash
   326  	err := walletdb.View(ctx, w.db, func(tx walletdb.ReadTx) error {
   327  		txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
   328  		var err error
   329  		startHash, err = w.txStore.GetMainChainBlockHashForHeight(
   330  			txmgrNs, startHeight)
   331  		return err
   332  	})
   333  	if err != nil {
   334  		p <- RescanProgress{Err: errors.E(op, err)}
   335  		return
   336  	}
   337  
   338  	err = w.rescan(ctx, n, &startHash, startHeight, p)
   339  	if err != nil {
   340  		p <- RescanProgress{Err: errors.E(op, err)}
   341  	}
   342  }
   343  
   344  func (w *Wallet) mainChainAncestor(dbtx walletdb.ReadTx, hash *chainhash.Hash) (*chainhash.Hash, error) {
   345  	for {
   346  		mainChain, _ := w.txStore.BlockInMainChain(dbtx, hash)
   347  		if mainChain {
   348  			break
   349  		}
   350  		h, err := w.txStore.GetBlockHeader(dbtx, hash)
   351  		if err != nil {
   352  			return nil, err
   353  		}
   354  		hash = &h.PrevBlock
   355  	}
   356  	return hash, nil
   357  }
   358  
   359  // RescanPoint returns the block hash at which a rescan should begin
   360  // (inclusive), or nil when no rescan is necessary.
   361  func (w *Wallet) RescanPoint(ctx context.Context) (*chainhash.Hash, error) {
   362  	const op errors.Op = "wallet.RescanPoint"
   363  	var rp *chainhash.Hash
   364  	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
   365  		var err error
   366  		rp, err = w.rescanPoint(dbtx)
   367  		return err
   368  	})
   369  	if err != nil {
   370  		return nil, errors.E(op, err)
   371  	}
   372  	return rp, nil
   373  }
   374  
   375  func (w *Wallet) rescanPoint(dbtx walletdb.ReadTx) (*chainhash.Hash, error) {
   376  	ns := dbtx.ReadBucket(wtxmgrNamespaceKey)
   377  	r := w.txStore.ProcessedTxsBlockMarker(dbtx)
   378  	r, err := w.mainChainAncestor(dbtx, r) // Walk back to the main chain ancestor
   379  	if err != nil {
   380  		return nil, err
   381  	}
   382  	if tipHash, _ := w.txStore.MainChainTip(dbtx); *r == tipHash {
   383  		return nil, nil
   384  	}
   385  	// r is not the tip, so a child block must exist in the main chain.
   386  	h, err := w.txStore.GetBlockHeader(dbtx, r)
   387  	if err != nil {
   388  		log.Info(err)
   389  		return nil, err
   390  	}
   391  	rescanPoint, err := w.txStore.GetMainChainBlockHashForHeight(ns, int32(h.Height)+1)
   392  	if err != nil {
   393  		log.Info(err)
   394  		return nil, err
   395  	}
   396  	return &rescanPoint, nil
   397  }