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 }