mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
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
This commit is contained in:
parent
654faa6174
commit
de16d0f0b4
@ -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
|
there's a member `error_message`, that member is sent to the peer
|
||||||
before disconnection.
|
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`
|
#### `htlc_accepted`
|
||||||
|
|
||||||
The `htlc_accepted` hook is called whenever an incoming HTLC is accepted, and
|
The `htlc_accepted` hook is called whenever an incoming HTLC is accepted, and
|
||||||
|
@ -455,7 +455,7 @@ static void opening_fundee_finished(struct subd *openingd,
|
|||||||
u32 feerate;
|
u32 feerate;
|
||||||
u8 channel_flags;
|
u8 channel_flags;
|
||||||
struct channel *channel;
|
struct channel *channel;
|
||||||
u8 *remote_upfront_shutdown_script;
|
u8 *remote_upfront_shutdown_script, *local_upfront_shutdown_script;
|
||||||
struct per_peer_state *pps;
|
struct per_peer_state *pps;
|
||||||
|
|
||||||
log_debug(uc->log, "Got opening_fundee_finish_response");
|
log_debug(uc->log, "Got opening_fundee_finish_response");
|
||||||
@ -482,6 +482,7 @@ static void opening_fundee_finished(struct subd *openingd,
|
|||||||
&feerate,
|
&feerate,
|
||||||
&funding_signed,
|
&funding_signed,
|
||||||
&uc->our_config.channel_reserve,
|
&uc->our_config.channel_reserve,
|
||||||
|
&local_upfront_shutdown_script,
|
||||||
&remote_upfront_shutdown_script)) {
|
&remote_upfront_shutdown_script)) {
|
||||||
log_broken(uc->log, "bad OPENING_FUNDEE_REPLY %s",
|
log_broken(uc->log, "bad OPENING_FUNDEE_REPLY %s",
|
||||||
tal_hex(reply, reply));
|
tal_hex(reply, reply));
|
||||||
@ -510,7 +511,7 @@ static void opening_fundee_finished(struct subd *openingd,
|
|||||||
channel_flags,
|
channel_flags,
|
||||||
&channel_info,
|
&channel_info,
|
||||||
feerate,
|
feerate,
|
||||||
NULL,
|
local_upfront_shutdown_script,
|
||||||
remote_upfront_shutdown_script);
|
remote_upfront_shutdown_script);
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
uncommitted_channel_disconnect(uc, "Commit channel failed");
|
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)
|
const jsmntok_t *toks)
|
||||||
{
|
{
|
||||||
struct subd *openingd = payload->openingd;
|
struct subd *openingd = payload->openingd;
|
||||||
|
const u8 *our_upfront_shutdown_script;
|
||||||
const char *errmsg = NULL;
|
const char *errmsg = NULL;
|
||||||
|
|
||||||
/* We want to free this, whatever happens. */
|
/* 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,
|
log_debug(openingd->ld->log,
|
||||||
"openchannel_hook_cb says '%s'",
|
"openchannel_hook_cb says '%s'",
|
||||||
errmsg);
|
errmsg);
|
||||||
|
our_upfront_shutdown_script = NULL;
|
||||||
} else if (!json_tok_streq(buffer, t, "continue"))
|
} else if (!json_tok_streq(buffer, t, "continue"))
|
||||||
fatal("Plugin returned an invalid result for the "
|
fatal("Plugin returned an invalid result for the "
|
||||||
"openchannel hook: %.*s",
|
"openchannel hook: %.*s",
|
||||||
t->end - t->start, buffer + t->start);
|
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,
|
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,
|
REGISTER_PLUGIN_HOOK(openchannel,
|
||||||
@ -808,7 +837,7 @@ static void opening_got_offer(struct subd *openingd,
|
|||||||
if (peer_active_channel(uc->peer)) {
|
if (peer_active_channel(uc->peer)) {
|
||||||
subd_send_msg(openingd,
|
subd_send_msg(openingd,
|
||||||
take(towire_opening_got_offer_reply(NULL,
|
take(towire_opening_got_offer_reply(NULL,
|
||||||
"Already have active channel")));
|
"Already have active channel", NULL)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ msgdata,opening_got_offer,shutdown_scriptpubkey,u8,shutdown_len
|
|||||||
# master->openingd: optional rejection message
|
# master->openingd: optional rejection message
|
||||||
msgtype,opening_got_offer_reply,6105
|
msgtype,opening_got_offer_reply,6105
|
||||||
msgdata,opening_got_offer_reply,rejection,?wirestring,
|
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.
|
# Openingd->master: we've successfully offered channel.
|
||||||
# This gives their sig, means we can broadcast tx: we're done.
|
# 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,msglen,u16,
|
||||||
msgdata,opening_fundee,funding_signed_msg,u8,msglen
|
msgdata,opening_fundee,funding_signed_msg,u8,msglen
|
||||||
msgdata,opening_fundee,our_channel_reserve_satoshis,amount_sat,
|
msgdata,opening_fundee,our_channel_reserve_satoshis,amount_sat,
|
||||||
msgdata,opening_fundee,shutdown_len,u16,
|
msgdata,opening_fundee,local_shutdown_len,u16,
|
||||||
msgdata,opening_fundee,shutdown_scriptpubkey,u8,shutdown_len
|
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?
|
# master -> openingd: do you have a memleak?
|
||||||
msgtype,opening_dev_memleak,6033
|
msgtype,opening_dev_memleak,6033
|
||||||
|
|
@ -878,7 +878,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
|
|||||||
struct bitcoin_signature theirsig, sig;
|
struct bitcoin_signature theirsig, sig;
|
||||||
struct bitcoin_tx *local_commit, *remote_commit;
|
struct bitcoin_tx *local_commit, *remote_commit;
|
||||||
struct bitcoin_blkid chain_hash;
|
struct bitcoin_blkid chain_hash;
|
||||||
u8 *msg;
|
u8 *msg, *our_upfront_shutdown_script;
|
||||||
const u8 *wscript;
|
const u8 *wscript;
|
||||||
u8 channel_flags;
|
u8 channel_flags;
|
||||||
char* err_reason;
|
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));
|
wire_sync_write(REQ_FD, take(msg));
|
||||||
msg = wire_sync_read(tmpctx, REQ_FD);
|
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);
|
master_badmsg(WIRE_OPENING_GOT_OFFER_REPLY, msg);
|
||||||
|
|
||||||
/* If they give us a reason to reject, do so. */
|
/* 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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!our_upfront_shutdown_script)
|
||||||
|
our_upfront_shutdown_script = dev_upfront_shutdown_script(state);
|
||||||
|
|
||||||
/* OK, we accept! */
|
/* OK, we accept! */
|
||||||
msg = towire_accept_channel_option_upfront_shutdown_script(NULL, &state->channel_id,
|
msg = towire_accept_channel_option_upfront_shutdown_script(NULL, &state->channel_id,
|
||||||
state->localconf.dust_limit,
|
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.delayed_payment,
|
||||||
&state->our_points.htlc,
|
&state->our_points.htlc,
|
||||||
&state->first_per_commitment_point[LOCAL],
|
&state->first_per_commitment_point[LOCAL],
|
||||||
dev_upfront_shutdown_script(tmpctx));
|
our_upfront_shutdown_script);
|
||||||
|
|
||||||
sync_crypto_write(state->pps, take(msg));
|
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,
|
state->feerate_per_kw,
|
||||||
msg,
|
msg,
|
||||||
state->localconf.channel_reserve,
|
state->localconf.channel_reserve,
|
||||||
|
our_upfront_shutdown_script,
|
||||||
state->remote_upfront_shutdown_script);
|
state->remote_upfront_shutdown_script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
tests/plugins/accepter_close_to.py
Executable file
35
tests/plugins/accepter_close_to.py
Executable file
@ -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()
|
@ -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.")
|
@unittest.skipIf(TEST_NETWORK != 'regtest', "External wallet support doesn't work with elements yet.")
|
||||||
def test_funding_close_upfront(node_factory, bitcoind):
|
def test_funding_close_upfront(node_factory, bitcoind):
|
||||||
l1 = node_factory.get_node()
|
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)
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
||||||
assert(l1.rpc.listpeers()['peers'][0]['id'] == l2.info['id'])
|
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)
|
resp = l1.rpc.fundchannel_start(l2.info['id'], amount, close_to=close_to)
|
||||||
address = resp['funding_address']
|
address = resp['funding_address']
|
||||||
|
|
||||||
@ -1123,16 +1131,16 @@ def test_funding_close_upfront(node_factory, bitcoind):
|
|||||||
|
|
||||||
for node in [l1, l2]:
|
for node in [l1, l2]:
|
||||||
node.daemon.wait_for_log(r'State changed from CHANNELD_AWAITING_LOCKIN to CHANNELD_NORMAL')
|
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']
|
assert amount * 1000 == channel['msatoshi_total']
|
||||||
|
|
||||||
# check that normal peer close works
|
# 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'
|
assert l1.rpc.close(l2.info['id'])['type'] == 'mutual'
|
||||||
|
|
||||||
# check that you can provide a closing address upfront
|
# check that you can provide a closing address upfront
|
||||||
addr = l1.rpc.newaddr()['bech32']
|
addr = l1.rpc.newaddr()['bech32']
|
||||||
_fundchannel(l1, l2, addr)
|
_fundchannel(l1, l2, amt_normal, addr)
|
||||||
# confirm that it appears in listpeers
|
# confirm that it appears in listpeers
|
||||||
assert addr == only_one(l1.rpc.listpeers()['peers'])['channels'][1]['close_to_addr']
|
assert addr == only_one(l1.rpc.listpeers()['peers'])['channels'][1]['close_to_addr']
|
||||||
resp = l1.rpc.close(l2.info['id'])
|
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
|
# check that passing in the same addr to close works
|
||||||
addr = bitcoind.rpc.getnewaddress()
|
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']
|
assert addr == only_one(l1.rpc.listpeers()['peers'])['channels'][2]['close_to_addr']
|
||||||
resp = l1.rpc.close(l2.info['id'], destination=addr)
|
resp = l1.rpc.close(l2.info['id'], destination=addr)
|
||||||
assert resp['type'] == 'mutual'
|
assert resp['type'] == 'mutual'
|
||||||
assert only_one(only_one(bitcoind.rpc.decoderawtransaction(resp['tx'])['vout'])['scriptPubKey']['addresses']) == addr
|
assert only_one(only_one(bitcoind.rpc.decoderawtransaction(resp['tx'])['vout'])['scriptPubKey']['addresses']) == addr
|
||||||
|
|
||||||
# check that remote peer closing works as expected
|
# check that remote peer closing works as expected (and that remote's close_to works)
|
||||||
_fundchannel(l1, l2, addr)
|
_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'])
|
resp = l2.rpc.close(l1.info['id'])
|
||||||
assert resp['type'] == 'mutual'
|
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
|
# check that passing in a different addr to close causes an RPC error
|
||||||
addr2 = l1.rpc.newaddr()['bech32']
|
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'):
|
with pytest.raises(RpcError, match=r'does not match previous shutdown script'):
|
||||||
l1.rpc.close(l2.info['id'], destination=addr2)
|
l1.rpc.close(l2.info['id'], destination=addr2)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user