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 }