Browse Source

openchannel hook: add new `close_to` field

Rounds out the application of `upfront_shutdown_script`, allowing
an accepting node to specify a close_to address.

Prior to this, only the opening node could specify one.

Changelog-Added: Plugins: Allow the 'accepter' to specify an upfront_shutdown_script for a channel via a `close_to` field in the openchannel hook result
travis-debug
lisa neigut 5 years ago
committed by Rusty Russell
parent
commit
de16d0f0b4
  1. 15
      doc/PLUGINS.md
  2. 39
      lightningd/opening_control.c
  3. 8
      openingd/opening_wire.csv
  4. 11
      openingd/openingd.c
  5. 35
      tests/plugins/accepter_close_to.py
  6. 37
      tests/test_connection.py

15
doc/PLUGINS.md

@ -577,6 +577,21 @@ the string `reject` or `continue`. If `reject` and
there's a member `error_message`, that member is sent to the peer
before disconnection.
For a 'continue'd result, you can also include a `close_to` address,
which will be used as the output address for a mutual close transaction.
e.g.
```json
{
"result": "continue",
"close_to": "bc1qlq8srqnz64wgklmqvurv7qnr4rvtq2u96hhfg2"
}
```
Note that `close_to` must be a valid address for the current chain; an invalid address will cause the node to exit with an error.
#### `htlc_accepted`
The `htlc_accepted` hook is called whenever an incoming HTLC is accepted, and

39
lightningd/opening_control.c

@ -455,7 +455,7 @@ static void opening_fundee_finished(struct subd *openingd,
u32 feerate;
u8 channel_flags;
struct channel *channel;
u8 *remote_upfront_shutdown_script;
u8 *remote_upfront_shutdown_script, *local_upfront_shutdown_script;
struct per_peer_state *pps;
log_debug(uc->log, "Got opening_fundee_finish_response");
@ -482,6 +482,7 @@ static void opening_fundee_finished(struct subd *openingd,
&feerate,
&funding_signed,
&uc->our_config.channel_reserve,
&local_upfront_shutdown_script,
&remote_upfront_shutdown_script)) {
log_broken(uc->log, "bad OPENING_FUNDEE_REPLY %s",
tal_hex(reply, reply));
@ -510,7 +511,7 @@ static void opening_fundee_finished(struct subd *openingd,
channel_flags,
&channel_info,
feerate,
NULL,
local_upfront_shutdown_script,
remote_upfront_shutdown_script);
if (!channel) {
uncommitted_channel_disconnect(uc, "Commit channel failed");
@ -753,6 +754,7 @@ static void openchannel_hook_cb(struct openchannel_hook_payload *payload,
const jsmntok_t *toks)
{
struct subd *openingd = payload->openingd;
const u8 *our_upfront_shutdown_script;
const char *errmsg = NULL;
/* We want to free this, whatever happens. */
@ -782,14 +784,41 @@ static void openchannel_hook_cb(struct openchannel_hook_payload *payload,
log_debug(openingd->ld->log,
"openchannel_hook_cb says '%s'",
errmsg);
our_upfront_shutdown_script = NULL;
} else if (!json_tok_streq(buffer, t, "continue"))
fatal("Plugin returned an invalid result for the "
"openchannel hook: %.*s",
t->end - t->start, buffer + t->start);
}
/* Check for a 'close_to' address passed back */
if (!errmsg) {
t = json_get_member(buffer, toks, "close_to");
if (t) {
switch (json_to_address_scriptpubkey(tmpctx, chainparams,
buffer, t,
&our_upfront_shutdown_script)) {
case ADDRESS_PARSE_UNRECOGNIZED:
fatal("Plugin returned an invalid response to the"
" openchannel.close_to hook: %.*s",
t->end - t->start, buffer + t->start);
case ADDRESS_PARSE_WRONG_NETWORK:
fatal("Plugin returned invalid response to the"
" openchannel.close_to hook: address %s is"
" not on network %s",
tal_hex(NULL, our_upfront_shutdown_script),
chainparams->network_name);
case ADDRESS_PARSE_SUCCESS:
errmsg = NULL;
}
} else
our_upfront_shutdown_script = NULL;
}
} else
our_upfront_shutdown_script = NULL;
subd_send_msg(openingd,
take(towire_opening_got_offer_reply(NULL, errmsg)));
take(towire_opening_got_offer_reply(NULL, errmsg,
our_upfront_shutdown_script)));
}
REGISTER_PLUGIN_HOOK(openchannel,
@ -808,7 +837,7 @@ static void opening_got_offer(struct subd *openingd,
if (peer_active_channel(uc->peer)) {
subd_send_msg(openingd,
take(towire_opening_got_offer_reply(NULL,
"Already have active channel")));
"Already have active channel", NULL)));
return;
}

8
openingd/opening_wire.csv

@ -44,6 +44,8 @@ msgdata,opening_got_offer,shutdown_scriptpubkey,u8,shutdown_len
# master->openingd: optional rejection message
msgtype,opening_got_offer_reply,6105
msgdata,opening_got_offer_reply,rejection,?wirestring,
msgdata,opening_got_offer_reply,shutdown_len,u16,
msgdata,opening_got_offer_reply,our_shutdown_scriptpubkey,?u8,shutdown_len
# Openingd->master: we've successfully offered channel.
# This gives their sig, means we can broadcast tx: we're done.
@ -118,8 +120,10 @@ msgdata,opening_fundee,feerate_per_kw,u32,
msgdata,opening_fundee,msglen,u16,
msgdata,opening_fundee,funding_signed_msg,u8,msglen
msgdata,opening_fundee,our_channel_reserve_satoshis,amount_sat,
msgdata,opening_fundee,shutdown_len,u16,
msgdata,opening_fundee,shutdown_scriptpubkey,u8,shutdown_len
msgdata,opening_fundee,local_shutdown_len,u16,
msgdata,opening_fundee,local_shutdown_scriptpubkey,u8,local_shutdown_len
msgdata,opening_fundee,remote_shutdown_len,u16,
msgdata,opening_fundee,remote_shutdown_scriptpubkey,u8,remote_shutdown_len
# master -> openingd: do you have a memleak?
msgtype,opening_dev_memleak,6033

Can't render this file because it has a wrong number of fields in line 6.

11
openingd/openingd.c

@ -878,7 +878,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
struct bitcoin_signature theirsig, sig;
struct bitcoin_tx *local_commit, *remote_commit;
struct bitcoin_blkid chain_hash;
u8 *msg;
u8 *msg, *our_upfront_shutdown_script;
const u8 *wscript;
u8 channel_flags;
char* err_reason;
@ -1064,7 +1064,8 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
wire_sync_write(REQ_FD, take(msg));
msg = wire_sync_read(tmpctx, REQ_FD);
if (!fromwire_opening_got_offer_reply(tmpctx, msg, &err_reason))
if (!fromwire_opening_got_offer_reply(NULL, msg, &err_reason,
&our_upfront_shutdown_script))
master_badmsg(WIRE_OPENING_GOT_OFFER_REPLY, msg);
/* If they give us a reason to reject, do so. */
@ -1075,6 +1076,9 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
return NULL;
}
if (!our_upfront_shutdown_script)
our_upfront_shutdown_script = dev_upfront_shutdown_script(state);
/* OK, we accept! */
msg = towire_accept_channel_option_upfront_shutdown_script(NULL, &state->channel_id,
state->localconf.dust_limit,
@ -1090,7 +1094,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
&state->our_points.delayed_payment,
&state->our_points.htlc,
&state->first_per_commitment_point[LOCAL],
dev_upfront_shutdown_script(tmpctx));
our_upfront_shutdown_script);
sync_crypto_write(state->pps, take(msg));
@ -1258,6 +1262,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
state->feerate_per_kw,
msg,
state->localconf.channel_reserve,
our_upfront_shutdown_script,
state->remote_upfront_shutdown_script);
}

35
tests/plugins/accepter_close_to.py

@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Simple plugin to test the openchannel_hook's
'close_to' address functionality.
If the funding amount is:
- a multiple of 11: we send back a valid address (regtest)
- a multiple of 7: we send back an empty address
- a multiple of 5: we send back an address for the wrong chain (mainnet)
- otherwise: we don't include the close_to
"""
from lightning import Plugin, Millisatoshi
plugin = Plugin()
@plugin.hook('openchannel')
def on_openchannel(openchannel, plugin, **kwargs):
# - a multiple of 11: we send back a valid address (regtest)
if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 11 == 0:
return {'result': 'continue', 'close_to': 'bcrt1q7gtnxmlaly9vklvmfj06amfdef3rtnrdazdsvw'}
# - a multiple of 7: we send back an empty address
if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 7 == 0:
return {'result': 'continue', 'close_to': ''}
# - a multiple of 5: we send back an address for the wrong chain (mainnet)
if Millisatoshi(openchannel['funding_satoshis']).to_satoshi() % 5 == 0:
return {'result': 'continue', 'close_to': 'bc1qlq8srqnz64wgklmqvurv7qnr4rvtq2u96hhfg2'}
# - otherwise: we don't include the close_to
return {'result': 'continue'}
plugin.run()

37
tests/test_connection.py

@ -1079,13 +1079,21 @@ def test_funding_cancel_race(node_factory, bitcoind, executor):
@unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.")
def test_funding_close_upfront(node_factory, bitcoind):
l1 = node_factory.get_node()
l2 = node_factory.get_node()
def _fundchannel(l1, l2, close_to):
opts = {'plugin': os.path.join(os.getcwd(), 'tests/plugins/accepter_close_to.py')}
l2 = node_factory.get_node(options=opts)
# The 'accepter_close_to' plugin uses the channel funding amount to determine
# whether or not to include a 'close_to' address
amt_normal = 100007 # continues without returning a close_to
amt_addr = 100001 # returns valid regtest address
remote_valid_addr = 'bcrt1q7gtnxmlaly9vklvmfj06amfdef3rtnrdazdsvw'
def _fundchannel(l1, l2, amount, close_to):
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
assert(l1.rpc.listpeers()['peers'][0]['id'] == l2.info['id'])
amount = 2**24 - 1
resp = l1.rpc.fundchannel_start(l2.info['id'], amount, close_to=close_to)
address = resp['funding_address']
@ -1123,16 +1131,16 @@ def test_funding_close_upfront(node_factory, bitcoind):
for node in [l1, l2]:
node.daemon.wait_for_log(r'State changed from CHANNELD_AWAITING_LOCKIN to CHANNELD_NORMAL')
channel = node.rpc.listpeers()['peers'][0]['channels'][0]
channel = node.rpc.listpeers()['peers'][0]['channels'][-1]
assert amount * 1000 == channel['msatoshi_total']
# check that normal peer close works
_fundchannel(l1, l2, None)
_fundchannel(l1, l2, amt_normal, None)
assert l1.rpc.close(l2.info['id'])['type'] == 'mutual'
# check that you can provide a closing address upfront
addr = l1.rpc.newaddr()['bech32']
_fundchannel(l1, l2, addr)
_fundchannel(l1, l2, amt_normal, addr)
# confirm that it appears in listpeers
assert addr == only_one(l1.rpc.listpeers()['peers'])['channels'][1]['close_to_addr']
resp = l1.rpc.close(l2.info['id'])
@ -1141,21 +1149,28 @@ def test_funding_close_upfront(node_factory, bitcoind):
# check that passing in the same addr to close works
addr = bitcoind.rpc.getnewaddress()
_fundchannel(l1, l2, addr)
_fundchannel(l1, l2, amt_normal, addr)
assert addr == only_one(l1.rpc.listpeers()['peers'])['channels'][2]['close_to_addr']
resp = l1.rpc.close(l2.info['id'], destination=addr)
assert resp['type'] == 'mutual'
assert only_one(only_one(bitcoind.rpc.decoderawtransaction(resp['tx'])['vout'])['scriptPubKey']['addresses']) == addr
# check that remote peer closing works as expected
_fundchannel(l1, l2, addr)
# check that remote peer closing works as expected (and that remote's close_to works)
_fundchannel(l1, l2, amt_addr, addr)
# send some money to remote so that they have a closeout
l1.rpc.pay(l2.rpc.invoice((amt_addr // 2) * 1000, 'test_remote_close_to', 'desc')['bolt11'])
assert only_one(l2.rpc.listpeers()['peers'])['channels'][-1]['close_to_addr'] == remote_valid_addr
resp = l2.rpc.close(l1.info['id'])
assert resp['type'] == 'mutual'
assert only_one(only_one(bitcoind.rpc.decoderawtransaction(resp['tx'])['vout'])['scriptPubKey']['addresses']) == addr
vouts = bitcoind.rpc.decoderawtransaction(resp['tx'])['vout']
assert len(vouts) == 2
for vout in vouts:
assert only_one(vout['scriptPubKey']['addresses']) in [addr, remote_valid_addr]
# check that passing in a different addr to close causes an RPC error
addr2 = l1.rpc.newaddr()['bech32']
_fundchannel(l1, l2, addr)
_fundchannel(l1, l2, amt_normal, addr)
with pytest.raises(RpcError, match=r'does not match previous shutdown script'):
l1.rpc.close(l2.info['id'], destination=addr2)

Loading…
Cancel
Save