github.com/decred/dcrlnd@v0.7.6/contractcourt/htlc_outgoing_contest_resolver.go (about) 1 package contractcourt 2 3 import ( 4 "fmt" 5 "io" 6 7 "github.com/decred/dcrd/dcrutil/v4" 8 "github.com/decred/dcrlnd/channeldb" 9 "github.com/decred/dcrlnd/lnwallet" 10 ) 11 12 // htlcOutgoingContestResolver is a ContractResolver that's able to resolve an 13 // outgoing HTLC that is still contested. An HTLC is still contested, if at the 14 // time that we broadcast the commitment transaction, it isn't able to be fully 15 // resolved. In this case, we'll either wait for the HTLC to timeout, or for us 16 // to learn of the preimage. 17 type htlcOutgoingContestResolver struct { 18 // htlcTimeoutResolver is the inner solver that this resolver may turn 19 // into. This only happens if the HTLC expires on-chain. 20 *htlcTimeoutResolver 21 } 22 23 // newOutgoingContestResolver instantiates a new outgoing contested htlc 24 // resolver. 25 func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution, 26 broadcastHeight uint32, htlc channeldb.HTLC, 27 resCfg ResolverConfig) *htlcOutgoingContestResolver { 28 29 timeout := newTimeoutResolver( 30 res, broadcastHeight, htlc, resCfg, 31 ) 32 33 return &htlcOutgoingContestResolver{ 34 htlcTimeoutResolver: timeout, 35 } 36 } 37 38 // Resolve commences the resolution of this contract. As this contract hasn't 39 // yet timed out, we'll wait for one of two things to happen 40 // 41 // 1. The HTLC expires. In this case, we'll sweep the funds and send a clean 42 // up cancel message to outside sub-systems. 43 // 44 // 2. The remote party sweeps this HTLC on-chain, in which case we'll add the 45 // pre-image to our global cache, then send a clean up settle message 46 // backwards. 47 // 48 // When either of these two things happens, we'll create a new resolver which 49 // is able to handle the final resolution of the contract. We're only the pivot 50 // point. 51 func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) { 52 // If we're already full resolved, then we don't have anything further 53 // to do. 54 if h.resolved { 55 return nil, nil 56 } 57 58 // Otherwise, we'll watch for two external signals to decide if we'll 59 // morph into another resolver, or fully resolve the contract. 60 // 61 // The output we'll be watching for is the *direct* spend from the HTLC 62 // output. If this isn't our commitment transaction, it'll be right on 63 // the resolution. Otherwise, we fetch this pointer from the input of 64 // the time out transaction. 65 outPointToWatch, scriptToWatch, err := h.chainDetailsToWatch() 66 if err != nil { 67 return nil, err 68 } 69 70 // First, we'll register for a spend notification for this output. If 71 // the remote party sweeps with the pre-image, we'll be notified. 72 spendNtfn, err := h.Notifier.RegisterSpendNtfn( 73 outPointToWatch, scriptToWatch, h.broadcastHeight, 74 ) 75 if err != nil { 76 return nil, err 77 } 78 79 // We'll quickly check to see if the output has already been spent. 80 select { 81 // If the output has already been spent, then we can stop early and 82 // sweep the pre-image from the output. 83 case commitSpend, ok := <-spendNtfn.Spend: 84 if !ok { 85 return nil, errResolverShuttingDown 86 } 87 88 // TODO(roasbeef): Checkpoint? 89 return h.claimCleanUp(commitSpend) 90 91 // If it hasn't, then we'll watch for both the expiration, and the 92 // sweeping out this output. 93 default: 94 } 95 96 // If we reach this point, then we can't fully act yet, so we'll await 97 // either of our signals triggering: the HTLC expires, or we learn of 98 // the preimage. 99 blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil) 100 if err != nil { 101 return nil, err 102 } 103 defer blockEpochs.Cancel() 104 105 for { 106 select { 107 108 // A new block has arrived, we'll check to see if this leads to 109 // HTLC expiration. 110 case newBlock, ok := <-blockEpochs.Epochs: 111 if !ok { 112 return nil, errResolverShuttingDown 113 } 114 115 // If the current height is >= expiry-1, then a timeout 116 // path spend will be valid to be included in the next 117 // block, and we can immediately return the resolver. 118 // 119 // TODO(joostjager): Statement above may not be valid. 120 // For CLTV locks, the expiry value is the last 121 // _invalid_ block. The likely reason that this does not 122 // create a problem, is that utxonursery is checking the 123 // expiry again (in the proper way). 124 // 125 // Source: 126 // https://github.com/decred/dcrd/blob/991d32e72fe84d5fbf9c47cd604d793a0cd3a072/blockchain/validate.go#L154 127 newHeight := uint32(newBlock.Height) 128 if newHeight >= h.htlcResolution.Expiry-1 { 129 log.Infof("%T(%v): HTLC has expired "+ 130 "(height=%v, expiry=%v), transforming "+ 131 "into timeout resolver", h, 132 h.htlcResolution.ClaimOutpoint, 133 newHeight, h.htlcResolution.Expiry) 134 return h.htlcTimeoutResolver, nil 135 } 136 137 // The output has been spent! This means the preimage has been 138 // revealed on-chain. 139 case commitSpend, ok := <-spendNtfn.Spend: 140 if !ok { 141 return nil, errResolverShuttingDown 142 } 143 144 // The only way this output can be spent by the remote 145 // party is by revealing the preimage. So we'll perform 146 // our duties to clean up the contract once it has been 147 // claimed. 148 return h.claimCleanUp(commitSpend) 149 150 case <-h.quit: 151 return nil, fmt.Errorf("resolver canceled") 152 } 153 } 154 } 155 156 // report returns a report on the resolution state of the contract. 157 func (h *htlcOutgoingContestResolver) report() *ContractReport { 158 // No locking needed as these values are read-only. 159 160 finalAmt := h.htlc.Amt.ToAtoms() 161 if h.htlcResolution.SignedTimeoutTx != nil { 162 finalAmt = dcrutil.Amount( 163 h.htlcResolution.SignedTimeoutTx.TxOut[0].Value, 164 ) 165 } 166 167 return &ContractReport{ 168 Outpoint: h.htlcResolution.ClaimOutpoint, 169 Type: ReportOutputOutgoingHtlc, 170 Amount: finalAmt, 171 MaturityHeight: h.htlcResolution.Expiry, 172 LimboBalance: finalAmt, 173 Stage: 1, 174 } 175 } 176 177 // Stop signals the resolver to cancel any current resolution processes, and 178 // suspend. 179 // 180 // NOTE: Part of the ContractResolver interface. 181 func (h *htlcOutgoingContestResolver) Stop() { 182 close(h.quit) 183 } 184 185 // IsResolved returns true if the stored state in the resolve is fully 186 // resolved. In this case the target output can be forgotten. 187 // 188 // NOTE: Part of the ContractResolver interface. 189 func (h *htlcOutgoingContestResolver) IsResolved() bool { 190 return h.resolved 191 } 192 193 // SupplementState allows the user of a ContractResolver to supplement it with 194 // state required for the proper resolution of a contract. 195 // 196 // NOTE: Part of the ContractResolver interface. 197 func (h *htlcOutgoingContestResolver) SupplementState(state *channeldb.OpenChannel) { 198 h.htlcTimeoutResolver.SupplementState(state) 199 } 200 201 // Encode writes an encoded version of the ContractResolver into the passed 202 // Writer. 203 // 204 // NOTE: Part of the ContractResolver interface. 205 func (h *htlcOutgoingContestResolver) Encode(w io.Writer) error { 206 return h.htlcTimeoutResolver.Encode(w) 207 } 208 209 // newOutgoingContestResolverFromReader attempts to decode an encoded ContractResolver 210 // from the passed Reader instance, returning an active ContractResolver 211 // instance. 212 func newOutgoingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) ( 213 *htlcOutgoingContestResolver, error) { 214 215 h := &htlcOutgoingContestResolver{} 216 timeoutResolver, err := newTimeoutResolverFromReader(r, resCfg) 217 if err != nil { 218 return nil, err 219 } 220 h.htlcTimeoutResolver = timeoutResolver 221 return h, nil 222 } 223 224 // A compile time assertion to ensure htlcOutgoingContestResolver meets the 225 // ContractResolver interface. 226 var _ htlcContractResolver = (*htlcOutgoingContestResolver)(nil)