Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(images): add support for tapd v0.3.0-alpha #787

Merged
merged 9 commits into from
Oct 20, 2023
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Supported Network Node Versions:
- [Core Lightning](https://github.com/ElementsProject/lightning) - v23.05.2, v23.02.2, v22.11, v0.12.0, v0.11.2, v0.10.2
- [Eclair](https://github.com/ACINQ/eclair/) - v0.9.0, v0.8.0, v0.7.0, v0.6.2, v0.5.0
- [Bitcoin Core](https://github.com/bitcoin/bitcoin) - v25.0, v24.0, v23.0, v22.0, v0.21.1
- [Taproot Assets](https://github.com/lightninglabs/taproot-assets) - v0.2.3, v0.2.2, v0.2.0
- [Taproot Assets](https://github.com/lightninglabs/taproot-assets) - v0.3.0

## Dependencies

Expand Down
1 change: 1 addition & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Replace `<version>` with the desired Eclair version (ex: `0.3.3`).

### Tags

- `0.3.0-alpha` ([tap/Dockerfile](https://github.com/jamaljsr/polar/blob/master/docker/tapd/Dockerfile))
- `0.2.3-alpha` ([tap/Dockerfile](https://github.com/jamaljsr/polar/blob/master/docker/tapd/Dockerfile))
- `0.2.2-alpha` ([tap/Dockerfile](https://github.com/jamaljsr/polar/blob/master/docker/tapd/Dockerfile))
- `0.2.0-alpha` ([tap/Dockerfile](https://github.com/jamaljsr/polar/blob/master/docker/tapd/Dockerfile))
Expand Down
10 changes: 4 additions & 6 deletions docker/nodes.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": 54,
"version": 55,
"images": {
"LND": {
"latest": "0.17.0-beta",
Expand Down Expand Up @@ -41,12 +41,10 @@
"versions": []
},
"tapd": {
"latest": "0.2.3-alpha",
"versions": ["0.2.3-alpha", "0.2.2-alpha", "0.2.0-alpha"],
"latest": "0.3.0-alpha",
"versions": ["0.3.0-alpha"],
"compatibility": {
"0.2.3-alpha": "0.16.0-beta",
"0.2.2-alpha": "0.16.0-beta",
"0.2.0-alpha": "0.16.0-beta"
"0.3.0-alpha": "0.16.0-beta"
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions electron/appIpcListener.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrowserWindow, IpcMain } from 'electron';
import { app, BrowserWindow, IpcMain } from 'electron';
import { debug } from 'electron-log';
import windowState from 'electron-window-state';
import { join } from 'path';
Expand All @@ -14,7 +14,8 @@ const openWindow = async (args: { url: string }): Promise<boolean> => {
const winState = windowState({
defaultWidth: 800,
defaultHeight: 600,
file: `window-state-terminal.json`,
file: `${args.url.replace(/\//g, '_')}.json`,
path: join(app.getPath('userData'), 'window-state'),
});
let window: BrowserWindow | null = new BrowserWindow({
x: winState.x,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"tsc": "tsc --noEmit"
},
"dependencies": {
"@lightningpolar/tapd-api": "0.2.0",
"@lightningpolar/tapd-api": "0.3.0-alpha",
"@radar/lnrpc": "0.11.1-beta.1",
"@types/detect-port": "1.3.3",
"archiver": "6.0.1",
Expand All @@ -52,6 +52,7 @@
"electron-log": "4.4.8",
"electron-window-state": "5.0.3",
"fs-extra": "11.1.0",
"semver": "7.5.4",
"shell-env": "3.0.1",
"unzipper": "0.10.14",
"xterm": "5.3.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/AdvancedOptionsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ const AdvancedOptionsModal: React.FC<Props> = ({ network }) => {
});

const handleSubmit = (values: any) => {
const { lightning, bitcoin } = network.nodes;
const nodes: CommonNode[] = [...lightning, ...bitcoin];
const { lightning, bitcoin, tap } = network.nodes;
const nodes: CommonNode[] = [...lightning, ...bitcoin, ...tap];
const node = nodes.find(n => n.name === nodeName);
if (!node) return;
updateAsync.execute(node, values.command);
Expand Down
29 changes: 23 additions & 6 deletions src/components/common/RemoveNode.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CommonNode, Status } from 'shared/types';
import { BitcoindLibrary, DockerLibrary } from 'types';
import { initChartFromNetwork } from 'utils/chart';
import { defaultRepoState } from 'utils/constants';
import { createBitcoindNetworkNode } from 'utils/network';
import { createBitcoindNetworkNode, createLndNetworkNode } from 'utils/network';
import {
getNetwork,
injections,
Expand All @@ -26,6 +26,12 @@ describe('RemoveNode', () => {
nodeType?: CommonNode['type'],
) => {
const network = getNetwork(1, 'test network', status, 2);
// add an extra lightning node to the network without a connected tapd node
const lnd = defaultRepoState.images.LND;
network.nodes.lightning.push(
createLndNetworkNode(network, lnd.latest, lnd.compatibility, testNodeDocker),
);

if (status === Status.Error) {
network.nodes.lightning.forEach(n => (n.errorMsg = 'test-error'));
}
Expand All @@ -43,6 +49,11 @@ describe('RemoveNode', () => {
},
activeId: 1,
},
lightning: {
nodes: {
carol: {},
},
},
};
const { lightning, bitcoin, tap } = network.nodes;
const node = [...lightning, ...bitcoin, ...tap].find(
Expand Down Expand Up @@ -72,28 +83,31 @@ describe('RemoveNode', () => {
it('should remove the node with the network stopped', async () => {
const { getByText, findByText, getByLabelText } = renderComponent(
Status.Stopped,
'bob',
'carol',
);
expect(getByText('Remove')).toBeInTheDocument();
fireEvent.click(getByText('Remove'));
fireEvent.click(await findByText('Yes'));
// wait for the error notification to be displayed
await waitFor(() => getByLabelText('check-circle'));
expect(
getByText('The node bob has been removed from the network'),
getByText('The node carol has been removed from the network'),
).toBeInTheDocument();
expect(dockerServiceMock.removeNode).toBeCalledTimes(0);
});

it('should remove the node with the network started', async () => {
const { getByText, findByText, getByLabelText } = renderComponent(Status.Started);
const { getByText, findByText, getByLabelText } = renderComponent(
Status.Started,
'carol',
);
expect(getByText('Remove')).toBeInTheDocument();
fireEvent.click(getByText('Remove'));
fireEvent.click(await findByText('Yes'));
// wait for the error notification to be displayed
await waitFor(() => getByLabelText('check-circle'));
expect(
getByText('The node alice has been removed from the network'),
getByText('The node carol has been removed from the network'),
).toBeInTheDocument();
expect(dockerServiceMock.removeNode).toBeCalledTimes(1);
});
Expand All @@ -103,7 +117,10 @@ describe('RemoveNode', () => {
// this suppresses those errors from being displayed in test runs
await suppressConsoleErrors(async () => {
dockerServiceMock.removeNode.mockRejectedValue(new Error('test error'));
const { getByText, findByText, getByLabelText } = renderComponent(Status.Started);
const { getByText, findByText, getByLabelText } = renderComponent(
Status.Started,
'carol',
);
expect(getByText('Remove')).toBeInTheDocument();
fireEvent.click(getByText('Remove'));
fireEvent.click(await findByText('Yes'));
Expand Down
41 changes: 37 additions & 4 deletions src/components/designer/NetworkDesigner.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { act } from '@testing-library/react';
import { Status } from 'shared/types';
import { themeColors } from 'theme/colors';
import { initChartFromNetwork } from 'utils/chart';
import { getNetwork, renderWithProviders } from 'utils/tests';
import {
getNetwork,
renderWithProviders,
suppressConsoleErrors,
testRepoState,
} from 'utils/tests';
import NetworkDesigner from './NetworkDesigner';

describe('NetworkDesigner Component', () => {
Expand Down Expand Up @@ -198,6 +203,7 @@ describe('NetworkDesigner Component', () => {
).toBeInTheDocument();
fireEvent.click(getByText('Cancel'));
});

it('should display the Send Address modal', async () => {
const { getByText, findByText, store } = renderComponent();
expect(await findByText('backend1')).toBeInTheDocument();
Expand All @@ -219,16 +225,43 @@ describe('NetworkDesigner Component', () => {
});

it('should remove a node from the network', async () => {
const { getByText, findByText, queryByText } = renderComponent();
const { getByText, findByText, queryByText, store } = renderComponent();
// add a new LN node that doesn't have a tap node connected
store.getActions().designer.onCanvasDrop({
config: { snapToGrid: true },
data: { type: 'LND', version: testRepoState.images.LND.latest },
position: { x: 584, y: 343 },
id: 'c815fd9d-bbeb-4263-ad96-00bc488d8d60',
});

expect(await findByText('carol')).toBeInTheDocument();
act(() => {
fireEvent.click(getByText('carol'));
});
fireEvent.click(await findByText('Actions'));
fireEvent.click(await findByText('Remove'));
fireEvent.click(await findByText('Yes'));
await waitForElementToBeRemoved(() => queryByText('Yes'));
expect(queryByText('carol')).toBeNull();
});

it('should not remove an LND node with a connected tapd node', async () => {
const { getByText, findByText } = renderComponent();
expect(await findByText('alice')).toBeInTheDocument();
act(() => {
fireEvent.click(getByText('alice'));
});
fireEvent.click(await findByText('Actions'));
fireEvent.click(await findByText('Remove'));
fireEvent.click(await findByText('Yes'));
await waitForElementToBeRemoved(() => queryByText('Yes'));
expect(queryByText('alice')).toBeNull();

await suppressConsoleErrors(async () => {
expect(
await findByText(
'Cannot remove a Lightning node that has a Taproot Assets node connected to it.',
),
).toBeInTheDocument();
});
});

it('should remove a TAP node from the network', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,12 @@ describe('ChangeTapBackendModal', () => {
it('should display the compatibility warning for older LND node', async () => {
const { getByText, queryByText, changeSelect, store } = await renderComponent();
store.getActions().app.setRepoState(testRepoState);
const tapdLatest = testRepoState.images.tapd.latest;
const compatibleLnd = testRepoState.images.tapd.compatibility![tapdLatest];
const warning =
`alice-tap is running tapd v0.2.3-alpha which is compatible with LND v0.16.0-beta and newer.` +
` dave is running LND v0.7.1-beta so it cannot be used.`;
`alice-tap is running tapd v${tapdLatest} which is ` +
`compatible with LND v${compatibleLnd} and newer. ` +
`dave is running LND v0.7.1-beta so it cannot be used.`;
expect(queryByText(warning)).not.toBeInTheDocument();
expect(getByText('Cancel')).toBeInTheDocument();
changeSelect('LND Node', 'dave');
Expand Down
15 changes: 14 additions & 1 deletion src/components/designer/tap/actions/MintAssetModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ describe('MintAssetModal', () => {
expect(btn.parentElement).toBeInstanceOf(HTMLButtonElement);
});

it('should update amount when type changes', async () => {
const { getByLabelText, changeSelect } = await renderComponent();
expect(getByLabelText('Amount')).toHaveValue('1,000');
changeSelect('Asset Type', 'Collectible');
expect(getByLabelText('Amount')).toHaveValue('1');
changeSelect('Asset Type', 'Normal');
expect(getByLabelText('Amount')).toHaveValue('1,000');
});

it('should hide modal when cancel is clicked', async () => {
const { getByText, queryByText, store } = await renderComponent();
const btn = getByText('Cancel');
Expand All @@ -99,7 +108,11 @@ describe('MintAssetModal', () => {
Promise.resolve(balances((node.id + 100).toString())),
);
tapServiceMock.mintAsset.mockResolvedValue({
batchKey: Buffer.from('mocked success!'),
pendingBatch: {
batchKey: Buffer.from('mocked success!'),
assets: [],
state: 'BATCH_STATE_FINALIZED',
},
});

lightningServiceMock.getBalances.mockResolvedValue({
Expand Down
13 changes: 11 additions & 2 deletions src/components/designer/tap/actions/MintAssetModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useAsyncCallback } from 'react-async-hook';
import { Alert, Checkbox, Form, Input, InputNumber, Modal, Select } from 'antd';
import { usePrefixedTranslation } from 'hooks';
Expand Down Expand Up @@ -53,6 +53,10 @@ const MintAssetModal: React.FC<Props> = ({ network }) => {
getWalletBalance(lndNode);
}, []);

const handleTypeChange = useCallback((value: number) => {
form.setFieldsValue({ amount: value === 1 ? 1 : 1000 });
}, []);

const lowBalance = useMemo(() => {
const lndNodeModel = lightningNodes[thisTapNode?.lndName];
return Number(lndNodeModel?.walletBalance?.confirmed) < TAP_MIN_LND_BALANCE;
Expand Down Expand Up @@ -96,7 +100,10 @@ const MintAssetModal: React.FC<Props> = ({ network }) => {
onFinish={handleSubmit}
>
<Form.Item name="assetType" label={l('assetType')}>
<Select placeholder={l('assetTypePlaceholder')}>
<Select<number>
placeholder={l('assetTypePlaceholder')}
onChange={handleTypeChange}
>
<Select.Option value={0}>{l('assetTypeNormal')}</Select.Option>
<Select.Option value={1}>{l('assetTypeCollectible')}</Select.Option>
</Select>
Expand All @@ -118,12 +125,14 @@ const MintAssetModal: React.FC<Props> = ({ network }) => {
min={1}
/>
</Form.Item>

{/*
Hidden until asset groups is fully supported
<Form.Item name="enableEmission" valuePropName="checked">
<Checkbox>{l('enableEmission')}</Checkbox>
</Form.Item>
*/}

<Form.Item name="finalize" valuePropName="checked">
<Checkbox>{l('finalize')}</Checkbox>
</Form.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ describe('NewAddressModal', () => {
expect(await findByText('Successfully created address')).toBeInTheDocument();
expect(getByDisplayValue('tap1address')).toBeInTheDocument();
const node = network.nodes.tap[0];
expect(tapServiceMock.newAddress).toBeCalledWith(node, 'test-id', 100);
expect(tapServiceMock.newAddress).toBeCalledWith(node, 'test-id', '100');
});

it('should close the modal', async () => {
Expand Down
30 changes: 22 additions & 8 deletions src/components/designer/tap/actions/NewAddressModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ const Styled = {
Dropdown: styled(Dropdown)`
float: right;
`,
AssetOption: styled.div`
display: flex;
justify-content: space-between;

code {
color: #888;
font-size: 0.8em;
}
`,
};

interface Props {
Expand All @@ -49,7 +58,7 @@ const NewAddressModal: React.FC<Props> = ({ network }) => {
const { syncUniverse, getNewAddress } = useStoreActions(s => s.tap);
const { nodes } = useStoreState(s => s.tap);

const [selectedAmount, setSelectedAmount] = useState(10);
const [selectedAmount, setSelectedAmount] = useState(100);
const [selectedName, setSelectedName] = useState('');
const [tapAddress, setTapAddress] = useState('');

Expand Down Expand Up @@ -99,10 +108,7 @@ const NewAddressModal: React.FC<Props> = ({ network }) => {
const assetOptions = useMemo(() => {
const node = nodes[thisTapNode.name];
if (node && node.assetRoots) {
return node.assetRoots.map(asset => ({
label: asset.name,
value: asset.id,
}));
return node.assetRoots;
}
return [];
}, [nodes, thisTapNode.name]);
Expand All @@ -118,7 +124,7 @@ const NewAddressModal: React.FC<Props> = ({ network }) => {
colon={false}
initialValues={{
assetId: '',
amount: '10',
amount: '100',
}}
onFinish={handleSubmit}
>
Expand All @@ -143,10 +149,18 @@ const NewAddressModal: React.FC<Props> = ({ network }) => {
}
>
<Select
options={assetOptions}
disabled={handleSync.loading}
onChange={(_, option: any) => setSelectedName(option.label)}
/>
>
{assetOptions.map(a => (
<Select.Option key={a.id} value={a.id}>
<Styled.AssetOption>
<span>{a.name}</span>
<code>{ellipseInner(a.id, 4)}</code>
</Styled.AssetOption>
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={l('amount')}
Expand Down
Loading