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

refactor: make bitcoin implementation flexible #1092

Merged
merged 3 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/common/RemoveNode.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react';
import { fireEvent, waitFor } from '@testing-library/react';
import { CommonNode, Status } from 'shared/types';
import { BitcoindLibrary, DockerLibrary } from 'types';
import { DockerLibrary } from 'types';
import { initChartFromNetwork } from 'utils/chart';
import { defaultRepoState } from 'utils/constants';
import { createBitcoindNetworkNode, createLndNetworkNode } from 'utils/network';
import {
getNetwork,
injections,
lightningServiceMock,
bitcoinServiceMock,
renderWithProviders,
suppressConsoleErrors,
tapServiceMock,
Expand All @@ -17,7 +18,6 @@ import {
import RemoveNode from './RemoveNode';

const dockerServiceMock = injections.dockerService as jest.Mocked<DockerLibrary>;
const bitcoindServiceMock = injections.bitcoindService as jest.Mocked<BitcoindLibrary>;

describe('RemoveNode', () => {
const renderComponent = (
Expand Down Expand Up @@ -136,7 +136,7 @@ describe('RemoveNode', () => {
beforeEach(() => {
lightningServiceMock.getChannels.mockResolvedValue([]);
lightningServiceMock.waitUntilOnline.mockResolvedValue(Promise.resolve());
bitcoindServiceMock.waitUntilOnline.mockResolvedValue(Promise.resolve());
bitcoinServiceMock.waitUntilOnline.mockResolvedValue(Promise.resolve());
tapServiceMock.waitUntilOnline.mockResolvedValue(Promise.resolve());
});

Expand Down
5 changes: 2 additions & 3 deletions src/components/common/RenameNodeModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';
import { fireEvent, waitFor } from '@testing-library/react';
import { Status } from 'shared/types';
import { BitcoindLibrary } from 'types';
import * as asyncUtil from 'utils/async';
import { initChartFromNetwork } from 'utils/chart';
import { defaultRepoState } from 'utils/constants';
import { createNetwork } from 'utils/network';
import {
injections,
lightningServiceMock,
bitcoinServiceMock,
litdServiceMock,
renderWithProviders,
tapServiceMock,
Expand All @@ -22,7 +22,6 @@ const asyncUtilMock = asyncUtil as jest.Mocked<typeof asyncUtil>;
const dockerServiceMock = injections.dockerService as jest.Mocked<
typeof injections.dockerService
>;
const bitcoindServiceMock = injections.bitcoindService as jest.Mocked<BitcoindLibrary>;

describe('RenameNodeModal', () => {
let unmount: () => void;
Expand Down Expand Up @@ -144,7 +143,7 @@ describe('RenameNodeModal', () => {
it('should update the started Backend node name', async () => {
asyncUtilMock.delay.mockResolvedValue(Promise.resolve());
lightningServiceMock.waitUntilOnline.mockResolvedValue();
bitcoindServiceMock.waitUntilOnline.mockResolvedValue();
bitcoinServiceMock.waitUntilOnline.mockResolvedValue();
tapServiceMock.waitUntilOnline.mockResolvedValue();
litdServiceMock.waitUntilOnline.mockResolvedValue();
const { getByText, getByLabelText, store } = await renderComponent(
Expand Down
2 changes: 1 addition & 1 deletion src/components/designer/NetworkDesigner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useStoreActions, useStoreState } from 'store';
import { Network } from 'types';
import { Loader, RenameNodeModal } from 'components/common';
import AdvancedOptionsModal from 'components/common/AdvancedOptionsModal';
import SendOnChainModal from './bitcoind/actions/SendOnChainModal';
import SendOnChainModal from './bitcoin/actions/SendOnChainModal';
import { Link, NodeInner, Port, Ports } from './custom';
import { CanvasOuterDark, CanvasOuterLight } from './custom/CanvasOuter';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/components/designer/NodeContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from 'components/common';
import { ViewLogsButton } from 'components/dockerLogs';
import { OpenTerminalButton } from 'components/terminal';
import SendOnChainButton from './bitcoind/actions/SendOnChainButton';
import SendOnChainButton from './bitcoin/actions/SendOnChainButton';
import { OpenChannelButtons, PaymentButtons } from './lightning/actions';
import { MintAssetButton, NewAddressButton, SendAssetButton } from './tap/actions';

Expand Down
6 changes: 3 additions & 3 deletions src/components/designer/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useMemo } from 'react';
import { IChart } from '@mrblenny/react-flow-chart';
import { LightningNode, TapNode } from 'shared/types';
import { BitcoindNode, LightningNode, TapNode } from 'shared/types';
import { Network } from 'types';
import BitcoindDetails from './bitcoind/BitcoindDetails';
import BitcoindDetails from './bitcoin/BitcoinDetails';
import DefaultSidebar from './default/DefaultSidebar';
import LightningDetails from './lightning/LightningDetails';
import LinkDetails from './link/LinkDetails';
Expand All @@ -21,7 +21,7 @@ const Sidebar: React.FC<Props> = ({ network, chart }) => {
const { bitcoin, lightning, tap } = network.nodes;
const node = [...bitcoin, ...lightning, ...tap].find(n => n.name === id);
if (node && node.implementation === 'bitcoind') {
return <BitcoindDetails node={node} />;
return <BitcoindDetails node={node as BitcoindNode} />;
} else if (node && node.type === 'lightning') {
return <LightningDetails node={node as LightningNode} />;
} else if (node && node.type === 'tap') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { shell } from 'electron';
import { fireEvent, waitFor } from '@testing-library/react';
import { Status } from 'shared/types';
import { bitcoinCredentials, dockerConfigs } from 'utils/constants';
import { getNetwork, injections, renderWithProviders } from 'utils/tests';
import BitcoindDetails from './BitcoindDetails';
import { getNetwork, renderWithProviders, bitcoinServiceMock } from 'utils/tests';
import BitcoindDetails from './BitcoinDetails';

describe('BitcoindDetails', () => {
const renderComponent = (status?: Status, custom = false) => {
Expand Down Expand Up @@ -98,8 +98,8 @@ describe('BitcoindDetails', () => {
});

describe('with node Started', () => {
const chainMock = injections.bitcoindService.getBlockchainInfo as jest.Mock;
const walletMock = injections.bitcoindService.getWalletInfo as jest.Mock;
const chainMock = bitcoinServiceMock.getBlockchainInfo as jest.Mock;
const walletMock = bitcoinServiceMock.getWalletInfo as jest.Mock;

beforeEach(() => {
chainMock.mockResolvedValue({ blocks: 123, bestblockhash: 'abcdef' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { getNetworkBackendId } from 'utils/network';
const BitcoindDetails: React.FC<{ node: BitcoinNode }> = ({ node }) => {
const { l } = usePrefixedTranslation('cmps.designer.bitcoind.BitcoinDetails');
const [activeTab, setActiveTab] = useState('info');
const { getInfo } = useStoreActions(s => s.bitcoind);
const { nodes } = useStoreState(s => s.bitcoind);
const { getInfo } = useStoreActions(s => s.bitcoin);
const { nodes } = useStoreState(s => s.bitcoin);
const getInfoAsync = useAsync(
async (node: BitcoinNode) => {
if (node.status === Status.Started) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface Props {

const InfoTab: React.FC<Props> = ({ node }) => {
const { l } = usePrefixedTranslation('cmps.designer.bitcoind.InfoTab');
const { nodes } = useStoreState(s => s.bitcoind);
const { nodes } = useStoreState(s => s.bitcoin);
const details: DetailValues = [
{ label: l('nodeType'), value: node.type },
{ label: l('implementation'), value: dockerConfigs[node.implementation].name },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { fireEvent, waitFor } from '@testing-library/dom';
import { Status } from 'shared/types';
import {
getNetwork,
injections,
lightningServiceMock,
renderWithProviders,
tapServiceMock,
bitcoinServiceMock,
} from 'utils/tests';
import MineBlocksInput from './MineBlocksInput';

Expand Down Expand Up @@ -50,7 +50,7 @@ describe('MineBlocksInput', () => {
});

it('should mine a block when the button is clicked', async () => {
const mineMock = injections.bitcoindService.mine as jest.Mock;
const mineMock = bitcoinServiceMock.mine as jest.Mock;
mineMock.mockResolvedValue(true);
const { input, btn, store } = renderComponent();
const numBlocks = 5;
Expand All @@ -63,7 +63,7 @@ describe('MineBlocksInput', () => {
});

it('should mine 1 block when a invalid value is specified', async () => {
const mineMock = injections.bitcoindService.mine as jest.Mock;
const mineMock = bitcoinServiceMock.mine as jest.Mock;
mineMock.mockResolvedValue(true);
const { input, btn, store } = renderComponent();
fireEvent.change(input, { target: { value: 'asdf' } });
Expand All @@ -75,7 +75,7 @@ describe('MineBlocksInput', () => {
});

it('should display an error if mining fails', async () => {
const mineMock = injections.bitcoindService.mine as jest.Mock;
const mineMock = bitcoinServiceMock.mine as jest.Mock;
mineMock.mockRejectedValue(new Error('connection failed'));
const { input, btn, findByText } = renderComponent();
const numBlocks = 5;
Expand All @@ -93,7 +93,7 @@ describe('MineBlocksInput', () => {
});

it('should display an error if lightning nodes cannot update after mining', async () => {
const mineMock = injections.bitcoindService.mine as jest.Mock;
const mineMock = bitcoinServiceMock.mine as jest.Mock;
mineMock.mockResolvedValue(true);
lightningServiceMock.getInfo.mockRejectedValueOnce(new Error('info-error'));
const { input, btn, findByText } = renderComponent(Status.Started);
Expand All @@ -104,7 +104,7 @@ describe('MineBlocksInput', () => {
});

it('should display an error if tap nodes cannot update after mining', async () => {
const mineMock = injections.bitcoindService.mine as jest.Mock;
const mineMock = bitcoinServiceMock.mine as jest.Mock;
mineMock.mockResolvedValue(true);
tapServiceMock.listAssets.mockRejectedValueOnce(new Error('info-error'));
const { input, btn, findByText } = renderComponent(Status.Started);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const MineBlocksInput: React.FC<{ node: BitcoinNode }> = ({ node }) => {
const { l } = usePrefixedTranslation('cmps.designer.bitcoind.MineBlocksInput');
const [value, setValue] = useState(6);
const { notify } = useStoreActions(s => s.app);
const { mine } = useStoreActions(s => s.bitcoind);
const { mine } = useStoreActions(s => s.bitcoin);
const mineAsync = useAsyncCallback(async () => {
try {
await mine({ blocks: value, node });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ import React from 'react';
import { fireEvent } from '@testing-library/dom';
import { waitFor } from '@testing-library/react';
import { Status } from 'shared/types';
import { BitcoindLibrary } from 'types';
import { initChartFromNetwork } from 'utils/chart';
import { defaultRepoState } from 'utils/constants';
import { createNetwork } from 'utils/network';
import {
injections,
renderWithProviders,
suppressConsoleErrors,
testManagedImages,
bitcoinServiceMock,
} from 'utils/tests';
import SendOnChainModal from './SendOnChainModal';

const bitcoindServiceMock = injections.bitcoindService as jest.Mocked<BitcoindLibrary>;

describe('SendOnChainModal', () => {
let unmount: () => void;

Expand Down Expand Up @@ -123,13 +120,13 @@ describe('SendOnChainModal', () => {
});

it('should display the correct balance for the selected backend', async () => {
bitcoindServiceMock.getWalletInfo.mockResolvedValueOnce({ balance: 123 } as any);
bitcoindServiceMock.getWalletInfo.mockResolvedValueOnce({ balance: 456 } as any);
bitcoindServiceMock.getWalletInfo.mockResolvedValueOnce({ balance: 0 } as any);
bitcoinServiceMock.getWalletInfo.mockResolvedValueOnce({ balance: 123 } as any);
bitcoinServiceMock.getWalletInfo.mockResolvedValueOnce({ balance: 456 } as any);
bitcoinServiceMock.getWalletInfo.mockResolvedValueOnce({ balance: 0 } as any);
const { findByText, changeSelect, store, network } = await renderComponent();
store.getActions().bitcoind.getInfo(network.nodes.bitcoin[0]);
store.getActions().bitcoind.getInfo(network.nodes.bitcoin[1]);
store.getActions().bitcoind.getInfo(network.nodes.bitcoin[2]);
store.getActions().bitcoin.getInfo(network.nodes.bitcoin[0]);
store.getActions().bitcoin.getInfo(network.nodes.bitcoin[1]);
store.getActions().bitcoin.getInfo(network.nodes.bitcoin[2]);
expect(await findByText('Balance: 123 BTC')).toBeInTheDocument();
changeSelect('From Bitcoin Node', 'backend2');
expect(await findByText('Balance: 456 BTC')).toBeInTheDocument();
Expand All @@ -139,14 +136,14 @@ describe('SendOnChainModal', () => {

describe('with form submitted', () => {
beforeEach(() => {
bitcoindServiceMock.getWalletInfo.mockResolvedValue({ balance: 123 } as any);
bitcoindServiceMock.mine.mockResolvedValue([]);
bitcoindServiceMock.sendFunds.mockResolvedValue('txid123');
bitcoinServiceMock.getWalletInfo.mockResolvedValue({ balance: 123 } as any);
bitcoinServiceMock.mine.mockResolvedValue([]);
bitcoinServiceMock.sendFunds.mockResolvedValue('txid123');
});

it('should send coins successfully', async () => {
const { getByText, getByLabelText, store, network } = await renderComponent();
store.getActions().bitcoind.getInfo(network.nodes.bitcoin[0]);
store.getActions().bitcoin.getInfo(network.nodes.bitcoin[0]);
fireEvent.change(getByLabelText('Amount (BTC)'), { target: { value: '0.001' } });
fireEvent.change(getByLabelText('Destination Onchain Address'), {
target: { value: 'bc1...' },
Expand All @@ -156,13 +153,13 @@ describe('SendOnChainModal', () => {
expect(store.getState().modals.sendOnChain.visible).toBe(false);
});
const node = network.nodes.bitcoin[0];
expect(bitcoindServiceMock.sendFunds).toHaveBeenCalledWith(node, 'bc1...', 0.001);
expect(bitcoindServiceMock.mine).toHaveBeenCalledWith(6, node);
expect(bitcoinServiceMock.sendFunds).toHaveBeenCalledWith(node, 'bc1...', 0.001);
expect(bitcoinServiceMock.mine).toHaveBeenCalledWith(6, node);
});

it('should not mine block when the option is unchecked', async () => {
const { getByText, getByLabelText, store, network } = await renderComponent();
store.getActions().bitcoind.getInfo(network.nodes.bitcoin[0]);
store.getActions().bitcoin.getInfo(network.nodes.bitcoin[0]);
fireEvent.change(getByLabelText('Amount (BTC)'), { target: { value: '0.001' } });
fireEvent.change(getByLabelText('Destination Onchain Address'), {
target: { value: 'bc1...' },
Expand All @@ -175,13 +172,13 @@ describe('SendOnChainModal', () => {
expect(store.getState().modals.sendOnChain.visible).toBe(false);
});
const node = network.nodes.bitcoin[0];
expect(bitcoindServiceMock.sendFunds).toHaveBeenCalledWith(node, 'bc1...', 0.001);
expect(bitcoindServiceMock.mine).not.toHaveBeenCalled();
expect(bitcoinServiceMock.sendFunds).toHaveBeenCalledWith(node, 'bc1...', 0.001);
expect(bitcoinServiceMock.mine).not.toHaveBeenCalled();
});

it('should display an error when amount is above balance', async () => {
const { getByText, getByLabelText, store, network } = await renderComponent();
store.getActions().bitcoind.getInfo(network.nodes.bitcoin[0]);
store.getActions().bitcoin.getInfo(network.nodes.bitcoin[0]);
fireEvent.change(getByLabelText('Amount (BTC)'), { target: { value: '125' } });
fireEvent.change(getByLabelText('Destination Onchain Address'), {
target: { value: 'bc1...' },
Expand All @@ -196,9 +193,9 @@ describe('SendOnChainModal', () => {
});

it('should display an error when sending funds fails', async () => {
bitcoindServiceMock.sendFunds.mockRejectedValue(new Error('error-msg'));
bitcoinServiceMock.sendFunds.mockRejectedValue(new Error('error-msg'));
const { getByText, getByLabelText, store, network } = await renderComponent();
store.getActions().bitcoind.getInfo(network.nodes.bitcoin[0]);
store.getActions().bitcoin.getInfo(network.nodes.bitcoin[0]);
fireEvent.change(getByLabelText('Amount (BTC)'), { target: { value: '0.001' } });
fireEvent.change(getByLabelText('Destination Onchain Address'), {
target: { value: 'bc1...' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ const SendOnChainModal: React.FC<Props> = ({ network }) => {
const { l } = usePrefixedTranslation('cmps.designer.bitcoind.actions.SendOnChainModal');
const [form] = Form.useForm();
const { visible, backendName } = useStoreState(s => s.modals.sendOnChain);
const { nodes } = useStoreState(s => s.bitcoind);
const { nodes } = useStoreState(s => s.bitcoin);
const { hideSendOnChain } = useStoreActions(s => s.modals);
const { notify } = useStoreActions(s => s.app);
const { sendFunds } = useStoreActions(s => s.bitcoind);
const { sendFunds } = useStoreActions(s => s.bitcoin);
const [selected, setSelected] = useState(backendName || '');

const balance: number = useMemo(() => {
Expand Down
13 changes: 5 additions & 8 deletions src/components/designer/lightning/actions/Deposit.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import React from 'react';
import { fireEvent, waitFor } from '@testing-library/react';
import { BitcoindLibrary } from 'types';
import {
defaultStateInfo,
getNetwork,
injections,
lightningServiceMock,
renderWithProviders,
bitcoinServiceMock,
} from 'utils/tests';
import { Deposit } from './';

const bitcoindServiceMock = injections.bitcoindService as jest.Mocked<BitcoindLibrary>;

describe('Deposit', () => {
const renderComponent = () => {
const network = getNetwork(1, 'test network');
Expand All @@ -31,7 +28,7 @@ describe('Deposit', () => {
};

beforeEach(() => {
bitcoindServiceMock.sendFunds.mockResolvedValue('txid');
bitcoinServiceMock.sendFunds.mockResolvedValue('txid');
lightningServiceMock.getNewAddress.mockResolvedValue({ address: 'bc1aaaa' });
lightningServiceMock.getInfo.mockResolvedValue(
defaultStateInfo({
Expand Down Expand Up @@ -76,7 +73,7 @@ describe('Deposit', () => {
fireEvent.click(btn);
await waitFor(() => getByText('Deposited 250,000 sats to alice'));
expect(lightningServiceMock.getNewAddress).toBeCalledTimes(1);
expect(bitcoindServiceMock.sendFunds).toBeCalledWith(
expect(bitcoinServiceMock.sendFunds).toBeCalledWith(
expect.anything(),
'bc1aaaa',
0.0025,
Expand All @@ -90,15 +87,15 @@ describe('Deposit', () => {
fireEvent.click(btn);
await waitFor(() => getByText('Deposited 1,000,000 sats to alice'));
expect(lightningServiceMock.getNewAddress).toBeCalledTimes(1);
expect(bitcoindServiceMock.sendFunds).toBeCalledWith(
expect(bitcoinServiceMock.sendFunds).toBeCalledWith(
expect.anything(),
'bc1aaaa',
0.01,
);
});

it('should display an error if mining fails', async () => {
bitcoindServiceMock.sendFunds.mockRejectedValue(new Error('connection failed'));
bitcoinServiceMock.sendFunds.mockRejectedValue(new Error('connection failed'));
const { input, btn, findByText } = renderComponent();
const numBlocks = 5;
fireEvent.change(input, { target: { value: numBlocks } });
Expand Down
Loading
Loading