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

     1  package dcrwallet
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"sync"
     7  
     8  	"github.com/decred/dcrd/chaincfg/chainhash"
     9  	"github.com/decred/dcrd/chaincfg/v3"
    10  	"github.com/decred/dcrd/dcrutil/v4"
    11  	"github.com/decred/dcrd/rpcclient/v8"
    12  	"github.com/decred/dcrd/wire"
    13  
    14  	"github.com/decred/dcrlnd/blockcache"
    15  	"github.com/decred/dcrlnd/lnwallet"
    16  
    17  	"decred.org/dcrwallet/v4/errors"
    18  )
    19  
    20  var (
    21  	// ErrOutputSpent is returned by the GetUtxo method if the target output
    22  	// for lookup has already been spent.
    23  	ErrOutputSpent = errors.New("target output has been spent")
    24  
    25  	// ErrUnconnected is returned when an IO operation was requested by the
    26  	// backend is not connected to the network.
    27  	//
    28  	// TODO(decred) this should probably be exported by lnwallet and
    29  	// expected by the BlockChainIO interface.
    30  	ErrUnconnected = errors.New("unconnected to the network")
    31  )
    32  
    33  // RPCChainIO implements the required methods for performing chain io services.
    34  type RPCChainIO struct {
    35  	net        *chaincfg.Params
    36  	blockCache *blockcache.BlockCache
    37  
    38  	// mu is a mutex that protects the chain field.
    39  	mu    sync.Mutex
    40  	chain *rpcclient.Client
    41  }
    42  
    43  // Compile time check to ensure RPCChainIO fulfills lnwallet.BlockChainIO.
    44  var _ lnwallet.BlockChainIO = (*RPCChainIO)(nil)
    45  
    46  // NewRPCChainIO initializes a new blockchain IO implementation backed by a
    47  // full dcrd node.  It requires the config for reaching the dcrd instance and
    48  // the corresponding network this instance should be in.
    49  func NewRPCChainIO(rpcConfig rpcclient.ConnConfig, net *chaincfg.Params,
    50  	blockCache *blockcache.BlockCache) (*RPCChainIO, error) {
    51  	// TODO: bring back this. rpcclient/v6 changed the semantics of the
    52  	// context passed to Connect().
    53  	/*
    54  		connectTimeout := 30 * time.Second
    55  		ctx, cancel := context.WithTimeout(context.Background(), connectTimeout)
    56  		defer cancel()
    57  	*/
    58  	ctx := context.Background()
    59  
    60  	rpcConfig.DisableConnectOnNew = true
    61  	rpcConfig.DisableAutoReconnect = false
    62  	chain, err := rpcclient.New(&rpcConfig, nil)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	// Try to connect to the given node.
    68  	if err := chain.Connect(ctx, true); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return &RPCChainIO{
    73  		net:        net,
    74  		chain:      chain,
    75  		blockCache: blockCache,
    76  	}, nil
    77  }
    78  
    79  // GetBestBlock returns the current height and hash of the best known block
    80  // within the main chain.
    81  //
    82  // This method is a part of the lnwallet.BlockChainIO interface.
    83  func (s *RPCChainIO) GetBestBlock() (*chainhash.Hash, int32, error) {
    84  	s.mu.Lock()
    85  	defer s.mu.Unlock()
    86  	if s.chain == nil {
    87  		return nil, 0, ErrUnconnected
    88  	}
    89  	hash, height, err := s.chain.GetBestBlock(context.TODO())
    90  	return hash, int32(height), err
    91  }
    92  
    93  // GetUtxo returns the original output referenced by the passed outpoint that
    94  // create the target pkScript.
    95  //
    96  // This method is a part of the lnwallet.BlockChainIO interface.
    97  func (s *RPCChainIO) GetUtxo(op *wire.OutPoint, pkScript []byte,
    98  	heightHint uint32, cancel <-chan struct{}) (*wire.TxOut, error) {
    99  
   100  	s.mu.Lock()
   101  	defer s.mu.Unlock()
   102  	if s.chain == nil {
   103  		return nil, ErrUnconnected
   104  	}
   105  
   106  	txout, err := s.chain.GetTxOut(context.TODO(), &op.Hash, op.Index, op.Tree, false)
   107  	if err != nil {
   108  		return nil, err
   109  	} else if txout == nil {
   110  		return nil, ErrOutputSpent
   111  	}
   112  
   113  	pkScript, err = hex.DecodeString(txout.ScriptPubKey.Hex)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// Sadly, gettxout returns the output value in DCR instead of atoms.
   119  	amt, err := dcrutil.NewAmount(txout.Value)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	return &wire.TxOut{
   125  		Value:    int64(amt),
   126  		PkScript: pkScript,
   127  	}, nil
   128  }
   129  
   130  // GetBlock returns a raw block from the server given its hash.
   131  //
   132  // This method is a part of the lnwallet.BlockChainIO interface.
   133  func (s *RPCChainIO) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
   134  	return s.blockCache.GetBlock(context.TODO(), blockHash, s.getBlock)
   135  }
   136  
   137  func (s *RPCChainIO) getBlock(ctx context.Context, blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
   138  	s.mu.Lock()
   139  	defer s.mu.Unlock()
   140  	if s.chain == nil {
   141  		return nil, ErrUnconnected
   142  	}
   143  	return s.chain.GetBlock(context.TODO(), blockHash)
   144  }
   145  
   146  // GetBlockHash returns the hash of the block in the best blockchain at the
   147  // given height.
   148  //
   149  // This method is a part of the lnwallet.BlockChainIO interface.
   150  func (s *RPCChainIO) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
   151  	s.mu.Lock()
   152  	defer s.mu.Unlock()
   153  	if s.chain == nil {
   154  		return nil, ErrUnconnected
   155  	}
   156  	return s.chain.GetBlockHash(context.TODO(), blockHeight)
   157  }