github.com/decred/dcrlnd@v0.7.6/lnwallet/dcrwallet/dcrwchainio.go (about)

     1  package dcrwallet
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"github.com/decred/dcrd/chaincfg/chainhash"
     8  	"github.com/decred/dcrd/wire"
     9  
    10  	"github.com/decred/dcrlnd/chainscan"
    11  	"github.com/decred/dcrlnd/chainscan/csdrivers"
    12  	"github.com/decred/dcrlnd/lnwallet"
    13  
    14  	"decred.org/dcrwallet/v4/errors"
    15  	"decred.org/dcrwallet/v4/wallet"
    16  )
    17  
    18  // Compile time check to ensure DcrWallet fulfills lnwallet.BlockChainIO.
    19  var _ lnwallet.BlockChainIO = (*DcrWallet)(nil)
    20  
    21  // GetBestBlock returns the current height and hash of the best known block
    22  // within the main chain.
    23  //
    24  // This method is a part of the lnwallet.BlockChainIO interface.
    25  func (b *DcrWallet) GetBestBlock() (*chainhash.Hash, int32, error) {
    26  	bh, h := b.wallet.MainChainTip(b.ctx)
    27  	return &bh, h, nil
    28  }
    29  
    30  func runAndLogOnError(ctx context.Context, f func(context.Context) error, name string) {
    31  	go func() {
    32  		err := f(ctx)
    33  		select {
    34  		case <-ctx.Done():
    35  			// Any errs were due to done() so, ok
    36  			return
    37  		default:
    38  		}
    39  		if err != nil {
    40  			dcrwLog.Errorf("Dcrwallet error while running %s: %v", name, err)
    41  		}
    42  	}()
    43  }
    44  
    45  // GetUtxoWithHistorical returns the original output referenced by the passed
    46  // outpoint that created the target pkScript, searched for using the passed
    47  // historical chain scanner.
    48  func GetUtxoWithHistorical(ctx context.Context, historical *chainscan.Historical, op *wire.OutPoint,
    49  	pkScript []byte, heightHint uint32) (*wire.TxOut, error) {
    50  	scriptVersion := uint16(0)
    51  	confirmCompleted := make(chan struct{})
    52  	spendCompleted := make(chan struct{})
    53  	var confirmOut *wire.TxOut
    54  	var spent *chainscan.Event
    55  
    56  	// Use a context to stop the search once both a spend and confirm have
    57  	// been found.
    58  	searchCtx, searchCancel := context.WithCancel(ctx)
    59  	defer searchCancel()
    60  
    61  	dcrwLog.Debugf("GetUtxo looking for %s start at %d", op, heightHint)
    62  
    63  	foundSpend := func(e chainscan.Event, _ chainscan.FindFunc) {
    64  		dcrwLog.Debugf("Found spend of %s on block %d (%s) for GetUtxo",
    65  			op, e.BlockHeight, e.BlockHash)
    66  		spent = &e
    67  		searchCancel() // Found the spend, stop searching.
    68  	}
    69  
    70  	foundConfirm := func(e chainscan.Event, findExtra chainscan.FindFunc) {
    71  		// Found confirmation of the outpoint. Try to find someone
    72  		// spending it.
    73  		confirmOut = e.Tx.TxOut[e.Index]
    74  		dcrwLog.Debugf("Found confirmation of %s on block %d (%s) for GetUtxo",
    75  			op, e.BlockHeight, e.BlockHash)
    76  		err := findExtra(
    77  			chainscan.SpentOutPoint(*op, scriptVersion, pkScript),
    78  			chainscan.WithStartHeight(e.BlockHeight+1),
    79  			chainscan.WithFoundCallback(foundSpend),
    80  			chainscan.WithCancelChan(searchCtx.Done()),
    81  			chainscan.WithCompleteChan(spendCompleted),
    82  		)
    83  		if err != nil {
    84  			dcrwLog.Warnf("Unable to chainscan for spend of UTXO: %v", err)
    85  		}
    86  	}
    87  
    88  	// First search for the confirmation of the given script, then for its
    89  	// spending.
    90  	historical.Find(
    91  		chainscan.ConfirmedOutPoint(*op, scriptVersion, pkScript),
    92  		chainscan.WithStartHeight(int32(heightHint)),
    93  		chainscan.WithCancelChan(searchCtx.Done()),
    94  		chainscan.WithFoundCallback(foundConfirm),
    95  		chainscan.WithCompleteChan(confirmCompleted),
    96  	)
    97  
    98  	for confirmCompleted != nil && spendCompleted != nil {
    99  		select {
   100  		case <-ctx.Done():
   101  			return nil, ctx.Err()
   102  
   103  		case <-searchCtx.Done():
   104  			// Spend found.
   105  			confirmCompleted = nil
   106  			spendCompleted = nil
   107  
   108  		case <-confirmCompleted:
   109  			confirmCompleted = nil
   110  			if confirmOut == nil {
   111  				spendCompleted = nil
   112  			}
   113  
   114  		case <-spendCompleted:
   115  			spendCompleted = nil
   116  		}
   117  	}
   118  
   119  	switch {
   120  	case spent != nil:
   121  		return nil, lnwallet.ErrUtxoAlreadySpent{
   122  			PrevOutPoint: *op,
   123  			BlockHash:    spent.BlockHash,
   124  			BlockHeight:  spent.BlockHeight,
   125  			TxIndex:      spent.TxIndex,
   126  			SpendingOutPoint: wire.OutPoint{
   127  				Hash:  *spent.Tx.CachedTxHash(),
   128  				Index: uint32(spent.Index),
   129  				Tree:  spent.Tree,
   130  			},
   131  		}
   132  	case confirmOut != nil:
   133  		return confirmOut, nil
   134  	default:
   135  		return nil, errors.Errorf("output %s not found during chain scan", op)
   136  	}
   137  
   138  }
   139  
   140  // GetUtxo returns the original output referenced by the passed outpoint that
   141  // created the target pkScript.
   142  //
   143  // This method is a part of the lnwallet.BlockChainIO interface.
   144  func (b *DcrWallet) GetUtxo(op *wire.OutPoint, pkScript []byte,
   145  	heightHint uint32, cancel <-chan struct{}) (*wire.TxOut, error) {
   146  
   147  	// Setup a context that is canceled when either the wallet or the passed
   148  	// cancelChan are canceled.
   149  	ctx, cancelCtx := context.WithCancel(b.ctx)
   150  	defer cancelCtx()
   151  	go func() {
   152  		select {
   153  		case <-cancel:
   154  			cancelCtx()
   155  		case <-ctx.Done():
   156  		}
   157  	}()
   158  
   159  	src := csdrivers.NewDcrwalletCSDriver(b.wallet, b.cfg.BlockCache)
   160  	historical := chainscan.NewHistorical(src)
   161  	runAndLogOnError(ctx, src.Run, "GetUtxo.DcrwalletCSDriver")
   162  	runAndLogOnError(ctx, historical.Run, "GetUtxo.Historical")
   163  
   164  	return GetUtxoWithHistorical(ctx, historical, op, pkScript, heightHint)
   165  }
   166  
   167  // GetBlock returns a raw block from the server given its hash.
   168  //
   169  // This method is a part of the lnwallet.BlockChainIO interface.
   170  func (b *DcrWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
   171  	// TODO: unify with the driver on chainscan.
   172  	ctx := b.ctx
   173  
   174  getblock:
   175  	for {
   176  		// Keep trying to get the network backend until the context is
   177  		// canceled.
   178  		n, err := b.wallet.NetworkBackend()
   179  		if errors.Is(err, errors.NoPeers) {
   180  			select {
   181  			case <-ctx.Done():
   182  				return nil, ctx.Err()
   183  			case <-time.After(time.Second):
   184  				continue getblock
   185  			}
   186  		}
   187  
   188  		blocks, err := n.Blocks(ctx, []*chainhash.Hash{blockHash})
   189  		if len(blocks) > 0 && err == nil {
   190  			return blocks[0], nil
   191  		}
   192  
   193  		// The syncer might have failed due to any number of reasons,
   194  		// but it's likely it will come back online shortly. So wait
   195  		// until we can try again.
   196  		select {
   197  		case <-ctx.Done():
   198  			return nil, ctx.Err()
   199  		case <-time.After(time.Second):
   200  		}
   201  	}
   202  
   203  }
   204  
   205  // GetBlockHash returns the hash of the block in the best blockchain at the
   206  // given height.
   207  //
   208  // This method is a part of the lnwallet.BlockChainIO interface.
   209  func (b *DcrWallet) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
   210  	id := wallet.NewBlockIdentifierFromHeight(int32(blockHeight))
   211  	bl, err := b.wallet.BlockInfo(b.ctx, id)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	return &bl.Hash, err
   217  }