@node-dlc/wire
Version:
Lightning Network Wire Protocol
125 lines (106 loc) • 3.42 kB
text/typescript
import { ShortChannelId } from '@node-dlc/common';
import { ILogger } from '@node-dlc/logger';
import { QueryShortChannelIdsMessage } from '../messages/QueryShortChannelIdsMessage';
import { ReplyShortChannelIdsEndMessage } from '../messages/ReplyShortChannelIdsEndMessage';
import { IMessageSender } from '../Peer';
import { GossipError, GossipErrorCode } from './GossipError';
export enum ChannelsQueryState {
Idle,
Active,
Complete,
Failed,
}
/**
* This class manages the state machine for executing query_short_channel_ids
* and will resolve as either complete or with an error. This class can accept
* an arbitrarily large number of short channel ids and will chunk the requests
* appropriately.
*/
export class ChannelsQuery {
public chunkSize = 2000;
private _queue: ShortChannelId[] = [];
private _state: ChannelsQueryState;
private _error: GossipError;
private _resolve: () => void;
private _reject: (error: GossipError) => void;
constructor(
readonly chainHash: Buffer,
readonly peer: IMessageSender,
readonly logger: ILogger,
) {
this._state = ChannelsQueryState.Idle;
}
public get state(): ChannelsQueryState {
return this._state;
}
public get error(): Error {
return this._error;
}
public handleReplyShortChannelIdsEnd(
msg: ReplyShortChannelIdsEndMessage,
): void {
this.logger.debug(
'received reply_short_channel_ids_end - complete: %d',
msg.complete,
);
// If we receive a reply with complete=false, the remote peer
// does not maintain up-to-date channel information for the
// requested chain_hash
if (!msg.complete) {
const error = new GossipError(GossipErrorCode.ReplyChannelsNoInfo, msg);
this._transitionFailed(error);
return;
}
// This occurs when the last batch of information has been received
// but there is still more short_channel_ids to request. This scenario
// requires sending another QueryShortIds message
if (this._queue.length > 0) {
this._sendQuery();
} else {
this._transitionSuccess();
return;
}
}
/**
*
* @param scids
*/
public query(...scids: ShortChannelId[]): Promise<void> {
return new Promise((resolve, reject) => {
// enqueue the short ids
for (const scid of scids) {
this._queue.push(scid);
}
// Ensure we are in the active state
this._state = ChannelsQueryState.Active;
// send our query to the peer
this._sendQuery();
// capture the promise method for use when complete
this._resolve = resolve;
this._reject = reject;
});
}
private _transitionSuccess() {
if (this._state !== ChannelsQueryState.Active) return;
this._state = ChannelsQueryState.Complete;
this._resolve();
}
private _transitionFailed(error: GossipError) {
if (this._state !== ChannelsQueryState.Active) return;
this._state = ChannelsQueryState.Failed;
this._error = error;
this._reject(error);
}
private _sendQuery() {
// splice a chunk of work to do from the suqery
const scids = this._queue.splice(0, this.chunkSize);
const msg = new QueryShortChannelIdsMessage();
msg.chainHash = this.chainHash;
msg.shortChannelIds = scids;
this.logger.debug(
'sending query_short_channel_ids - scid_count:',
scids.length,
);
this.peer.sendMessage(msg);
}
}