balanceofsatoshis
Version: 
Lightning balance CLI
203 lines (168 loc) • 6.49 kB
JavaScript
const asyncAuto = require('async/auto');
const {getIdentity} = require('ln-service');
const {getNetwork} = require('ln-sync');
const {returnResult} = require('asyncjs-util');
const {subscribeToOpenRequests} = require('ln-service');
const detectOpenRuleViolation = require('./detect_open_rule_violation');
const openRequestViolation = require('./open_request_violation');
const {outputScriptForAddress} = require('./../chain');
const {isArray} = Array;
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
const isTooLongReason = n => Buffer.byteLength(n, 'utf8') > 500;
const notEmpty = n => !!n.length ? n : undefined;
/** Reject inbound channels
  {
    addresses: [<Cooperative Close Address String>]
    lnd: <Authenticated LND API Object>
    logger: <Winston Logger Object>
    [reason]: <Reason Error Message String>
    rules: [<Rule for Inbound Channel String>]
    trust: [<Trust Funding From Node With Identity Public Key Hex String>]
  }
*/
module.exports = ({addresses, lnd, logger, reason, rules, trust}, cbk) => {
  return new Promise((resolve, reject) => {
    return asyncAuto({
      // Check arguments
      validate: cbk => {
        if (!isArray(addresses)) {
          return cbk([400, 'ExpectedArrayOfCloseAddressesToInterceptChans']);
        }
        if (!lnd) {
          return cbk([400, 'ExpectedAuthenticatedLndToRejectInboundChannels']);
        }
        if (!logger) {
          return cbk([400, 'ExpectedWinstonLoggerToRejectInboundChannels']);
        }
        if (!!reason && isTooLongReason(reason)) {
          return cbk([400, 'ExpectedShorterRejectionReasonToRejectChannels']);
        }
        if (!isArray(rules)) {
          return cbk([400, 'ExpectedArrayOfRejectRulesToRejectChannels']);
        }
        if (!!trust.filter(n => !isPublicKey(n)).length) {
          return cbk([400, 'ExpectedValidTrustPublicKeysToInterceptChannels']);
        }
        if (!!rules.length) {
          // Check if a test request would cause any rules parsing errors
          try {
            openRequestViolation({
              rules,
              capacities: [1],
              capacity: 2,
              channel_ages: [],
              fee_rates: [3],
              is_clearnet: false,
              is_obsolete: false,
              is_private: false,
              is_tor: false,
              joint_public_capacity: 1,
              local_balance: 4,
              public_key: Buffer.alloc(33, 2).toString('hex'),
            });
          } catch (err) {
            return cbk([400, 'InvalidInboundChannelRequestOpenRule', {err}]);
          }
        }
        if (!isArray(trust)) {
          return cbk([400, 'ExpectedArrayOfTrustedKeysToInterceptChannels']);
        }
        return cbk();
      },
      // Check the cooperative close address
      checkAddress: ['validate', ({}, cbk) => {
        // Exit early when there is no address to check
        if (!addresses.length) {
          return cbk();
        }
        // Find the network of this node to compare it to the provided address
        return getNetwork({lnd}, (err, res) => {
          if (!!err) {
            return cbk(err);
          }
          // Exit early when network is not recognized
          if (!res.bitcoinjs) {
            return cbk();
          }
          // Make sure that the addresses look ok
          try {
            addresses.forEach(address => {
              outputScriptForAddress({address, network: res.network});
            });
          } catch (err) {
            return cbk([400, 'FailedToParseCooperativeCloseAddress', {err}]);
          }
          return cbk();
        });
      }],
      // Get the node key identity
      getIdentity: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
      // Subscribe to open requests
      subscribe: ['checkAddress', 'getIdentity', ({getIdentity}, cbk) => {
        const sub = subscribeToOpenRequests({lnd});
        // Copy the addresses into a pool
        const cooperativeCloseAddresses = addresses.slice();
        // Exit with error when there is an error
        sub.once('error', err => {
          sub.removeAllListeners();
          return cbk([503, 'UnexpectedErrorInOpenRequestsSub', {err}]);
        });
        logger.info({
          enforcing_inbound_channel_rules: rules,
          requesting_cooperative_close_address: notEmpty(addresses),
          do_not_require_conf_funds_from: notEmpty(trust),
        });
        sub.on('channel_request', request => {
          const peerId = request.partner_public_key;
          // Exit early when requester is not trusted for trusted funding
          if (!!request.is_trusted_funding && !trust.includes(peerId)) {
            logger.info({
              rejected: peerId,
              reason: {trusted_funding_not_configured_for_peer: true},
            });
            return request.reject({reason: 'TrustedFundingAccessDenied'});
          }
          return detectOpenRuleViolation({
            lnd,
            rules,
            capacity: request.capacity,
            id: getIdentity.public_key,
            is_private: request.is_private,
            local_balance: request.local_balance,
            partner_public_key: peerId,
            type: request.type,
          },
          (err, res) => {
            if (!!err) {
              logger.error({err});
              // Reject without reason when there is a generic failure
              return request.reject({});
            }
            // Exit early when a channel open rule violation rejects a channel
            if (!!res.rule) {
              logger.info({
                rejected: peerId,
                capacity: request.capacity,
                rule: res.rule,
              });
              return request.reject({reason});
            }
            // Restock cooperative addresses when depleted
            if (!!addresses.length && !cooperativeCloseAddresses.length) {
              addresses.forEach(n => cooperativeCloseAddresses.push(n));
            }
            // Cycle through cooperative close addresses
            const address = cooperativeCloseAddresses.shift();
            // Accept the channel open request
            return request.accept({
              cooperative_close_address: address,
              is_trusted_funding: request.is_trusted_funding,
            });
          });
          return;
        });
      }],
    },
    returnResult({reject, resolve}, cbk));
  });
};