From 641cb554041238f5832db4358fe44d06d6ba5139 Mon Sep 17 00:00:00 2001 From: jamaljsr <1356600+jamaljsr@users.noreply.github.com> Date: Mon, 18 Nov 2019 18:08:26 -0500 Subject: [PATCH] refactor(network): update Network type for multiple lightning nodes --- .gitignore | 1 + src/components/designer/Sidebar.tsx | 3 +- .../designer/lnd/LndDetails.spec.tsx | 3 +- .../designer/lnd/actions/Deposit.spec.tsx | 4 +- .../designer/lnd/actions/OpenChannelModal.tsx | 11 +++-- .../designer/lnd/actions/RemoveNode.spec.tsx | 4 +- .../terminal/OpenTerminalButton.spec.tsx | 3 +- src/lib/docker/composeFile.spec.ts | 3 +- src/lib/docker/dockerService.spec.ts | 2 +- src/lib/lnd/lndProxyClient.spec.ts | 3 +- src/lib/lnd/lndService.spec.ts | 5 +- src/shared/types.ts | 1 + src/store/models/designer.ts | 4 +- src/store/models/lnd.spec.ts | 3 +- src/store/models/network.ts | 37 ++++++++------ src/types/index.ts | 4 +- src/utils/chart.ts | 4 +- src/utils/network.spec.ts | 4 +- src/utils/network.ts | 48 +++++++++++++++---- 19 files changed, 100 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 646e0a4..f389c53 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ typings/ .env.development.local .env.test.local .env.production.local +docker/docker-compose.yml npm-debug.log* yarn-debug.log* diff --git a/src/components/designer/Sidebar.tsx b/src/components/designer/Sidebar.tsx index 361c92e..234d36e 100644 --- a/src/components/designer/Sidebar.tsx +++ b/src/components/designer/Sidebar.tsx @@ -5,6 +5,7 @@ import BitcoindDetails from './bitcoind/BitcoindDetails'; import DefaultSidebar from './default/DefaultSidebar'; import LinkDetails from './link/LinkDetails'; import LndDetails from './lnd/LndDetails'; +import { LndNode } from 'shared/types'; interface Props { network: Network; @@ -21,7 +22,7 @@ const Sidebar: React.FC = ({ network, chart }) => { if (node && node.implementation === 'bitcoind') { return ; } else if (node && node.implementation === 'LND') { - return ; + return ; } } else if (type === 'link' && id) { const link = chart.links[id]; diff --git a/src/components/designer/lnd/LndDetails.spec.tsx b/src/components/designer/lnd/LndDetails.spec.tsx index c52ac7b..4c4a011 100644 --- a/src/components/designer/lnd/LndDetails.spec.tsx +++ b/src/components/designer/lnd/LndDetails.spec.tsx @@ -5,6 +5,7 @@ import { defaultInfo, defaultListChannels, defaultPendingChannels } from 'shared import { Status } from 'shared/types'; import { LndLibrary } from 'types'; import * as files from 'utils/files'; +import { groupNodes } from 'utils/network'; import { getNetwork, injections, renderWithProviders } from 'utils/tests'; import LndDetails from './LndDetails'; @@ -26,7 +27,7 @@ describe('LndDetails', () => { }, }, }; - const node = network.nodes.lightning[0]; + const node = groupNodes(network).lnd[0]; const cmp = ; const result = renderWithProviders(cmp, { initialState }); return { diff --git a/src/components/designer/lnd/actions/Deposit.spec.tsx b/src/components/designer/lnd/actions/Deposit.spec.tsx index 2a05030..1e5a589 100644 --- a/src/components/designer/lnd/actions/Deposit.spec.tsx +++ b/src/components/designer/lnd/actions/Deposit.spec.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { fireEvent, waitForElement } from '@testing-library/dom'; import { defaultInfo } from 'shared'; import { BitcoindLibrary, LndLibrary } from 'types'; +import { groupNodes } from 'utils/network'; import { getNetwork, injections, renderWithProviders } from 'utils/tests'; import { Deposit } from './'; @@ -16,7 +17,8 @@ describe('Deposit', () => { networks: [network], }, }; - const cmp = ; + const node = groupNodes(network).lnd[0]; + const cmp = ; const result = renderWithProviders(cmp, { initialState }); return { ...result, diff --git a/src/components/designer/lnd/actions/OpenChannelModal.tsx b/src/components/designer/lnd/actions/OpenChannelModal.tsx index 33b20d3..50cc874 100644 --- a/src/components/designer/lnd/actions/OpenChannelModal.tsx +++ b/src/components/designer/lnd/actions/OpenChannelModal.tsx @@ -6,6 +6,7 @@ import { usePrefixedTranslation } from 'hooks'; import { useStoreActions, useStoreState } from 'store'; import { OpenChannelPayload } from 'store/models/lnd'; import { Network } from 'types'; +import { groupNodes } from 'utils/network'; import { Loader } from 'components/common'; import LightningNodeSelect from 'components/common/form/LightningNodeSelect'; @@ -30,8 +31,8 @@ const OpenChannelModal: React.FC = ({ network, form }) => { const getBalancesAsync = useAsync(async () => { if (!visible) return; - const { lightning } = network.nodes; - for (const node of lightning) { + const { lnd } = groupNodes(network); + for (const node of lnd) { await getWalletBalance(node); } }, [network.nodes, visible]); @@ -60,9 +61,9 @@ const OpenChannelModal: React.FC = ({ network, form }) => { form.validateFields((err, values: FormFields) => { if (err) return; - const { lightning } = network.nodes; - const fromNode = lightning.find(n => n.name === values.from); - const toNode = lightning.find(n => n.name === values.to); + const { lnd } = groupNodes(network); + const fromNode = lnd.find(n => n.name === values.from); + const toNode = lnd.find(n => n.name === values.to); if (!fromNode || !toNode) return; const autoFund = showDeposit && values.autoFund; openChanAsync.execute({ from: fromNode, to: toNode, sats: values.sats, autoFund }); diff --git a/src/components/designer/lnd/actions/RemoveNode.spec.tsx b/src/components/designer/lnd/actions/RemoveNode.spec.tsx index f3d3ecf..09a4e6e 100644 --- a/src/components/designer/lnd/actions/RemoveNode.spec.tsx +++ b/src/components/designer/lnd/actions/RemoveNode.spec.tsx @@ -4,6 +4,7 @@ import { ipcChannels, withDefaults } from 'shared'; import { Status } from 'shared/types'; import { DockerLibrary, LndLibrary } from 'types'; import { initChartFromNetwork } from 'utils/chart'; +import { groupNodes } from 'utils/network'; import { getNetwork, injections, @@ -32,7 +33,8 @@ describe('RemoveNode', () => { activeId: 1, }, }; - const node = network.nodes.lightning[status === Status.Started ? 0 : 1]; + const { lnd } = groupNodes(network); + const node = lnd[status === Status.Started ? 0 : 1]; const cmp = ; const result = renderWithProviders(cmp, { initialState }); return { diff --git a/src/components/terminal/OpenTerminalButton.spec.tsx b/src/components/terminal/OpenTerminalButton.spec.tsx index ac029f7..b3c6123 100644 --- a/src/components/terminal/OpenTerminalButton.spec.tsx +++ b/src/components/terminal/OpenTerminalButton.spec.tsx @@ -3,6 +3,7 @@ import { fireEvent, wait } from '@testing-library/dom'; import { ipcChannels } from 'shared'; import { BitcoinNode, LndNode } from 'shared/types'; import { Network } from 'types'; +import { groupNodes } from 'utils/network'; import { getNetwork, injections, renderWithProviders } from 'utils/tests'; import OpenTerminalButton from './OpenTerminalButton'; @@ -29,7 +30,7 @@ describe('OpenTerminalButton', () => { }); it('should render lnd help text', () => { - const { getByText } = renderComponent(n => n.nodes.lightning[0]); + const { getByText } = renderComponent(n => groupNodes(n).lnd[0]); const help = getByText("Run 'lncli' commands directly on the node"); expect(help).toBeInTheDocument(); }); diff --git a/src/lib/docker/composeFile.spec.ts b/src/lib/docker/composeFile.spec.ts index 99fb57e..3551cb6 100644 --- a/src/lib/docker/composeFile.spec.ts +++ b/src/lib/docker/composeFile.spec.ts @@ -1,3 +1,4 @@ +import { groupNodes } from 'utils/network'; import { getNetwork } from 'utils/tests'; import ComposeFile from './composeFile'; @@ -5,7 +6,7 @@ describe('ComposeFile', () => { let composeFile = new ComposeFile(); const network = getNetwork(); const btcNode = network.nodes.bitcoin[0]; - const lndNode = network.nodes.lightning[0]; + const lndNode = groupNodes(network).lnd[0]; beforeEach(() => { composeFile = new ComposeFile(); diff --git a/src/lib/docker/dockerService.spec.ts b/src/lib/docker/dockerService.spec.ts index f94e676..5ec339e 100644 --- a/src/lib/docker/dockerService.spec.ts +++ b/src/lib/docker/dockerService.spec.ts @@ -180,7 +180,7 @@ describe('DockerService', () => { }); it('should not save unknown lightning implementation', () => { - network.nodes.lightning[0].implementation = 'c-lightning'; + network.nodes.lightning[0].implementation = 'eclair'; dockerService.saveComposeFile(network); expect(filesMock.write).toBeCalledWith( expect.stringContaining('docker-compose.yml'), diff --git a/src/lib/lnd/lndProxyClient.spec.ts b/src/lib/lnd/lndProxyClient.spec.ts index 7b1d14b..c2331f2 100644 --- a/src/lib/lnd/lndProxyClient.spec.ts +++ b/src/lib/lnd/lndProxyClient.spec.ts @@ -1,10 +1,11 @@ import { ipcChannels } from 'shared'; import { IpcSender } from 'lib/ipc/ipcService'; +import { groupNodes } from 'utils/network'; import { getNetwork } from 'utils/tests'; import lndProxyClient from './lndProxyClient'; describe('LndService', () => { - const node = getNetwork().nodes.lightning[0]; + const node = groupNodes(getNetwork()).lnd[0]; let actualIpc: IpcSender; beforeEach(() => { diff --git a/src/lib/lnd/lndService.spec.ts b/src/lib/lnd/lndService.spec.ts index eb9c3d3..4ef7e7c 100644 --- a/src/lib/lnd/lndService.spec.ts +++ b/src/lib/lnd/lndService.spec.ts @@ -1,3 +1,4 @@ +import { groupNodes } from 'utils/network'; import { getNetwork } from 'utils/tests'; import lndProxyClient from './lndProxyClient'; import lndService from './lndService'; @@ -5,7 +6,7 @@ import lndService from './lndService'; jest.mock('./lndProxyClient'); describe('LndService', () => { - const [node, node2] = getNetwork().nodes.lightning; + const [node, node2] = groupNodes(getNetwork()).lnd; it('should get node info', async () => { const expected = { identityPubkey: 'asdf' }; @@ -51,7 +52,7 @@ describe('LndService', () => { it('should call onNodesDeleted', async () => { const network = getNetwork(); - await lndService.onNodesDeleted(network.nodes.lightning); + await lndService.onNodesDeleted(groupNodes(network).lnd); const [n1, n2] = network.nodes.lightning; expect(lndProxyClient.onNodesDeleted).toBeCalledWith([n1, n2]); }); diff --git a/src/shared/types.ts b/src/shared/types.ts index 5f627ab..61ee4ad 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -21,6 +21,7 @@ export interface LightningNode extends CommonNode { type: 'lightning'; implementation: 'LND' | 'c-lightning' | 'eclair'; backendName: string; + ports: Record; } export enum LndVersion { diff --git a/src/store/models/designer.ts b/src/store/models/designer.ts index 9a32d8a..54ab373 100644 --- a/src/store/models/designer.ts +++ b/src/store/models/designer.ts @@ -15,6 +15,7 @@ import { LndNode, Status } from 'shared/types'; import { Network, StoreInjections } from 'types'; import { createLndChartNode, rotate, snap, updateChartFromLnd } from 'utils/chart'; import { LOADING_NODE_ID } from 'utils/constants'; +import { groupNodes } from 'utils/network'; import { prefixTranslation } from 'utils/translate'; import { RootModel } from './'; @@ -97,8 +98,7 @@ const designerModel: DesignerModel = { syncChart: thunk( async (actions, network, { getState, getStoreState, getStoreActions }) => { // fetch data from all of the nodes - const lndNodes = network.nodes.lightning.filter(n => n.implementation === 'LND'); - await Promise.all(lndNodes.map(getStoreActions().lnd.getAllInfo)); + await Promise.all(groupNodes(network).lnd.map(getStoreActions().lnd.getAllInfo)); const nodesData = getStoreState().lnd.nodes; const { allCharts } = getState(); diff --git a/src/store/models/lnd.spec.ts b/src/store/models/lnd.spec.ts index a0aed11..8962cf6 100644 --- a/src/store/models/lnd.spec.ts +++ b/src/store/models/lnd.spec.ts @@ -1,6 +1,7 @@ import * as LND from '@radar/lnrpc'; import { createStore } from 'easy-peasy'; import { defaultInfo, ipcChannels, withDefaults } from 'shared'; +import { LndNode } from 'shared/types'; import { BitcoindLibrary, LndLibrary } from 'types'; import * as asyncUtil from 'utils/async'; import { getNetwork, injections } from 'utils/tests'; @@ -24,7 +25,7 @@ describe('LND Model', () => { }; // initialize store for type inference let store = createStore(rootModel, { injections, initialState }); - const node = initialState.network.networks[0].nodes.lightning[0]; + const node = initialState.network.networks[0].nodes.lightning[0] as LndNode; beforeEach(() => { // reset the store before each test run diff --git a/src/store/models/network.ts b/src/store/models/network.ts index 18c4625..e59b999 100644 --- a/src/store/models/network.ts +++ b/src/store/models/network.ts @@ -2,7 +2,7 @@ import { info } from 'electron-log'; import { join } from 'path'; import { push } from 'connected-react-router'; import { Action, action, Computed, computed, Thunk, thunk } from 'easy-peasy'; -import { CommonNode, LndNode, LndVersion, Status } from 'shared/types'; +import { CommonNode, LightningNode, LndNode, LndVersion, Status } from 'shared/types'; import { Network, StoreInjections } from 'types'; import { initChartFromNetwork } from 'utils/chart'; import { rm } from 'utils/files'; @@ -10,6 +10,7 @@ import { createLndNetworkNode, createNetwork, getOpenPorts, + groupNodes, OpenPorts, } from 'utils/network'; import { prefixTranslation } from 'utils/translate'; @@ -46,7 +47,7 @@ export interface NetworkModel { RootModel, Promise >; - removeNode: Thunk; + removeNode: Thunk; setStatus: Action< NetworkModel, { id: number; status: Status; only?: string; all?: boolean; error?: Error } @@ -138,13 +139,18 @@ const networkModel: NetworkModel = { const network = networks.find(n => n.id === node.networkId); if (!network) throw new Error(l('networkByIdErr', { networkId: node.networkId })); network.nodes.lightning = network.nodes.lightning.filter(n => n !== node); - getStoreActions().lnd.removeNode(node.name); + if (node.implementation === 'LND') getStoreActions().lnd.removeNode(node.name); await injections.dockerService.removeNode(network, node); getStoreActions().designer.removeNode(node.name); actions.setNetworks([...networks]); await actions.save(); - rm(join(network.path, 'volumes', 'lnd', node.name)); - await injections.lndService.onNodesDeleted([node, ...network.nodes.lightning]); + if (node.implementation === 'LND') { + rm(join(network.path, 'volumes', 'lnd', node.name)); + await injections.lndService.onNodesDeleted([ + node as LndNode, + ...groupNodes(network).lnd, + ]); + } if (network.status === Status.Started) { getStoreActions().designer.syncChart(network); } @@ -193,27 +199,28 @@ const networkModel: NetworkModel = { await getStoreActions().app.getDockerImages(); // set the status of only the network to Started actions.setStatus({ id, status: Status.Started, all: false }); + const { lnd } = groupNodes(network); // wait for lnd nodes to come online before updating their status - for (const lnd of network.nodes.lightning) { + for (const node of lnd) { // use .then() to continue execution while the promises are waiting to complete injections.lndService - .waitUntilOnline(lnd) - .then(() => actions.setStatus({ id, status: Status.Started, only: lnd.name })) + .waitUntilOnline(node) + .then(() => actions.setStatus({ id, status: Status.Started, only: node.name })) .catch(error => - actions.setStatus({ id, status: Status.Error, only: lnd.name, error }), + actions.setStatus({ id, status: Status.Error, only: node.name, error }), ); } // wait for bitcoind nodes to come online before updating their status - for (const bitcoind of network.nodes.bitcoin) { + for (const node of network.nodes.bitcoin) { // use .then() to continue execution while the promises are waiting to complete injections.bitcoindService - .waitUntilOnline(bitcoind.ports.rpc) + .waitUntilOnline(node.ports.rpc) .then(() => { - actions.setStatus({ id, status: Status.Started, only: bitcoind.name }); - return getStoreActions().bitcoind.getInfo(bitcoind); + actions.setStatus({ id, status: Status.Started, only: node.name }); + return getStoreActions().bitcoind.getInfo(node); }) .catch(error => - actions.setStatus({ id, status: Status.Error, only: bitcoind.name, error }), + actions.setStatus({ id, status: Status.Error, only: node.name, error }), ); } } catch (e) { @@ -266,7 +273,7 @@ const networkModel: NetworkModel = { actions.setNetworks(newNetworks); getStoreActions().designer.removeChart(networkId); await actions.save(); - await injections.lndService.onNodesDeleted(network.nodes.lightning); + await injections.lndService.onNodesDeleted(groupNodes(network).lnd); }), }; diff --git a/src/types/index.ts b/src/types/index.ts index 3a06461..6307708 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,7 +1,7 @@ import { IChart } from '@mrblenny/react-flow-chart'; import * as LND from '@radar/lnrpc'; import { ChainInfo, WalletInfo } from 'bitcoin-core'; -import { BitcoinNode, CommonNode, LndNode, Status } from 'shared/types'; +import { BitcoinNode, CommonNode, LightningNode, LndNode, Status } from 'shared/types'; import { IpcSender } from 'lib/ipc/ipcService'; export interface LocaleConfig { @@ -18,7 +18,7 @@ export interface Network { path: string; nodes: { bitcoin: BitcoinNode[]; - lightning: LndNode[]; + lightning: LightningNode[]; }; } diff --git a/src/utils/chart.ts b/src/utils/chart.ts index 470647a..07f764a 100644 --- a/src/utils/chart.ts +++ b/src/utils/chart.ts @@ -1,6 +1,6 @@ import { IChart, IConfig, ILink, INode, IPosition } from '@mrblenny/react-flow-chart'; import { Channel, PendingChannel } from '@radar/lnrpc'; -import { BitcoinNode, LndNode } from 'shared/types'; +import { BitcoinNode, LightningNode } from 'shared/types'; import { LndNodeMapping } from 'store/models/lnd'; import { Network } from 'types'; import btclogo from 'resources/bitcoin.svg'; @@ -34,7 +34,7 @@ export const snap = (position: IPosition, config?: IConfig) => ? { x: Math.round(position.x / 20) * 20, y: Math.round(position.y / 20) * 20 } : position; -export const createLndChartNode = (lnd: LndNode) => { +export const createLndChartNode = (lnd: LightningNode) => { const node: INode = { id: lnd.name, type: 'lightning', diff --git a/src/utils/network.spec.ts b/src/utils/network.spec.ts index b117939..dc49505 100644 --- a/src/utils/network.spec.ts +++ b/src/utils/network.spec.ts @@ -1,5 +1,5 @@ import detectPort from 'detect-port'; -import { Status } from 'shared/types'; +import { LndNode, Status } from 'shared/types'; import { Network } from 'types'; import { getOpenPortRange, getOpenPorts, OpenPorts } from './network'; import { getNetwork } from './tests'; @@ -77,7 +77,7 @@ describe('Network Utils', () => { // alice ports should not be changed expect(ports[network.nodes.lightning[0].name]).toBeUndefined(); // bob ports should change - const lnd2 = network.nodes.lightning[1]; + const lnd2 = network.nodes.lightning[1] as LndNode; expect(ports[lnd2.name].grpc).toBe(lnd2.ports.grpc + 1); expect(ports[lnd2.name].rest).toBe(lnd2.ports.rest + 1); }); diff --git a/src/utils/network.ts b/src/utils/network.ts index f17d3c6..20c4d7f 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -1,6 +1,13 @@ import { join } from 'path'; import detectPort from 'detect-port'; -import { BitcoinNode, CommonNode, LndNode, LndVersion, Status } from 'shared/types'; +import { + BitcoinNode, + CommonNode, + LightningdNode, + LndNode, + LndVersion, + Status, +} from 'shared/types'; import { Network } from 'types'; import { networksPath } from './config'; import { getName } from './names'; @@ -9,6 +16,18 @@ import { range } from './numbers'; export const getContainerName = (node: CommonNode) => `polar-n${node.networkId}-${node.name}`; +export const groupNodes = (network: Network) => { + const { bitcoin, lightning } = network.nodes; + return { + bitcoind: bitcoin.filter(n => n.implementation === 'bitcoind') as BitcoinNode[], + lnd: lightning.filter(n => n.implementation === 'LND') as LndNode[], + lightningd: lightning.filter( + n => n.implementation === 'c-lightning', + ) as LightningdNode[], + eclair: lightning.filter(n => n.implementation === 'eclair'), + }; +}; + // long path games const getFilePaths = (name: string, network: Network) => { // returns /volumes/lnd/lnd-1 @@ -176,29 +195,42 @@ export const getOpenPorts = async (network: Network): Promise n.status !== Status.Started); - if (lightning.length) { - let existingPorts = lightning.map(n => n.ports.grpc); + lnd = lnd.filter(n => n.status !== Status.Started); + if (lnd.length) { + let existingPorts = lnd.map(n => n.ports.grpc); let openPorts = await getOpenPortRange(existingPorts); if (openPorts.join() !== existingPorts.join()) { openPorts.forEach((port, index) => { - ports[lightning[index].name] = { grpc: port }; + ports[lnd[index].name] = { grpc: port }; }); } - existingPorts = lightning.map(n => n.ports.rest); + existingPorts = lnd.map(n => n.ports.rest); openPorts = await getOpenPortRange(existingPorts); if (openPorts.join() !== existingPorts.join()) { openPorts.forEach((port, index) => { - ports[lightning[index].name] = { - ...(ports[lightning[index].name] || {}), + ports[lnd[index].name] = { + ...(ports[lnd[index].name] || {}), rest: port, }; }); } } + lightningd = lightningd.filter(n => n.status !== Status.Started); + if (lightningd.length) { + const existingPorts = lightningd.map(n => n.ports.rest); + const openPorts = await getOpenPortRange(existingPorts); + if (openPorts.join() !== existingPorts.join()) { + openPorts.forEach((port, index) => { + ports[lightningd[index].name] = { rpc: port }; + }); + } + } + // return undefined if no ports where updated return Object.keys(ports).length > 0 ? ports : undefined; };