All files / lib/gossip 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 117 118 119 120 121 122 123 124 125      1x     1x   1x 1x 1x 1x 1x                 1x 13x   13x             13x 13x 13x   13x       4x                   13x               13x 7x 7x 7x           6x 2x   4x 4x                 13x   13x 13x       13x     13x     13x 13x         4x 4x 4x       7x 7x 7x 7x         15x   15x 15x 15x 15x       15x      
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() {
    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);
  }
}