All files / lib/gossip/queries ChannelsQuery.ts

93.62% Statements 44/47
80% Branches 8/10
90% Functions 9/10
97.78% Lines 44/45

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    1x     1x   1x 1x 1x 1x 1x                 1x 18x   18x             18x 18x 18x   18x       4x               63x         63x 8x 8x 8x           55x 49x   6x 6x                 16x   16x 96015x       16x     16x     16x 16x         6x 6x 6x       8x 8x 8x 8x         65x   65x 65x 65x 65x 65x      
import { ShortChannelId } from "@node-lightning/core";
import { ILogger } from "@node-lightning/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() {
        Iif (this._state !== ChannelsQueryState.Active) return;
        this._state = ChannelsQueryState.Complete;
        this._resolve();
    }
 
    private _transitionFailed(error: GossipError) {
        Iif (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);
    }
}