Skip to content

Commit

Permalink
fix: wait until all bitcoin nodes are online to mine first block
Browse files Browse the repository at this point in the history
  • Loading branch information
jamaljsr committed Mar 25, 2020
1 parent 4f2779f commit 1dbadc2
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 20 deletions.
13 changes: 0 additions & 13 deletions src/store/models/bitcoind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export interface BitcoindModel {
StoreInjections,
RootModel
>;
mineFirstBlock: Thunk<BitcoindModel, BitcoinNode, StoreInjections, RootModel>;
}

const bitcoindModel: BitcoindModel = {
Expand Down Expand Up @@ -67,18 +66,6 @@ const bitcoindModel: BitcoindModel = {
network.nodes.bitcoin.filter(n => n.status === Status.Started).map(actions.getInfo),
);
}),
mineFirstBlock: thunk(async (actions, node, { injections, getState }) => {
// if this is the first time starting the network, then automatically mine the first
// block. Otherwise, Eclair nodes will fail to start.
if (node.name === 'backend1') {
// only mine this block on the first node
const { chainInfo } = getState().nodes['backend1'];
if (chainInfo) {
await injections.bitcoindService.mine(1, node);
await actions.getInfo(node);
}
}
}),
};

export default bitcoindModel;
14 changes: 13 additions & 1 deletion src/store/models/network.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ describe('Network model', () => {
bitcoin.forEach(node => expect(node.errorMsg).toBe('test-error'));
});

it('should mine the first block on startup', async () => {
it('should mine a block on startup', async () => {
bitcoindServiceMock.getBlockchainInfo.mockResolvedValue({ blocks: 0 } as any);
const { start } = store.getActions().network;
const network = firstNetwork();
Expand All @@ -568,6 +568,18 @@ describe('Network model', () => {
expect(bitcoindServiceMock.mine).toBeCalledWith(1, btcNode);
});

it('should not throw when mining a block on startup fails', async () => {
bitcoindServiceMock.mine.mockRejectedValue(new Error('test-error'));
const { start } = store.getActions().network;
const network = firstNetwork();
await expect(start(network.id)).resolves.not.toThrow();
const btcNode = {
...firstNetwork().nodes.bitcoin[0],
status: Status.Starting,
};
expect(bitcoindServiceMock.mine).toBeCalledWith(1, btcNode);
});

it('should not save compose file and networks if all ports are available', async () => {
detectPortMock.mockImplementation(port => Promise.resolve(port));
(injections.dockerService.saveComposeFile as jest.Mock).mockReset();
Expand Down
24 changes: 18 additions & 6 deletions src/store/models/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Status,
} from 'shared/types';
import { CustomImage, Network, StoreInjections } from 'types';
import { delay } from 'utils/async';
import { initChartFromNetwork } from 'utils/chart';
import { APP_VERSION, DOCKER_REPO } from 'utils/constants';
import { rm } from 'utils/files';
Expand Down Expand Up @@ -584,7 +585,8 @@ const networkModel: NetworkModel = {
const network = getStoreState().network.networks.find(n => n.id === id);
if (!network) throw new Error(l('networkByIdErr', { networkId: id }));

const allNodesOnline: Promise<void>[] = [];
const lnNodesOnline: Promise<void>[] = [];
const btcNodesOnline: Promise<void>[] = [];
for (const node of nodes) {
// wait for lnd nodes to come online before updating their status
if (node.type === 'lightning') {
Expand All @@ -599,29 +601,39 @@ const networkModel: NetworkModel = {
.catch(error =>
actions.setStatus({ id, status: Status.Error, only: ln.name, error }),
);
allNodesOnline.push(promise);
lnNodesOnline.push(promise);
} else if (node.type === 'bitcoin') {
const btc = node as BitcoinNode;
// wait for bitcoind nodes to come online before updating their status
// use .then() to continue execution while the promises are waiting to complete
injections.bitcoindService
const promise = injections.bitcoindService
.waitUntilOnline(btc)
.then(async () => {
actions.setStatus({ id, status: Status.Started, only: btc.name });
// connect each bitcoin node to it's peers so tx & block propagation is fast
await injections.bitcoindService.connectPeers(btc);
await getStoreActions().bitcoind.getInfo(btc);
await getStoreActions().bitcoind.mineFirstBlock(btc);
})
.catch(error =>
actions.setStatus({ id, status: Status.Error, only: btc.name, error }),
);
btcNodesOnline.push(promise);
}
}
// after all bitcoin nodes are online, mine one block so that Eclair nodes will start
if (btcNodesOnline.length) {
const node = network.nodes.bitcoin[0];
await Promise.all(btcNodesOnline)
.then(async () => {
await delay(2000);
await getStoreActions().bitcoind.mine({ node, blocks: 1 });
})
.catch(e => info('Failed to mine a block after network startup', e));
}
// after all LN nodes are online, connect each of them to each other. This helps
// ensure that each node is aware of the entire graph and can route payments properly
if (allNodesOnline.length) {
await Promise.all(allNodesOnline)
if (lnNodesOnline.length) {
await Promise.all(lnNodesOnline)
.then(async () => await getStoreActions().lightning.connectAllPeers(network))
.catch(e => info('Failed to connect all LN peers', e));
}
Expand Down

0 comments on commit 1dbadc2

Please sign in to comment.