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 }