diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 6d4b603fa..d84817d67 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1542,6 +1542,43 @@ static void handle_peer_tx_sigs_sent(struct subd *dualopend, } } +static void handle_dry_run_finished(struct subd *dualopend, const u8 *msg) +{ + struct json_stream *response; + struct channel_id c_id; + struct channel *channel = dualopend->channel; + struct command *cmd; + struct lease_rates *rates; + struct amount_sat their_funding, our_funding; + + assert(channel->open_attempt); + cmd = channel->open_attempt->cmd; + channel->open_attempt->cmd = NULL; + + if (!fromwire_dualopend_dry_run(msg, msg, &c_id, + &our_funding, + &their_funding, + &rates)) { + channel_internal_error(channel, + "Bad WIRE_DUALOPEND_DRY_RUN_FINISHED: %s", + tal_hex(msg, msg)); + + return; + } + + /* Free up this open attempt */ + channel->open_attempt = tal_free(channel->open_attempt); + + response = json_stream_success(cmd); + json_add_amount_sat_only(response, "our_funding_msat", our_funding); + json_add_amount_sat_only(response, "their_funding_msat", their_funding); + + if (rates) + json_add_lease_rates(response, rates); + + was_pending(command_success(cmd, response)); +} + static void handle_peer_locked(struct subd *dualopend, const u8 *msg) { struct pubkey remote_per_commit; @@ -2358,6 +2395,105 @@ static struct command_result *init_set_feerate(struct command *cmd, return NULL; } +static struct command_result *json_queryrates(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct node_id *id; + struct peer *peer; + struct channel *channel; + u32 *feerate_per_kw_funding; + u32 *feerate_per_kw; + struct amount_sat *amount, *request_amt; + struct wally_psbt *psbt; + struct open_attempt *oa; + u8 *msg; + struct command_result *res; + + if (!param(cmd, buffer, params, + p_req("id", param_node_id, &id), + p_req("amount", param_sat, &amount), + p_req("request_amt", param_sat, &request_amt), + p_opt("commitment_feerate", param_feerate, &feerate_per_kw), + p_opt("funding_feerate", param_feerate, &feerate_per_kw_funding), + NULL)) + return command_param_failed(); + + res = init_set_feerate(cmd, &feerate_per_kw, &feerate_per_kw_funding); + if (res) + return res; + + peer = peer_by_id(cmd->ld, id); + if (!peer) { + return command_fail(cmd, FUNDING_UNKNOWN_PEER, "Unknown peer"); + } + + /* We can't query rates for a peer we have a channel with */ + channel = peer_active_channel(peer); + if (channel) + return command_fail(cmd, LIGHTNINGD, "Peer in state %s," + " can't query peer's rates if already" + " have a channel", + channel_state_name(channel)); + + channel = peer_unsaved_channel(peer); + if (!channel || !channel->owner) + return command_fail(cmd, FUNDING_PEER_NOT_CONNECTED, + "Peer not connected"); + if (channel->open_attempt + || !list_empty(&channel->inflights)) + return command_fail(cmd, FUNDING_STATE_INVALID, + "Channel funding in-progress. %s", + channel_state_name(channel)); + + if (!feature_negotiated(cmd->ld->our_features, + peer->their_features, + OPT_DUAL_FUND)) { + return command_fail(cmd, FUNDING_V2_NOT_SUPPORTED, + "v2 openchannel not supported " + "by peer, can't query rates"); + } + + /* BOLT #2: + * - if both nodes advertised `option_support_large_channel`: + * - MAY set `funding_satoshis` greater than or equal to 2^24 satoshi. + * - otherwise: + * - MUST set `funding_satoshis` to less than 2^24 satoshi. + */ + if (!feature_negotiated(cmd->ld->our_features, + peer->their_features, OPT_LARGE_CHANNELS) + && amount_sat_greater(*amount, chainparams->max_funding)) + return command_fail(cmd, FUND_MAX_EXCEEDED, + "Amount exceeded %s", + type_to_string(tmpctx, struct amount_sat, + &chainparams->max_funding)); + + /* Get a new open_attempt going, keeps us from re-initing + * while looking */ + channel->opener = LOCAL; + channel->open_attempt = oa = new_channel_open_attempt(channel); + channel->channel_flags = OUR_CHANNEL_FLAGS; + oa->funding = *amount; + oa->cmd = cmd; + /* empty psbt to start */ + psbt = create_psbt(tmpctx, 0, 0, 0); + + msg = towire_dualopend_opener_init(NULL, + psbt, *amount, + oa->our_upfront_shutdown_script, + *feerate_per_kw, + *feerate_per_kw_funding, + channel->channel_flags, + *request_amt, + get_block_height(cmd->ld->topology), + true); + + subd_send_msg(channel->owner, take(msg)); + return command_still_pending(cmd); + +} + static struct command_result *json_openchannel_init(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -2507,7 +2643,8 @@ static struct command_result *json_openchannel_init(struct command *cmd, *feerate_per_kw_funding, channel->channel_flags, *request_amt, - get_block_height(cmd->ld->topology)); + get_block_height(cmd->ld->topology), + false); subd_send_msg(channel->owner, take(msg)); return command_still_pending(cmd); @@ -2801,6 +2938,9 @@ static unsigned int dual_opend_msg(struct subd *dualopend, case WIRE_DUALOPEND_PEER_LOCKED: handle_peer_locked(dualopend, msg); return 0; + case WIRE_DUALOPEND_DRY_RUN: + handle_dry_run_finished(dualopend, msg); + return 0; case WIRE_DUALOPEND_CHANNEL_LOCKED: if (tal_count(fds) != 3) return 3; @@ -2851,6 +2991,14 @@ static unsigned int dual_opend_msg(struct subd *dualopend, return 0; } +static const struct json_command queryrates_command = { + "queryrates", + "channels", + json_queryrates, + "Ask a peer what their contribution and liquidity rates are" + " for the given {amount} and {requested_amt}" +}; + static const struct json_command openchannel_init_command = { "openchannel_init", "channels", @@ -2889,6 +3037,7 @@ static const struct json_command openchannel_abort_command = { "Abort {channel_id}'s open. Usable while `commitment_signed=false`." }; +AUTODATA(json_command, &queryrates_command); AUTODATA(json_command, &openchannel_init_command); AUTODATA(json_command, &openchannel_update_command); AUTODATA(json_command, &openchannel_signed_command); diff --git a/openingd/dualopend.c b/openingd/dualopend.c index 6f94056ff..2dc6eaac2 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -2004,7 +2004,6 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) lease_blockheight_start = 0; } - /* BOLT-* #2 * If the peer's revocation basepoint is unknown (e.g. * `open_channel2`), a temporary `channel_id` should be found @@ -2492,6 +2491,7 @@ static void opener_start(struct state *state, u8 *msg) char *err_reason; struct amount_sat total, requested_sats; u32 current_blockheight; + bool dry_run; struct tx_state *tx_state = state->tx_state; if (!fromwire_dualopend_opener_init(state, msg, @@ -2502,7 +2502,8 @@ static void opener_start(struct state *state, u8 *msg) &tx_state->feerate_per_kw_funding, &state->channel_flags, &requested_sats, - ¤t_blockheight)) + ¤t_blockheight, + &dry_run)) master_badmsg(WIRE_DUALOPEND_OPENER_INIT, msg); state->our_role = TX_INITIATOR; @@ -2620,6 +2621,31 @@ static void opener_start(struct state *state, u8 *msg) &state->our_points.revocation, &state->their_points.revocation); + /* If this is a dry run, we just wanted to know + * how much they'd put into the channel and their terms */ + if (dry_run) { + msg = towire_dualopend_dry_run(NULL, &state->channel_id, + tx_state->opener_funding, + tx_state->accepter_funding, + a_tlv->will_fund + ? &a_tlv->will_fund->lease_rates : NULL); + + + wire_sync_write(REQ_FD, take(msg)); + + /* Note that this *normally* would return an error + * to the RPC caller. We head this off by + * sending a message to master just before this, + * which works as expected as long as + * these messages are queued+processed sequentially */ + open_err_warn(state, "%s", "Abort requested"); + } + + /* FIXME: BOLT QUOTE */ + if (open_tlv->request_funds && a_tlv->will_fund) { + /* OK! lease mode activated */ + } + /* Check that total funding doesn't overflow */ if (!amount_sat_add(&total, tx_state->opener_funding, tx_state->accepter_funding)) @@ -3387,6 +3413,7 @@ static u8 *handle_master_in(struct state *state) case WIRE_DUALOPEND_GOT_SHUTDOWN: case WIRE_DUALOPEND_SHUTDOWN_COMPLETE: case WIRE_DUALOPEND_FAIL_FALLEN_BEHIND: + case WIRE_DUALOPEND_DRY_RUN: break; } diff --git a/openingd/dualopend_wire.csv b/openingd/dualopend_wire.csv index 397b2ccbe..2cf80caa9 100644 --- a/openingd/dualopend_wire.csv +++ b/openingd/dualopend_wire.csv @@ -167,6 +167,7 @@ msgdata,dualopend_opener_init,feerate_per_kw_funding,u32, msgdata,dualopend_opener_init,channel_flags,u8, msgdata,dualopend_opener_init,requested_sats,amount_sat, msgdata,dualopend_opener_init,blockheight,u32, +msgdata,dualopend_opener_init,dry_run,bool, # dualopend->master received tx_sigs from peer msgtype,dualopend_funding_sigs,7010 @@ -213,3 +214,10 @@ msgtype,dualopend_dev_memleak,7033 msgtype,dualopend_dev_memleak_reply,7133 msgdata,dualopend_dev_memleak_reply,leak,bool, + +# dualopend -> master: this was a dry run, here's some info about this open +msgtype,dualopend_dry_run,7026 +msgdata,dualopend_dry_run,channel_id,channel_id, +msgdata,dualopend_dry_run,our_funding,amount_sat, +msgdata,dualopend_dry_run,their_funding,amount_sat, +msgdata,dualopend_dry_run,lease_rates,?lease_rates, diff --git a/openingd/dualopend_wiregen.c b/openingd/dualopend_wiregen.c index 5aa955466..97ed07980 100644 --- a/openingd/dualopend_wiregen.c +++ b/openingd/dualopend_wiregen.c @@ -46,6 +46,7 @@ const char *dualopend_wire_name(int e) case WIRE_DUALOPEND_SHUTDOWN_COMPLETE: return "WIRE_DUALOPEND_SHUTDOWN_COMPLETE"; case WIRE_DUALOPEND_DEV_MEMLEAK: return "WIRE_DUALOPEND_DEV_MEMLEAK"; case WIRE_DUALOPEND_DEV_MEMLEAK_REPLY: return "WIRE_DUALOPEND_DEV_MEMLEAK_REPLY"; + case WIRE_DUALOPEND_DRY_RUN: return "WIRE_DUALOPEND_DRY_RUN"; } snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e); @@ -81,6 +82,7 @@ bool dualopend_wire_is_defined(u16 type) case WIRE_DUALOPEND_SHUTDOWN_COMPLETE:; case WIRE_DUALOPEND_DEV_MEMLEAK:; case WIRE_DUALOPEND_DEV_MEMLEAK_REPLY:; + case WIRE_DUALOPEND_DRY_RUN:; return true; } return false; @@ -621,7 +623,7 @@ bool fromwire_dualopend_fail(const tal_t *ctx, const void *p, wirestring **reaso /* WIRE: DUALOPEND_OPENER_INIT */ /* master->dualopend: hello */ -u8 *towire_dualopend_opener_init(const tal_t *ctx, const struct wally_psbt *psbt, struct amount_sat funding_amount, const u8 *local_shutdown_scriptpubkey, u32 feerate_per_kw, u32 feerate_per_kw_funding, u8 channel_flags, struct amount_sat requested_sats, u32 blockheight) +u8 *towire_dualopend_opener_init(const tal_t *ctx, const struct wally_psbt *psbt, struct amount_sat funding_amount, const u8 *local_shutdown_scriptpubkey, u32 feerate_per_kw, u32 feerate_per_kw_funding, u8 channel_flags, struct amount_sat requested_sats, u32 blockheight, bool dry_run) { u16 local_shutdown_len = tal_count(local_shutdown_scriptpubkey); u8 *p = tal_arr(ctx, u8, 0); @@ -636,10 +638,11 @@ u8 *towire_dualopend_opener_init(const tal_t *ctx, const struct wally_psbt *psbt towire_u8(&p, channel_flags); towire_amount_sat(&p, requested_sats); towire_u32(&p, blockheight); + towire_bool(&p, dry_run); return memcheck(p, tal_count(p)); } -bool fromwire_dualopend_opener_init(const tal_t *ctx, const void *p, struct wally_psbt **psbt, struct amount_sat *funding_amount, u8 **local_shutdown_scriptpubkey, u32 *feerate_per_kw, u32 *feerate_per_kw_funding, u8 *channel_flags, struct amount_sat *requested_sats, u32 *blockheight) +bool fromwire_dualopend_opener_init(const tal_t *ctx, const void *p, struct wally_psbt **psbt, struct amount_sat *funding_amount, u8 **local_shutdown_scriptpubkey, u32 *feerate_per_kw, u32 *feerate_per_kw_funding, u8 *channel_flags, struct amount_sat *requested_sats, u32 *blockheight, bool *dry_run) { u16 local_shutdown_len; @@ -659,6 +662,7 @@ bool fromwire_dualopend_opener_init(const tal_t *ctx, const void *p, struct wall *channel_flags = fromwire_u8(&cursor, &plen); *requested_sats = fromwire_amount_sat(&cursor, &plen); *blockheight = fromwire_u32(&cursor, &plen); + *dry_run = fromwire_bool(&cursor, &plen); return cursor != NULL; } @@ -932,4 +936,42 @@ bool fromwire_dualopend_dev_memleak_reply(const void *p, bool *leak) *leak = fromwire_bool(&cursor, &plen); return cursor != NULL; } -// SHA256STAMP:d8a31a5de292561ccfb5b7050c911b590db44a50209e02469179904708328861 + +/* WIRE: DUALOPEND_DRY_RUN */ +/* dualopend -> master: this was a dry run */ +u8 *towire_dualopend_dry_run(const tal_t *ctx, const struct channel_id *channel_id, struct amount_sat our_funding, struct amount_sat their_funding, const struct lease_rates *lease_rates) +{ + u8 *p = tal_arr(ctx, u8, 0); + + towire_u16(&p, WIRE_DUALOPEND_DRY_RUN); + towire_channel_id(&p, channel_id); + towire_amount_sat(&p, our_funding); + towire_amount_sat(&p, their_funding); + if (!lease_rates) + towire_bool(&p, false); + else { + towire_bool(&p, true); + towire_lease_rates(&p, lease_rates); + } + + return memcheck(p, tal_count(p)); +} +bool fromwire_dualopend_dry_run(const tal_t *ctx, const void *p, struct channel_id *channel_id, struct amount_sat *our_funding, struct amount_sat *their_funding, struct lease_rates **lease_rates) +{ + const u8 *cursor = p; + size_t plen = tal_count(p); + + if (fromwire_u16(&cursor, &plen) != WIRE_DUALOPEND_DRY_RUN) + return false; + fromwire_channel_id(&cursor, &plen, channel_id); + *our_funding = fromwire_amount_sat(&cursor, &plen); + *their_funding = fromwire_amount_sat(&cursor, &plen); + if (!fromwire_bool(&cursor, &plen)) + *lease_rates = NULL; + else { + *lease_rates = tal(ctx, struct lease_rates); + fromwire_lease_rates(&cursor, &plen, *lease_rates); + } + return cursor != NULL; +} +// SHA256STAMP:a0ce1d5dfc6518a35d2e346c770754c498558ac426e3040526e10bbbc237db6f diff --git a/openingd/dualopend_wiregen.h b/openingd/dualopend_wiregen.h index 2ef8db7b3..66bb3de80 100644 --- a/openingd/dualopend_wiregen.h +++ b/openingd/dualopend_wiregen.h @@ -72,6 +72,8 @@ enum dualopend_wire { /* master -> dualopend: do you have a memleak? */ WIRE_DUALOPEND_DEV_MEMLEAK = 7033, WIRE_DUALOPEND_DEV_MEMLEAK_REPLY = 7133, + /* dualopend -> master: this was a dry run */ + WIRE_DUALOPEND_DRY_RUN = 7026, }; const char *dualopend_wire_name(int e); @@ -153,8 +155,8 @@ bool fromwire_dualopend_fail(const tal_t *ctx, const void *p, wirestring **reaso /* WIRE: DUALOPEND_OPENER_INIT */ /* master->dualopend: hello */ -u8 *towire_dualopend_opener_init(const tal_t *ctx, const struct wally_psbt *psbt, struct amount_sat funding_amount, const u8 *local_shutdown_scriptpubkey, u32 feerate_per_kw, u32 feerate_per_kw_funding, u8 channel_flags, struct amount_sat requested_sats, u32 blockheight); -bool fromwire_dualopend_opener_init(const tal_t *ctx, const void *p, struct wally_psbt **psbt, struct amount_sat *funding_amount, u8 **local_shutdown_scriptpubkey, u32 *feerate_per_kw, u32 *feerate_per_kw_funding, u8 *channel_flags, struct amount_sat *requested_sats, u32 *blockheight); +u8 *towire_dualopend_opener_init(const tal_t *ctx, const struct wally_psbt *psbt, struct amount_sat funding_amount, const u8 *local_shutdown_scriptpubkey, u32 feerate_per_kw, u32 feerate_per_kw_funding, u8 channel_flags, struct amount_sat requested_sats, u32 blockheight, bool dry_run); +bool fromwire_dualopend_opener_init(const tal_t *ctx, const void *p, struct wally_psbt **psbt, struct amount_sat *funding_amount, u8 **local_shutdown_scriptpubkey, u32 *feerate_per_kw, u32 *feerate_per_kw_funding, u8 *channel_flags, struct amount_sat *requested_sats, u32 *blockheight, bool *dry_run); /* WIRE: DUALOPEND_FUNDING_SIGS */ /* dualopend->master received tx_sigs from peer */ @@ -215,6 +217,11 @@ bool fromwire_dualopend_dev_memleak(const void *p); u8 *towire_dualopend_dev_memleak_reply(const tal_t *ctx, bool leak); bool fromwire_dualopend_dev_memleak_reply(const void *p, bool *leak); +/* WIRE: DUALOPEND_DRY_RUN */ +/* dualopend -> master: this was a dry run */ +u8 *towire_dualopend_dry_run(const tal_t *ctx, const struct channel_id *channel_id, struct amount_sat our_funding, struct amount_sat their_funding, const struct lease_rates *lease_rates); +bool fromwire_dualopend_dry_run(const tal_t *ctx, const void *p, struct channel_id *channel_id, struct amount_sat *our_funding, struct amount_sat *their_funding, struct lease_rates **lease_rates); + #endif /* LIGHTNING_OPENINGD_DUALOPEND_WIREGEN_H */ -// SHA256STAMP:d8a31a5de292561ccfb5b7050c911b590db44a50209e02469179904708328861 +// SHA256STAMP:a0ce1d5dfc6518a35d2e346c770754c498558ac426e3040526e10bbbc237db6f diff --git a/tests/test_opening.py b/tests/test_opening.py index 96e32586c..c38e4eb3a 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -15,6 +15,43 @@ def find_next_feerate(node, peer): return chan['next_feerate'] +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.openchannel('v2') +def test_queryrates(node_factory, bitcoind): + l1, l2 = node_factory.get_nodes(2) + + amount = 10 ** 6 + + l1.fundwallet(amount * 10) + l2.fundwallet(amount * 10) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + result = l1.rpc.queryrates(l2.info['id'], amount, amount * 10) + assert result['our_funding_msat'] == Millisatoshi(amount * 1000) + assert result['their_funding_msat'] == Millisatoshi(0) + assert 'weight_charge' not in result + + l2.rpc.call('funderupdate', {'policy': 'match', + 'policy_mod': 100, + 'per_channel_max': '1btc', + 'fuzz_percent': 0, + 'lease_fee_base_msat': '2sat', + 'funding_weight': 1000, + 'lease_fee_basis': 140, + 'channel_fee_max_base_msat': '3sat', + 'channel_fee_max_proportional_thousandths': 101}) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + result = l1.rpc.queryrates(l2.info['id'], amount, amount) + assert result['our_funding_msat'] == Millisatoshi(amount * 1000) + assert result['their_funding_msat'] == Millisatoshi(amount * 1000) + assert result['funding_weight'] == 1000 + assert result['lease_fee_base_msat'] == Millisatoshi(2000) + assert result['lease_fee_basis'] == 140 + assert result['channel_fee_max_base_msat'] == Millisatoshi(3000) + assert result['channel_fee_max_proportional_thousandths'] == 101 + + @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.developer("uses dev-disconnect") @pytest.mark.openchannel('v1') # Mixed v1 + v2, v2 manually turned on