@node-lightning/wire
Version:
Lightning Network Wire Protocol
187 lines • 7.18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GossipPeer = exports.ReadState = void 0;
const stream_1 = require("stream");
const InitFeatureFlags_1 = require("../flags/InitFeatureFlags");
const MessageType_1 = require("../MessageType");
const PeerState_1 = require("../PeerState");
const GossipError_1 = require("./GossipError");
const GossipQueriesReceiver_1 = require("./GossipQueriesReceiver");
const GossipQueriesSync_1 = require("./GossipQueriesSync");
var ReadState;
(function (ReadState) {
ReadState[ReadState["Ready"] = 0] = "Ready";
ReadState[ReadState["Reading"] = 1] = "Reading";
ReadState[ReadState["Blocked"] = 2] = "Blocked";
})(ReadState = exports.ReadState || (exports.ReadState = {}));
/**
* Implements a decorator for the standard Peer adding functionality for Gossip
* related activities. Messages from the GossipPeer occur after being pushed
* through a GossipFilter to validate they are aokay.
*/
class GossipPeer extends stream_1.Readable {
/**
* This class expects to be instantiated by a peer that is read and will
* throw if the peer is not yet in the ready state. This ensures we can
* construct the GossipPeer correctly using the negotiated features
* @param peer
* @param filter
* @param logger
*/
constructor(peer, filter, logger) {
super({ objectMode: true, highWaterMark: peer.highWaterMark });
this.peer = peer;
this.filter = filter;
this._readState = ReadState.Ready;
// Enforce that the peer is ready to rock
if (peer.state !== PeerState_1.PeerState.Ready) {
throw new GossipError_1.GossipError(GossipError_1.GossipErrorCode.PeerNotReady);
}
// Construct a sub-logger for use by the Gossiping systems
this.logger = logger.sub("gspeer", peer.id);
// Attach the appropriate events, many of them will simply be forwarded
// but we will intercept messages and funnel appropriate messages
// through the filter.
this.peer.on("readable", this._onPeerReadable.bind(this));
this.peer.on("close", () => this.emit("close"));
if (this.gossipQueries) {
this._receiver = new GossipQueriesReceiver_1.GossipQueriesReceiver(this.peer.localFeatures[0], this.peer, this.logger);
}
}
/**
* Returns true if the gossip_queries feature has been negotiated with the
* remote peer.
*/
get gossipQueries() {
return (this.peer.remoteFeatures.isSet(InitFeatureFlags_1.InitFeatureFlags.gossipQueriesOptional) ||
this.peer.remoteFeatures.isSet(InitFeatureFlags_1.InitFeatureFlags.gossipQueriesRequired));
}
/**
* Performs Gossip synchronization using the negotiated strategy. Currently
* only support gossip_queries
* @param firstBlock
* @param numBlocks
*/
async syncRange(firstBlock, numBlocks) {
if (this.gossipQueries) {
const chainHash = this.peer.localChains[0];
this._syncRangeTask = new GossipQueriesSync_1.GossipQueriesSync(chainHash, this, this.logger);
await this._syncRangeTask.queryRange(firstBlock, numBlocks);
this._syncRangeTask = undefined;
return true;
}
return false;
}
/**
* Enables the receipt of rumor mongered messages.
*/
enableGossip() {
if (this.gossipQueries) {
this._receiver.activate();
}
}
/**
* Disables the receipt of rumor mongered messages.
*/
disableGossip() {
if (this.gossipQueries) {
this._receiver.deactivate();
}
}
/**
* Sends the serialized data to the peer.
* @param buf
*/
send(buf) {
this.peer.send(buf);
}
/**
* Sends a message to the peer using the default serialization.
* @param msg
*/
sendMessage(msg) {
this.peer.sendMessage(msg);
}
/**
* Disconnects the peer
*/
disconnect() {
this.peer.disconnect();
}
/**
* Fires when a peer triggers the readable event. This method locks
* to ensure only a single read event occurs at a time.
* @returns
*/
async _onPeerReadable() {
while (this._readState === ReadState.Ready) {
this._readState = ReadState.Reading;
const msg = this.peer.read();
if (!msg) {
this._readState = ReadState.Ready;
return;
}
const filteredMsgs = await this._onPeerMessage(msg);
this._readState = ReadState.Ready;
for (const filteredMsg of filteredMsgs) {
if (!this.push(filteredMsg)) {
this._readState = ReadState.Blocked;
}
}
}
}
/**
* Internally process messages. If the message is a routing related message
* it will pass through the GossipFilter, otherwise it will be immediately
* broadcast.
* @param msg
*/
async _onPeerMessage(msg) {
// HACK: we're adding the sync task message tracking here
if (this._syncRangeTask) {
this._syncRangeTask.handleWireMessage(msg);
}
// Run gossip message through the filter
if (msg.type === MessageType_1.MessageType.ChannelAnnouncement ||
msg.type === MessageType_1.MessageType.ChannelUpdate ||
msg.type === MessageType_1.MessageType.NodeAnnouncement) {
try {
const result = await this.filter.validateMessage(msg);
if (result.isOk) {
return result.value;
}
else {
// Handled error should be emitted to the caller
// but we prevent the transform stream from
// stopping by calling the callback without an
// error.
this.emit("gossip_error", result.error);
}
// Unhandled error is something unexpected and our peer
// is now in a broken state and we need to disconnect.
}
catch (err) {
this.disconnect();
this.emit("error", err);
return [];
}
}
return [msg];
}
_read() {
// This method will be triggered when .read() is called by the
// consumer. Therefore we can consider the read unblocked.
if (this._readState === ReadState.Blocked) {
this._readState = ReadState.Ready;
}
// Trigger a read but wait until the end of the event loop.
// This is necessary when reading in paused mode where
// _read was triggered by stream.read() originating inside
// a "readable" event handler. Attempting to push more data
// synchronously will not trigger another "readable" event.
// eslint-disable-next-line @typescript-eslint/no-misused-promises
setImmediate(() => this._onPeerReadable());
}
}
exports.GossipPeer = GossipPeer;
//# sourceMappingURL=GossipPeer.js.map