@node-lightning/graph
Version:
Lightning Network P2P Graph
144 lines (127 loc) • 5.62 kB
text/typescript
import { OutPoint } from "@node-lightning/core";
import { IGossipEmitter, IWireMessage, MessageType } from "@node-lightning/wire";
import { ChannelAnnouncementMessage } from "@node-lightning/wire";
import { ChannelUpdateMessage } from "@node-lightning/wire";
import { NodeAnnouncementMessage } from "@node-lightning/wire";
import { EventEmitter } from "events";
import { Channel } from "./channel";
import { ChannelSettings } from "./channel-settings";
import { channelFromMessage } from "./deserialize/channel-from-message";
import { channelSettingsFromMessage } from "./deserialize/channel-settings-from-message";
import { Graph } from "./graph";
import { ChannelNotFoundError } from "./graph-error";
import { GraphError } from "./graph-error";
import { Node } from "./node";
// tslint:disable-next-line: interface-name
export declare interface GraphManager {
on(event: "node", fn: (node: Node) => void): this;
on(event: "channel", fn: (channel: Channel) => void): this;
on(event: "channel_update", fn: (channel: Channel, settings: ChannelSettings) => void): this;
on(event: "error", fn: (err: GraphError) => void): this;
}
/**
* GraphManager is a facade around a Graph object. It converts in-bound
* gossip messages from the wire into a graph representation. Channels
* can also be removed by monitoring the block chain via a chainmon object.
*/
export class GraphManager extends EventEmitter {
public graph: Graph;
public gossipEmitter: IGossipEmitter;
constructor(gossipManager: IGossipEmitter, graph = new Graph()) {
super();
this.graph = graph;
this.gossipEmitter = gossipManager;
this.gossipEmitter.on("message", this._onMessage.bind(this));
}
/**
* Closes channel via the outpoint
* @param outpoint
*/
public removeChannel(outpoint: OutPoint) {
const outpointStr = outpoint.toString();
for (const channel of this.graph.channels.values()) {
if (outpointStr === channel?.channelPoint?.toString()) {
this.graph.removeChannel(channel);
this.emit("channel_closed", channel);
return;
}
}
}
private _onMessage(msg: IWireMessage) {
// channel_announcement messages are processed by:
// First ensuring that we don't already have a duplicate channel.
// We then check to see if we need to insert node
// references. Inserting temporary node's is required because we
// may receieve a channel_announcement without ever receiving
// node_announcement messages.
if (isChannelAnnouncment(msg)) {
const channel = channelFromMessage(msg);
// abort processing if the channel already exists
if (this.graph.getChannel(msg.shortChannelId)) {
return;
}
// construct node1 if required
if (!this.graph.getNode(msg.nodeId1)) {
const node1 = new Node();
node1.nodeId = msg.nodeId1;
this.graph.addNode(node1);
this.emit("node", node1);
}
// construct node2 if required
if (!this.graph.getNode(msg.nodeId2)) {
const node2 = new Node();
node2.nodeId = msg.nodeId2;
this.graph.addNode(node2);
this.emit("node", node2);
}
// finally attach the channel
this.graph.addChannel(channel);
this.emit("channel", channel);
return;
}
// channel_update messages are processed by:
// * looking for the existing channel, if it doesn't then an error is thrown.
// * updating the existing channel
// The GossipFilter in Wire should ensure that channel_announcement messages
// are always transmitted prior to channel_update messages being announced.
if (isChannelUpdate(msg)) {
// first validate we have a channel
const channel = this.graph.getChannel(msg.shortChannelId);
if (!channel) {
this.emit("error", new ChannelNotFoundError(msg.shortChannelId));
return;
}
// construct the settings and update the channel
const settings = channelSettingsFromMessage(msg);
channel.updateSettings(settings);
this.emit("channel_update", channel, settings);
return;
}
// node_announcement messages are processed by:
// * finding or creating the node (if it doesn't exist)
// * updating the node with values from the announcement
if (isNodeAnnouncement(msg)) {
let node = this.graph.getNode(msg.nodeId);
if (!node) {
node = new Node();
node.nodeId = msg.nodeId;
this.graph.addNode(node);
}
node.features = msg.features;
node.lastUpdate = msg.timestamp;
node.alias = msg.alias;
node.rgbColor = msg.rgbColor;
node.addresses = msg.addresses;
this.emit("node", node);
}
}
}
function isChannelAnnouncment(msg: IWireMessage): msg is ChannelAnnouncementMessage {
return msg.type === MessageType.ChannelAnnouncement;
}
function isChannelUpdate(msg: IWireMessage): msg is ChannelUpdateMessage {
return msg.type === MessageType.ChannelUpdate;
}
function isNodeAnnouncement(msg: IWireMessage): msg is NodeAnnouncementMessage {
return msg.type === MessageType.NodeAnnouncement;
}