github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/qln/multihop.go (about) 1 package qln 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "fmt" 7 8 "github.com/mit-dci/lit/bech32" 9 "github.com/mit-dci/lit/consts" 10 "github.com/mit-dci/lit/crypto/fastsha256" 11 "github.com/mit-dci/lit/lnutil" 12 "github.com/mit-dci/lit/logging" 13 ) 14 15 func (nd *LitNode) PayMultihop(dstLNAdr string, originCoinType uint32, destCoinType uint32, amount int64) (bool, error) { 16 var targetAdr [20]byte 17 _, adr, err := bech32.Decode(dstLNAdr) 18 if err != nil { 19 return false, err 20 } 21 22 wal, ok := nd.SubWallet[originCoinType] 23 if !ok { 24 return false, fmt.Errorf("not connected to cointype %d", originCoinType) 25 } 26 27 fee := wal.Fee() * 1000 28 29 if amount < consts.MinOutput+fee { 30 return false, fmt.Errorf("cannot send %d because it's less than minOutput + fee: %d", amount, consts.MinOutput+fee) 31 } 32 33 // Connect to the node 34 if _, err := nd.FindPeerIndexByAddress(dstLNAdr); err != nil { 35 err = nd.DialPeer(dstLNAdr) 36 if err != nil { 37 return false, fmt.Errorf("error connected to destination node for multihop: %s", err.Error()) 38 } 39 } 40 41 copy(targetAdr[:], adr) 42 logging.Infof("Finding route to %s", dstLNAdr) 43 path, err := nd.FindPath(targetAdr, destCoinType, originCoinType, amount) 44 if err != nil { 45 return false, err 46 } 47 logging.Debugf("Done route to %s", dstLNAdr) 48 49 idx, err := nd.FindPeerIndexByAddress(dstLNAdr) 50 if err != nil { 51 return false, err 52 } 53 54 inFlight := new(InFlightMultihop) 55 inFlight.Path = path 56 inFlight.Amt = amount 57 nd.MultihopMutex.Lock() 58 nd.InProgMultihop = append(nd.InProgMultihop, inFlight) 59 nd.MultihopMutex.Unlock() 60 61 logging.Infof("Sending payment request to %s", dstLNAdr) 62 msg := lnutil.NewMultihopPaymentRequestMsg(idx, destCoinType) 63 nd.tmpSendLitMsg(msg) 64 logging.Debugf("Done sending payment request to %s", dstLNAdr) 65 return true, nil 66 } 67 68 func (nd *LitNode) MultihopPaymentRequestHandler(msg lnutil.MultihopPaymentRequestMsg) error { 69 // Generate private preimage and send ack with the hash 70 logging.Infof("Received multihop payment request from peer %d\n", msg.Peer()) 71 inFlight := new(InFlightMultihop) 72 var pkh [20]byte 73 74 id, _ := nd.GetPubHostFromPeerIdx(msg.Peer()) 75 idHash := fastsha256.Sum256(id[:]) 76 copy(pkh[:], idHash[:20]) 77 inFlight.Path = []lnutil.RouteHop{{pkh, msg.Cointype}} 78 79 rand.Read(inFlight.PreImage[:]) 80 hash := fastsha256.Sum256(inFlight.PreImage[:]) 81 82 inFlight.HHash = hash 83 84 nd.MultihopMutex.Lock() 85 nd.InProgMultihop = append(nd.InProgMultihop, inFlight) 86 err := nd.SaveMultihopPayment(inFlight) 87 if err != nil { 88 nd.MultihopMutex.Unlock() 89 return err 90 } 91 nd.MultihopMutex.Unlock() 92 93 outMsg := lnutil.NewMultihopPaymentAckMsg(msg.Peer(), hash) 94 nd.tmpSendLitMsg(outMsg) 95 return nil 96 } 97 98 func (nd *LitNode) MultihopPaymentAckHandler(msg lnutil.MultihopPaymentAckMsg) error { 99 logging.Infof("Received multihop payment ack from peer %d, hash %x\n", msg.Peer(), msg.HHash) 100 101 nd.MultihopMutex.Lock() 102 defer nd.MultihopMutex.Unlock() 103 for idx, mh := range nd.InProgMultihop { 104 var nullHash [32]byte 105 if !mh.Succeeded && bytes.Equal(nullHash[:], mh.HHash[:]) { 106 targetNode := mh.Path[len(mh.Path)-1] 107 targetIdx, err := nd.FindPeerIndexByAddress(bech32.Encode("ln", targetNode.Node[:])) 108 if err != nil { 109 return fmt.Errorf("not connected to destination peer") 110 } 111 if msg.Peer() == targetIdx { 112 logging.Debugf("Found the right pending multihop. Sending setup msg to first hop\n") 113 // found the right one. Set this up 114 firstHop := mh.Path[1] 115 ourHop := mh.Path[0] 116 firstHopIdx, err := nd.FindPeerIndexByAddress(bech32.Encode("ln", firstHop.Node[:])) 117 if err != nil { 118 return fmt.Errorf("not connected to first hop in route") 119 } 120 121 nd.RemoteMtx.Lock() 122 var qc *Qchan 123 for _, ch := range nd.RemoteCons[firstHopIdx].QCs { 124 if ch.Coin() == ourHop.CoinType && ch.State.MyAmt-consts.MinOutput-ch.State.Fee >= mh.Amt && !ch.CloseData.Closed && !ch.State.Failed { 125 qc = ch 126 break 127 } 128 } 129 130 if qc == nil { 131 nd.RemoteMtx.Unlock() 132 return fmt.Errorf("could not find suitable channel to route payment") 133 } 134 135 nd.RemoteMtx.Unlock() 136 137 nd.InProgMultihop[idx].HHash = msg.HHash 138 err = nd.SaveMultihopPayment(nd.InProgMultihop[idx]) 139 if err != nil { 140 return err 141 } 142 143 // Calculate what initial locktime we need 144 wal, ok := nd.SubWallet[ourHop.CoinType] 145 if !ok { 146 return fmt.Errorf("not connected to wallet for cointype %d", ourHop.CoinType) 147 } 148 149 height := wal.CurrentHeight() 150 151 // Allow 5 blocks of leeway per hop in case people's wallets are out of sync 152 locktime := height + int32(len(mh.Path)*(consts.DefaultLockTime+5)) 153 154 // This handler needs to return before OfferHTLC can work 155 go func() { 156 logging.Infof("offering HTLC with RHash: %x", msg.HHash) 157 err = nd.OfferHTLC(qc, uint32(mh.Amt), msg.HHash, uint32(locktime), [32]byte{}) 158 if err != nil { 159 logging.Errorf("error offering HTLC: %s", err.Error()) 160 return 161 } 162 163 // Set the dirty flag on each of the nodes' channels we used 164 nd.ChannelMapMtx.Lock() 165 for _, hop := range nd.InProgMultihop[idx].Path { 166 for i, channel := range nd.ChannelMap[hop.Node] { 167 if channel.Link.CoinType == hop.CoinType { 168 nd.ChannelMap[hop.Node][i].Dirty = true 169 break 170 } 171 } 172 } 173 nd.ChannelMapMtx.Unlock() 174 175 var data [32]byte 176 outMsg := lnutil.NewMultihopPaymentSetupMsg(firstHopIdx, msg.HHash, mh.Path, data) 177 logging.Debugf("Sending multihoppaymentsetup to peer %d\n", firstHopIdx) 178 nd.tmpSendLitMsg(outMsg) 179 }() 180 181 break 182 } 183 } 184 } 185 return nil 186 } 187 188 func (nd *LitNode) MultihopPaymentSetupHandler(msg lnutil.MultihopPaymentSetupMsg) error { 189 logging.Infof("Received multihop payment setup from peer %d, hash %x\n", msg.Peer(), msg.HHash) 190 191 inFlight := new(InFlightMultihop) 192 inFlight.Path = msg.NodeRoute 193 inFlight.HHash = msg.HHash 194 195 // Forward 196 var pkh [20]byte 197 id := nd.IdKey().PubKey().SerializeCompressed() 198 idHash := fastsha256.Sum256(id[:]) 199 copy(pkh[:], idHash[:20]) 200 var nextHop, ourHop, incomingHop *lnutil.RouteHop 201 for i, node := range inFlight.Path { 202 if bytes.Equal(pkh[:], node.Node[:]) { 203 if i == 0 { 204 return fmt.Errorf("path is invalid") 205 } 206 if i+1 < len(inFlight.Path) { 207 nextHop = &inFlight.Path[i+1] 208 } 209 ourHop = &inFlight.Path[i] 210 incomingHop = &inFlight.Path[i-1] 211 break 212 } 213 } 214 215 // Check there is a corresponding incoming HTLC 216 HTLCs, chans, err := nd.FindHTLCsByHash(msg.HHash) 217 if err != nil { 218 return fmt.Errorf("error finding HTLCs: %s", err.Error()) 219 } 220 221 var found bool 222 var prevHTLC *HTLC 223 for idx, h := range HTLCs { 224 if h.Incoming && !h.Cleared && !h.Clearing && !h.ClearedOnChain && chans[idx].Coin() == incomingHop.CoinType { 225 found = true 226 prevHTLC = &h 227 break 228 } 229 230 // We already have an outgoing HTLC with this hash 231 if !h.Incoming && !h.Cleared && !h.ClearedOnChain { 232 return fmt.Errorf("we already have an uncleared offered HTLC with RHash: %x", msg.HHash) 233 } 234 } 235 236 if !found { 237 return fmt.Errorf("no corresponding incoming HTLC found for multihop payment with RHash: %x", msg.HHash) 238 } 239 240 var nullBytes [16]byte 241 nd.MultihopMutex.Lock() 242 defer nd.MultihopMutex.Unlock() 243 for _, mh := range nd.InProgMultihop { 244 hash := fastsha256.Sum256(mh.PreImage[:]) 245 246 if !bytes.Equal(mh.PreImage[:], nullBytes[:]) && bytes.Equal(msg.HHash[:], hash[:]) && mh.Path[len(mh.Path)-1].CoinType == incomingHop.CoinType { 247 // We already know this. If we have a Preimage, then we're the receiving 248 // end and we should send a settlement message to the 249 // predecessor 250 go func() { 251 _, err := nd.ClaimHTLC(mh.PreImage) 252 if err != nil { 253 logging.Errorf("error claiming HTLC: %s", err.Error()) 254 } 255 }() 256 257 return nil 258 } 259 } 260 261 if nextHop == nil { 262 return fmt.Errorf("route is invalid") 263 } 264 265 wal, ok := nd.SubWallet[incomingHop.CoinType] 266 if !ok { 267 return fmt.Errorf("not connected to wallet for cointype %d", incomingHop.CoinType) 268 } 269 270 height := wal.CurrentHeight() 271 if prevHTLC.Locktime-consts.DefaultLockTime < uint32(height+consts.DefaultLockTime) { 272 return fmt.Errorf("locktime of preceeding hop is too close for comfort: %d, height: %d", prevHTLC.Locktime-consts.DefaultLockTime, height) 273 } 274 275 wal, ok = nd.SubWallet[ourHop.CoinType] 276 if !ok { 277 return fmt.Errorf("not connected to wallet for cointype %d", ourHop.CoinType) 278 } 279 280 fee := wal.Fee() * 1000 281 282 newLocktime := ((((prevHTLC.Locktime - uint32(height)) / consts.DefaultLockTime) - 1) * consts.DefaultLockTime) + uint32(wal.CurrentHeight()) 283 284 nd.InProgMultihop = append(nd.InProgMultihop, inFlight) 285 286 err = nd.SaveMultihopPayment(inFlight) 287 if err != nil { 288 return err 289 } 290 291 lnAdr := bech32.Encode("ln", nextHop.Node[:]) 292 293 // Connect to the node 294 if _, err := nd.FindPeerIndexByAddress(lnAdr); err != nil { 295 err = nd.DialPeer(lnAdr) 296 if err != nil { 297 return fmt.Errorf("error connecting to node for multihop: %s", err.Error()) 298 } 299 } 300 301 sendToIdx, err := nd.FindPeerIndexByAddress(lnAdr) 302 if err != nil { 303 return fmt.Errorf("not connected to peer in route") 304 } 305 306 amtRqd := prevHTLC.Amt 307 308 // do we need to exchange? 309 // is the last hop coin type the same as this one? 310 if incomingHop.CoinType != ourHop.CoinType { 311 // we need to exchange, but is it possible? 312 var rd *lnutil.RateDesc 313 var rates []lnutil.RateDesc 314 nd.ChannelMapMtx.Lock() 315 defer nd.ChannelMapMtx.Unlock() 316 for _, link := range nd.ChannelMap[pkh] { 317 if link.Link.CoinType == ourHop.CoinType { 318 rates = link.Link.Rates 319 break 320 } 321 } 322 323 for _, rate := range rates { 324 if rate.CoinType == incomingHop.CoinType && rate.Rate > 0 { 325 rd = &rate 326 break 327 } 328 } 329 330 // it's not possible to exchange these two coin types 331 if rd == nil { 332 return fmt.Errorf("can't exchange %d for %d via us", incomingHop.CoinType, ourHop.CoinType) 333 } 334 335 // required capacity is last hop amt * rate 336 if rd.Reciprocal { 337 // prior hop coin type is worth less than this one 338 amtRqd /= rd.Rate 339 } else { 340 // prior hop coin type is worth more than this one 341 amtRqd *= rd.Rate 342 } 343 } 344 345 if amtRqd < consts.MinOutput+fee { 346 // exchanging to this point has pushed the amount too low 347 return fmt.Errorf("exchanging %d for %d via us pushes the amount too low: %d", incomingHop.CoinType, ourHop.CoinType, amtRqd) 348 } 349 350 nd.RemoteMtx.Lock() 351 var qc *Qchan 352 for _, ch := range nd.RemoteCons[sendToIdx].QCs { 353 if ch.Coin() == ourHop.CoinType && ch.State.MyAmt-consts.MinOutput-fee >= amtRqd && !ch.CloseData.Closed && !ch.State.Failed { 354 qc = ch 355 break 356 } 357 } 358 359 if qc == nil { 360 nd.RemoteMtx.Unlock() 361 return fmt.Errorf("could not find suitable channel to route payment") 362 } 363 364 nd.RemoteMtx.Unlock() 365 366 // This handler needs to return so run this in a goroutine 367 go func() { 368 logging.Infof("offering HTLC with RHash: %x", msg.HHash) 369 err = nd.OfferHTLC(qc, uint32(amtRqd), msg.HHash, newLocktime, [32]byte{}) 370 if err != nil { 371 logging.Errorf("error offering HTLC: %s", err.Error()) 372 return 373 } 374 375 // Set the dirty flag on the nodes' channels in this route so we 376 // don't attempt to use them for routing before we get an link update 377 nd.ChannelMapMtx.Lock() 378 for _, hop := range msg.NodeRoute { 379 if _, ok := nd.ChannelMap[hop.Node]; ok { 380 for idx, channel := range nd.ChannelMap[hop.Node] { 381 if channel.Link.CoinType == hop.CoinType { 382 nd.ChannelMap[hop.Node][idx].Dirty = true 383 break 384 } 385 } 386 } 387 } 388 nd.ChannelMapMtx.Unlock() 389 390 msg.PeerIdx = sendToIdx 391 msg.NodeRoute = msg.NodeRoute[1:] 392 nd.tmpSendLitMsg(msg) 393 }() 394 395 return nil 396 }