ln-sync
Version:
LN metadata persistence methods
180 lines (144 loc) • 6.33 kB
JavaScript
const asyncAuto = require('async/auto');
const {getChannel} = require('ln-service');
const {getChannels} = require('ln-service');
const {getPendingChannels} = require('ln-service');
const {returnResult} = require('asyncjs-util');
const {updateRoutingFees} = require('ln-service');
const {abs} = Math;
const asOut = n => `${n.transaction_id}:${n.transaction_vout}`;
const feeRateBuffer = 1;
/** Update the fee for an individual channel
{
[base_fee_mtokens]: <Base Fee Millitokens String>
[cltv_delta]: <CLTV Delta to Use Number>
fee_rate: <Fee Rate Number>
from: <Local Node Public Key Hex String>
[inbound_rate_discount]: <Source Based PPM Fee Rate Discount Number>
lnd: <Authenticated LND API Object>
[max_htlc_mtokens]: <Maximum HTLC Millitokens to Forward String>
[min_htlc_mtokens]: <Minimum HTLC Millitokens to Forward String>
transaction_id: <Funding Transaction Id Hex String>
transaction_vout: <Funding Transaction Output Index Number>
}
@returns via cbk or Promise
*/
module.exports = (args, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (args.fee_rate === undefined) {
return cbk([400, 'ExpectedFeeRateToUpdateChannelFee']);
}
if (!args.from) {
return cbk([400, 'ExpectedFromPublicKeyToUpdateChannelFee']);
}
if (!args.lnd) {
return cbk([400, 'ExpectedLndToUpdateChannelFee']);
}
if (!args.transaction_id) {
return cbk([400, 'ExpectedTransactionIdToUpdateChannelFee']);
}
if (args.transaction_vout === undefined) {
return cbk([400, 'ExpectedTransactionVoutToUpdateChannelFee']);
}
return cbk();
},
// Get the list of pending channels
getPending: ['validate', ({}, cbk) => {
return getPendingChannels({lnd: args.lnd}, cbk);
}],
// Check that the channel is not pending
checkPending: ['getPending', ({getPending}, cbk) => {
const channel = args.transaction_id;
const pending = getPending.pending_channels.filter(n => n.is_opening);
if (!!pending.find(n => asOut(n) === asOut(args))) {
return cbk([503, 'ChannelToSetFeeRateForIsStillPending', {channel}]);
}
return cbk();
}],
// Get the list of active channels
getChannels: ['checkPending', ({}, cbk) => {
return getChannels({lnd: args.lnd}, cbk);
}],
// Get channel id
getChannelId: ['getChannels', ({getChannels}, cbk) => {
const chan = getChannels.channels.find(n => asOut(n) === asOut(args));
if (!chan) {
return cbk([404, 'ExpectedKnownChannelToUpdateChannelFee']);
}
return cbk(null, chan.id);
}],
// Get the existing channel routing policies
getChannel: ['getChannelId', ({getChannelId}, cbk) => {
return getChannel({lnd: args.lnd, id: getChannelId}, cbk);
}],
// Find the current policy for this channel
policy: ['getChannel', ({getChannel}, cbk) => {
const rate = getChannel.policies.find(n => n.public_key === args.from);
if (!rate) {
return cbk([404, 'ExpectedExistingChannelPolicyToUpdate']);
}
if (!rate.base_fee_mtokens) {
return cbk([404, 'UnexpectedMissingBaseFeeMtokensUpdatingChanFee']);
}
if (!rate.cltv_delta) {
return cbk([404, 'UnexpectedMissingCltvDeltaUpdatingChannelFee']);
}
return cbk(null, rate);
}],
// Determine what inbound rate discount should be applied
inbound: ['policy', ({policy}, cbk) => {
// Exit early with existing when there was no change to inbound rate
if (args.inbound_rate_discount === undefined) {
return cbk(null, {rate_discount: policy.inbound_rate_discount});
}
return cbk(null, {rate_discount: args.inbound_rate_discount});
}],
// Update the fee rate to the specified rate
updateFeeRate: ['inbound', 'policy', ({inbound, policy}, cbk) => {
const isFeeRateAdjusted = args.fee_rate !== undefined;
return updateRoutingFees({
base_fee_mtokens: args.base_fee_mtokens || policy.base_fee_mtokens,
cltv_delta: args.cltv_delta || policy.cltv_delta,
fee_rate: isFeeRateAdjusted ? args.fee_rate : policy.fee_rate,
inbound_base_discount_mtokens: policy.inbound_base_discount_mtokens,
inbound_rate_discount: inbound.rate_discount,
lnd: args.lnd,
max_htlc_mtokens: args.max_htlc_mtokens || policy.max_htlc_mtokens,
min_htlc_mtokens: args.min_htlc_mtokens || policy.min_htlc_mtokens,
transaction_id: args.transaction_id,
transaction_vout: args.transaction_vout,
},
cbk);
}],
// Get the updated channel routing policies
getUpdated: ['getChannelId', 'updateFeeRate', ({getChannelId}, cbk) => {
return getChannel({lnd: args.lnd, id: getChannelId}, cbk);
}],
// Make sure that the fee rate has been applied
checkUpdated: ['getUpdated', ({getUpdated}, cbk) => {
const hasBaseFee = args.base_fee_mtokens !== undefined;
const hasCltvDelta = !!args.cltv_delta;
const hasFeeRate = args.fee_rate !== undefined;
const hasInboundRate = args.inbound_rate_discount !== undefined;
const rate = getUpdated.policies.find(n => n.public_key === args.from);
const inboundRate = rate.inbound_rate_discount;
if (hasBaseFee && rate.base_fee_mtokens !== args.base_fee_mtokens) {
return cbk([503, 'FailedToUpdateChannelPolicyToNewBaseFee']);
}
if (hasCltvDelta && rate.cltv_delta !== args.cltv_delta) {
return cbk([503, 'FailedToUpdateCltvDeltaToNewCltvDelta']);
}
if (hasFeeRate && abs(rate.fee_rate - args.fee_rate) > feeRateBuffer) {
return cbk([503, 'FailedToUpdateChannelPolicyToNewFeeRate', {rate}]);
}
if (hasInboundRate && inboundRate !== args.inbound_rate_discount) {
return cbk([503, 'FailedToUpdateInboundDiscountToNewRateDiscount']);
}
return cbk();
}],
},
returnResult({reject, resolve}, cbk));
});
};