1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

added dot generation in router

This commit is contained in:
pm47 2017-02-03 17:47:50 +01:00
parent d182d9db6e
commit 6d28879ebb
3 changed files with 77 additions and 18 deletions

View File

@ -1,17 +1,20 @@
package fr.acinq.eclair.router
import java.io.{ByteArrayOutputStream, OutputStreamWriter}
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.pattern.pipe
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.Script.{pay2wsh, write}
import fr.acinq.eclair._
import fr.acinq.bitcoin.{BinaryData, Transaction}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.{GetTx, GetTxResponse, WatchEventSpent, WatchSpent}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire._
import org.jgrapht.alg.shortestpath.DijkstraShortestPath
import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge}
import org.jgrapht.ext._
import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge, SimpleGraph}
import scala.collection.JavaConversions._
import scala.concurrent.duration._
@ -42,11 +45,11 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
def receive: Receive = main(nodes = Map(), channels = Map(), updates = Map(), rebroadcast = Nil, awaiting = Set(), stash = Nil)
def mainWithLog(nodes: Map[BinaryData, NodeAnnouncement],
channels: Map[Long, ChannelAnnouncement],
updates: Map[ChannelDesc, ChannelUpdate],
rebroadcast: Seq[RoutingMessage],
awaiting: Set[ChannelAnnouncement],
stash: Seq[RoutingMessage]) = {
channels: Map[Long, ChannelAnnouncement],
updates: Map[ChannelDesc, ChannelUpdate],
rebroadcast: Seq[RoutingMessage],
awaiting: Set[ChannelAnnouncement],
stash: Seq[RoutingMessage]) = {
log.info(s"current status channels=${channels.size} nodes=${nodes.size} updates=${updates.size}")
main(nodes, channels, updates, rebroadcast, awaiting, stash)
}
@ -167,6 +170,8 @@ class Router(watcher: ActorRef) extends Actor with ActorLogging {
case 'updates => sender ! updates.values
case 'dot => graph2dot(nodes, channels) pipeTo sender
case RouteRequest(start, end) => findRoute(start, end, updates).map(RouteResponse(_)) pipeTo sender
case other => log.warning(s"unhandled message $other")
@ -203,4 +208,40 @@ object Router {
findRouteDijkstra(localNodeId, targetNodeId, updates.keys)
.map(desc => Hop(desc.a, desc.b, updates(desc)))
}
def graph2dot(nodes: Map[BinaryData, NodeAnnouncement], channels: Map[Long, ChannelAnnouncement])(implicit ec: ExecutionContext): Future[BinaryData] = Future {
case class DescEdge(channelId: Long) extends DefaultEdge
val g = new SimpleGraph[BinaryData, DescEdge](classOf[DescEdge])
channels.foreach(d => {
g.addVertex(d._2.nodeId1)
g.addVertex(d._2.nodeId2)
g.addEdge(d._2.nodeId1, d._2.nodeId2, new DescEdge(d._1))
})
val vertexIDProvider = new ComponentNameProvider[BinaryData]() {
override def getName(nodeId: BinaryData): String = "\"" + nodeId.toString() + "\""
}
val edgeLabelProvider = new ComponentNameProvider[DescEdge]() {
override def getName(e: DescEdge): String = e.channelId.toString
}
val vertexAttributeProvider = new ComponentAttributeProvider[BinaryData]() {
override def getComponentAttributes(nodeId: BinaryData): java.util.Map[String, String] =
nodes.get(nodeId) match {
case Some(ann) => Map("label" -> ann.alias, "color" -> f"#${ann.rgbColor._1}%02x${ann.rgbColor._2}%02x${ann.rgbColor._3}%02x")
case None => Map.empty[String, String]
}
}
val exporter = new DOTExporter[BinaryData, DescEdge](vertexIDProvider, null, edgeLabelProvider, vertexAttributeProvider, null)
val bos = new ByteArrayOutputStream()
val writer = new OutputStreamWriter(bos)
try {
exporter.exportGraph(g, writer)
bos.toByteArray
} finally {
writer.close()
bos.close()
}
}
}

View File

@ -31,12 +31,12 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
val ann_a = NodeAnnouncement(DUMMY_SIG, 0, a, (0, 0, 0), "node-A", "0000", Nil)
val ann_b = NodeAnnouncement(DUMMY_SIG, 0, b, (0, 0, 0), "node-B", "0000", Nil)
val ann_c = NodeAnnouncement(DUMMY_SIG, 0, c, (0, 0, 0), "node-C", "0000", Nil)
val ann_d = NodeAnnouncement(DUMMY_SIG, 0, d, (0, 0, 0), "node-D", "0000", Nil)
val ann_e = NodeAnnouncement(DUMMY_SIG, 0, e, (0, 0, 0), "node-E", "0000", Nil)
val ann_f = NodeAnnouncement(DUMMY_SIG, 0, f, (0, 0, 0), "node-F", "0000", Nil)
val ann_a = NodeAnnouncement(DUMMY_SIG, 0, a, (15, 10, -70), "node-A", "0000", Nil)
val ann_b = NodeAnnouncement(DUMMY_SIG, 0, b, (50, 99, -80), "node-B", "0000", Nil)
val ann_c = NodeAnnouncement(DUMMY_SIG, 0, c, (123, 100, -40), "node-C", "0000", Nil)
val ann_d = NodeAnnouncement(DUMMY_SIG, 0, d, (-120, -20, 60), "node-D", "0000", Nil)
val ann_e = NodeAnnouncement(DUMMY_SIG, 0, e, (-50, 0, 10), "node-E", "0000", Nil)
val ann_f = NodeAnnouncement(DUMMY_SIG, 0, f, (30, 10, -50), "node-F", "0000", Nil)
val channelId_ab = toShortId(420000, 1, 0)
val channelId_bc = toShortId(420000, 2, 0)

View File

@ -1,9 +1,13 @@
package fr.acinq.eclair.router
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, File}
import javafx.application.Platform
import akka.actor.Status.Failure
import akka.testkit.TestProbe
import com.google.common.io.Files
import fr.acinq.bitcoin.Script.{pay2wsh, write}
import fr.acinq.bitcoin.{Satoshi, Transaction, TxOut}
import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction, TxOut}
import fr.acinq.eclair.blockchain.{GetTx, GetTxResponse, WatchEventSpent, WatchSpent}
import fr.acinq.eclair.channel.BITCOIN_FUNDING_OTHER_CHANNEL_SPENT
import fr.acinq.eclair.toShortId
@ -59,7 +63,7 @@ class RouterSpec extends BaseRouterSpec {
}
test("route not found (unreachable target)") { case (router, watcher) =>
test("route not found (unreachable target)") { case (router, _) =>
val sender = TestProbe()
// no route a->f
sender.send(router, RouteRequest(a, f))
@ -67,7 +71,7 @@ class RouterSpec extends BaseRouterSpec {
assert(res.cause.getMessage === "route not found")
}
test("route not found (non-existing source)") { case (router, watcher) =>
test("route not found (non-existing source)") { case (router, _) =>
val sender = TestProbe()
// no route a->f
sender.send(router, RouteRequest(randomPubkey, f))
@ -75,7 +79,7 @@ class RouterSpec extends BaseRouterSpec {
assert(res.cause.getMessage === "graph must contain the source vertex")
}
test("route not found (non-existing target)") { case (router, watcher) =>
test("route not found (non-existing target)") { case (router, _) =>
val sender = TestProbe()
// no route a->f
sender.send(router, RouteRequest(a, randomPubkey))
@ -83,7 +87,7 @@ class RouterSpec extends BaseRouterSpec {
assert(res.cause.getMessage === "graph must contain the sink vertex")
}
test("route found") { case (router, watcher) =>
test("route found") { case (router, _) =>
val sender = TestProbe()
sender.send(router, RouteRequest(a, d))
val res = sender.expectMsgType[RouteResponse]
@ -91,4 +95,18 @@ class RouterSpec extends BaseRouterSpec {
assert(res.hops.last.nextNodeId === d.toBin)
}
ignore("export graph in dot format") { case (router, _) =>
val sender = TestProbe()
sender.send(router, 'dot)
val dot = sender.expectMsgType[BinaryData]
Files.write(dot.toArray, new File("graph.dot"))
import scala.sys.process._
val input = new ByteArrayInputStream(dot.toArray)
val output = new ByteArrayOutputStream()
"dot -Tpng" #< input #> output !
val img = output.toByteArray
Files.write(img, new File("graph.png"))
}
}