Skip to content

Commit

Permalink
fix error emission in channels subscription
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbosworth committed Dec 11, 2019
1 parent d505bb0 commit fe9625c
Show file tree
Hide file tree
Showing 17 changed files with 320 additions and 55 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Versions

## 47.5.6

- `subscribeToChannels`: Fix incorrect error emission

## 47.5.5

Introducing `safe_fee` and `safe_tokens` for payments. This represents token
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3349,7 +3349,7 @@ Subscribe to the status of a past payment
Requires LND built with `routerrpc` build tag

{
[id]: <Payment Request Hash Hex String>
id: <Payment Request Hash Hex String>
lnd: <Authenticated Lnd gRPC API Object>
}

Expand Down
4 changes: 4 additions & 0 deletions grpc/protos/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2293,6 +2293,9 @@ message InvoiceHTLC {
/// Current state the htlc is in.
InvoiceHTLCState state = 8 [json_name = "state"];

/// Custom tlv records.
map<uint64, bytes> custom_records = 9 [json_name = "custom_records"];

enum InvoiceHTLCState {
ACCEPTED = 0;
SETTLED = 1;
Expand Down Expand Up @@ -2512,6 +2515,7 @@ message PayReq {
int64 cltv_expiry = 9 [json_name = "cltv_expiry"];
repeated RouteHint route_hints = 10 [json_name = "route_hints"];
bytes payment_addr = 11 [json_name = "payment_addr"];
int64 num_msat = 12 [json_name = "num_msat"];
}

message FeeReportRequest {}
Expand Down
10 changes: 10 additions & 0 deletions invoices/settle_hodl_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const {returnResult} = require('asyncjs-util');

const bufferFromHex = hex => Buffer.from(hex, 'hex');
const expectedSecretLen = 64;
const htlcNotYetAcceptedError = 'invoice still open';
const invalidSecretError = 'unable to locate invoice';

/** Settle hodl invoice
Expand Down Expand Up @@ -38,6 +40,14 @@ module.exports = ({lnd, secret}, cbk) => {
preimage: bufferFromHex(secret),
},
err => {
if (!!err && err.details === htlcNotYetAcceptedError) {
return cbk([402, 'CannotSettleHtlcBeforeHtlcReceived']);
}

if (!!err && err.details === invalidSecretError) {
return cbk([404, 'SecretDoesNotMatchAnyExistingHodlInvoice']);
}

if (!!err) {
return cbk([503, 'UnexpectedErrorWhenSettlingHodlInvoice', {err}]);
}
Expand Down
2 changes: 2 additions & 0 deletions lightning/create_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const {round} = Math;
created_at: <ISO 8601 Date String>
description: <Description String>
id: <Payment Hash Hex String>
mtokens: <Millitokens String>
request: <BOLT 11 Encoded Payment Request String>
secret: <Hex Encoded Payment Secret String>
tokens: <Tokens Number>
Expand Down Expand Up @@ -149,6 +150,7 @@ module.exports = (args, cbk) => {
created_at: getInvoice.created_at,
description: addInvoice.description,
id: addInvoice.id,
mtokens: getInvoice.mtokens,
request: addInvoice.request,
secret: getInvoice.secret,
tokens: addInvoice.tokens || 0,
Expand Down
8 changes: 6 additions & 2 deletions lightning/decode_payment_request.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module.exports = ({lnd, request}, cbk) => {
// Get payment request values
values: ['validate', ({}, cbk) => {
try {
// Native parsing is used to get millitokens in LND 0.8.1 and prior
const parsed = parsePaymentRequest({request});

return cbk(null, parsed);
Expand Down Expand Up @@ -113,7 +114,11 @@ module.exports = ({lnd, request}, cbk) => {

const expiryDateMs = createdAtMs + (expiresInMs || defaultExpireMs);

// LND versions 0.8.1 and below do not return precision
const mtokens = res.num_msat !== '0' ? res.num_msat : values.mtokens;

return cbk(null, {
mtokens,
chain_address: res.fallback_addr || undefined,
cltv_delta: parseInt(res.cltv_expiry || 0, decBase) || undefined,
created_at: new Date(createdAtMs).toISOString(),
Expand All @@ -123,12 +128,11 @@ module.exports = ({lnd, request}, cbk) => {
expires_at: new Date(expiryDateMs).toISOString(),
id: res.payment_hash,
is_expired: now() > expiryDateMs,
mtokens: values.mtokens,
routes: res.route_hints.map(route => routeFromRouteHint({
destination: res.destination,
hop_hints: route.hop_hints,
})),
safe_tokens: values.safe_tokens,
safe_tokens: safeTokens({mtokens}).safe,
tokens: Number(res.num_satoshis),
});
});
Expand Down
8 changes: 3 additions & 5 deletions lightning/get_channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const asyncMap = require('async/map');
const {chanFormat} = require('bolt07');
const {returnResult} = require('asyncjs-util');

const {isLnd} = require('./../grpc');

const decBase = 10;
const {isArray} = Array;
const msPerSec = 1e3;
Expand Down Expand Up @@ -60,7 +62,7 @@ module.exports = (args, cbk) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!args.lnd || !args.lnd.default || !args.lnd.default.listChannels) {
if (!isLnd({lnd: args.lnd, method: 'listChannels', type: 'default'})) {
return cbk([400, 'ExpectedLndToGetChannels']);
}

Expand Down Expand Up @@ -99,10 +101,6 @@ module.exports = (args, cbk) => {
return cbk([503, 'ExpectedChannelCapacity']);
}

if (!channel.chan_id) {
return cbk([503, 'ExpectedChanId']);
}

try {
const _ = chanFormat({number: channel.chan_id});
} catch (err) {
Expand Down
5 changes: 4 additions & 1 deletion lightning/get_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const mtokensPerToken = BigInt('1000');
[is_held]: <HTLC is Held Bool>
is_outgoing: <Invoice is Outgoing Bool>
is_private: <Invoice is Private Bool>
mtokens: <Millitokens Number>
payments: [{
[confirmed_at]: <Payment Settled At ISO 8601 Date String>
created_at: <Payment Held Since ISO 860 Date String>
Expand Down Expand Up @@ -114,6 +115,8 @@ module.exports = ({id, lnd}, cbk) => {

const createdAtMs = createdAtEpochTime * msPerSec;

const mtok = (BigInt(response.value) * mtokensPerToken).toString();

return cbk(null, {
id,
chain_address: response.fallback_addr || undefined,
Expand All @@ -127,7 +130,7 @@ module.exports = ({id, lnd}, cbk) => {
is_confirmed: response.settled,
is_held: response.state === 'ACCEPTED' || undefined,
is_private: response.private,
mtokens: (BigInt(response.value) * mtokensPerToken).toString(),
mtokens: response.value_msat === '0' ? mtok : response.value_msat,
payments: response.htlcs.map(htlcAsPayment),
received: parseInt(response.amt_paid_sat, decBase),
received_mtokens: response.amt_paid_msat,
Expand Down
72 changes: 39 additions & 33 deletions lightning/subscribe_to_channels.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,20 @@ module.exports = ({lnd}) => {
const eventEmitter = new EventEmitter();
const subscription = lnd.default.subscribeChannelEvents({});

const error = err => {
// Exit early when no one is listening to the error
if (!eventEmitter.listenerCount('error')) {
return;
}

return eventEmitter.emit('error', err);
};

subscription.on('data', update => {
const updatedAt = new Date().toISOString();

if (!update || !update.type || typeof update.type !== 'string') {
return eventEmitter.emit('error', new Error('UnexpectedDataInChanSub'));
return error(new Error('UnexpectedDataInChanSubscription'));
}

const updateType = update.type.toLowerCase();
Expand All @@ -94,11 +103,11 @@ module.exports = ({lnd}) => {
case updateTypes.channel_activated:
case updateTypes.channel_deactivated:
if (!update[updateType].funding_txid_bytes) {
return eventEmitter.emit('error', new Error('ExpectedActiveChanTxId'));
return error(new Error('ExpectedActiveChannelTransactionIdInEvent'));
}

if (update[updateType].output_index) {
return eventEmitter.emit('error', new Error('ExpectedActiveChanVout'));
if (update[updateType].output_index === undefined) {
return error(new Error('ExpectedActiveChannelVoutInUpdateEvent'));
}

const changedTxId = update[updateType].funding_txid_bytes.reverse();
Expand All @@ -112,42 +121,42 @@ module.exports = ({lnd}) => {

case updateTypes.channel_closed:
if (!update[updateType].capacity) {
eventEmitter.emit('error', new Error('ExpectedClosedChannelCapacity'));
error(new Error('ExpectedClosedChannelCapacityInCloseEvent'));
break;
}

if (!update[updateType].chan_id) {
eventEmitter.emit('error', new Error('ExpectedClosedChannelId'));
error(new Error('ExpectedClosedChannelIdInCloseEvent'));
break;
}

if (!update[updateType].channel_point) {
eventEmitter.emit('error', new Error('ExpectedClosedChannelOutpoint'));
error(new Error('ExpectedClosedChannelOutpointInCloseEvent'));
break;
}

if (update[updateType].close_height === undefined) {
eventEmitter.emit('error', new Error('ExpectedClosedChannelHeight'));
error(new Error('ExpectedClosedChannelHeightInCloseEvent'));
break;
}

if (!update[updateType].closing_tx_hash) {
eventEmitter.emit('error', new Error('ExpectedClosedChannelTxId'));
error(new Error('ExpectedClosedChannelTransactionIdInCloseEvent'));
break;
}

if (!update[updateType].remote_pubkey) {
eventEmitter.emit('error', new Error('ExpectedClosedChanPeerPubKey'));
error(new Error('ExpectedClosedChanPeerPubKeyInCloseEvent'));
break;
}

if (!update[updateType].settled_balance) {
eventEmitter.emit('error', new Error('ExpectedClosedChanBalance'));
error(new Error('ExpectedClosedChanBalanceInCloseEvent'));
break;
}

if (!update[updateType].time_locked_balance) {
eventEmitter.emit('error', new Error('ExpectedClosedTimelockedFunds'));
error(new Error('ExpectedClosedTimelockedFundsInCloseEvent'));
break;
}

Expand Down Expand Up @@ -181,93 +190,90 @@ module.exports = ({lnd}) => {
const channel = update[updateType];

if (channel.active === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenChanActiveState'));
error(new Error('ExpectedOpenChanActiveState'));
break;
}

if (channel.capacity === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenChannelCapacity'));
error(new Error('ExpectedOpenChannelCapacity'));
break;
}

if (!channel.channel_point) {
eventEmitter.emit('error', new Error('ExpectedOpenChannelPoint'));
error(new Error('ExpectedOpenChannelPoint'));
break;
}

if (channel.commit_fee === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenCommitFee'));
error(new Error('ExpectedOpenCommitFee'));
break;
}

if (channel.commit_weight === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenCommitWeight'));
error(new Error('ExpectedOpenCommitWeight'));
break;
}

if (channel.fee_per_kw === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenFeePerKw'));
error(new Error('ExpectedOpenFeePerKw'));
break;
}

if (channel.local_balance === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenLocalBalance'));
error(new Error('ExpectedOpenLocalBalance'));
break;
}

if (channel.num_updates === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenNumUpdates'));
error(new Error('ExpectedOpenNumUpdates'));
break;
}

if (!Array.isArray(channel.pending_htlcs)) {
eventEmitter.emit('error', new Error('ExpectedOpenChanPendingHtlcs'));
error(new Error('ExpectedOpenChanPendingHtlcs'));
break;
}

if (channel.private !== true && channel.private !== false) {
eventEmitter.emit('error', new Error('ExpectedOpenChanPrivateStatus'));
error(new Error('ExpectedOpenChanPrivateStatus'));
break;
}

if (channel.remote_balance === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenRemoteBalance'));
error(new Error('ExpectedOpenRemoteBalance'));
break;
}

if (!channel.remote_pubkey) {
eventEmitter.emit('error', new Error('ExpectedOpenRemotePubkey'));
error(new Error('ExpectedOpenRemotePubkey'));
break;
}

if (channel.total_satoshis_received === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenChanTotalReceived'));
error(new Error('ExpectedOpenChanTotalReceived'));
break;
}

if (channel.total_satoshis_sent === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenChannelTotalSent'));
error(new Error('ExpectedOpenChannelTotalSent'));
break;
}

if (channel.unsettled_balance === undefined) {
eventEmitter.emit('error', new Error('ExpectedOpenChanUnsettled'));
error(new Error('ExpectedOpenChanUnsettled'));
break;
}

const {initiator} = channel;
const [transactionId, vout] = channel.channel_point.split(':');

const notInitiator = initiator === false ? undefined : !initiator;

eventEmitter.emit('channel_opened', {
capacity: parseInt(channel.capacity, decBase),
commit_transaction_fee: parseInt(channel.commit_fee, decBase),
commit_transaction_weight: parseInt(channel.commit_weight, decBase),
is_active: channel.active,
is_closing: false,
is_opening: false,
is_partner_initiated: notInitiator,
is_partner_initiated: !channel.initiator,
is_private: channel.private,
local_balance: parseInt(channel.local_balance, decBase),
partner_public_key: channel.remote_pubkey,
Expand All @@ -287,15 +293,15 @@ module.exports = ({lnd}) => {
break;

default:
eventEmitter.emit('error', new Error('UnexpectedChannelUpdate'));
error(new Error('UnexpectedChannelUpdate'));
break;
}

return;
});

subscription.on('end', () => eventEmitter.emit('end'));
subscription.on('error', err => eventEmitter.emit('error', err));
subscription.on('error', err => error(err));
subscription.on('status', status => eventEmitter.emit('status', status));

return eventEmitter;
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@
"tower_server-integration-tests": "tap --no-coverage test/tower_serverrpc-integration/*.js",
"wallet-integration-tests": "tap --no-coverage test/walletrpc-integration/*.js"
},
"version": "47.5.5"
"version": "47.5.6"
}
Loading

0 comments on commit fe9625c

Please sign in to comment.