diff --git a/TODO.md b/TODO.md index 0cc6bff..82429c4 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,5 @@ # TODO List -- add peer nodes after waitUntilOnline() - Small Stuff - fix pasting text in terminal diff --git a/src/components/designer/default/DefaultSidebar.tsx b/src/components/designer/default/DefaultSidebar.tsx index 324b283..ca517a8 100644 --- a/src/components/designer/default/DefaultSidebar.tsx +++ b/src/components/designer/default/DefaultSidebar.tsx @@ -1,8 +1,9 @@ import React from 'react'; import styled from '@emotion/styled'; import { usePrefixedTranslation } from 'hooks'; -import { CLightningVersion, LndVersion } from 'shared/types'; +import { BitcoindVersion, CLightningVersion, LndVersion } from 'shared/types'; import { Network } from 'types'; +import bitcoindLogo from 'resources/bitcoin.svg'; import clightningLogo from 'resources/clightning.png'; import lndLogo from 'resources/lnd.png'; import SidebarCard from '../SidebarCard'; @@ -51,6 +52,16 @@ const DefaultSidebar: React.FC = ({ network }) => { properties={{ type: 'c-lightning', version }} /> ))} + {Object.keys(BitcoindVersion) + .filter(v => v !== 'latest') + .map(version => ( + + ))} ); }; diff --git a/src/shared/types.ts b/src/shared/types.ts index a60f941..8e65ee9 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -35,6 +35,11 @@ export enum CLightningVersion { '0.7.3' = '0.7.3', } +export enum BitcoindVersion { + latest = '0.18.1', + '0.18.1' = '0.18.1', +} + export interface LndNode extends LightningNode { paths: { tlsCert: string; diff --git a/src/store/models/bitcoind.ts b/src/store/models/bitcoind.ts index 7173f75..c2bb559 100644 --- a/src/store/models/bitcoind.ts +++ b/src/store/models/bitcoind.ts @@ -17,6 +17,7 @@ export interface BitcoindNodeModel { export interface BitcoindModel { nodes: BitcoindNodeMapping; + removeNode: Action; setChainInfo: Action; setWalletinfo: Action; getInfo: Thunk; @@ -27,6 +28,11 @@ const bitcoindModel: BitcoindModel = { // computed properties/functions nodes: {}, // reducer actions (mutations allowed thx to immer) + removeNode: action((state, name) => { + if (state.nodes[name]) { + delete state.nodes[name]; + } + }), setChainInfo: action((state, { node, chainInfo }) => { if (!state.nodes[node.name]) state.nodes[node.name] = {}; state.nodes[node.name].chainInfo = chainInfo; diff --git a/src/store/models/designer.ts b/src/store/models/designer.ts index 4b7670c..61fe51e 100644 --- a/src/store/models/designer.ts +++ b/src/store/models/designer.ts @@ -11,9 +11,10 @@ import { ThunkOn, thunkOn, } from 'easy-peasy'; -import { LightningNode, Status } from 'shared/types'; +import { BitcoinNode, LightningNode, Status } from 'shared/types'; import { Network, StoreInjections } from 'types'; import { + createBitcoinChartNode, createLightningChartNode, rotate, snap, @@ -39,7 +40,10 @@ export interface DesignerModel { onNetworkSetStatus: ActionOn; removeLink: Action; removeNode: Action; - addNode: Action; + addNode: Action< + DesignerModel, + { newNode: LightningNode | BitcoinNode; position: IPosition } + >; onLinkCompleteListener: ThunkOn; onCanvasDropListener: ThunkOn; // Flowchart component callbacks @@ -151,12 +155,15 @@ const designerModel: DesignerModel = { } }); }), - addNode: action((state, { lnNode, position }) => { + addNode: action((state, { newNode, position }) => { const chart = state.allCharts[state.activeId]; - const { node, link } = createLightningChartNode(lnNode); + const { node, link } = + newNode.type === 'lightning' + ? createLightningChartNode(newNode) + : createBitcoinChartNode(newNode); node.position = position; chart.nodes[node.id] = node; - chart.links[link.id] = link; + if (link) chart.links[link.id] = link; }), onLinkCompleteListener: thunkOn( actions => actions.onLinkComplete, @@ -210,14 +217,14 @@ const designerModel: DesignerModel = { message: l('dropErrTitle'), error: new Error(l('dropErrMsg')), }); - } else if (['lnd', 'c-lightning'].includes(data.type)) { + } else if (['lnd', 'c-lightning', 'bitcoind'].includes(data.type)) { const { addNode, start } = getStoreActions().network; - const lnNode = await addNode({ + const newNode = await addNode({ id: activeId, type: data.type, version: data.version, }); - actions.addNode({ lnNode, position }); + actions.addNode({ newNode, position }); actions.redrawChart(); if (network.status === Status.Started) { await start(activeId); diff --git a/src/store/models/network.ts b/src/store/models/network.ts index 291dbf3..a21980f 100644 --- a/src/store/models/network.ts +++ b/src/store/models/network.ts @@ -3,6 +3,8 @@ import { join } from 'path'; import { push } from 'connected-react-router'; import { Action, action, Computed, computed, Thunk, thunk } from 'easy-peasy'; import { + BitcoindVersion, + BitcoinNode, CLightningVersion, CommonNode, LightningNode, @@ -13,6 +15,7 @@ import { Network, StoreInjections } from 'types'; import { initChartFromNetwork } from 'utils/chart'; import { rm } from 'utils/files'; import { + createBitcoindNetworkNode, createCLightningNetworkNode, createLndNetworkNode, createNetwork, @@ -49,10 +52,14 @@ export interface NetworkModel { >; addNode: Thunk< NetworkModel, - { id: number; type: string; version: LndVersion | CLightningVersion }, + { + id: number; + type: string; + version: LndVersion | CLightningVersion | BitcoindVersion; + }, StoreInjections, RootModel, - Promise + Promise >; removeNode: Thunk; setStatus: Action< @@ -139,11 +146,23 @@ const networkModel: NetworkModel = { const networks = getState().networks; const network = networks.find(n => n.id === id); if (!network) throw new Error(l('networkByIdErr', { networkId: id })); - const node = - type === 'c-lightning' - ? createCLightningNetworkNode(network, version as CLightningVersion) - : createLndNetworkNode(network, version as LndVersion); - network.nodes.lightning.push(node); + let node: LightningNode | BitcoinNode; + switch (type) { + case 'lnd': + node = createLndNetworkNode(network, version as LndVersion); + network.nodes.lightning.push(node); + break; + case 'c-lightning': + node = createCLightningNetworkNode(network, version as CLightningVersion); + network.nodes.lightning.push(node); + break; + case 'bitcoind': + node = createBitcoindNetworkNode(network, version as BitcoindVersion); + network.nodes.bitcoin.push(node); + break; + default: + throw new Error(`Cannot add uknown node type '${type}' to the network`); + } actions.setNetworks([...networks]); await actions.save(); await injections.dockerService.saveComposeFile(network); @@ -288,6 +307,7 @@ const networkModel: NetworkModel = { actions.setNetworks(newNetworks); getStoreActions().designer.removeChart(networkId); network.nodes.lightning.forEach(n => getStoreActions().lightning.removeNode(n.name)); + network.nodes.bitcoin.forEach(n => getStoreActions().bitcoind.removeNode(n.name)); await actions.save(); await getStoreActions().app.clearAppCache(); }), diff --git a/src/utils/chart.ts b/src/utils/chart.ts index 9fc5414..fcc7c68 100644 --- a/src/utils/chart.ts +++ b/src/utils/chart.ts @@ -83,13 +83,14 @@ export const createBitcoinChartNode = (btc: BitcoinNode) => { }; let link: ILink | undefined; - const peer = btc.peers[btc.peers.length - 1]; - if (peer && btc.name < peer) { + // the first peer is always the prev node unless this is the first node in the network + const peer = btc.peers[0]; + if (peer && btc.name > peer) { // only add one link from left to right (ex: 'backend2' < 'backend3') link = { - id: `${btc.name}-${peer}`, - from: { nodeId: btc.name, portId: 'peer-right' }, - to: { nodeId: peer, portId: 'peer-left' }, + id: `${peer}-${btc.name}`, + from: { nodeId: peer, portId: 'peer-right' }, + to: { nodeId: btc.name, portId: 'peer-left' }, properties: { type: 'btcpeer', }, diff --git a/src/utils/network.ts b/src/utils/network.ts index b740c1b..1c90ee6 100644 --- a/src/utils/network.ts +++ b/src/utils/network.ts @@ -2,6 +2,7 @@ import { debug } from 'electron-log'; import { join } from 'path'; import detectPort from 'detect-port'; import { + BitcoindVersion, BitcoinNode, CLightningNode, CLightningVersion, @@ -106,22 +107,32 @@ export const createCLightningNetworkNode = ( export const createBitcoindNetworkNode = ( network: Network, - status: Status, + version: BitcoindVersion, + status = Status.Stopped, ): BitcoinNode => { const { bitcoin } = network.nodes; const id = bitcoin.length ? Math.max(...bitcoin.map(n => n.id)) + 1 : 0; const name = `backend${id + 1}`; - return { + const node: BitcoinNode = { id, networkId: network.id, name: name, type: 'bitcoin', implementation: 'bitcoind', - version: '0.18.1', + version, peers: [], status, ports: { rpc: BasePorts.bitcoind.rest + id }, }; + + // peer up with the previous node in both directions + if (bitcoin.length > 0) { + const prev = bitcoin[bitcoin.length - 1]; + node.peers.push(prev.name); + prev.peers.push(node.name); + } + + return node; }; export const createNetwork = (config: { @@ -150,12 +161,7 @@ export const createNetwork = (config: { const { bitcoin, lightning } = network.nodes; range(bitcoindNodes).forEach(() => { - bitcoin.push(createBitcoindNetworkNode(network, status)); - }); - // peer with the nodes immediately before and after - bitcoin.forEach((n, i) => { - if (i > 0) n.peers.push(bitcoin[i - 1].name); - if (i < bitcoin.length - 1) n.peers.push(bitcoin[i + 1].name); + bitcoin.push(createBitcoindNetworkNode(network, BitcoindVersion.latest, status)); }); // add lightning nodes in an alternating pattern