Browse Source

fix(lightning): fix 'unable to find route' error with lightning payments

master
jamaljsr 5 years ago
parent
commit
40549b8322
  1. 2
      src/lib/bitcoin/bitcoindService.ts
  2. 2
      src/lib/docker/nodeTemplates.ts
  3. 15
      src/lib/lightning/clightning/clightningService.ts
  4. 17
      src/lib/lightning/lnd/lndService.ts
  5. 4
      src/lib/lightning/notImplementedService.ts
  6. 18
      src/store/models/lightning.ts
  7. 16
      src/store/models/network.ts
  8. 2
      src/types/index.ts
  9. 2
      src/utils/tests/renderWithProviders.tsx

2
src/lib/bitcoin/bitcoindService.ts

@ -34,7 +34,7 @@ class BitcoindService implements BitcoindLibrary {
try {
await client.addNode(peer, 'add');
} catch (error) {
logger.debug('Failed to add peer', error);
logger.debug(`Failed to add peer '${peer}' to bitcoind node ${node.name}`, error);
}
}
}

2
src/lib/docker/nodeTemplates.ts

@ -69,6 +69,7 @@ export const lnd = (
command: trimInside(`
lnd
--noseedbackup
--trickledelay=5000
--alias=${name}
--externalip=${name}
--tlsextradomain=${name}
@ -122,6 +123,7 @@ export const clightning = (
--bitcoin-rpcport=18443
--log-level=debug
--dev-bitcoind-poll=2
--dev-fast-gossip
--plugin=/opt/c-lightning-rest/plugin.js
--rest-port=8080
--rest-protocol=http

15
src/lib/lightning/clightning/clightningService.ts

@ -1,3 +1,4 @@
import { debug } from 'electron-log';
import { CLightningNode, LightningNode } from 'shared/types';
import * as PLN from 'lib/lightning/types';
import { LightningService } from 'types';
@ -87,9 +88,15 @@ class CLightningService implements LightningService {
}));
}
async connectPeer(node: LightningNode, toRpcUrl: string): Promise<void> {
const body = { id: toRpcUrl };
await httpPost<{ id: string }>(this.cast(node), 'peer/connect', body);
async connectPeers(node: LightningNode, rpcUrls: string[]): Promise<void> {
for (const toRpcUrl of rpcUrls) {
try {
const body = { id: toRpcUrl };
await httpPost<{ id: string }>(this.cast(node), 'peer/connect', body);
} catch (error) {
debug(`Failed to connect peer '${toRpcUrl}' to LND node ${node.name}`, error);
}
}
}
async openChannel(
@ -105,7 +112,7 @@ class CLightningService implements LightningService {
const [toPubKey] = toRpcUrl.split('@');
// add peer if not connected
if (!peers.some(p => p.pubkey === toPubKey)) {
await this.connectPeer(clnFrom, toRpcUrl);
await this.connectPeers(clnFrom, [toRpcUrl]);
}
// open the channel

17
src/lib/lightning/lnd/lndService.ts

@ -1,3 +1,4 @@
import { debug } from 'electron-log';
import * as LND from '@radar/lnrpc';
import { LightningNode, LndNode } from 'shared/types';
import * as PLN from 'lib/lightning/types';
@ -62,10 +63,16 @@ class LndService implements LightningService {
}));
}
async connectPeer(node: LightningNode, toRpcUrl: string): Promise<void> {
const [toPubKey, host] = toRpcUrl.split('@');
const addr: LND.LightningAddress = { pubkey: toPubKey, host };
await proxy.connectPeer(this.cast(node), { addr });
async connectPeers(node: LightningNode, rpcUrls: string[]): Promise<void> {
for (const toRpcUrl of rpcUrls) {
try {
const [toPubKey, host] = toRpcUrl.split('@');
const addr: LND.LightningAddress = { pubkey: toPubKey, host };
await proxy.connectPeer(this.cast(node), { addr });
} catch (error) {
debug(`Failed to connect peer '${toRpcUrl}' to LND node ${node.name}`, error);
}
}
}
async openChannel(
@ -81,7 +88,7 @@ class LndService implements LightningService {
const [toPubKey] = toRpcUrl.split('@');
// add peer if not connected
if (!peers.some(p => p.pubkey === toPubKey)) {
await this.connectPeer(lndFrom, toRpcUrl);
await this.connectPeers(lndFrom, [toRpcUrl]);
}
// open channel

4
src/lib/lightning/notImplementedService.ts

@ -27,8 +27,8 @@ class NotImplementedService implements LightningService {
getPeers(node: LightningNode): Promise<PLN.LightningNodePeer[]> {
throw new Error(`getPeers is not implemented for ${node.implementation} nodes`);
}
connectPeer(node: LightningNode, toRpcUrl: string): Promise<void> {
throw new Error(`connectPeer is not implemented for ${node.implementation} nodes`);
connectPeers(node: LightningNode, rpcUrls: string[]): Promise<void> {
throw new Error(`connectPeers is not implemented for ${node.implementation} nodes`);
}
openChannel(
from: LightningNode,

18
src/store/models/lightning.ts

@ -1,7 +1,7 @@
import { Action, action, Thunk, thunk, ThunkOn, thunkOn } from 'easy-peasy';
import { LightningNode, Status } from 'shared/types';
import * as PLN from 'lib/lightning/types';
import { StoreInjections } from 'types';
import { Network, StoreInjections } from 'types';
import { delay } from 'utils/async';
import { BLOCKS_TIL_COMFIRMED } from 'utils/constants';
import { fromSatsNumeric } from 'utils/units';
@ -57,6 +57,7 @@ export interface LightningModel {
>;
getChannels: Thunk<LightningModel, LightningNode, StoreInjections, RootModel>;
getAllInfo: Thunk<LightningModel, LightningNode, StoreInjections, RootModel>;
connectAllPeers: Thunk<LightningModel, Network, StoreInjections, RootModel>;
depositFunds: Thunk<LightningModel, DepositFundsPayload, StoreInjections, RootModel>;
openChannel: Thunk<LightningModel, OpenChannelPayload, StoreInjections, RootModel>;
closeChannel: Thunk<
@ -124,6 +125,21 @@ const lightningModel: LightningModel = {
await actions.getWalletBalance(node);
await actions.getChannels(node);
}),
connectAllPeers: thunk(async (actions, network, { injections, getState }) => {
// fetch info for each ln node
for (const node of network.nodes.lightning) {
await actions.getInfo(node);
}
const { nodes } = getState();
// get a list of rpcUrls
const rpcUrls = Object.values(nodes)
.map(n => (n.info && n.info.rpcUrl) || '')
.filter(n => !!n);
for (const node of network.nodes.lightning) {
await injections.lightningFactory.getService(node).connectPeers(node, rpcUrls);
}
}),
depositFunds: thunk(
async (actions, { node, sats }, { injections, getStoreState, getStoreActions }) => {
const { nodes } = getStoreState().network.networkById(node.networkId);

16
src/store/models/network.ts

@ -311,24 +311,34 @@ const networkModel: NetworkModel = {
actions.setStatus({ id, status: Status.Started, all: false });
// wait for lnd nodes to come online before updating their status
const allNodesOnline: Promise<void>[] = [];
for (const node of network.nodes.lightning) {
// use .then() to continue execution while the promises are waiting to complete
injections.lightningFactory
const promise = injections.lightningFactory
.getService(node)
.waitUntilOnline(node)
.then(() => actions.setStatus({ id, status: Status.Started, only: node.name }))
.then(async () => {
actions.setStatus({ id, status: Status.Started, only: node.name });
})
.catch(error =>
actions.setStatus({ id, status: Status.Error, only: node.name, error }),
);
allNodesOnline.push(promise);
}
// after all LN nodes are online, connect each of them to eachother. This helps
// ensure that each node is aware of the entire graph and can route payments properly
Promise.all(allNodesOnline).then(async () => {
await getStoreActions().lightning.connectAllPeers(network as Network);
});
// wait for bitcoind nodes to come online before updating their status
for (const node of network.nodes.bitcoin) {
// use .then() to continue execution while the promises are waiting to complete
injections.bitcoindService
.waitUntilOnline(node)
.then(async () => {
await injections.bitcoindService.connectPeers(node);
actions.setStatus({ id, status: Status.Started, only: node.name });
// connect each bitcoin node to it's peers so tx & block propagation is fast
await injections.bitcoindService.connectPeers(node);
await getStoreActions().bitcoind.getInfo(node);
})
.catch(error =>

2
src/types/index.ts

@ -54,7 +54,7 @@ export interface LightningService {
getNewAddress: (node: LightningNode) => Promise<PLN.LightningNodeAddress>;
getChannels: (node: LightningNode) => Promise<PLN.LightningNodeChannel[]>;
getPeers: (node: LightningNode) => Promise<PLN.LightningNodePeer[]>;
connectPeer: (node: LightningNode, toRpcUrl: string) => Promise<void>;
connectPeers: (node: LightningNode, rpcUrls: string[]) => Promise<void>;
openChannel: (
from: LightningNode,
toRpcUrl: string,

2
src/utils/tests/renderWithProviders.tsx

@ -13,7 +13,7 @@ export const lightningServiceMock: jest.Mocked<LightningService> = {
getNewAddress: jest.fn(),
getChannels: jest.fn(),
getPeers: jest.fn(),
connectPeer: jest.fn(),
connectPeers: jest.fn(),
openChannel: jest.fn(),
closeChannel: jest.fn(),
createInvoice: jest.fn(),

Loading…
Cancel
Save