Skip to content

Commit 4321f6e

Browse files
committed
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>
1 parent eb61a11 commit 4321f6e

File tree

9 files changed

+137
-16
lines changed

9 files changed

+137
-16
lines changed

doc/lightning-close.7

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/lightning-close.7.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ lightning-close -- Command for closing channels with direct peers
44
SYNOPSIS
55
--------
66

7-
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\]
7+
**close** *id* \[*unilateraltimeout*\] \[*destination*\] \[*fee_negotiation_step*\] \[*wrong_funding\*]
88

99
DESCRIPTION
1010
-----------
@@ -44,6 +44,15 @@ insist on our fee as much as possible.
4444
we quickly accept the peer's proposal.
4545
The default is "50%".
4646

47+
*wrong_funding_txid* can only be specified if both sides have offered
48+
the "shutdown_wrong_funding" feature (enabled by the
49+
**experimental-shutdown-wrong-funding** option): it must be a
50+
transaction id followed by a colon then the output number. Instead of
51+
negotiating a shutdown to spend the expected funding transaction, the
52+
shutdown transaction will spend this output instead. This is only
53+
allowed if this peer opened the channel and the channel is unused: it
54+
can rescue openings which have been manually miscreated.
55+
4756
The peer needs to be live and connected in order to negotiate a mutual
4857
close. The default of unilaterally closing after 48 hours is usually a
4958
reasonable indication that you can no longer contact the peer.
@@ -87,7 +96,7 @@ ZmnSCPxj <<ZmnSCPxj@protonmail.com>> is mainly responsible.
8796
SEE ALSO
8897
--------
8998

90-
lightning-disconnect(7), lightning-fundchannel(7)
99+
lightning-disconnect(7), lightning-fundchannel(7), lightningd-config(5).
91100

92101
RESOURCES
93102
---------

lightningd/peer_control.c

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,19 @@ command_find_channel(struct command *cmd,
14921492
}
14931493
}
14941494

1495+
static struct command_result *param_outpoint(struct command *cmd,
1496+
const char *name,
1497+
const char *buffer,
1498+
const jsmntok_t *tok,
1499+
struct bitcoin_outpoint **outp)
1500+
{
1501+
*outp = tal(cmd, struct bitcoin_outpoint);
1502+
if (json_to_outpoint(buffer, tok, *outp))
1503+
return NULL;
1504+
return command_fail_badparam(cmd, name, buffer, tok,
1505+
"should be a txid:outnum");
1506+
}
1507+
14951508
static struct command_result *json_close(struct command *cmd,
14961509
const char *buffer,
14971510
const jsmntok_t *obj UNNEEDED,
@@ -1502,8 +1515,9 @@ static struct command_result *json_close(struct command *cmd,
15021515
struct channel *channel COMPILER_WANTS_INIT("gcc 7.3.0 fails, 8.3 OK");
15031516
unsigned int *timeout;
15041517
const u8 *close_to_script = NULL;
1505-
bool close_script_set;
1518+
bool close_script_set, wrong_funding_changed;
15061519
const char *fee_negotiation_step_str;
1520+
struct bitcoin_outpoint *wrong_funding;
15071521
char* end;
15081522

15091523
if (!param(cmd, buffer, params,
@@ -1513,6 +1527,7 @@ static struct command_result *json_close(struct command *cmd,
15131527
p_opt("destination", param_bitcoin_address, &close_to_script),
15141528
p_opt("fee_negotiation_step", param_string,
15151529
&fee_negotiation_step_str),
1530+
p_opt("wrong_funding", param_outpoint, &wrong_funding),
15161531
NULL))
15171532
return command_param_failed();
15181533

@@ -1544,7 +1559,6 @@ static struct command_result *json_close(struct command *cmd,
15441559
"Peer has no active channel");
15451560
}
15461561

1547-
15481562
/* If we've set a local shutdown script for this peer, and it's not the
15491563
* default upfront script, try to close to a different channel.
15501564
* Error is an operator error */
@@ -1618,6 +1632,33 @@ static struct command_result *json_close(struct command *cmd,
16181632
fee_negotiation_step_str);
16191633
}
16201634

1635+
if (wrong_funding) {
1636+
if (!feature_negotiated(cmd->ld->our_features,
1637+
peer->their_features,
1638+
OPT_SHUTDOWN_WRONG_FUNDING)) {
1639+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
1640+
"wrong_funding feature not negotiated"
1641+
" (we said %s, they said %s: try experimental-shutdown-wrong-funding?)",
1642+
feature_offered(cmd->ld->our_features
1643+
->bits[INIT_FEATURE],
1644+
OPT_SHUTDOWN_WRONG_FUNDING)
1645+
? "yes" : "no",
1646+
feature_offered(peer->their_features,
1647+
OPT_SHUTDOWN_WRONG_FUNDING)
1648+
? "yes" : "no");
1649+
}
1650+
1651+
wrong_funding_changed = true;
1652+
channel->shutdown_wrong_funding
1653+
= tal_steal(channel, wrong_funding);
1654+
} else {
1655+
if (channel->shutdown_wrong_funding) {
1656+
channel->shutdown_wrong_funding
1657+
= tal_free(channel->shutdown_wrong_funding);
1658+
wrong_funding_changed = true;
1659+
}
1660+
}
1661+
16211662
/* Normal case.
16221663
* We allow states shutting down and sigexchange; a previous
16231664
* close command may have timed out, and this current command
@@ -1663,8 +1704,9 @@ static struct command_result *json_close(struct command *cmd,
16631704
/* Register this command for later handling. */
16641705
register_close_command(cmd->ld, cmd, channel, *timeout);
16651706

1666-
/* If we set `channel->shutdown_scriptpubkey[LOCAL]`, save it. */
1667-
if (close_script_set)
1707+
/* If we set `channel->shutdown_scriptpubkey[LOCAL]` or
1708+
* changed shutdown_wrong_funding, save it. */
1709+
if (close_script_set || wrong_funding_changed)
16681710
wallet_channel_save(cmd->ld->wallet, channel);
16691711

16701712
/* Wait until close drops down to chain. */

lightningd/test/run-invoice-select-inchan.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ bool feature_is_set(const u8 *features UNNEEDED, size_t bit UNNEEDED)
172172
bool feature_negotiated(const struct feature_set *our_features UNNEEDED,
173173
const u8 *their_features UNNEEDED, size_t f UNNEEDED)
174174
{ fprintf(stderr, "feature_negotiated called!\n"); abort(); }
175+
/* Generated stub for feature_offered */
176+
bool feature_offered(const u8 *features UNNEEDED, size_t f UNNEEDED)
177+
{ fprintf(stderr, "feature_offered called!\n"); abort(); }
175178
/* Generated stub for fixup_htlcs_out */
176179
void fixup_htlcs_out(struct lightningd *ld UNNEEDED)
177180
{ 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
374377
bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
375378
struct node_id *id UNNEEDED)
376379
{ fprintf(stderr, "json_to_node_id called!\n"); abort(); }
380+
/* Generated stub for json_to_outpoint */
381+
bool json_to_outpoint(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
382+
struct bitcoin_outpoint *op UNNEEDED)
383+
{ fprintf(stderr, "json_to_outpoint called!\n"); abort(); }
377384
/* Generated stub for json_to_short_channel_id */
378385
bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
379386
struct short_channel_id *scid UNNEEDED)

tests/test_closing.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from fixtures import * # noqa: F401,F403
22
from flaky import flaky
3-
from pyln.client import RpcError
3+
from pyln.client import RpcError, Millisatoshi
44
from shutil import copyfile
55
from pyln.testing.utils import SLOW_MACHINE
66
from utils import (
77
only_one, sync_blockheight, wait_for, DEVELOPER, TIMEOUT,
88
account_balance, first_channel_id, basic_fee, TEST_NETWORK,
9-
EXPERIMENTAL_FEATURES,
9+
EXPERIMENTAL_FEATURES, EXPERIMENTAL_DUAL_FUND,
1010
)
1111

1212
import os
@@ -2695,3 +2695,49 @@ def test_segwit_shutdown_script(node_factory, bitcoind, executor):
26952695
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
26962696
with pytest.raises(RpcError, match=r'Unacceptable upfront_shutdown_script'):
26972697
l1.rpc.fundchannel(l2.info['id'], 10**6)
2698+
2699+
2700+
@unittest.skipIf(EXPERIMENTAL_DUAL_FUND, "Uses fundchannel_start")
2701+
def test_shutdown_alternate_txid(node_factory, bitcoind):
2702+
l1, l2 = node_factory.line_graph(2, fundchannel=False,
2703+
opts={'experimental-shutdown-wrong-funding': None})
2704+
2705+
amount = 1000000
2706+
amount_msat = Millisatoshi(amount * 1000)
2707+
2708+
# Let's make a classic fundchannel mistake (wrong txid!)
2709+
addr = l1.rpc.fundchannel_start(l2.info['id'], amount_msat)['funding_address']
2710+
txid = bitcoind.rpc.sendtoaddress(addr, amount / 10**8)
2711+
bitcoind.generate_block(1, wait_for_mempool=1)
2712+
2713+
# Gotta figure out which output manually :(
2714+
tx = bitcoind.rpc.getrawtransaction(txid, 1)
2715+
for n, out in enumerate(tx['vout']):
2716+
if out['scriptPubKey']['addresses'][0] == addr:
2717+
txout = n
2718+
2719+
# Wrong txid, wrong txout!
2720+
wrong_txid = txid[16:] + txid[:16]
2721+
wrong_txout = txout ^ 1
2722+
l1.rpc.fundchannel_complete(l2.info['id'], wrong_txid, wrong_txout)
2723+
2724+
wait_for(lambda: only_one(l2.rpc.listpeers()['peers'])['channels'] != [])
2725+
wait_for(lambda: only_one(l2.rpc.listpeers()['peers'])['channels'][0]['state'] == 'CHANNELD_AWAITING_LOCKIN')
2726+
2727+
closeaddr = l1.rpc.newaddr()['bech32']
2728+
2729+
# Oops, try rescuing it!
2730+
l1.rpc.call('close', {'id': l2.info['id'], 'destination': closeaddr, 'wrong_funding': txid + ':' + str(txout)})
2731+
2732+
# Just make sure node has no funds.
2733+
assert l1.rpc.listfunds()['outputs'] == []
2734+
2735+
bitcoind.generate_block(100, wait_for_mempool=1)
2736+
sync_blockheight(bitcoind, [l1, l2])
2737+
2738+
# We will see our funds return.
2739+
assert len(l1.rpc.listfunds()['outputs']) == 1
2740+
2741+
# FIXME: we should close channels, but we don't!
2742+
# wait_for(lambda: l2.rpc.listpeers()['peers'] == [])
2743+
# wait_for(lambda: l1.rpc.listpeers()['peers'] == [])

wallet/db_postgres_sqlgen.c

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wallet/db_sqlite3_sqlgen.c

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wallet/statements_gettextgen.po

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wallet/test/run-wallet.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ void fatal(const char *fmt UNNEEDED, ...)
119119
bool feature_negotiated(const struct feature_set *our_features UNNEEDED,
120120
const u8 *their_features UNNEEDED, size_t f UNNEEDED)
121121
{ fprintf(stderr, "feature_negotiated called!\n"); abort(); }
122+
/* Generated stub for feature_offered */
123+
bool feature_offered(const u8 *features UNNEEDED, size_t f UNNEEDED)
124+
{ fprintf(stderr, "feature_offered called!\n"); abort(); }
122125
/* Generated stub for fromwire_channeld_dev_memleak_reply */
123126
bool fromwire_channeld_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNEEDED)
124127
{ fprintf(stderr, "fromwire_channeld_dev_memleak_reply called!\n"); abort(); }
@@ -400,6 +403,10 @@ bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
400403
bool json_to_number(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
401404
unsigned int *num UNNEEDED)
402405
{ fprintf(stderr, "json_to_number called!\n"); abort(); }
406+
/* Generated stub for json_to_outpoint */
407+
bool json_to_outpoint(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
408+
struct bitcoin_outpoint *op UNNEEDED)
409+
{ fprintf(stderr, "json_to_outpoint called!\n"); abort(); }
403410
/* Generated stub for json_to_preimage */
404411
bool json_to_preimage(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct preimage *preimage UNNEEDED)
405412
{ fprintf(stderr, "json_to_preimage called!\n"); abort(); }

0 commit comments

Comments
 (0)