From 5aad642c5934426149caa7c799148ec52921fb9b Mon Sep 17 00:00:00 2001 From: lisa neigut Date: Fri, 31 May 2019 14:57:04 -0700 Subject: [PATCH] opening: add fundchannel_cancel command Provide the option to cancel a funding-opening with a peer. Must either call `fundchannel_cancel` or `fundchannel_continue` --- contrib/pylightning/lightning/lightning.py | 9 +++ lightningd/opening_control.c | 71 ++++++++++++++++++++++ openingd/opening_wire.csv | 3 + openingd/openingd.c | 10 +++ tests/test_connection.py | 8 +++ 5 files changed, 101 insertions(+) diff --git a/contrib/pylightning/lightning/lightning.py b/contrib/pylightning/lightning/lightning.py index 723d5d3de..e79de28f8 100644 --- a/contrib/pylightning/lightning/lightning.py +++ b/contrib/pylightning/lightning/lightning.py @@ -499,6 +499,15 @@ class LightningRpc(UnixDomainSocketRpc): } return self.call("fundchannel_start", payload) + def fundchannel_cancel(self, node_id): + """ + Cancel a 'started' fundchannel with node {id}. + """ + payload = { + "id": node_id, + } + return self.call("fundchannel_cancel", payload) + def fundchannel_continue(self, node_id, funding_txid, funding_txout): """ Complete channel establishment with {id}, using {funding_txid} at {funding_txout} diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index b63a80329..e501c21c8 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -1021,6 +1021,7 @@ static unsigned int openingd_msg(struct subd *openingd, case WIRE_OPENING_FUNDER: case WIRE_OPENING_FUNDER_START: case WIRE_OPENING_FUNDER_CONTINUE: + case WIRE_OPENING_FUNDER_CANCEL: case WIRE_OPENING_GOT_OFFER_REPLY: case WIRE_OPENING_DEV_MEMLEAK: /* Replies never get here */ @@ -1148,6 +1149,68 @@ static struct command_result *json_fund_channel_continue(struct command *cmd, return command_still_pending(cmd); } +/** + * json_fund_channel_cancel - Entrypoint for cancelling an in flight channel-funding + */ +static struct command_result *json_fund_channel_cancel(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + + struct node_id *id; + struct peer *peer; + u8 *msg; + + if (!param(cmd, buffer, params, + p_req("id", param_node_id, &id), + NULL)) + return command_param_failed(); + + peer = peer_by_id(cmd->ld, id); + if (!peer) { + return command_fail(cmd, LIGHTNINGD, "Unknown peer"); + } + + if (!peer->uncommitted_channel) { + return command_fail(cmd, LIGHTNINGD, "Peer not connected"); + } + + if (!peer->uncommitted_channel->fc || !peer->uncommitted_channel->fc->inflight) + return command_fail(cmd, LIGHTNINGD, "No channel funding in progress."); + + /** + * there's a question of 'state machinery' here. as is, we're not checking + * to see if you've already called `continue` -- we expect you + * the caller to EITHER pick 'continue' or 'cancel'. + * but if for some reason you've decided to test your luck, how much + * 'handling' can we do for that case? the easiest thing to do is to + * say "sorry you've already called continue", we can't cancel this. + * + * there's also the state you might end up in where you've called + * continue (and it's completed and been passed off to channeld) but + * you've decided (for whatever reason) not to broadcast the transaction + * so your channels have ended up in this 'waiting' state. neither of us + * are actually out any amount of cash, but it'd be nice if there's a way + * to signal to c-lightning (+ your peer) that this channel is dead on arrival. + * ... but also if you then broadcast this tx you'd be in trouble cuz we're + * both going to forget about it. the meta question here is how 'undoable' + * should we make any of this. how much tools do we give you, reader? + * + * for now, let's settle for the EITHER / OR case and disregard the larger + * question about 'how long cancelable'. + */ + + /* Update the cmd to the new cmd */ + peer->uncommitted_channel->fc->cmd = cmd; + msg = towire_opening_funder_cancel(NULL); + subd_send_msg(peer->uncommitted_channel->openingd, take(msg)); + return command_still_pending(cmd); +} + +/** + * json_fund_channel_start - Entrypoint for funding an externally funded channel + */ static struct command_result *json_fund_channel_start(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -1367,6 +1430,14 @@ static const struct json_command fund_channel_start_command = { }; AUTODATA(json_command, &fund_channel_start_command); +static const struct json_command fund_channel_cancel_command = { + "fundchannel_cancel", + "channels", + json_fund_channel_cancel, + "Cancel inflight channel establishment with peer {id}." +}; +AUTODATA(json_command, &fund_channel_cancel_command); + static const struct json_command fund_channel_continue_command = { "fundchannel_continue", "channels", diff --git a/openingd/opening_wire.csv b/openingd/opening_wire.csv index 164ad7011..bbfbed77d 100644 --- a/openingd/opening_wire.csv +++ b/openingd/opening_wire.csv @@ -98,6 +98,9 @@ opening_funder_continue,6012 opening_funder_continue,,funding_txid,struct bitcoin_txid opening_funder_continue,,funding_txout,u16 +#master->openingd: cancel channel establishment for a funding +opening_funder_cancel,6013 + # Openingd->master: we failed to negotiation channel opening_funder_failed,6004 opening_funder_failed,,reason,wirestring diff --git a/openingd/openingd.c b/openingd/openingd.c index 482480c5f..402359707 100644 --- a/openingd/openingd.c +++ b/openingd/openingd.c @@ -1683,6 +1683,16 @@ static u8 *handle_master_in(struct state *state) state->funding_txid = funding_txid; state->funding_txout = funding_txout; return funder_channel_continue(state); + case WIRE_OPENING_FUNDER_CANCEL: + /* We're aborting this, simple */ + if (!fromwire_opening_funder_cancel(msg)) + master_badmsg(WIRE_OPENING_FUNDER_CANCEL, msg); + + msg = towire_errorfmt(NULL, &state->channel_id, "Channel open canceled by us"); + sync_crypto_write(state->pps, take(msg)); + negotiation_aborted(state, true, + "Channel open canceled by RPC", false); + return NULL; case WIRE_OPENING_DEV_MEMLEAK: #if DEVELOPER handle_dev_memleak(state, msg); diff --git a/tests/test_connection.py b/tests/test_connection.py index 1dcd64b95..0c4c3e0e3 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -844,6 +844,14 @@ def test_funding_external_wallet_corners(node_factory, bitcoind): with pytest.raises(RpcError, match=r'No channel funding in progress.'): l1.rpc.fundchannel_continue(l2.info['id'], fake_txid, fake_txout) + l1.rpc.fundchannel_start(l2.info['id'], amount) + with pytest.raises(RpcError, match=r'Already funding channel'): + l1.rpc.fundchannel(l2.info['id'], amount) + + l1.rpc.fundchannel_cancel(l2.info['id']) + # Should be able to 'restart' after canceling + l1.rpc.fundchannel_start(l2.info['id'], amount) + def test_funding_external_wallet(node_factory, bitcoind): l1 = node_factory.get_node()