openingd: disconnect from peer when an error occurs.

openingd currently holds the connection to idle peers, but we're about
to change that: it will only look after peers which are actively
opening a connection.  We can start this process by disconnecting
whenever we have a negotiation failure.

We could stay connected if we wanted to, but that would be up to
connectd to decide.  Right now it's easier if we disconnect from any
idle peer once it's been active.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2022-03-23 06:56:29 +10:30
parent 16e9ba0361
commit 10e36e073c
4 changed files with 51 additions and 73 deletions

View File

@@ -561,22 +561,25 @@ opening_funder_failed_cancel_commands(struct uncommitted_channel *uc,
*/ */
uc->fc = tal_free(uc->fc); uc->fc = tal_free(uc->fc);
} }
static void opening_funder_failed(struct subd *openingd, const u8 *msg,
struct uncommitted_channel *uc) static void openingd_failed(struct subd *openingd, const u8 *msg,
struct uncommitted_channel *uc)
{ {
char *desc; char *desc;
if (!fromwire_openingd_funder_failed(msg, msg, &desc)) { if (!fromwire_openingd_failed(msg, msg, &desc)) {
log_broken(uc->log, log_broken(uc->log,
"bad OPENING_FUNDER_FAILED %s", "bad OPENINGD_FAILED %s",
tal_hex(tmpctx, msg)); tal_hex(tmpctx, msg));
was_pending(command_fail(uc->fc->cmd, LIGHTNINGD, if (uc->fc)
"bad OPENING_FUNDER_FAILED %s", was_pending(command_fail(uc->fc->cmd, LIGHTNINGD,
tal_hex(uc->fc->cmd, msg))); "bad OPENINGD_FAILED %s",
tal_hex(uc->fc->cmd, msg)));
tal_free(uc); tal_free(uc);
return; return;
} }
/* Noop if we're not funder. */
opening_funder_failed_cancel_commands(uc, desc); opening_funder_failed_cancel_commands(uc, desc);
} }
@@ -853,14 +856,8 @@ static unsigned int openingd_msg(struct subd *openingd,
} }
opening_funder_start_replied(openingd, msg, fds, uc->fc); opening_funder_start_replied(openingd, msg, fds, uc->fc);
return 0; return 0;
case WIRE_OPENINGD_FUNDER_FAILED: case WIRE_OPENINGD_FAILED:
if (!uc->fc) { openingd_failed(openingd, msg, uc);
log_unusual(openingd->log, "Unexpected FUNDER_FAILED %s",
tal_hex(tmpctx, msg));
tal_free(openingd);
return 0;
}
opening_funder_failed(openingd, msg, uc);
return 0; return 0;
case WIRE_OPENINGD_FUNDEE: case WIRE_OPENINGD_FUNDEE:

View File

@@ -102,10 +102,9 @@ struct state {
struct feature_set *our_features; struct feature_set *our_features;
}; };
/*~ If we can't agree on parameters, we fail to open the channel. If we're /*~ If we can't agree on parameters, we fail to open the channel.
* the opener, we need to tell lightningd, otherwise it never really notices. */ * Tell lightningd why. */
static void negotiation_aborted(struct state *state, bool am_opener, static void NORETURN negotiation_aborted(struct state *state, const char *why)
const char *why)
{ {
status_debug("aborted opening negotiation: %s", why); status_debug("aborted opening negotiation: %s", why);
/*~ The "billboard" (exposed as "status" in the JSON listpeers RPC /*~ The "billboard" (exposed as "status" in the JSON listpeers RPC
@@ -116,29 +115,14 @@ static void negotiation_aborted(struct state *state, bool am_opener,
* status. */ * status. */
peer_billboard(true, why); peer_billboard(true, why);
/* If necessary, tell master that funding failed. */ /* Tell master that funding failed. */
if (am_opener) { wire_sync_write(REQ_FD, take(towire_openingd_failed(NULL, why)));
u8 *msg = towire_openingd_funder_failed(NULL, why); exit(0);
wire_sync_write(REQ_FD, take(msg));
}
/* Default is no shutdown_scriptpubkey: free any leftover ones. */
state->upfront_shutdown_script[LOCAL]
= tal_free(state->upfront_shutdown_script[LOCAL]);
state->upfront_shutdown_script[REMOTE]
= tal_free(state->upfront_shutdown_script[REMOTE]);
/*~ Reset state. We keep gossipping with them, even though this open
* failed. */
memset(&state->channel_id, 0, sizeof(state->channel_id));
state->channel = tal_free(state->channel);
state->channel_type = tal_free(state->channel_type);
} }
/*~ For negotiation failures: we tell them the parameter we didn't like. */ /*~ For negotiation failures: we tell them the parameter we didn't like. */
static void negotiation_failed(struct state *state, bool am_opener, static void NORETURN negotiation_failed(struct state *state,
const char *fmt, ...) const char *fmt, ...)
{ {
va_list ap; va_list ap;
const char *errmsg; const char *errmsg;
@@ -152,7 +136,7 @@ static void negotiation_failed(struct state *state, bool am_opener,
"You gave bad parameters: %s", errmsg); "You gave bad parameters: %s", errmsg);
peer_write(state->pps, take(msg)); peer_write(state->pps, take(msg));
negotiation_aborted(state, am_opener, errmsg); negotiation_aborted(state, errmsg);
} }
/* We always set channel_reserve_satoshis to 1%, rounded down. */ /* We always set channel_reserve_satoshis to 1%, rounded down. */
@@ -177,7 +161,6 @@ static void set_reserve(struct state *state, const struct amount_sat dust_limit)
/*~ Handle random messages we might get during opening negotiation, (eg. gossip) /*~ Handle random messages we might get during opening negotiation, (eg. gossip)
* returning the first non-handled one, or NULL if we aborted negotiation. */ * returning the first non-handled one, or NULL if we aborted negotiation. */
static u8 *opening_negotiate_msg(const tal_t *ctx, struct state *state, static u8 *opening_negotiate_msg(const tal_t *ctx, struct state *state,
bool am_opener,
const struct channel_id *alternate) const struct channel_id *alternate)
{ {
/* This is an event loop of its own. That's generally considered poor /* This is an event loop of its own. That's generally considered poor
@@ -219,7 +202,7 @@ static u8 *opening_negotiate_msg(const tal_t *ctx, struct state *state,
tal_free(msg); tal_free(msg);
continue; continue;
} }
negotiation_aborted(state, am_opener, negotiation_aborted(state,
tal_fmt(tmpctx, "They sent %s", tal_fmt(tmpctx, "They sent %s",
err)); err));
/* Return NULL so caller knows to stop negotiating. */ /* Return NULL so caller knows to stop negotiating. */
@@ -384,7 +367,7 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags)
"Funding channel start: offered, now waiting for accept_channel"); "Funding channel start: offered, now waiting for accept_channel");
/* ... since their reply should be immediate. */ /* ... since their reply should be immediate. */
msg = opening_negotiate_msg(tmpctx, state, true, NULL); msg = opening_negotiate_msg(tmpctx, state, NULL);
if (!msg) if (!msg)
return NULL; return NULL;
@@ -426,7 +409,7 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags)
if (accept_tlvs->channel_type if (accept_tlvs->channel_type
&& !featurebits_eq(accept_tlvs->channel_type, && !featurebits_eq(accept_tlvs->channel_type,
state->channel_type->features)) { state->channel_type->features)) {
negotiation_failed(state, true, negotiation_failed(state,
"Return unoffered channel_type: %s", "Return unoffered channel_type: %s",
fmt_featurebits(tmpctx, fmt_featurebits(tmpctx,
accept_tlvs->channel_type)); accept_tlvs->channel_type));
@@ -447,7 +430,7 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags)
if (amount_sat_greater(state->remoteconf.dust_limit, if (amount_sat_greater(state->remoteconf.dust_limit,
state->localconf.channel_reserve)) { state->localconf.channel_reserve)) {
negotiation_failed(state, true, negotiation_failed(state,
"dust limit %s" "dust limit %s"
" would be above our reserve %s", " would be above our reserve %s",
type_to_string(tmpctx, struct amount_sat, type_to_string(tmpctx, struct amount_sat,
@@ -468,7 +451,7 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags)
state->their_features, state->their_features,
OPT_ANCHOR_OUTPUTS), OPT_ANCHOR_OUTPUTS),
&err_reason)) { &err_reason)) {
negotiation_failed(state, true, "%s", err_reason); negotiation_failed(state, "%s", err_reason);
return NULL; return NULL;
} }
@@ -579,7 +562,7 @@ static bool funder_finalize_channel_setup(struct state *state,
if (!*tx) { if (!*tx) {
/* This should not happen: we should never create channels we /* This should not happen: we should never create channels we
* can't afford the fees for after reserve. */ * can't afford the fees for after reserve. */
negotiation_failed(state, true, negotiation_failed(state,
"Could not meet their fees and reserve: %s", err_reason); "Could not meet their fees and reserve: %s", err_reason);
goto fail; goto fail;
} }
@@ -644,7 +627,7 @@ static bool funder_finalize_channel_setup(struct state *state,
* transaction. Note that errors may refer to the temporary channel * transaction. Note that errors may refer to the temporary channel
* id (state->channel_id), but success should refer to the new * id (state->channel_id), but success should refer to the new
* "cid" */ * "cid" */
msg = opening_negotiate_msg(tmpctx, state, true, &cid); msg = opening_negotiate_msg(tmpctx, state, &cid);
if (!msg) if (!msg)
goto fail; goto fail;
@@ -696,7 +679,7 @@ static bool funder_finalize_channel_setup(struct state *state,
&state->first_per_commitment_point[LOCAL], &state->first_per_commitment_point[LOCAL],
LOCAL, direct_outputs, &err_reason); LOCAL, direct_outputs, &err_reason);
if (!*tx) { if (!*tx) {
negotiation_failed(state, true, negotiation_failed(state,
"Could not meet our fees and reserve: %s", err_reason); "Could not meet our fees and reserve: %s", err_reason);
goto fail; goto fail;
} }
@@ -842,7 +825,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
state->our_features, state->our_features,
state->their_features); state->their_features);
if (!state->channel_type) { if (!state->channel_type) {
negotiation_failed(state, false, negotiation_failed(state,
"Did not support channel_type %s", "Did not support channel_type %s",
fmt_featurebits(tmpctx, fmt_featurebits(tmpctx,
open_tlvs->channel_type)); open_tlvs->channel_type));
@@ -861,7 +844,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
* that is unknown to the receiver. * that is unknown to the receiver.
*/ */
if (!bitcoin_blkid_eq(&chain_hash, &chainparams->genesis_blockhash)) { if (!bitcoin_blkid_eq(&chain_hash, &chainparams->genesis_blockhash)) {
negotiation_failed(state, false, negotiation_failed(state,
"Unknown chain-hash %s", "Unknown chain-hash %s",
type_to_string(tmpctx, type_to_string(tmpctx,
struct bitcoin_blkid, struct bitcoin_blkid,
@@ -879,7 +862,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
if (!feature_negotiated(state->our_features, state->their_features, if (!feature_negotiated(state->our_features, state->their_features,
OPT_LARGE_CHANNELS) OPT_LARGE_CHANNELS)
&& amount_sat_greater(state->funding_sats, chainparams->max_funding)) { && amount_sat_greater(state->funding_sats, chainparams->max_funding)) {
negotiation_failed(state, false, negotiation_failed(state,
"funding_satoshis %s too large", "funding_satoshis %s too large",
type_to_string(tmpctx, struct amount_sat, type_to_string(tmpctx, struct amount_sat,
&state->funding_sats)); &state->funding_sats));
@@ -911,14 +894,14 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
* unreasonably large. * unreasonably large.
*/ */
if (state->feerate_per_kw < state->min_feerate) { if (state->feerate_per_kw < state->min_feerate) {
negotiation_failed(state, false, negotiation_failed(state,
"feerate_per_kw %u below minimum %u", "feerate_per_kw %u below minimum %u",
state->feerate_per_kw, state->min_feerate); state->feerate_per_kw, state->min_feerate);
return NULL; return NULL;
} }
if (state->feerate_per_kw > state->max_feerate) { if (state->feerate_per_kw > state->max_feerate) {
negotiation_failed(state, false, negotiation_failed(state,
"feerate_per_kw %u above maximum %u", "feerate_per_kw %u above maximum %u",
state->feerate_per_kw, state->max_feerate); state->feerate_per_kw, state->max_feerate);
return NULL; return NULL;
@@ -938,7 +921,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
*/ */
if (amount_sat_greater(state->remoteconf.dust_limit, if (amount_sat_greater(state->remoteconf.dust_limit,
state->localconf.channel_reserve)) { state->localconf.channel_reserve)) {
negotiation_failed(state, false, negotiation_failed(state,
"Our channel reserve %s" "Our channel reserve %s"
" would be below their dust %s", " would be below their dust %s",
type_to_string(tmpctx, struct amount_sat, type_to_string(tmpctx, struct amount_sat,
@@ -949,7 +932,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
} }
if (amount_sat_greater(state->localconf.dust_limit, if (amount_sat_greater(state->localconf.dust_limit,
state->remoteconf.channel_reserve)) { state->remoteconf.channel_reserve)) {
negotiation_failed(state, false, negotiation_failed(state,
"Our dust limit %s" "Our dust limit %s"
" would be above their reserve %s", " would be above their reserve %s",
type_to_string(tmpctx, struct amount_sat, type_to_string(tmpctx, struct amount_sat,
@@ -971,7 +954,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
state->their_features, state->their_features,
OPT_ANCHOR_OUTPUTS), OPT_ANCHOR_OUTPUTS),
&err_reason)) { &err_reason)) {
negotiation_failed(state, false, "%s", err_reason); negotiation_failed(state, "%s", err_reason);
return NULL; return NULL;
} }
@@ -1001,7 +984,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
/* If they give us a reason to reject, do so. */ /* If they give us a reason to reject, do so. */
if (err_reason) { if (err_reason) {
negotiation_failed(state, false, "%s", err_reason); negotiation_failed(state, "%s", err_reason);
tal_free(err_reason); tal_free(err_reason);
return NULL; return NULL;
} }
@@ -1044,7 +1027,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
"Incoming channel: accepted, now waiting for them to create funding tx"); "Incoming channel: accepted, now waiting for them to create funding tx");
/* This is a loop which handles gossip until we get a non-gossip msg */ /* This is a loop which handles gossip until we get a non-gossip msg */
msg = opening_negotiate_msg(tmpctx, state, false, NULL); msg = opening_negotiate_msg(tmpctx, state, NULL);
if (!msg) if (!msg)
return NULL; return NULL;
@@ -1130,7 +1113,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
LOCAL, NULL, &err_reason); LOCAL, NULL, &err_reason);
/* This shouldn't happen either, AFAICT. */ /* This shouldn't happen either, AFAICT. */
if (!local_commit) { if (!local_commit) {
negotiation_failed(state, false, negotiation_failed(state,
"Could not meet our fees and reserve: %s", err_reason); "Could not meet our fees and reserve: %s", err_reason);
return NULL; return NULL;
} }
@@ -1189,7 +1172,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
&state->first_per_commitment_point[REMOTE], &state->first_per_commitment_point[REMOTE],
REMOTE, direct_outputs, &err_reason); REMOTE, direct_outputs, &err_reason);
if (!remote_commit) { if (!remote_commit) {
negotiation_failed(state, false, negotiation_failed(state,
"Could not meet their fees and reserve: %s", err_reason); "Could not meet their fees and reserve: %s", err_reason);
return NULL; return NULL;
} }
@@ -1353,7 +1336,7 @@ static u8 *handle_master_in(struct state *state)
msg = towire_errorfmt(NULL, &state->channel_id, "Channel open canceled by us"); msg = towire_errorfmt(NULL, &state->channel_id, "Channel open canceled by us");
peer_write(state->pps, take(msg)); peer_write(state->pps, take(msg));
negotiation_aborted(state, true, "Channel open canceled by RPC"); negotiation_aborted(state, "Channel open canceled by RPC");
return NULL; return NULL;
case WIRE_OPENINGD_DEV_MEMLEAK: case WIRE_OPENINGD_DEV_MEMLEAK:
#if DEVELOPER #if DEVELOPER
@@ -1365,7 +1348,7 @@ static u8 *handle_master_in(struct state *state)
case WIRE_OPENINGD_FUNDER_REPLY: case WIRE_OPENINGD_FUNDER_REPLY:
case WIRE_OPENINGD_FUNDER_START_REPLY: case WIRE_OPENINGD_FUNDER_START_REPLY:
case WIRE_OPENINGD_FUNDEE: case WIRE_OPENINGD_FUNDEE:
case WIRE_OPENINGD_FUNDER_FAILED: case WIRE_OPENINGD_FAILED:
case WIRE_OPENINGD_GOT_OFFER: case WIRE_OPENINGD_GOT_OFFER:
case WIRE_OPENINGD_GOT_OFFER_REPLY: case WIRE_OPENINGD_GOT_OFFER_REPLY:
case WIRE_OPENINGD_GOT_REESTABLISH: case WIRE_OPENINGD_GOT_REESTABLISH:

View File

@@ -104,8 +104,8 @@ msgdata,openingd_funder_complete,channel_type,channel_type,
msgtype,openingd_funder_cancel,6013 msgtype,openingd_funder_cancel,6013
# Openingd->master: we failed to negotiation channel # Openingd->master: we failed to negotiation channel
msgtype,openingd_funder_failed,6004 msgtype,openingd_failed,6004
msgdata,openingd_funder_failed,reason,wirestring, msgdata,openingd_failed,reason,wirestring,
# Openingd->master: they offered channel. # Openingd->master: they offered channel.
# This gives their txid and info, means we can send funding_signed: we're done. # This gives their txid and info, means we can send funding_signed: we're done.
1 #include <bitcoin/chainparams.h>
104 msgdata,openingd_fundee,pbase,?penalty_base,
105 msgdata,openingd_fundee,first_commit_sig,bitcoin_signature,
106 msgdata,openingd_fundee,revocation_basepoint,pubkey,
107 msgdata,openingd_fundee,payment_basepoint,pubkey,
108 msgdata,openingd_fundee,htlc_basepoint,pubkey,
109 msgdata,openingd_fundee,delayed_payment_basepoint,pubkey,
110 msgdata,openingd_fundee,their_per_commit_point,pubkey,
111 msgdata,openingd_fundee,remote_fundingkey,pubkey,

View File

@@ -998,13 +998,9 @@ def test_funding_fail(node_factory, bitcoind):
with pytest.raises(RpcError, match=r'to_self_delay \d+ larger than \d+'): with pytest.raises(RpcError, match=r'to_self_delay \d+ larger than \d+'):
l1.rpc.fundchannel(l2.info['id'], int(funds / 10)) l1.rpc.fundchannel(l2.info['id'], int(funds / 10))
# dual-funded channels disconnect on failure # channels disconnect on failure
if not l1.config('experimental-dual-fund'): assert len(l1.rpc.listpeers()['peers']) == 0
assert only_one(l1.rpc.listpeers()['peers'])['connected'] assert len(l2.rpc.listpeers()['peers']) == 0
assert only_one(l2.rpc.listpeers()['peers'])['connected']
else:
assert len(l1.rpc.listpeers()['peers']) == 0
assert len(l2.rpc.listpeers()['peers']) == 0
# Restart l2 without ridiculous locktime. # Restart l2 without ridiculous locktime.
del l2.daemon.opts['watchtime-blocks'] del l2.daemon.opts['watchtime-blocks']
@@ -1217,7 +1213,9 @@ def test_funding_external_wallet_corners(node_factory, bitcoind):
l1.rpc.txdiscard(wrongaddr['txid']) l1.rpc.txdiscard(wrongaddr['txid'])
l1.rpc.fundchannel_cancel(l2.info['id']) l1.rpc.fundchannel_cancel(l2.info['id'])
# Should be able to 'restart' after canceling
# Cancelling causes disconnection.
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
amount2 = 1000000 amount2 = 1000000
funding_addr = l1.rpc.fundchannel_start(l2.info['id'], amount2)['funding_address'] funding_addr = l1.rpc.fundchannel_start(l2.info['id'], amount2)['funding_address']