All files / lib graph-manager.ts

100% Statements 59/59
94.74% Branches 18/19
100% Functions 6/6
100% Lines 59/59

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144  1x       1x     1x 1x 1x 1x   1x                             1x         9x 9x 9x 9x               1x 1x 2x 1x 1x 1x                         14x 8x     8x 1x       7x 6x 6x 6x 6x       7x 6x 6x 6x 6x       7x 7x 7x               6x   3x 3x 1x 1x       2x 2x 2x 2x           3x 3x 3x 2x 2x 2x   3x 3x 3x 3x 3x 3x           14x       6x       3x    
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
        Eif (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;
}