close: accept wrong_funding outpoint arg if we negotiated the feature.

Changelog-Added: lightningd: experimental-shutdown-wrong-funding to allow remote nodes to close incorrectly opened channels.
Changelog-Added: JSON-RPC: close has a new `wrong_funding` option to try to close out unused channels where we messed up the funding tx.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2021-03-16 06:56:12 +10:30
parent 1cfb7b84d0
commit b62706aa01
11 changed files with 146 additions and 22 deletions

16
doc/lightning-close.7 generated
View File

@ -3,7 +3,7 @@
lightning-close - Command for closing channels with direct peers
.SH SYNOPSIS
\fBclose\fR \fIid\fR [\fIunilateraltimeout\fR] [\fIdestination\fR] [\fIfee_negotiation_step\fR]
\fBclose\fR \fIid\fR [\fIunilateraltimeout\fR] [\fIdestination\fR] [\fIfee_negotiation_step\fR] [\fIwrong_funding\\\fR]
.SH DESCRIPTION
@ -54,6 +54,16 @@ The default is "50%"\.
.RE
\fIwrong_funding_txid\fR can only be specified if both sides have offered
the "shutdown_wrong_funding" feature (enabled by the
\fBexperimental-shutdown-wrong-funding\fR option): it must be a
transaction id followed by a colon then the output number\. Instead of
negotiating a shutdown to spend the expected funding transaction, the
shutdown transaction will spend this output instead\. This is only
allowed if this peer opened the channel and the channel is unused: it
can rescue openings which have been manually miscreated\.
The peer needs to be live and connected in order to negotiate a mutual
close\. The default of unilaterally closing after 48 hours is usually a
reasonable indication that you can no longer contact the peer\.
@ -95,10 +105,10 @@ ZmnSCPxj \fI<ZmnSCPxj@protonmail.com\fR> is mainly responsible\.
.SH SEE ALSO
\fBlightning-disconnect\fR(7), \fBlightning-fundchannel\fR(7)
\fBlightning-disconnect\fR(7), \fBlightning-fundchannel\fR(7), \fBlightningd-config\fR(5)\.
.SH RESOURCES
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
\" SHA256STAMP:d16e185f9a781f23987dfb65aaa1eae8dab19796975433c69e16f1f6b18751c5
\" SHA256STAMP:f835c6a11e0df5610761e858e78783f9f99d530b8d2a6bedaf6f301eccdc31e0

View File

@ -4,7 +4,7 @@ lightning-close -- Command for closing channels with direct peers
SYNOPSIS
--------
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\]
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\] \[*wrong_funding\*]
DESCRIPTION
-----------
@ -44,6 +44,15 @@ insist on our fee as much as possible.
we quickly accept the peer's proposal.
The default is "50%".
*wrong_funding_txid* can only be specified if both sides have offered
the "shutdown_wrong_funding" feature (enabled by the
**experimental-shutdown-wrong-funding** option): it must be a
transaction id followed by a colon then the output number. Instead of
negotiating a shutdown to spend the expected funding transaction, the
shutdown transaction will spend this output instead. This is only
allowed if this peer opened the channel and the channel is unused: it
can rescue openings which have been manually miscreated.
The peer needs to be live and connected in order to negotiate a mutual
close. The default of unilaterally closing after 48 hours is usually a
reasonable indication that you can no longer contact the peer.
@ -87,7 +96,7 @@ ZmnSCPxj <<ZmnSCPxj@protonmail.com>> is mainly responsible.
SEE ALSO
--------
lightning-disconnect(7), lightning-fundchannel(7)
lightning-disconnect(7), lightning-fundchannel(7), lightningd-config(5).
RESOURCES
---------

View File

@ -588,9 +588,9 @@ This usually requires \fBexperimental-onion-messages\fR as well\. See
Specifying this allows the \fBwrong_funding\fR field in shutdown: if a
remote node has opened a channel using the incorrect txid (and it
hasn't been used yet at all) this allows them to negotiate a clean
shutdown with the txid they offer\.
remote node has opened a channel but claims it used the incorrect txid
(and the channel hasn't been used yet at all) this allows them to
negotiate a clean shutdown with the txid they offer\.
.SH BUGS
@ -617,4 +617,4 @@ Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
Note: the modules in the ccan/ directory have their own licenses, but
the rest of the code is covered by the BSD-style MIT license\.
\" SHA256STAMP:ecfd7ac60b50a20cad39dfa0e423e4885256a94658b5886f577df50773555e7e
\" SHA256STAMP:b0cdc467650b05758f90f2523ab8b45e526ac818e426cb2c2bc42cb98673e373

View File

@ -486,9 +486,9 @@ lightning-offer(7) and lightning-fetchinvoice(7).
**experimental-shutdown-wrong-funding**
Specifying this allows the `wrong_funding` field in shutdown: if a
remote node has opened a channel using the incorrect txid (and it
hasn't been used yet at all) this allows them to negotiate a clean
shutdown with the txid they offer.
remote node has opened a channel but claims it used the incorrect txid
(and the channel hasn't been used yet at all) this allows them to
negotiate a clean shutdown with the txid they offer.
BUGS
----

View File

@ -1492,6 +1492,19 @@ command_find_channel(struct command *cmd,
}
}
static struct command_result *param_outpoint(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct bitcoin_outpoint **outp)
{
*outp = tal(cmd, struct bitcoin_outpoint);
if (json_to_outpoint(buffer, tok, *outp))
return NULL;
return command_fail_badparam(cmd, name, buffer, tok,
"should be a txid:outnum");
}
static struct command_result *json_close(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
@ -1502,8 +1515,9 @@ static struct command_result *json_close(struct command *cmd,
struct channel *channel COMPILER_WANTS_INIT("gcc 7.3.0 fails, 8.3 OK");
unsigned int *timeout;
const u8 *close_to_script = NULL;
bool close_script_set;
bool close_script_set, wrong_funding_changed;
const char *fee_negotiation_step_str;
struct bitcoin_outpoint *wrong_funding;
char* end;
if (!param(cmd, buffer, params,
@ -1513,6 +1527,7 @@ static struct command_result *json_close(struct command *cmd,
p_opt("destination", param_bitcoin_address, &close_to_script),
p_opt("fee_negotiation_step", param_string,
&fee_negotiation_step_str),
p_opt("wrong_funding", param_outpoint, &wrong_funding),
NULL))
return command_param_failed();
@ -1618,6 +1633,34 @@ static struct command_result *json_close(struct command *cmd,
fee_negotiation_step_str);
}
if (wrong_funding) {
if (!feature_negotiated(cmd->ld->our_features,
channel->peer->their_features,
OPT_SHUTDOWN_WRONG_FUNDING)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"wrong_funding feature not negotiated"
" (we said %s, they said %s: try experimental-shutdown-wrong-funding?)",
feature_offered(cmd->ld->our_features
->bits[INIT_FEATURE],
OPT_SHUTDOWN_WRONG_FUNDING)
? "yes" : "no",
feature_offered(channel->peer->their_features,
OPT_SHUTDOWN_WRONG_FUNDING)
? "yes" : "no");
}
wrong_funding_changed = true;
channel->shutdown_wrong_funding
= tal_steal(channel, wrong_funding);
} else {
if (channel->shutdown_wrong_funding) {
channel->shutdown_wrong_funding
= tal_free(channel->shutdown_wrong_funding);
wrong_funding_changed = true;
} else
wrong_funding_changed = false;
}
/* Normal case.
* We allow states shutting down and sigexchange; a previous
* close command may have timed out, and this current command
@ -1663,8 +1706,9 @@ static struct command_result *json_close(struct command *cmd,
/* Register this command for later handling. */
register_close_command(cmd->ld, cmd, channel, *timeout);
/* If we set `channel->shutdown_scriptpubkey[LOCAL]`, save it. */
if (close_script_set)
/* If we set `channel->shutdown_scriptpubkey[LOCAL]` or
* changed shutdown_wrong_funding, save it. */
if (close_script_set || wrong_funding_changed)
wallet_channel_save(cmd->ld->wallet, channel);
/* Wait until close drops down to chain. */

View File

@ -172,6 +172,9 @@ bool feature_is_set(const u8 *features UNNEEDED, size_t bit UNNEEDED)
bool feature_negotiated(const struct feature_set *our_features UNNEEDED,
const u8 *their_features UNNEEDED, size_t f UNNEEDED)
{ fprintf(stderr, "feature_negotiated called!\n"); abort(); }
/* Generated stub for feature_offered */
bool feature_offered(const u8 *features UNNEEDED, size_t f UNNEEDED)
{ fprintf(stderr, "feature_offered called!\n"); abort(); }
/* Generated stub for fixup_htlcs_out */
void fixup_htlcs_out(struct lightningd *ld UNNEEDED)
{ fprintf(stderr, "fixup_htlcs_out called!\n"); abort(); }
@ -374,6 +377,10 @@ enum address_parse_result json_to_address_scriptpubkey(const tal_t *ctx UNNEEDED
bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
struct node_id *id UNNEEDED)
{ fprintf(stderr, "json_to_node_id called!\n"); abort(); }
/* Generated stub for json_to_outpoint */
bool json_to_outpoint(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
struct bitcoin_outpoint *op UNNEEDED)
{ fprintf(stderr, "json_to_outpoint called!\n"); abort(); }
/* Generated stub for json_to_short_channel_id */
bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
struct short_channel_id *scid UNNEEDED)

View File

@ -1,12 +1,12 @@
from fixtures import * # noqa: F401,F403
from flaky import flaky
from pyln.client import RpcError
from pyln.client import RpcError, Millisatoshi
from shutil import copyfile
from pyln.testing.utils import SLOW_MACHINE
from utils import (
only_one, sync_blockheight, wait_for, DEVELOPER, TIMEOUT,
account_balance, first_channel_id, basic_fee, TEST_NETWORK,
EXPERIMENTAL_FEATURES,
EXPERIMENTAL_FEATURES, EXPERIMENTAL_DUAL_FUND,
)
import os
@ -2695,3 +2695,50 @@ Try a range of future segwit versions as shutdown scripts. We create many nodes
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
with pytest.raises(RpcError, match=r'Unacceptable upfront_shutdown_script'):
l1.rpc.fundchannel(l2.info['id'], 10**6)
@unittest.skipIf(EXPERIMENTAL_DUAL_FUND, "Uses fundchannel_start")
def test_shutdown_alternate_txid(node_factory, bitcoind):
l1, l2 = node_factory.line_graph(2, fundchannel=False,
opts={'experimental-shutdown-wrong-funding': None})
amount = 1000000
amount_msat = Millisatoshi(amount * 1000)
# Let's make a classic fundchannel mistake (wrong txid!)
addr = l1.rpc.fundchannel_start(l2.info['id'], amount_msat)['funding_address']
txid = bitcoind.rpc.sendtoaddress(addr, amount / 10**8)
# Gotta figure out which output manually :(
tx = bitcoind.rpc.getrawtransaction(txid, 1)
for n, out in enumerate(tx['vout']):
if 'addresses' in out['scriptPubKey'] and out['scriptPubKey']['addresses'][0] == addr:
txout = n
bitcoind.generate_block(1, wait_for_mempool=1)
# Wrong txid, wrong txout!
wrong_txid = txid[16:] + txid[:16]
wrong_txout = txout ^ 1
l1.rpc.fundchannel_complete(l2.info['id'], wrong_txid, wrong_txout)
wait_for(lambda: only_one(l2.rpc.listpeers()['peers'])['channels'] != [])
wait_for(lambda: only_one(l2.rpc.listpeers()['peers'])['channels'][0]['state'] == 'CHANNELD_AWAITING_LOCKIN')
closeaddr = l1.rpc.newaddr()['bech32']
# Oops, try rescuing it!
l1.rpc.call('close', {'id': l2.info['id'], 'destination': closeaddr, 'wrong_funding': txid + ':' + str(txout)})
# Just make sure node has no funds.
assert l1.rpc.listfunds()['outputs'] == []
bitcoind.generate_block(100, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1, l2])
# We will see our funds return.
assert len(l1.rpc.listfunds()['outputs']) == 1
# FIXME: we should close channels, but we don't!
# wait_for(lambda: l2.rpc.listpeers()['peers'] == [])
# wait_for(lambda: l1.rpc.listpeers()['peers'] == [])

View File

@ -1888,4 +1888,4 @@ struct db_query db_postgres_queries[] = {
#endif /* LIGHTNINGD_WALLET_GEN_DB_POSTGRES */
// SHA256STAMP:2456747feb02494cb6237921d03c41c45fc86cf20289f4b5c379db7e548761f7
// SHA256STAMP:fa582dba4c41760ea1760e8c98a53ca4a450ab8236d68bc8749eda0efb8c59af

View File

@ -1888,4 +1888,4 @@ struct db_query db_sqlite3_queries[] = {
#endif /* LIGHTNINGD_WALLET_GEN_DB_SQLITE3 */
// SHA256STAMP:2456747feb02494cb6237921d03c41c45fc86cf20289f4b5c379db7e548761f7
// SHA256STAMP:fa582dba4c41760ea1760e8c98a53ca4a450ab8236d68bc8749eda0efb8c59af

View File

@ -1238,11 +1238,11 @@ msgstr ""
msgid "not a valid SQL statement"
msgstr ""
#: wallet/test/run-wallet.c:1434
#: wallet/test/run-wallet.c:1441
msgid "SELECT COUNT(1) FROM channel_funding_inflights WHERE channel_id = ?;"
msgstr ""
#: wallet/test/run-wallet.c:1632
#: wallet/test/run-wallet.c:1639
msgid "INSERT INTO channels (id) VALUES (1);"
msgstr ""
# SHA256STAMP:6a7cda25fc90182775e1fd2a4055e2c1e74ddca42f8a0160fea20e7aa3d045cb
# SHA256STAMP:249962d1ad354071c65dcbdbabf051fde833e7c9b36f78b2db093d3209e419d5

View File

@ -119,6 +119,9 @@ void fatal(const char *fmt UNNEEDED, ...)
bool feature_negotiated(const struct feature_set *our_features UNNEEDED,
const u8 *their_features UNNEEDED, size_t f UNNEEDED)
{ fprintf(stderr, "feature_negotiated called!\n"); abort(); }
/* Generated stub for feature_offered */
bool feature_offered(const u8 *features UNNEEDED, size_t f UNNEEDED)
{ fprintf(stderr, "feature_offered called!\n"); abort(); }
/* Generated stub for fromwire_channeld_dev_memleak_reply */
bool fromwire_channeld_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNEEDED)
{ fprintf(stderr, "fromwire_channeld_dev_memleak_reply called!\n"); abort(); }
@ -405,6 +408,10 @@ bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
bool json_to_number(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
unsigned int *num UNNEEDED)
{ fprintf(stderr, "json_to_number called!\n"); abort(); }
/* Generated stub for json_to_outpoint */
bool json_to_outpoint(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
struct bitcoin_outpoint *op UNNEEDED)
{ fprintf(stderr, "json_to_outpoint called!\n"); abort(); }
/* Generated stub for json_to_preimage */
bool json_to_preimage(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct preimage *preimage UNNEEDED)
{ fprintf(stderr, "json_to_preimage called!\n"); abort(); }