Skip to content

Commit

Permalink
close: accept wrong_funding outpoint arg if we negotiated the feature.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
rustyrussell committed Mar 10, 2021
1 parent eb61a11 commit 4321f6e
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 16 deletions.
16 changes: 13 additions & 3 deletions doc/lightning-close.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions doc/lightning-close.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
---------
Expand Down
50 changes: 46 additions & 4 deletions lightningd/peer_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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();

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


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

if (wrong_funding) {
if (!feature_negotiated(cmd->ld->our_features,
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(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;
}
}

/* Normal case.
* We allow states shutting down and sigexchange; a previous
* close command may have timed out, and this current command
Expand Down Expand Up @@ -1663,8 +1704,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. */
Expand Down
7 changes: 7 additions & 0 deletions lightningd/test/run-invoice-select-inchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down Expand Up @@ -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)
Expand Down
50 changes: 48 additions & 2 deletions tests/test_closing.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -2695,3 +2695,49 @@ def test_segwit_shutdown_script(node_factory, bitcoind, executor):
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)
bitcoind.generate_block(1, wait_for_mempool=1)

# Gotta figure out which output manually :(
tx = bitcoind.rpc.getrawtransaction(txid, 1)
for n, out in enumerate(tx['vout']):
if out['scriptPubKey']['addresses'][0] == addr:
txout = n

# 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'] == [])
2 changes: 1 addition & 1 deletion wallet/db_postgres_sqlgen.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion wallet/db_sqlite3_sqlgen.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions wallet/statements_gettextgen.po

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions wallet/test/run-wallet.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down Expand Up @@ -400,6 +403,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(); }
Expand Down

0 comments on commit 4321f6e

Please sign in to comment.