github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/qln/routing.go (about)

     1  package qln
     2  
     3  import (
     4  	"bytes"
     5  	"container/heap"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"math"
    10  	"os"
    11  	"path/filepath"
    12  	"strconv"
    13  	"time"
    14  
    15  	"github.com/awalterschulze/gographviz"
    16  	"github.com/mit-dci/lit/bech32"
    17  	"github.com/mit-dci/lit/coinparam"
    18  	"github.com/mit-dci/lit/consts"
    19  	"github.com/mit-dci/lit/crypto/fastsha256"
    20  	"github.com/mit-dci/lit/lnutil"
    21  	"github.com/mit-dci/lit/logging"
    22  )
    23  
    24  func (nd *LitNode) InitRouting() {
    25  	nd.ChannelMapMtx.Lock()
    26  	defer nd.ChannelMapMtx.Unlock()
    27  	nd.ChannelMap = make(map[[20]byte][]LinkDesc)
    28  	nd.ExchangeRates = make(map[uint32][]lnutil.RateDesc)
    29  
    30  	err := nd.PopulateRates()
    31  	if err != nil {
    32  		if os.IsNotExist(err) {
    33  			logging.Infof("Rates file not found.")
    34  		}
    35  		logging.Warnf("failure loading exchange rates: %s", err.Error())
    36  	}
    37  
    38  	nd.AdvTimeout = time.NewTicker(15 * time.Second)
    39  
    40  	go func() {
    41  		seq := uint32(0)
    42  
    43  		for {
    44  			nd.cleanStaleChannels()
    45  			nd.advertiseLinks(seq)
    46  			seq++
    47  			<-nd.AdvTimeout.C
    48  		}
    49  	}()
    50  }
    51  
    52  func (nd *LitNode) VisualiseGraph() string {
    53  	graph := gographviz.NewGraph()
    54  	graph.SetName("Lit")
    55  
    56  	nd.ChannelMapMtx.Lock()
    57  	defer nd.ChannelMapMtx.Unlock()
    58  
    59  	for pkh, node := range nd.ChannelMap {
    60  		lnAdr := bech32.Encode("ln", pkh[:])
    61  		if !graph.IsNode(lnAdr) {
    62  			graph.AddNode("Lit", lnAdr, nil)
    63  		}
    64  
    65  		for _, channel := range node {
    66  			theirLnAdr := bech32.Encode("ln", channel.Link.BPKH[:])
    67  			if !graph.IsNode(theirLnAdr) {
    68  				graph.AddNode("Lit", theirLnAdr, nil)
    69  			}
    70  
    71  			attrs := make(map[string]string)
    72  
    73  			switch channel.Link.CoinType {
    74  			case 0:
    75  				attrs["color"] = "orange"
    76  			case 28:
    77  				attrs["color"] = "green"
    78  			}
    79  
    80  			attrs["label"] = strconv.FormatUint(uint64(channel.Link.CoinType), 10)
    81  
    82  			graph.AddEdge(lnAdr, theirLnAdr, true, attrs)
    83  		}
    84  	}
    85  
    86  	return "di" + graph.String()
    87  }
    88  
    89  // FindPath uses Bellman-Ford and Dijkstra to find the path with the best price that has enough capacity to route the payment
    90  func (nd *LitNode) FindPath(targetPkh [20]byte, destCoinType uint32, originCoinType uint32, amount int64) ([]lnutil.RouteHop, error) {
    91  	var myIdPkh [20]byte
    92  	idHash := fastsha256.Sum256(nd.IdKey().PubKey().SerializeCompressed())
    93  	copy(myIdPkh[:], idHash[:20])
    94  
    95  	type routeHop struct {
    96  		Node     [20]byte
    97  		CoinType uint32
    98  		Terminus bool
    99  	}
   100  
   101  	type channelEdge struct {
   102  		W        float64
   103  		U        routeHop
   104  		V        routeHop
   105  		Rate     lnutil.RateDesc
   106  		Capacity int64
   107  	}
   108  
   109  	type channelEdgeLight struct {
   110  		W        float64
   111  		U        int
   112  		V        int
   113  		Rate     lnutil.RateDesc
   114  		Capacity int64
   115  	}
   116  
   117  	// set up initial graph
   118  	var edges []channelEdge
   119  	var vertices []routeHop
   120  	var edgesLight []channelEdgeLight
   121  
   122  	verticesMap := make(map[routeHop]int)
   123  
   124  	nd.ChannelMapMtx.Lock()
   125  
   126  	// for each node visit the nodes connected via its channels and add a
   127  	// BPKH:channel_cointype vertex to the graph for each of its channels.
   128  	// Then for the current node (APKH), for each channel add an edge from
   129  	// APKH:channel_cointype to each BPKH:cointype pair in the graph.
   130  
   131  	for pkh, channels := range nd.ChannelMap {
   132  		logging.Debugf("processing channels from %s", bech32.Encode("ln", pkh[:]))
   133  
   134  		for _, channel := range channels {
   135  			logging.Debugf("...processing channel %s:%d", bech32.Encode("ln", channel.Link.BPKH[:]), channel.Link.CoinType)
   136  			var newEdges []channelEdge
   137  			origin := routeHop{
   138  				channel.Link.APKH,
   139  				channel.Link.CoinType,
   140  				false,
   141  			}
   142  			verticesMap[origin] = -1
   143  
   144  			coinTypes := map[uint32]bool{}
   145  
   146  			for _, theirChannel := range nd.ChannelMap[channel.Link.BPKH] {
   147  				logging.Debugf("......checking outbound connection %s:%d", bech32.Encode("ln", theirChannel.Link.BPKH[:]), theirChannel.Link.CoinType)
   148  				if _, ok := coinTypes[theirChannel.Link.CoinType]; !ok {
   149  					var rd *lnutil.RateDesc
   150  					for _, rate := range theirChannel.Link.Rates {
   151  						if rate.CoinType == channel.Link.CoinType {
   152  							rd = &rate
   153  							break
   154  						}
   155  					}
   156  
   157  					if rd == nil {
   158  						// this trade is not possible
   159  						logging.Debugf(".........ignoring channel because trade %d->%d is not possible", channel.Link.CoinType, theirChannel.Link.CoinType)
   160  						continue
   161  					}
   162  
   163  					vertex := routeHop{
   164  						channel.Link.BPKH,
   165  						theirChannel.Link.CoinType,
   166  						false,
   167  					}
   168  					verticesMap[vertex] = -1
   169  
   170  					var price float64
   171  					if rd.Reciprocal {
   172  						price = 1.0 / float64(rd.Rate)
   173  					} else {
   174  						price = float64(rd.Rate)
   175  					}
   176  
   177  					weight := -math.Log(price)
   178  
   179  					edge := channelEdge{
   180  						weight,
   181  						origin,
   182  						vertex,
   183  						*rd,
   184  						channel.Link.ACapacity,
   185  					}
   186  
   187  					logging.Debugf(".........adding edge: %s:%d->%s:%d", bech32.Encode("ln", edge.U.Node[:]), edge.U.CoinType, bech32.Encode("ln", edge.V.Node[:]), edge.V.CoinType)
   188  
   189  					newEdges = append(newEdges, edge)
   190  					coinTypes[theirChannel.Link.CoinType] = true
   191  				} else {
   192  					logging.Debugf(".........ignoring channel because its cointype has already been covered")
   193  				}
   194  			}
   195  
   196  			vertex := routeHop{
   197  				channel.Link.BPKH,
   198  				channel.Link.CoinType,
   199  				true,
   200  			}
   201  			verticesMap[vertex] = -1
   202  
   203  			edge := channelEdge{
   204  				0,
   205  				origin,
   206  				vertex,
   207  				lnutil.RateDesc{
   208  					channel.Link.CoinType,
   209  					1,
   210  					false,
   211  				},
   212  				channel.Link.ACapacity,
   213  			}
   214  
   215  			logging.Debugf("...adding sink: %s:%d->%s:%d", bech32.Encode("ln", edge.U.Node[:]), edge.U.CoinType, bech32.Encode("ln", edge.V.Node[:]), edge.V.CoinType)
   216  
   217  			newEdges = append(newEdges, edge)
   218  
   219  			edges = append(edges, newEdges...)
   220  		}
   221  	}
   222  	nd.ChannelMapMtx.Unlock()
   223  
   224  	var predecessor []int
   225  	var distance []float64
   226  
   227  	for k, _ := range verticesMap {
   228  		vertices = append(vertices, k)
   229  		distance = append(distance, math.MaxFloat64)
   230  		predecessor = append(predecessor, -1)
   231  		verticesMap[k] = len(vertices) - 1
   232  		logging.Debugf("vertex %x:%d: %d", k.Node, k.CoinType, len(vertices)-1)
   233  	}
   234  
   235  	for _, edge := range edges {
   236  		edgesLight = append(edgesLight, channelEdgeLight{
   237  			edge.W,
   238  			verticesMap[edge.U],
   239  			verticesMap[edge.V],
   240  			edge.Rate,
   241  			edge.Capacity,
   242  		})
   243  		U := vertices[edgesLight[len(edgesLight)-1].U]
   244  		V := vertices[edgesLight[len(edgesLight)-1].V]
   245  		logging.Debugf("adding edgeLight: %x:%d->%x:%d", U.Node, U.CoinType, V.Node, V.CoinType)
   246  	}
   247  
   248  	graph := gographviz.NewGraph()
   249  	graph.SetName("\"bf-step\"")
   250  	for _, edge := range edgesLight {
   251  		U := fmt.Sprintf("\"%s:%d\"", bech32.Encode("ln", vertices[edge.U].Node[:]), vertices[edge.U].CoinType)
   252  		V := fmt.Sprintf("\"%s:%d\"", bech32.Encode("ln", vertices[edge.V].Node[:]), vertices[edge.V].CoinType)
   253  
   254  		nodeAttrs := make(map[string]string)
   255  		if vertices[edge.V].Terminus {
   256  			nodeAttrs["peripheries"] = "2"
   257  			V = fmt.Sprintf("\"%s:%d terminus\"", bech32.Encode("ln", vertices[edge.V].Node[:]), vertices[edge.V].CoinType)
   258  		}
   259  
   260  		if !graph.IsNode(U) {
   261  			graph.AddNode("\"bf-step\"", U, nil)
   262  		}
   263  		if !graph.IsNode(V) {
   264  			graph.AddNode("\"bf-step\"", V, nodeAttrs)
   265  		}
   266  
   267  		attrs := make(map[string]string)
   268  
   269  		attrs["label"] = fmt.Sprintf("\"weight: %f64, trade: %d->%d\"", edge.W, vertices[edge.U].CoinType, vertices[edge.V].CoinType)
   270  
   271  		graph.AddEdge(U, V, true, attrs)
   272  	}
   273  
   274  	logging.Debugf("bf-step graph: \n %s", "di"+graph.String())
   275  
   276  	// find my ID in map
   277  	myId, ok := verticesMap[routeHop{myIdPkh, originCoinType, false}]
   278  	if !ok {
   279  		return nil, fmt.Errorf("origin node not found")
   280  	}
   281  
   282  	targetId, ok := verticesMap[routeHop{targetPkh, destCoinType, true}]
   283  	if !ok {
   284  		return nil, fmt.Errorf("destination node not found")
   285  	}
   286  
   287  	// add dummy vertex q to the map
   288  	vertices = append(vertices, routeHop{[20]byte{}, 0, false})
   289  	distance = append(distance, 0)
   290  	predecessor = append(predecessor, -1)
   291  
   292  	// connect q to every other vertex
   293  	for idx := range vertices {
   294  		if idx < len(vertices)-1 {
   295  			edgesLight = append(edgesLight, channelEdgeLight{
   296  				0,
   297  				len(vertices) - 1,
   298  				idx,
   299  				lnutil.RateDesc{},
   300  				0,
   301  			})
   302  		}
   303  	}
   304  
   305  	// relax the edges from q
   306  	for i := 0; i < len(vertices); i++ {
   307  		var relaxed bool
   308  		for _, edge := range edgesLight {
   309  			if distance[edge.U]+edge.W < distance[edge.V] {
   310  				distance[edge.V] = distance[edge.U] + edge.W
   311  				predecessor[edge.V] = edge.U
   312  				relaxed = true
   313  			}
   314  		}
   315  
   316  		// we didn't relax any edges in the last round so we can quit early
   317  		if !relaxed {
   318  			break
   319  		}
   320  	}
   321  
   322  	// check for negative-weight cycles
   323  	for _, edge := range edgesLight {
   324  		if distance[edge.U]+edge.W < distance[edge.V] {
   325  			return nil, fmt.Errorf("negative weight cycle in channel graph")
   326  		}
   327  	}
   328  
   329  	// reweight original graph
   330  	for idx, edge := range edgesLight {
   331  		edgesLight[idx].W += distance[edge.U] - distance[edge.V]
   332  	}
   333  
   334  	// remove q and its edges
   335  	edgesLight = edgesLight[:len(edges)]
   336  	vertices = vertices[:len(vertices)-1]
   337  	predecessor = predecessor[:len(predecessor)-1]
   338  	distance = distance[:len(distance)-1]
   339  
   340  	// run dijkstra over the reweighted graph to find the lowest weight route
   341  	// with enough capacity to route the amount we want to send
   342  	dDistance := make([]*nodeWithDist, len(vertices))
   343  	dEdges := make([][]channelEdgeLight, len(vertices))
   344  
   345  	dDistance[myId] = &nodeWithDist{
   346  		0,
   347  		myId,
   348  		amount,
   349  	}
   350  
   351  	for idx := range predecessor {
   352  		predecessor[idx] = -1
   353  	}
   354  
   355  	for _, edge := range edgesLight {
   356  		dEdges[edge.U] = append(dEdges[edge.U], edge)
   357  	}
   358  
   359  	var nodeHeap distanceHeap
   360  
   361  	heap.Push(&nodeHeap, *dDistance[myId])
   362  
   363  	for nodeHeap.Len() > 0 {
   364  		partialPath := heap.Pop(&nodeHeap).(nodeWithDist)
   365  
   366  		logging.Debugf("popped %s:%d from heap", bech32.Encode("ln", vertices[partialPath.Node].Node[:]), vertices[partialPath.Node].CoinType)
   367  
   368  		p, ok := coinparam.RegisteredNets[vertices[partialPath.Node].CoinType]
   369  		if !ok {
   370  			logging.Debugf("ignoring %s:%d because cointype is unknown", bech32.Encode("ln", vertices[partialPath.Node].Node[:]), vertices[partialPath.Node].CoinType)
   371  			continue
   372  		}
   373  
   374  		fee := p.FeePerByte * 1000
   375  
   376  		for _, edge := range dEdges[partialPath.Node] {
   377  			logging.Debugf("considering edge %s:%d", bech32.Encode("ln", vertices[edge.V].Node[:]), vertices[edge.V].CoinType)
   378  
   379  			amtRqd := partialPath.Amt
   380  
   381  			if amtRqd < consts.MinOutput+fee {
   382  				// this amount is too small to route
   383  				logging.Debugf("ignoring %x:%d->%x:%d because amount rqd: %d less than minOutput+fee: %d", vertices[edge.U].Node, vertices[edge.U].CoinType, vertices[edge.V].Node, vertices[edge.V].CoinType, amtRqd, consts.MinOutput+fee)
   384  				continue
   385  			}
   386  
   387  			if amtRqd > edge.Capacity {
   388  				// this channel doesn't have enough capacity
   389  				logging.Debugf("ignoring %x:%d->%x:%d because amount rqd: %d less than capacity: %d", vertices[edge.U].Node, vertices[edge.U].CoinType, vertices[edge.V].Node, vertices[edge.V].CoinType, amtRqd, edge.Capacity)
   390  				continue
   391  			}
   392  
   393  			nextAmt := amtRqd
   394  
   395  			// required capacity for next hop is last hop amt * rate
   396  			if edge.Rate.Reciprocal {
   397  				// prior hop coin type is worth less than this one
   398  				nextAmt /= edge.Rate.Rate
   399  			} else {
   400  				// prior hop coin type is worth more than this one
   401  				nextAmt *= edge.Rate.Rate
   402  			}
   403  
   404  			alt := dDistance[partialPath.Node].Dist + edge.W
   405  			if dDistance[edge.V] == nil {
   406  				dDistance[edge.V] = &nodeWithDist{
   407  					alt,
   408  					edge.V,
   409  					nextAmt,
   410  				}
   411  			} else if alt < dDistance[edge.V].Dist {
   412  				dDistance[edge.V].Dist = alt
   413  				dDistance[edge.V].Amt = nextAmt
   414  			} else {
   415  				continue
   416  			}
   417  
   418  			logging.Debugf("could use edge %s:%d->%s:%d amt: %d capacity: %d", bech32.Encode("ln", vertices[edge.U].Node[:]), vertices[edge.U].CoinType, bech32.Encode("ln", vertices[edge.V].Node[:]), vertices[edge.V].CoinType, amtRqd, edge.Capacity)
   419  
   420  			predecessor[edge.V] = edge.U
   421  			heap.Push(&nodeHeap, *dDistance[edge.V])
   422  		}
   423  	}
   424  
   425  	for target, dist := range dDistance {
   426  		if dist != nil && vertices[target].Terminus {
   427  			price := math.Exp(-dist.Dist)
   428  			logging.Debugf("%s:%d: cap (recv): %d, price: %f64, cap (send): %d", bech32.Encode("ln", vertices[target].Node[:]), vertices[target].CoinType, dist.Amt, price, int(float64(dist.Amt)/price))
   429  		}
   430  	}
   431  
   432  	if dDistance[targetId] == nil {
   433  		return nil, fmt.Errorf("no route from origin to destination could be found")
   434  	}
   435  
   436  	routeIds := []int{predecessor[targetId], targetId}
   437  	for predecessor[routeIds[0]] != -1 {
   438  		routeIds = append([]int{predecessor[routeIds[0]]}, routeIds...)
   439  	}
   440  
   441  	var route []lnutil.RouteHop
   442  	for _, id := range routeIds {
   443  		route = append(route, lnutil.RouteHop{vertices[id].Node, vertices[id].CoinType})
   444  	}
   445  
   446  	return route, nil
   447  }
   448  
   449  func (nd *LitNode) cleanStaleChannels() {
   450  	nd.ChannelMapMtx.Lock()
   451  	defer nd.ChannelMapMtx.Unlock()
   452  
   453  	newChannelMap := make(map[[20]byte][]LinkDesc)
   454  
   455  	now := time.Now().Unix()
   456  
   457  	for pkh, node := range nd.ChannelMap {
   458  		for _, channel := range node {
   459  			if channel.Link.Timestamp+consts.ChannelAdvTimeout >= now {
   460  				newChannelMap[pkh] = append(newChannelMap[pkh], channel)
   461  			}
   462  		}
   463  	}
   464  
   465  	nd.ChannelMap = newChannelMap
   466  }
   467  
   468  func (nd *LitNode) advertiseLinks(seq uint32) {
   469  	caps := make(map[[20]byte]map[uint32]int64)
   470  
   471  	nd.RemoteMtx.Lock()
   472  	for _, peer := range nd.RemoteCons {
   473  		for _, q := range peer.QCs {
   474  			if !q.CloseData.Closed && q.State.MyAmt >= 2*(consts.MinOutput+q.State.Fee) && !q.State.Failed {
   475  				outHash := fastsha256.Sum256(peer.Con.RemotePub().SerializeCompressed())
   476  				var BPKH [20]byte
   477  				copy(BPKH[:], outHash[:20])
   478  
   479  				if _, ok := caps[BPKH]; !ok {
   480  					caps[BPKH] = make(map[uint32]int64)
   481  				}
   482  
   483  				amt := q.State.MyAmt - consts.MinOutput - q.State.Fee
   484  
   485  				// Since we don't yet perform multi-path routing, the capacity
   486  				// is the maximum available single channel capacity
   487  				if caps[BPKH][q.Coin()] < amt {
   488  					caps[BPKH][q.Coin()] = amt
   489  				}
   490  			}
   491  		}
   492  	}
   493  
   494  	nd.RemoteMtx.Unlock()
   495  
   496  	var msgs []lnutil.LinkMsg
   497  
   498  	outHash := fastsha256.Sum256(nd.IdKey().PubKey().SerializeCompressed())
   499  	var APKH [20]byte
   500  	copy(APKH[:], outHash[:20])
   501  
   502  	for BPKH, node := range caps {
   503  		for coin, capacity := range node {
   504  			var outmsg lnutil.LinkMsg
   505  			outmsg.CoinType = coin
   506  			outmsg.Seq = seq
   507  
   508  			outmsg.APKH = APKH
   509  			outmsg.BPKH = BPKH
   510  
   511  			outmsg.ACapacity = capacity
   512  
   513  			if rates, ok := nd.ExchangeRates[coin]; ok {
   514  				outmsg.Rates = rates
   515  			}
   516  
   517  			outmsg.PeerIdx = math.MaxUint32
   518  
   519  			msgs = append(msgs, outmsg)
   520  		}
   521  	}
   522  
   523  	for _, msg := range msgs {
   524  		nd.LinkMsgHandler(msg)
   525  	}
   526  }
   527  
   528  func (nd *LitNode) LinkMsgHandler(msg lnutil.LinkMsg) {
   529  	nd.ChannelMapMtx.Lock()
   530  	defer nd.ChannelMapMtx.Unlock()
   531  	nd.RemoteMtx.Lock()
   532  	defer nd.RemoteMtx.Unlock()
   533  
   534  	msg.Timestamp = time.Now().Unix()
   535  	newChan := true
   536  
   537  	// Check if node exists as a router
   538  	if _, ok := nd.ChannelMap[msg.APKH]; ok {
   539  		// Check if link state is most recent (seq)
   540  		for i, v := range nd.ChannelMap[msg.APKH] {
   541  			if bytes.Equal(v.Link.BPKH[:], msg.BPKH[:]) && v.Link.CoinType == msg.CoinType {
   542  				// This is the link we've been looking for
   543  				if msg.Seq <= v.Link.Seq {
   544  					// Old advert
   545  					return
   546  				}
   547  
   548  				// Update channel map
   549  				nd.ChannelMap[msg.APKH][i].Link = msg
   550  				nd.ChannelMap[msg.APKH][i].Dirty = false
   551  
   552  				newChan = false
   553  				break
   554  			}
   555  		}
   556  	}
   557  
   558  	if newChan {
   559  		// New peer or new channel
   560  		nd.ChannelMap[msg.APKH] = append(nd.ChannelMap[msg.APKH], LinkDesc{msg, false})
   561  	}
   562  
   563  	// Rebroadcast
   564  	origIdx := msg.PeerIdx
   565  
   566  	for peerIdx := range nd.RemoteCons {
   567  		if peerIdx != origIdx {
   568  			msg.PeerIdx = peerIdx
   569  
   570  			go func(omsg lnutil.LinkMsg) {
   571  				nd.tmpSendLitMsg(omsg)
   572  			}(msg)
   573  		}
   574  	}
   575  }
   576  
   577  func (nd *LitNode) PopulateRates() error {
   578  	ratesPath := filepath.Join(nd.LitFolder, "rates.json")
   579  
   580  	jsonFile, err := os.Open(ratesPath)
   581  	if err != nil {
   582  		return err
   583  	}
   584  	defer jsonFile.Close()
   585  
   586  	byteValue, err := ioutil.ReadAll(jsonFile)
   587  	if err != nil {
   588  		return err
   589  	}
   590  	err = json.Unmarshal(byteValue, &nd.ExchangeRates)
   591  	if err != nil {
   592  		return err
   593  	}
   594  
   595  	return nil
   596  }