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  }