From 6c708b58542526620a1f7de6a3ebf6cdb0b0d332 Mon Sep 17 00:00:00 2001 From: trueptolemy Date: Sat, 24 Aug 2019 05:34:52 +0800 Subject: [PATCH] API: `fundchannel_cancel` can cancel fundchannel process before funding broadcast --- channeld/channel_wire.csv | 7 ++ channeld/channeld.c | 18 +++++ lightningd/channel_control.c | 141 +++++++++++++++++++++++++++++++++++ lightningd/channel_control.h | 7 ++ lightningd/opening_control.c | 49 ++++-------- wallet/wallet.c | 17 +++++ wallet/wallet.h | 10 +++ 7 files changed, 215 insertions(+), 34 deletions(-) diff --git a/channeld/channel_wire.csv b/channeld/channel_wire.csv index 51aaba116..b949031b5 100644 --- a/channeld/channel_wire.csv +++ b/channeld/channel_wire.csv @@ -198,3 +198,10 @@ msgdata,channel_specific_feerates,feerate_ppm,u32, msgtype,channel_got_announcement,1017 msgdata,channel_got_announcement,remote_ann_node_sig,secp256k1_ecdsa_signature, msgdata,channel_got_announcement,remote_ann_bitcoin_sig,secp256k1_ecdsa_signature, + +# Ask channeld to send a error message. Used in forgetting channel case. +msgtype,channel_send_error,1008 +msgdata,channel_send_error,reason,wirestring, + +# Tell master channeld has sent the error message. +msgtype,channel_send_error_reply,1108 diff --git a/channeld/channeld.c b/channeld/channeld.c index 9e801e51a..633c3e047 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -2796,6 +2796,20 @@ static void handle_shutdown_cmd(struct peer *peer, const u8 *inmsg) start_commit_timer(peer); } +static void handle_send_error(struct peer *peer, const u8 *msg) +{ + char *reason; + if (!fromwire_channel_send_error(msg, msg, &reason)) + master_badmsg(WIRE_CHANNEL_SEND_ERROR, msg); + status_debug("Send error reason: %s", reason); + sync_crypto_write(peer->pps, + take(towire_errorfmt(NULL, &peer->channel_id, + "%s", reason))); + + wire_sync_write(MASTER_FD, + take(towire_channel_send_error_reply(NULL))); +} + #if DEVELOPER static void handle_dev_reenable_commit(struct peer *peer) { @@ -2849,6 +2863,9 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNEL_SEND_SHUTDOWN: handle_shutdown_cmd(peer, msg); return; + case WIRE_CHANNEL_SEND_ERROR: + handle_send_error(peer, msg); + return; #if DEVELOPER case WIRE_CHANNEL_DEV_REENABLE_COMMIT: handle_dev_reenable_commit(peer); @@ -2875,6 +2892,7 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNEL_DEV_REENABLE_COMMIT_REPLY: case WIRE_CHANNEL_FAIL_FALLEN_BEHIND: case WIRE_CHANNEL_DEV_MEMLEAK_REPLY: + case WIRE_CHANNEL_SEND_ERROR_REPLY: break; } master_badmsg(-1, msg); diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 6b85a530a..dc4ce7091 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -3,10 +3,13 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -15,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -230,6 +234,33 @@ static void peer_start_closingd_after_shutdown(struct channel *channel, channel_set_state(channel, CHANNELD_SHUTTING_DOWN, CLOSINGD_SIGEXCHANGE); } +static void handle_error_channel(struct channel *channel, + const u8 *msg) +{ + struct command **forgets = tal_steal(tmpctx, channel->forgets); + channel->forgets = tal_arr(channel, struct command *, 0); + + if (!fromwire_channel_send_error_reply(msg)) { + channel_internal_error(channel, "bad send_error_reply: %s", + tal_hex(tmpctx, msg)); + return; + } + + /* Forget the channel. */ + delete_channel(channel); + + for (size_t i = 0; i < tal_count(forgets); i++) { + assert(!forgets[i]->json_stream); + + struct json_stream *response; + response = json_stream_success(forgets[i]); + json_add_string(response, "cancelled", "Channel open canceled by RPC(after fundchannel_complete)"); + was_pending(command_success(forgets[i], response)); + } + + tal_free(forgets); +} + static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) { enum channel_wire_type t = fromwire_peektype(msg); @@ -262,6 +293,9 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNEL_FAIL_FALLEN_BEHIND: channel_fail_fallen_behind(sd->channel, msg); break; + case WIRE_CHANNEL_SEND_ERROR_REPLY: + handle_error_channel(sd->channel, msg); + break; /* And we never get these from channeld. */ case WIRE_CHANNEL_INIT: @@ -281,6 +315,7 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNEL_OFFER_HTLC_REPLY: case WIRE_CHANNEL_DEV_REENABLE_COMMIT_REPLY: case WIRE_CHANNEL_DEV_MEMLEAK_REPLY: + case WIRE_CHANNEL_SEND_ERROR: break; } @@ -578,3 +613,109 @@ void channel_notify_new_block(struct lightningd *ld, tal_free(to_forget); } + +static void process_check_funding_broadcast(struct bitcoind *bitcoind UNUSED, + const struct bitcoin_tx_output *txout, + void *arg) +{ + struct channel *cancel = arg; + + if (txout != NULL) { + for (size_t i = 0; i < tal_count(cancel->forgets); i++) + was_pending(command_fail(cancel->forgets[i], LIGHTNINGD, + "The funding transaction has been broadcast, " + "please consider `close` or `dev-fail`! ")); + tal_free(cancel->forgets); + cancel->forgets = tal_arr(cancel, struct command *, 0); + return; + } + + const char *error_reason = "Cancel channel by our RPC " + "command before funding " + "transaction broadcast."; + /* Set error so we don't try to reconnect. */ + cancel->error = towire_errorfmt(cancel, NULL, "%s", error_reason); + + subd_send_msg(cancel->owner, + take(towire_channel_send_error(NULL, error_reason))); +} + +struct command_result *cancel_channel_before_broadcast(struct command *cmd, + const char *buffer, + struct peer *peer, + const jsmntok_t *cidtok) +{ + struct channel *cancel_channel, *channel; + + cancel_channel = NULL; + if (!cidtok) { + list_for_each(&peer->channels, channel, list) { + if (cancel_channel) { + return command_fail(cmd, LIGHTNINGD, + "Multiple channels:" + " please specify channel_id"); + } + cancel_channel = channel; + } + if (!cancel_channel) + return command_fail(cmd, LIGHTNINGD, + "No channels matching that peer_id"); + } else { + struct channel_id channel_cid; + struct channel_id cid; + if (!json_tok_channel_id(buffer, cidtok, &cid)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Invalid channel_id parameter."); + + list_for_each(&peer->channels, channel, list) { + if (!channel) + return command_fail(cmd, LIGHTNINGD, + "No channels matching " + "that peer_id"); + derive_channel_id(&channel_cid, + &channel->funding_txid, + channel->funding_outnum); + if (channel_id_eq(&channel_cid, &cid)) { + cancel_channel = channel; + break; + } + } + if (!cancel_channel) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Channel ID not found: '%.*s'", + cidtok->end - cidtok->start, + buffer + cidtok->start); + } + + /* Check if we broadcast the transaction. (We store the transaction type into DB + * before broadcast). */ + enum wallet_tx_type type; + if(wallet_transaction_type(cmd->ld->wallet, + &cancel_channel->funding_txid, + &type)) + return command_fail(cmd, LIGHTNINGD, + "Has the funding transaction been broadcast? " + "Please use `close` or `dev-fail` instead."); + + if (channel_has_htlc_out(cancel_channel) || + channel_has_htlc_in(cancel_channel)) { + return command_fail(cmd, LIGHTNINGD, + "This channel has HTLCs attached and it is " + "not safe to cancel. Has the funding transaction " + "been broadcast? Please use `close` or `dev-fail` " + "instead."); + } + + tal_arr_expand(&cancel_channel->forgets, cmd); + + /* Check if the transaction is onchain. */ + /* Note: The above check and this check can't completely ensure that + * the funding transaction isn't broadcast. We can't know if the funding + * is broadcast by external wallet and the transaction hasn't been onchain. */ + bitcoind_gettxout(cmd->ld->topology->bitcoind, + &cancel_channel->funding_txid, + cancel_channel->funding_outnum, + process_check_funding_broadcast, + cancel_channel); + return command_still_pending(cmd); +} diff --git a/lightningd/channel_control.h b/lightningd/channel_control.h index 0edd5753b..0e50ce7f3 100644 --- a/lightningd/channel_control.h +++ b/lightningd/channel_control.h @@ -23,4 +23,11 @@ bool channel_tell_depth(struct lightningd *ld, void channel_notify_new_block(struct lightningd *ld, u32 block_height); +/* Cancel the channel after `fundchannel_complete` succeeds + * but before funding broadcasts. */ +struct command_result *cancel_channel_before_broadcast(struct command *cmd, + const char *buffer, + struct peer *peer, + const jsmntok_t *cidtok); + #endif /* LIGHTNING_LIGHTNINGD_CHANNEL_CONTROL_H */ diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index 456dd1c49..97106f735 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -295,7 +295,8 @@ static void funding_success(struct channel *channel) /* Well, those cancels didn't work! */ for (size_t i = 0; i < tal_count(fc->cancels); i++) was_pending(command_fail(fc->cancels[i], LIGHTNINGD, - "Funding succeeded before cancel")); + "Funding succeeded before cancel. " + "Try fundchannel_cancel again.")); response = json_stream_success(cmd); json_add_string(response, "channel_id", @@ -1189,7 +1190,7 @@ static struct command_result *json_fund_channel_complete(struct command *cmd, } /** - * json_fund_channel_cancel - Entrypoint for cancelling an in flight channel-funding + * json_fund_channel_cancel - Entrypoint for cancelling a channel which funding isn't broadcast */ static struct command_result *json_fund_channel_cancel(struct command *cmd, const char *buffer, @@ -1199,10 +1200,12 @@ static struct command_result *json_fund_channel_cancel(struct command *cmd, struct node_id *id; struct peer *peer; + const jsmntok_t *cidtok; u8 *msg; if (!param(cmd, buffer, params, p_req("id", param_node_id, &id), + p_opt("channel_id", param_tok, &cidtok), NULL)) return command_param_failed(); @@ -1211,40 +1214,18 @@ static struct command_result *json_fund_channel_cancel(struct command *cmd, 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."); + if (peer->uncommitted_channel) { + 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 `complete` -- we expect you - * the caller to EITHER pick 'complete' 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 complete", we can't cancel this. - * - * there's also the state you might end up in where you've called - * complete (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'. - */ + /* Make sure this gets notified if we succeed or cancel */ + tal_arr_expand(&peer->uncommitted_channel->fc->cancels, cmd); + msg = towire_opening_funder_cancel(NULL); + subd_send_msg(peer->uncommitted_channel->openingd, take(msg)); + return command_still_pending(cmd); + } - /* Make sure this gets notified if we succeed or cancel */ - tal_arr_expand(&peer->uncommitted_channel->fc->cancels, cmd); - msg = towire_opening_funder_cancel(NULL); - subd_send_msg(peer->uncommitted_channel->openingd, take(msg)); - return command_still_pending(cmd); + return cancel_channel_before_broadcast(cmd, buffer, peer, cidtok); } /** diff --git a/wallet/wallet.c b/wallet/wallet.c index 508e7e2e5..91c463520 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -2847,6 +2847,23 @@ void wallet_transaction_annotate(struct wallet *w, db_exec_prepared_v2(take(stmt)); } +bool wallet_transaction_type(struct wallet *w, const struct bitcoin_txid *txid, + enum wallet_tx_type *type) +{ + struct db_stmt *stmt = db_prepare_v2(w->db, SQL("SELECT type FROM transactions WHERE id=?")); + db_bind_sha256(stmt, 0, &txid->shad.sha); + db_query_prepared(stmt); + + if (!db_step(stmt)) { + tal_free(stmt); + return false; + } + + *type = db_column_int(stmt, 0); + tal_free(stmt); + return true; +} + u32 wallet_transaction_height(struct wallet *w, const struct bitcoin_txid *txid) { u32 blockheight; diff --git a/wallet/wallet.h b/wallet/wallet.h index 3437b4633..145c09565 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -1066,6 +1066,16 @@ void wallet_transaction_annotate(struct wallet *w, const struct bitcoin_txid *txid, enum wallet_tx_type type, u64 channel_id); +/** + * Get the type of a transaction we are watching by its + * txid. + * + * Returns false if the transaction was not stored in DB. + * Returns true if the transaction exists and sets the `type` parameter. + */ +bool wallet_transaction_type(struct wallet *w, const struct bitcoin_txid *txid, + enum wallet_tx_type *type); + /** * Get the confirmation height of a transaction we are watching by its * txid. Returns 0 if the transaction was not part of any block.