Skip to content

Commit f69fa7c

Browse files
committed
feat(rpc): CloseChannel call
This introduces the ability to close channels through xud. Currently this is only implemented for lnd, and is implemented in a naive way that relies on the peer we are trying to close channels with being online and cooperative. Closes #1471. Follow-ups are #1472 and #1476.
1 parent 6e6a4c8 commit f69fa7c

File tree

13 files changed

+1048
-319
lines changed

13 files changed

+1048
-319
lines changed

Diff for: docs/api.md

+30-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: lib/cli/commands/closechannel.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Arguments } from 'yargs';
2+
import { CloseChannelRequest } from '../../proto/xudrpc_pb';
3+
import { callback, loadXudClient } from '../command';
4+
5+
export const command = 'closechannel <node_identifier> <currency> [--force]';
6+
7+
export const describe = 'close any payment channels with a peer';
8+
9+
export const builder = {
10+
node_identifier: {
11+
description: 'the node key or alias of the connected peer to close the channel with',
12+
type: 'string',
13+
},
14+
currency: {
15+
description: 'the ticker symbol for the currency',
16+
type: 'string',
17+
},
18+
force: {
19+
type: 'boolean',
20+
description: 'whether to force close if the peer is offline',
21+
},
22+
};
23+
24+
export const handler = async (argv: Arguments<any>) => {
25+
const request = new CloseChannelRequest();
26+
request.setNodeIdentifier(argv.node_identifier);
27+
request.setCurrency(argv.currency.toUpperCase());
28+
if (argv.force) {
29+
request.setForce(argv.force);
30+
}
31+
(await loadXudClient(argv)).closeChannel(request, callback(argv));
32+
};

Diff for: lib/grpc/GrpcService.ts

+16
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,22 @@ class GrpcService {
216216
}
217217
}
218218

219+
/**
220+
* See [[Service.closeChannel]]
221+
*/
222+
public closeChannel: grpc.handleUnaryCall<xudrpc.CloseChannelRequest, xudrpc.CloseChannelResponse> = async (call, callback) => {
223+
if (!this.isReady(this.service, callback)) {
224+
return;
225+
}
226+
try {
227+
await this.service.closeChannel(call.request.toObject());
228+
const response = new xudrpc.CloseChannelResponse();
229+
callback(null, response);
230+
} catch (err) {
231+
callback(getGrpcError(err), null);
232+
}
233+
}
234+
219235
/**
220236
* See [[Service.removeOrder]]
221237
*/

Diff for: lib/lndclient/LndClient.ts

+40-24
Original file line numberDiff line numberDiff line change
@@ -1027,30 +1027,46 @@ class LndClient extends SwapClient {
10271027
/**
10281028
* Attempts to close an open channel.
10291029
*/
1030-
public closeChannel = (fundingTxId: string, outputIndex: number, force: boolean): void => {
1031-
if (!this.lightning) {
1032-
throw(errors.UNAVAILABLE(this.currency, this.status));
1033-
}
1034-
const request = new lndrpc.CloseChannelRequest();
1035-
const channelPoint = new lndrpc.ChannelPoint();
1036-
channelPoint.setFundingTxidStr(fundingTxId);
1037-
channelPoint.setOutputIndex(outputIndex);
1038-
request.setChannelPoint(channelPoint);
1039-
request.setForce(force);
1040-
this.lightning.closeChannel(request, this.meta)
1041-
// TODO: handle close channel events
1042-
.on('data', (message: string) => {
1043-
this.logger.info(`closeChannel update: ${message}`);
1044-
})
1045-
.on('end', () => {
1046-
this.logger.info('closeChannel ended');
1047-
})
1048-
.on('status', (status: string) => {
1049-
this.logger.debug(`closeChannel status: ${JSON.stringify(status)}`);
1050-
})
1051-
.on('error', (error: any) => {
1052-
this.logger.error(`closeChannel error: ${error}`);
1053-
});
1030+
public closeChannel = (fundingTxId: string, outputIndex: number, force: boolean): Promise<void> => {
1031+
return new Promise<void>((resolve, reject) => {
1032+
if (!this.lightning) {
1033+
throw(errors.UNAVAILABLE(this.currency, this.status));
1034+
}
1035+
const request = new lndrpc.CloseChannelRequest();
1036+
const channelPoint = new lndrpc.ChannelPoint();
1037+
channelPoint.setFundingTxidStr(fundingTxId);
1038+
channelPoint.setOutputIndex(outputIndex);
1039+
request.setChannelPoint(channelPoint);
1040+
request.setForce(force);
1041+
1042+
this.lightning.closeChannel(request, this.meta)
1043+
.on('data', (message: lndrpc.CloseStatusUpdate) => {
1044+
if (message.hasClosePending()) {
1045+
const txId = base64ToHex(message.getClosePending()!.getTxid_asB64());
1046+
if (txId) {
1047+
this.logger.info(`channel closed with tx id ${txId}`);
1048+
resolve();
1049+
}
1050+
}
1051+
})
1052+
.on('end', () => {
1053+
this.logger.debug('closeChannel ended');
1054+
// we should receeive a channel close update above before the end event
1055+
// if we don't, assume the call has failed and reject
1056+
// if we have already resolved the promise this line will do nothing
1057+
reject('channel close ended unexpectedly');
1058+
})
1059+
.on('status', (status: grpc.StatusObject) => {
1060+
this.logger.debug(`closeChannel status: ${status.code} ${status.details}`);
1061+
if (status.code !== grpc.status.OK) {
1062+
reject(status.details);
1063+
}
1064+
})
1065+
.on('error', (err: any) => {
1066+
this.logger.error(`closeChannel error: ${err}`);
1067+
reject(err);
1068+
});
1069+
});
10541070
}
10551071

10561072
/** Lnd specific procedure to disconnect from the server. */

Diff for: lib/proto/xudrpc.swagger.json

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: lib/proto/xudrpc_grpc_pb.d.ts

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: lib/proto/xudrpc_grpc_pb.js

+35
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: lib/proto/xudrpc_pb.d.ts

+46
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)