github.com/decred/dcrlnd@v0.7.6/routing/chainview/chainscan.go (about)

     1  package chainview
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"sync/atomic"
     8  
     9  	"github.com/decred/dcrd/chaincfg/chainhash"
    10  	"github.com/decred/dcrd/gcs/v4"
    11  	"github.com/decred/dcrd/wire"
    12  	"github.com/decred/dcrlnd/chainscan"
    13  	"github.com/decred/dcrlnd/channeldb"
    14  )
    15  
    16  type chainscanChainSource interface {
    17  	GetBlock(context.Context, *chainhash.Hash) (*wire.MsgBlock, error)
    18  	CurrentTip(context.Context) (*chainhash.Hash, int32, error)
    19  
    20  	ChainEvents(context.Context) <-chan chainscan.ChainEvent
    21  
    22  	GetCFilter(context.Context, int32) (*chainhash.Hash, [16]byte, *gcs.FilterV2, error)
    23  
    24  	GetBlockHash(context.Context, int32) (*chainhash.Hash, error)
    25  	GetBlockHeader(context.Context, *chainhash.Hash) (*wire.BlockHeader, error)
    26  
    27  	Run(context.Context) error
    28  }
    29  
    30  // chainscanFilteredChainView is an implementation of the FilteredChainView
    31  // interface which uses the chainscan package to perform its duties.
    32  type chainscanFilteredChainView struct {
    33  	started int32 // To be used atomically.
    34  	stopped int32 // To be used atomically.
    35  
    36  	// bestHeight is the height of the latest block added to the
    37  	// blockQueue from the onFilteredConnectedMethod. It is used to
    38  	// determine up to what height we would need to rescan in case
    39  	// of a filter update.
    40  	bestHeightMtx sync.Mutex
    41  	bestHeight    int64
    42  
    43  	// blockEventQueue is the ordered queue used to keep the order
    44  	// of connected and disconnected blocks sent to the reader of the
    45  	// chainView.
    46  	blockQueue *blockEventQueue
    47  
    48  	// filterUpdates is a channel in which updates to the utxo filter
    49  	// attached to this instance are sent over.
    50  	filterUpdates chan csFilterUpdate
    51  
    52  	// chainFilter is the set of utox's that we're currently watching
    53  	// spends for within the chain.
    54  	//
    55  	// It stores in the value the cancelChan that once closed removes the
    56  	// specified outpoint from the tipWatcher.
    57  	filterMtx   sync.RWMutex
    58  	chainFilter map[wire.OutPoint]chan struct{}
    59  
    60  	// filterBlockReqs is a channel in which requests to filter select
    61  	// blocks will be sent over.
    62  	filterBlockReqs chan *filterBlockReq
    63  
    64  	chainSource   chainscanChainSource
    65  	tipWatcher    *chainscan.TipWatcher
    66  	chainEvents   <-chan chainscan.ChainEvent
    67  	tipWatcherTxs map[chainhash.Hash]map[*wire.MsgTx]chainscan.Event
    68  
    69  	ctx       context.Context
    70  	cancelCtx func()
    71  
    72  	quit chan struct{}
    73  	wg   sync.WaitGroup
    74  }
    75  
    76  // A compile time check to ensure chainscanFilteredChainView implements the
    77  // chainview.FilteredChainView.
    78  var _ FilteredChainView = (*chainscanFilteredChainView)(nil)
    79  
    80  // newChainscanFilteredChainView creates a new instance of a FilteredChainView
    81  // from a compatible chain source implementation.
    82  func newChainscanFilteredChainView(cs chainscanChainSource) (*chainscanFilteredChainView, error) {
    83  	ctx, cancel := context.WithCancel(context.Background())
    84  
    85  	tipWatcher := chainscan.NewTipWatcher(cs)
    86  	chainView := &chainscanFilteredChainView{
    87  		chainFilter:     make(map[wire.OutPoint]chan struct{}),
    88  		filterUpdates:   make(chan csFilterUpdate),
    89  		filterBlockReqs: make(chan *filterBlockReq),
    90  		quit:            make(chan struct{}),
    91  		blockQueue:      newBlockEventQueue(),
    92  
    93  		chainSource:   cs,
    94  		tipWatcher:    tipWatcher,
    95  		chainEvents:   tipWatcher.ChainEvents(ctx),
    96  		tipWatcherTxs: make(map[chainhash.Hash]map[*wire.MsgTx]chainscan.Event),
    97  
    98  		ctx:       ctx,
    99  		cancelCtx: cancel,
   100  	}
   101  
   102  	return chainView, nil
   103  }
   104  
   105  func runAndLogOnError(ctx context.Context, f func(context.Context) error, name string) {
   106  	go func() {
   107  		err := f(ctx)
   108  		select {
   109  		case <-ctx.Done():
   110  			// Any errs were due to done() so, ok
   111  			return
   112  		default:
   113  		}
   114  		if err != nil {
   115  			log.Errorf("CSFilteredView error while running %s: %v", name, err)
   116  		}
   117  	}()
   118  }
   119  
   120  // Start starts all goroutines necessary for normal operation.
   121  //
   122  // NOTE: This is part of the FilteredChainView interface.
   123  func (b *chainscanFilteredChainView) Start() error {
   124  	// Already started?
   125  	if atomic.AddInt32(&b.started, 1) != 1 {
   126  		return nil
   127  	}
   128  
   129  	log.Infof("ChainscanFilteredChainView starting")
   130  
   131  	runAndLogOnError(b.ctx, b.chainSource.Run, "chainSource")
   132  	runAndLogOnError(b.ctx, b.tipWatcher.Run, "tipWatcher")
   133  
   134  	_, bestHeight, err := b.chainSource.CurrentTip(b.ctx)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	b.bestHeightMtx.Lock()
   140  	b.bestHeight = int64(bestHeight)
   141  	b.bestHeightMtx.Unlock()
   142  
   143  	b.blockQueue.Start()
   144  
   145  	b.wg.Add(2)
   146  	go b.handleChainEvents()
   147  	go b.chainFilterer()
   148  
   149  	return nil
   150  }
   151  
   152  // Stop stops all goroutines which we launched by the prior call to the Start
   153  // method.
   154  //
   155  // NOTE: This is part of the FilteredChainView interface.
   156  func (b *chainscanFilteredChainView) Stop() error {
   157  	// Already shutting down?
   158  	if atomic.AddInt32(&b.stopped, 1) != 1 {
   159  		return nil
   160  	}
   161  
   162  	// Shutdown chainscan services.
   163  	b.cancelCtx()
   164  
   165  	b.blockQueue.Stop()
   166  
   167  	log.Infof("FilteredChainView stopping")
   168  
   169  	close(b.quit)
   170  	b.wg.Wait()
   171  
   172  	return nil
   173  }
   174  
   175  func (b *chainscanFilteredChainView) foundAtTip(e chainscan.Event, _ chainscan.FindFunc) {
   176  	log.Tracef("Found at tip bh %s: %s", e.BlockHash, e)
   177  	txs, ok := b.tipWatcherTxs[e.BlockHash]
   178  	if !ok {
   179  		txs = make(map[*wire.MsgTx]chainscan.Event)
   180  		b.tipWatcherTxs[e.BlockHash] = txs
   181  	}
   182  	txs[e.Tx] = e
   183  }
   184  
   185  // filterBlock filters the given block hash against a currently processed block
   186  // by the tipWatcher.
   187  //
   188  // This removes any found spent utxos from the tipWatcher and the list of
   189  // watched utxos.
   190  func (b *chainscanFilteredChainView) filterBlock(bh *chainhash.Hash) []*wire.MsgTx {
   191  	matches := b.tipWatcherTxs[*bh]
   192  	delete(b.tipWatcherTxs, *bh)
   193  	txs := make([]*wire.MsgTx, 0, len(matches))
   194  	b.filterMtx.Lock()
   195  	for _, m := range matches {
   196  		txs = append(txs, m.Tx)
   197  		outp := m.Tx.TxIn[m.Index].PreviousOutPoint
   198  		if cancelChan, ok := b.chainFilter[outp]; ok {
   199  			close(cancelChan)
   200  			delete(b.chainFilter, outp)
   201  		}
   202  	}
   203  	b.filterMtx.Unlock()
   204  
   205  	return txs
   206  }
   207  
   208  // onBlockConnected is called for each block that's connected to the end of the
   209  // main chain. Based on our current chain filter, the block may or may not
   210  // include any relevant transactions.
   211  func (b *chainscanFilteredChainView) onBlockConnected(e chainscan.BlockConnectedEvent) {
   212  	txs := b.filterBlock(e.BlockHash())
   213  
   214  	// We record the height of the last connected block added to the
   215  	// blockQueue such that we can scan up to this height in case of a
   216  	// rescan. It must be protected by a mutex since a filter update might
   217  	// be trying to read it concurrently.
   218  	b.bestHeightMtx.Lock()
   219  	b.bestHeight = int64(e.Height)
   220  	b.bestHeightMtx.Unlock()
   221  
   222  	block := &FilteredBlock{
   223  		Hash:         e.Hash,
   224  		Height:       int64(e.Height),
   225  		Transactions: txs,
   226  	}
   227  
   228  	b.blockQueue.Add(&blockEvent{
   229  		eventType: connected,
   230  		block:     block,
   231  	})
   232  }
   233  
   234  // onBlockDisconnected is a callback which is executed once a block is
   235  // disconnected from the end of the main chain.
   236  func (b *chainscanFilteredChainView) onBlockDisconnected(e chainscan.BlockDisconnectedEvent) {
   237  	log.Debugf("Got disconnected block at height %d: %s", e.Height,
   238  		e.Hash)
   239  
   240  	filteredBlock := &FilteredBlock{
   241  		Hash:   e.Hash,
   242  		Height: int64(e.Height),
   243  	}
   244  
   245  	b.blockQueue.Add(&blockEvent{
   246  		eventType: disconnected,
   247  		block:     filteredBlock,
   248  	})
   249  }
   250  
   251  func (b *chainscanFilteredChainView) handleChainEvents() {
   252  	defer b.wg.Done()
   253  
   254  	for {
   255  		var ce chainscan.ChainEvent
   256  		select {
   257  		case <-b.ctx.Done():
   258  			return
   259  		case ce = <-b.chainEvents:
   260  		}
   261  
   262  		switch e := ce.(type) {
   263  		case chainscan.BlockConnectedEvent:
   264  			b.onBlockConnected(e)
   265  		case chainscan.BlockDisconnectedEvent:
   266  			b.onBlockDisconnected(e)
   267  		default:
   268  			log.Warnf("Unknown block event: %t", ce)
   269  		}
   270  	}
   271  }
   272  
   273  // FilterBlock takes a block hash, and returns a FilteredBlocks which is the
   274  // result of applying the current registered UTXO sub-set on the block
   275  // corresponding to that block hash. If any watched UTOX's are spent by the
   276  // selected lock, then the internal chainFilter will also be updated.
   277  //
   278  // NOTE: This is part of the FilteredChainView interface.
   279  func (b *chainscanFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*FilteredBlock, error) {
   280  	req := &filterBlockReq{
   281  		blockHash: blockHash,
   282  		resp:      make(chan *FilteredBlock, 1),
   283  		err:       make(chan error, 1),
   284  	}
   285  
   286  	select {
   287  	case b.filterBlockReqs <- req:
   288  	case <-b.quit:
   289  		return nil, fmt.Errorf("FilteredChainView shutting down")
   290  	}
   291  
   292  	return <-req.resp, <-req.err
   293  }
   294  
   295  func (b *chainscanFilteredChainView) rescanBlock(bh *chainhash.Hash) ([]*wire.MsgTx, int32, error) {
   296  	header, err := b.chainSource.GetBlockHeader(b.ctx, bh)
   297  	if err != nil {
   298  		return nil, 0, err
   299  	}
   300  
   301  	bh, cfkey, filter, err := b.chainSource.GetCFilter(b.ctx, int32(header.Height))
   302  	if err != nil {
   303  		return nil, 0, err
   304  	}
   305  
   306  	e := chainscan.BlockConnectedEvent{
   307  		Height:   int32(header.Height),
   308  		Hash:     *bh,
   309  		CFKey:    cfkey,
   310  		Filter:   filter,
   311  		PrevHash: header.PrevBlock,
   312  	}
   313  
   314  	if err := b.tipWatcher.ForceRescan(b.ctx, &e); err != nil {
   315  		return nil, 0, err
   316  	}
   317  
   318  	return b.filterBlock(bh), int32(header.Height), nil
   319  }
   320  
   321  func (b *chainscanFilteredChainView) forceRescan(updateHeight, bestHeight int64) error {
   322  	// Track the previous block hash to fill in the data.
   323  	prevHash, err := b.chainSource.GetBlockHash(b.ctx, int32(updateHeight))
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	for height := updateHeight + 1; height < bestHeight+1; height++ {
   329  		bh, cfkey, filter, err := b.chainSource.GetCFilter(b.ctx, int32(height))
   330  		if err != nil {
   331  			return err
   332  		}
   333  
   334  		e := chainscan.BlockConnectedEvent{
   335  			Height:   int32(height),
   336  			Hash:     *bh,
   337  			CFKey:    cfkey,
   338  			Filter:   filter,
   339  			PrevHash: *prevHash,
   340  		}
   341  		prevHash = bh
   342  
   343  		if err := b.tipWatcher.ForceRescan(b.ctx, &e); err != nil {
   344  			log.Warnf("Unable to rescan block "+
   345  				"with hash %s at height %d: %v",
   346  				bh, height, err)
   347  			continue
   348  		}
   349  
   350  		b.onBlockConnected(e)
   351  	}
   352  
   353  	return nil
   354  }
   355  
   356  // chainFilterer is the primary goroutine which: listens for new blocks coming
   357  // and dispatches the relevant FilteredBlock notifications, updates the filter
   358  // due to requests by callers, and finally is able to preform targeted block
   359  // filtration.
   360  //
   361  // TODO(roasbeef): change to use loadfilter RPC's
   362  func (b *chainscanFilteredChainView) chainFilterer() {
   363  	defer b.wg.Done()
   364  
   365  	for {
   366  		select {
   367  		// The caller has just sent an update to the current chain
   368  		// filter, so we'll apply the update, possibly rewinding our
   369  		// state partially.
   370  		case update := <-b.filterUpdates:
   371  
   372  			// First, we'll add all the new UTXO's to the set of
   373  			// watched UTXO's, eliminating any duplicates in the
   374  			// process.
   375  			log.Tracef("Updating chain filter with new UTXO's: %v",
   376  				newLogClosure(func() string {
   377  					var s string
   378  					for _, u := range update.newUtxos {
   379  						s = s + u.OutPoint.String() + " "
   380  					}
   381  					return s
   382  				}),
   383  			)
   384  
   385  			// All blocks gotten after we loaded the filter will
   386  			// have the filter applied, but we will need to rescan
   387  			// the blocks up to the height of the block we last
   388  			// added to the blockQueue.
   389  			b.bestHeightMtx.Lock()
   390  			bestHeight := b.bestHeight
   391  			b.bestHeightMtx.Unlock()
   392  
   393  			cancelChans := make(map[wire.OutPoint]chan struct{}, len(update.newUtxos))
   394  			b.filterMtx.Lock()
   395  			for _, newOp := range update.newUtxos {
   396  				// Ignore if we already watch this.
   397  				if _, ok := b.chainFilter[newOp.OutPoint]; ok {
   398  					continue
   399  				}
   400  
   401  				cancelChan := make(chan struct{})
   402  				b.chainFilter[newOp.OutPoint] = cancelChan
   403  				cancelChans[newOp.OutPoint] = cancelChan
   404  			}
   405  			b.filterMtx.Unlock()
   406  
   407  			// Add the new utxos as targets for out instance of the
   408  			// tip watcher.
   409  			for _, newOp := range update.newUtxos {
   410  				cancelChan := cancelChans[newOp.OutPoint]
   411  				scriptVersion := uint16(0)
   412  				swhChan := make(chan int32)
   413  				target := chainscan.SpentOutPoint(
   414  					newOp.OutPoint,
   415  					scriptVersion,
   416  					newOp.FundingPkScript,
   417  				)
   418  				b.tipWatcher.Find(
   419  					target,
   420  					chainscan.WithFoundCallback(b.foundAtTip),
   421  					chainscan.WithCancelChan(cancelChan),
   422  					chainscan.WithStartWatchHeightChan(swhChan),
   423  				)
   424  
   425  				// Wait until we know the tipWatcher has
   426  				// started watching.
   427  				select {
   428  				case <-swhChan:
   429  				case <-b.quit:
   430  					return
   431  				}
   432  			}
   433  
   434  			// If the update height matches our best known height,
   435  			// then we don't need to do any rewinding.
   436  			if update.updateHeight == bestHeight {
   437  				continue
   438  			}
   439  
   440  			// Otherwise, we'll rewind the state to ensure the
   441  			// caller doesn't miss any relevant notifications.
   442  			err := b.forceRescan(update.updateHeight, bestHeight)
   443  			if err != nil {
   444  				log.Errorf("Error forcing utxo rescan on range (%d,%d]: %v",
   445  					update.updateHeight, bestHeight, err)
   446  			}
   447  
   448  		// We've received a new request to manually filter a block.
   449  		case req := <-b.filterBlockReqs:
   450  			txs, height, err := b.rescanBlock(req.blockHash)
   451  			if err != nil {
   452  				req.err <- err
   453  				req.resp <- nil
   454  				continue
   455  			}
   456  
   457  			// Once we have this info, we can directly filter the
   458  			// block and dispatch the proper notification.
   459  			req.resp <- &FilteredBlock{
   460  				Hash:         *req.blockHash,
   461  				Height:       int64(height),
   462  				Transactions: txs,
   463  			}
   464  			req.err <- err
   465  
   466  		case <-b.quit:
   467  			return
   468  		}
   469  	}
   470  }
   471  
   472  // csFilterUpdate is a message sent to the chainFilterer to update the current
   473  // chainFilter state.
   474  type csFilterUpdate struct {
   475  	newUtxos     []channeldb.EdgePoint
   476  	updateHeight int64
   477  }
   478  
   479  // UpdateFilter updates the UTXO filter which is to be consulted when creating
   480  // FilteredBlocks to be sent to subscribed clients. This method is cumulative
   481  // meaning repeated calls to this method should _expand_ the size of the UTXO
   482  // sub-set currently being watched.  If the set updateHeight is _lower_ than
   483  // the best known height of the implementation, then the state should be
   484  // rewound to ensure all relevant notifications are dispatched.
   485  //
   486  // NOTE: This is part of the FilteredChainView interface.
   487  func (b *chainscanFilteredChainView) UpdateFilter(ops []channeldb.EdgePoint,
   488  	updateHeight int64) error {
   489  
   490  	// Make a copy to avoid having this changed under our feet.
   491  	newUtxos := make([]channeldb.EdgePoint, len(ops))
   492  	copy(newUtxos, ops)
   493  
   494  	select {
   495  
   496  	case b.filterUpdates <- csFilterUpdate{
   497  		newUtxos:     newUtxos,
   498  		updateHeight: updateHeight,
   499  	}:
   500  		return nil
   501  
   502  	case <-b.quit:
   503  		return fmt.Errorf("chain filter shutting down")
   504  	}
   505  }
   506  
   507  // FilteredBlocks returns the channel that filtered blocks are to be sent over.
   508  // Each time a block is connected to the end of a main chain, and appropriate
   509  // FilteredBlock which contains the transactions which mutate our watched UTXO
   510  // set is to be returned.
   511  //
   512  // NOTE: This is part of the FilteredChainView interface.
   513  func (b *chainscanFilteredChainView) FilteredBlocks() <-chan *FilteredBlock {
   514  	return b.blockQueue.newBlocks
   515  }
   516  
   517  // DisconnectedBlocks returns a receive only channel which will be sent upon
   518  // with the empty filtered blocks of blocks which are disconnected from the
   519  // main chain in the case of a re-org.
   520  //
   521  // NOTE: This is part of the FilteredChainView interface.
   522  func (b *chainscanFilteredChainView) DisconnectedBlocks() <-chan *FilteredBlock {
   523  	return b.blockQueue.staleBlocks
   524  }