From b6ef966ad411193af36db44b955f9b4f6820fa54 Mon Sep 17 00:00:00 2001 From: niftynei Date: Mon, 17 Oct 2022 14:51:22 -0500 Subject: [PATCH 01/13] chainparams/dual-open: set max_supply; use for max on wumbo channels We were leaving out the `channel_max_msat` for `openchannel2` when channels are 'wumbo' (the conversion to msat in the json helper overflowed, which resulted in the field not being printed) Changelog-Changed: Plugins: `openchannel2` now always includes the `channel_max_msat` --- bitcoin/chainparams.c | 8 ++++++++ bitcoin/chainparams.h | 2 ++ lightningd/dual_open_control.c | 10 ++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/bitcoin/chainparams.c b/bitcoin/chainparams.c index ac48ee5da763..d26e77910871 100644 --- a/bitcoin/chainparams.c +++ b/bitcoin/chainparams.c @@ -50,6 +50,7 @@ const struct chainparams networks[] = { */ .max_funding = AMOUNT_SAT_INIT((1 << 24) - 1), .max_payment = AMOUNT_MSAT_INIT(0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), /* "Lightning Charge Powers Developers & Blockstream Store" */ .when_lightning_became_cool = 504500, .p2pkh_version = 0, @@ -76,6 +77,7 @@ const struct chainparams networks[] = { .dust_limit = { 546 }, .max_funding = AMOUNT_SAT_INIT((1 << 24) - 1), .max_payment = AMOUNT_MSAT_INIT(0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), .when_lightning_became_cool = 1, .p2pkh_version = 111, .p2sh_version = 196, @@ -102,6 +104,7 @@ const struct chainparams networks[] = { .dust_limit = { 546 }, .max_funding = AMOUNT_SAT_INIT((1 << 24) - 1), .max_payment = AMOUNT_MSAT_INIT(0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), .when_lightning_became_cool = 1, .p2pkh_version = 111, .p2sh_version = 196, @@ -126,6 +129,7 @@ const struct chainparams networks[] = { .dust_limit = { 546 }, .max_funding = AMOUNT_SAT_INIT((1 << 24) - 1), .max_payment = AMOUNT_MSAT_INIT(0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), .p2pkh_version = 111, .p2sh_version = 196, .testnet = true, @@ -150,6 +154,7 @@ const struct chainparams networks[] = { .dust_limit = { 100000 }, .max_funding = AMOUNT_SAT_INIT(60 * ((1 << 24) - 1)), .max_payment = AMOUNT_MSAT_INIT(60 * 0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), .when_lightning_became_cool = 1320000, .p2pkh_version = 48, .p2sh_version = 50, @@ -175,6 +180,7 @@ const struct chainparams networks[] = { .dust_limit = { 100000 }, .max_funding = AMOUNT_SAT_INIT(60 * ((1 << 24) - 1)), .max_payment = AMOUNT_MSAT_INIT(60 * 0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), .when_lightning_became_cool = 1, .p2pkh_version = 111, .p2sh_version = 58, @@ -199,6 +205,7 @@ const struct chainparams networks[] = { .dust_limit = {546}, .max_funding = AMOUNT_SAT_INIT((1 << 24) - 1), .max_payment = AMOUNT_MSAT_INIT(0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), .when_lightning_became_cool = 1, .p2pkh_version = 91, .p2sh_version = 75, @@ -223,6 +230,7 @@ const struct chainparams networks[] = { .dust_limit = {546}, .max_funding = AMOUNT_SAT_INIT((1 << 24) - 1), .max_payment = AMOUNT_MSAT_INIT(0xFFFFFFFFULL), + .max_supply = AMOUNT_SAT_INIT(2100000000000000), .when_lightning_became_cool = 1, .p2pkh_version = 57, .p2sh_version = 39, diff --git a/bitcoin/chainparams.h b/bitcoin/chainparams.h index f4e493de66ac..a8e74fe17e8e 100644 --- a/bitcoin/chainparams.h +++ b/bitcoin/chainparams.h @@ -39,6 +39,8 @@ struct chainparams { const struct amount_sat dust_limit; const struct amount_sat max_funding; const struct amount_msat max_payment; + /* Total coins in network */ + const struct amount_sat max_supply; const u32 when_lightning_became_cool; const u8 p2pkh_version; const u8 p2sh_version; diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 9e593faff72e..8cd70683cb93 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1849,11 +1849,12 @@ static void rbf_got_offer(struct subd *dualopend, const u8 *msg) /* No error message known (yet) */ payload->err_msg = NULL; - payload->channel_max = chainparams->max_funding; if (feature_negotiated(dualopend->ld->our_features, channel->peer->their_features, OPT_LARGE_CHANNELS)) - payload->channel_max = AMOUNT_SAT(UINT_MAX); + payload->channel_max = chainparams->max_supply; + else + payload->channel_max = chainparams->max_funding; tal_add_destructor2(dualopend, rbf_channel_remove_dualopend, payload); plugin_hook_call_rbf_channel(dualopend->ld, NULL, payload); @@ -1910,11 +1911,12 @@ static void accepter_got_offer(struct subd *dualopend, payload->feerate_our_max = feerate_max(dualopend->ld, NULL); payload->node_blockheight = get_block_height(dualopend->ld->topology); - payload->channel_max = chainparams->max_funding; if (feature_negotiated(dualopend->ld->our_features, channel->peer->their_features, OPT_LARGE_CHANNELS)) - payload->channel_max = AMOUNT_SAT(UINT64_MAX); + payload->channel_max = chainparams->max_supply; + else + payload->channel_max = chainparams->max_funding; tal_add_destructor2(dualopend, openchannel2_remove_dualopend, payload); plugin_hook_call_openchannel2(dualopend->ld, NULL, payload); From 2506347bee5441e08955ff1323888b1ea842caf1 Mon Sep 17 00:00:00 2001 From: niftynei Date: Sun, 18 Sep 2022 09:56:03 -0500 Subject: [PATCH 02/13] funder: we always pass in channel_max, no need to special case it Parse the channel_max along with everything else. --- plugins/funder.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/plugins/funder.c b/plugins/funder.c index 6575f22022ea..0dbc7fcff928 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -549,7 +549,8 @@ json_openchannel2_call(struct command *cmd, ",to_self_delay:%" ",max_accepted_htlcs:%" ",channel_flags:%" - ",locktime:%}}", + ",locktime:%" + ",channel_max_msat:%}}", JSON_SCAN(json_to_node_id, &info->id), JSON_SCAN(json_to_channel_id, &info->cid), JSON_SCAN(json_to_msat_as_sats, &info->their_funding), @@ -562,7 +563,8 @@ json_openchannel2_call(struct command *cmd, JSON_SCAN(json_to_u32, &to_self_delay), JSON_SCAN(json_to_u32, &max_accepted_htlcs), JSON_SCAN(json_to_u16, &channel_flags), - JSON_SCAN(json_to_u32, &info->locktime)); + JSON_SCAN(json_to_u32, &info->locktime), + JSON_SCAN(json_to_msat_as_sats, &info->channel_max)); if (err) plugin_err(cmd->plugin, @@ -586,13 +588,6 @@ json_openchannel2_call(struct command *cmd, info->lease_blockheight = 0; } - /* If there's no channel_max, it's actually infinity */ - err = json_scan(tmpctx, buf, params, - "{openchannel2:{channel_max_msat:%}}", - JSON_SCAN(json_to_msat_as_sats, &info->channel_max)); - if (err) - info->channel_max = AMOUNT_SAT(UINT64_MAX); - /* We don't fund anything that's above or below our feerate */ if (info->funding_feerate_perkw < feerate_our_min || info->funding_feerate_perkw > feerate_our_max) { @@ -683,14 +678,16 @@ json_rbf_channel_call(struct command *cmd, ",funding_feerate_per_kw:%" ",feerate_our_max:%" ",feerate_our_min:%" - ",locktime:%}}", + ",locktime:%" + ",channel_max_msat:%}}", JSON_SCAN(json_to_node_id, &info->id), JSON_SCAN(json_to_channel_id, &info->cid), JSON_SCAN(json_to_msat_as_sats, &info->their_funding), JSON_SCAN(json_to_u64, &info->funding_feerate_perkw), JSON_SCAN(json_to_u64, &feerate_our_max), JSON_SCAN(json_to_u64, &feerate_our_min), - JSON_SCAN(json_to_u32, &info->locktime)); + JSON_SCAN(json_to_u32, &info->locktime), + JSON_SCAN(json_to_msat_as_sats, &info->channel_max)); if (err) plugin_err(cmd->plugin, @@ -698,13 +695,6 @@ json_rbf_channel_call(struct command *cmd, err, json_tok_full_len(params), json_tok_full(buf, params)); - /* If there's no channel_max, it's actually infinity */ - err = json_scan(tmpctx, buf, params, - "{rbf_channel:{channel_max_msat:%}}", - JSON_SCAN(json_to_msat_as_sats, &info->channel_max)); - if (err) - info->channel_max = AMOUNT_SAT(UINT64_MAX); - /* We don't fund anything that's above or below our feerate */ if (info->funding_feerate_perkw < feerate_our_min || info->funding_feerate_perkw > feerate_our_max) { From dba991ade7365c20149a6652ae2fb09b760cb4da Mon Sep 17 00:00:00 2001 From: niftynei Date: Thu, 22 Sep 2022 14:21:04 -0500 Subject: [PATCH 03/13] df: put requested_lease onto state, so it persists We're gonna need it for rbf requests/re-negotiations --- lightningd/dual_open_control.c | 6 ++--- openingd/dualopend.c | 48 ++++++++++++++++++++++------------ openingd/dualopend_wire.csv | 2 +- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 8cd70683cb93..4c2f55ca9ff1 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -243,7 +243,7 @@ struct openchannel2_payload { * this channel can hold */ struct amount_sat channel_max; /* If they've requested funds, this is their request */ - struct amount_sat requested_lease_amt; + struct amount_sat *requested_lease_amt; u32 lease_blockheight_start; u32 node_blockheight; @@ -287,9 +287,9 @@ static void openchannel2_hook_serialize(struct openchannel2_payload *payload, payload->shutdown_scriptpubkey); json_add_amount_sat_msat(stream, "channel_max_msat", payload->channel_max); - if (!amount_sat_zero(payload->requested_lease_amt)) { + if (payload->requested_lease_amt) { json_add_amount_sat_msat(stream, "requested_lease_msat", - payload->requested_lease_amt); + *payload->requested_lease_amt); json_add_num(stream, "lease_blockheight_start", payload->lease_blockheight_start); json_add_num(stream, "node_blockheight", diff --git a/openingd/dualopend.c b/openingd/dualopend.c index 1c8b9ebec1c3..ef1256070b2b 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -205,6 +205,10 @@ struct state { /* State of inflight funding transaction attempt */ struct tx_state *tx_state; + + /* Amount of leased sats requested, persisted across + * RBF attempts, so we know when we've messed up lol */ + struct amount_sat *requested_lease; }; /* psbt_changeset_get_next - Get next message to send @@ -2034,7 +2038,7 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) struct channel_id cid, full_cid; char *err_reason; u8 *msg; - struct amount_sat total, requested_amt, our_accept; + struct amount_sat total, our_accept; enum dualopend_wire msg_type; struct tx_state *tx_state = state->tx_state; @@ -2070,12 +2074,12 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) /* This is an `option_will_fund` request */ if (open_tlv->request_funds) { - requested_amt - = amount_sat(open_tlv->request_funds->requested_sats); + state->requested_lease = tal(state, struct amount_sat); + state->requested_lease->satoshis /* Raw: u64 -> sat conversion */ + = open_tlv->request_funds->requested_sats; tx_state->blockheight = open_tlv->request_funds->blockheight; - } else - requested_amt = AMOUNT_SAT(0); + } /* BOLT-* #2 * If the peer's revocation basepoint is unknown (e.g. @@ -2141,7 +2145,7 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) state->channel_flags, tx_state->tx_locktime, state->upfront_shutdown_script[REMOTE], - requested_amt, + state->requested_lease, tx_state->blockheight); wire_sync_write(REQ_FD, take(msg)); @@ -2199,9 +2203,10 @@ static void accepter_start(struct state *state, const u8 *oc2_msg) * `open_channel2`.`funding_satoshis`, the lease fee, * and their tx weight * `funding_feerate_perkw` / 1000. */ + assert(state->requested_lease); if (!lease_rates_calc_fee(tx_state->rates, tx_state->accepter_funding, - requested_amt, + *state->requested_lease, tx_state->feerate_per_kw_funding, &tx_state->lease_fee)) negotiation_failed(state, @@ -2664,10 +2669,11 @@ static void opener_start(struct state *state, u8 *msg) struct tlv_accept_tlvs *a_tlv; struct channel_id cid; char *err_reason; - struct amount_sat total, requested_sats; + struct amount_sat total; bool dry_run; struct lease_rates *expected_rates; struct tx_state *tx_state = state->tx_state; + struct amount_sat requested_lease; if (!fromwire_dualopend_opener_init(state, msg, &tx_state->psbt, @@ -2677,7 +2683,7 @@ static void opener_start(struct state *state, u8 *msg) &state->feerate_per_kw_commitment, &tx_state->feerate_per_kw_funding, &state->channel_flags, - &requested_sats, + &requested_lease, &tx_state->blockheight, &dry_run, &expected_rates)) @@ -2687,6 +2693,11 @@ static void opener_start(struct state *state, u8 *msg) tx_state->tx_locktime = tx_state->psbt->tx->locktime; open_tlv = tlv_opening_tlvs_new(tmpctx); + if (!amount_sat_zero(requested_lease)) { + state->requested_lease = tal(state, struct amount_sat); + *state->requested_lease = requested_lease; + } + /* BOLT-* #2 * If the peer's revocation basepoint is unknown (e.g. * `open_channel2`), a temporary `channel_id` should be found @@ -2709,11 +2720,11 @@ static void opener_start(struct state *state, u8 *msg) state->upfront_shutdown_script[LOCAL]; } - if (!amount_sat_zero(requested_sats)) { + if (state->requested_lease) { open_tlv->request_funds = tal(open_tlv, struct tlv_opening_tlvs_request_funds); open_tlv->request_funds->requested_sats = - requested_sats.satoshis; /* Raw: struct -> wire */ + state->requested_lease->satoshis; /* Raw: struct -> wire */ open_tlv->request_funds->blockheight = tx_state->blockheight; } @@ -2822,26 +2833,26 @@ static void opener_start(struct state *state, u8 *msg) /* If we've requested funds and they've failed to provide * to lease us (or give them to us for free?!) then we fail. * This isn't spec'd but it makes the UX predictable */ - if (!amount_sat_zero(requested_sats) - && amount_sat_less(tx_state->accepter_funding, requested_sats)) + if (state->requested_lease + && amount_sat_less(tx_state->accepter_funding, + *state->requested_lease)) negotiation_failed(state, "We requested %s, which is more" " than they've offered to provide" " (%s)", type_to_string(tmpctx, struct amount_sat, - &requested_sats), + state->requested_lease), type_to_string(tmpctx, struct amount_sat, &tx_state->accepter_funding)); - /* BOLT- #2: * The accepting node: ... * - if they decide to accept the offer: * - MUST include a `will_fund` tlv */ - if (!amount_sat_zero(requested_sats) && a_tlv->will_fund) { + if (state->requested_lease && a_tlv->will_fund) { char *err_msg; struct lease_rates *rates = &a_tlv->will_fund->lease_rates; @@ -2884,7 +2895,7 @@ static void opener_start(struct state *state, u8 *msg) * and their tx weight * `funding_feerate_perkw` / 1000. */ if (!lease_rates_calc_fee(rates, tx_state->accepter_funding, - requested_sats, + *state->requested_lease, tx_state->feerate_per_kw_funding, &tx_state->lease_fee)) negotiation_failed(state, @@ -3841,6 +3852,9 @@ int main(int argc, char *argv[]) = state->shutdown_sent[REMOTE] = false; + /* No lease requested at start! */ + state->requested_lease = NULL; + } else if (fromwire_dualopend_reinit(state, msg, &chainparams, &state->our_features, diff --git a/openingd/dualopend_wire.csv b/openingd/dualopend_wire.csv index 45defc1f4ead..d437561e2ba9 100644 --- a/openingd/dualopend_wire.csv +++ b/openingd/dualopend_wire.csv @@ -83,7 +83,7 @@ msgdata,dualopend_got_offer,channel_flags,u8, msgdata,dualopend_got_offer,locktime,u32, msgdata,dualopend_got_offer,shutdown_len,u16, msgdata,dualopend_got_offer,shutdown_scriptpubkey,u8,shutdown_len -msgdata,dualopend_got_offer,requested_amt,amount_sat, +msgdata,dualopend_got_offer,requested_amt,?amount_sat, msgdata,dualopend_got_offer,lease_blockheight_start,u32, # master->dualopend: reply back with our first funding info/contribs From c7cbe3b8d69829639aade2e174d73c2252c86338 Mon Sep 17 00:00:00 2001 From: niftynei Date: Thu, 22 Sep 2022 14:22:01 -0500 Subject: [PATCH 04/13] df: for rbfs, since we know what they asked for, we can abort if they request less than we wanted/accepted FIXME: add a test for this? --- openingd/dualopend.c | 18 ++++++++++++++++++ tests/test_opening.py | 1 + 2 files changed, 19 insertions(+) diff --git a/openingd/dualopend.c b/openingd/dualopend.c index ef1256070b2b..cfece012e782 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -3220,6 +3220,24 @@ static void rbf_local_start(struct state *state, u8 *msg) goto free_rbf_ctx; } + /* If their new amount is less than the lease we asked for, + * abort, abort! */ + if (state->requested_lease + && amount_sat_less(tx_state->accepter_funding, + *state->requested_lease)) { + negotiation_failed(state, + "We requested %s, which is more" + " than they've offered to provide" + " (%s)", + type_to_string(tmpctx, + struct amount_sat, + state->requested_lease), + type_to_string(tmpctx, + struct amount_sat, + &tx_state->accepter_funding)); + goto free_rbf_ctx; + } + /* Now that we know the total of the channel, we can set the reserve */ set_reserve(tx_state, total, state->our_role); diff --git a/tests/test_opening.py b/tests/test_opening.py index ef4c5bdd939b..c1b280694010 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -343,6 +343,7 @@ def test_v2_rbf_single(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') +@pytest.mark.xfail def test_v2_rbf_liquidity_ad(node_factory, bitcoind, chainparams): opts = {'funder-policy': 'match', 'funder-policy-mod': 100, From 0482ad240b4f51328b1ffcb8994abca7e1206c54 Mon Sep 17 00:00:00 2001 From: niftynei Date: Mon, 3 Oct 2022 20:32:24 -0500 Subject: [PATCH 05/13] openchannel2: may re-use rates If more than one plugin calls `openchannel2`, the payload will get re-freshed/re-parsed. --- lightningd/dual_open_control.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 4c2f55ca9ff1..043445f3ed07 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -745,7 +745,9 @@ openchannel2_hook_deserialize(struct openchannel2_payload *payload, struct amount_msat fee_base, fee_max_base; - payload->rates = tal(payload, struct lease_rates); + /* deserialized may be called multiple times */ + if (!payload->rates) + payload->rates = tal(payload, struct lease_rates); err = json_scan(payload, buffer, toks, "{lease_fee_base_msat:%" ",lease_fee_basis:%" @@ -1880,6 +1882,7 @@ static void accepter_got_offer(struct subd *dualopend, payload->accepter_funding = AMOUNT_SAT(0); payload->our_shutdown_scriptpubkey = NULL; payload->peer_id = channel->peer->id; + payload->rates = NULL; payload->err_msg = NULL; if (!fromwire_dualopend_got_offer(payload, msg, From a5ef12f68bb079b098cd392d689333b230d851ed Mon Sep 17 00:00:00 2001 From: niftynei Date: Mon, 3 Oct 2022 21:18:31 -0500 Subject: [PATCH 06/13] df: pass lease data back to funder for rbfs let's let RBFs know about our lease info! --- doc/PLUGINS.md | 5 +++- lightningd/dual_open_control.c | 41 +++++++++++++++++-------- openingd/dualopend.c | 44 ++++++++++++--------------- openingd/dualopend_wire.csv | 8 +++-- plugins/funder.c | 51 ++++++++++++++++++++++---------- plugins/funder_policy.c | 17 +++++++++++ plugins/funder_policy.h | 1 + plugins/test/run-funder_policy.c | 23 ++++++++++++++ 8 files changed, 134 insertions(+), 56 deletions(-) diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index 448b0085f505..d00867a7763e 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -1372,12 +1372,15 @@ requests an RBF for a channel funding transaction. "rbf_channel": { "id": "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f", "channel_id": "252d1b0a1e57895e84137f28cf19ab2c35847e284c112fefdecc7afeaa5c1de7", + "their_last_funding_msat": 100000000, "their_funding_msat": 100000000, + "our_last_funding_msat": 100000000, "funding_feerate_per_kw": 7500, "feerate_our_max": 10000, "feerate_our_min": 253, "channel_max_msat": 16777215000, - "locktime": 2453 + "locktime": 2453, + "requested_lease_msat": 100000000, } } ``` diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 043445f3ed07..68b30a49b92e 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -168,7 +168,9 @@ struct rbf_channel_payload { /* Info specific to this RBF */ struct channel_id channel_id; - struct amount_sat their_funding; + struct amount_sat their_last_funding; + struct amount_sat their_proposed_funding; + struct amount_sat our_last_funding; u32 funding_feerate_per_kw; u32 locktime; @@ -179,6 +181,9 @@ struct rbf_channel_payload { * this channel can hold */ struct amount_sat channel_max; + /* If they've requested funds, this is their request */ + struct amount_sat *requested_lease_amt; + /* Returned from hook */ struct amount_sat our_funding; struct wally_psbt *psbt; @@ -192,9 +197,12 @@ static void rbf_channel_hook_serialize(struct rbf_channel_payload *payload, json_object_start(stream, "rbf_channel"); json_add_node_id(stream, "id", &payload->peer_id); json_add_channel_id(stream, "channel_id", &payload->channel_id); - json_add_amount_sats_deprecated(stream, - "their_funding", "their_funding_msat", - payload->their_funding); + json_add_amount_sat_msat(stream, "their_last_funding_msat", + payload->their_last_funding); + json_add_amount_sat_msat(stream, "their_funding_msat", + payload->their_proposed_funding); + json_add_amount_sat_msat(stream, "our_last_funding_msat", + payload->our_last_funding); json_add_num(stream, "locktime", payload->locktime); json_add_num(stream, "feerate_our_max", payload->feerate_our_max); @@ -204,6 +212,10 @@ static void rbf_channel_hook_serialize(struct rbf_channel_payload *payload, payload->funding_feerate_per_kw); json_add_amount_sat_msat(stream, "channel_max_msat", payload->channel_max); + + if (payload->requested_lease_amt) + json_add_amount_sat_msat(stream, "requested_lease_msat", + *payload->requested_lease_amt); json_object_end(stream); } @@ -1814,11 +1826,14 @@ static void rbf_got_offer(struct subd *dualopend, const u8 *msg) payload->dualopend = dualopend; payload->channel = channel; - if (!fromwire_dualopend_got_rbf_offer(msg, + if (!fromwire_dualopend_got_rbf_offer(payload, msg, &payload->channel_id, - &payload->their_funding, + &payload->their_last_funding, + &payload->their_proposed_funding, + &payload->our_last_funding, &payload->funding_feerate_per_kw, - &payload->locktime)) { + &payload->locktime, + &payload->requested_lease_amt)) { channel_internal_error(channel, "Bad WIRE_DUALOPEND_GOT_RBF_OFFER: %s", tal_hex(msg, msg)); @@ -1844,8 +1859,6 @@ static void rbf_got_offer(struct subd *dualopend, const u8 *msg) payload->feerate_our_max = feerate_max(dualopend->ld, NULL); payload->feerate_our_min = feerate_min(dualopend->ld, NULL); - /* Set our contributions to empty, in case there is no plugin */ - payload->our_funding = AMOUNT_SAT(0); payload->psbt = NULL; /* No error message known (yet) */ @@ -2761,7 +2774,8 @@ static struct command_result *json_openchannel_init(struct command *cmd, *feerate_per_kw, *feerate_per_kw_funding, channel->channel_flags, - *request_amt, + amount_sat_zero(*request_amt) ? + NULL : request_amt, get_block_height(cmd->ld->topology), false, rates); @@ -3248,7 +3262,8 @@ static struct command_result *json_queryrates(struct command *cmd, *feerate_per_kw, *feerate_per_kw_funding, channel->channel_flags, - *request_amt, + amount_sat_zero(*request_amt) ? + NULL : request_amt, get_block_height(cmd->ld->topology), true, NULL); @@ -3498,7 +3513,9 @@ bool peer_restart_dualopend(struct peer *peer, inflight->lease_expiry, inflight->lease_commit_sig, inflight->lease_chan_max_msat, - inflight->lease_chan_max_ppt); + inflight->lease_chan_max_ppt, + /* FIXME: requested lease? */ + NULL); subd_send_msg(channel->owner, take(msg)); return true; diff --git a/openingd/dualopend.c b/openingd/dualopend.c index cfece012e782..bce9ac197f4f 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -2673,7 +2673,7 @@ static void opener_start(struct state *state, u8 *msg) bool dry_run; struct lease_rates *expected_rates; struct tx_state *tx_state = state->tx_state; - struct amount_sat requested_lease; + struct amount_sat *requested_lease; if (!fromwire_dualopend_opener_init(state, msg, &tx_state->psbt, @@ -2693,10 +2693,8 @@ static void opener_start(struct state *state, u8 *msg) tx_state->tx_locktime = tx_state->psbt->tx->locktime; open_tlv = tlv_opening_tlvs_new(tmpctx); - if (!amount_sat_zero(requested_lease)) { - state->requested_lease = tal(state, struct amount_sat); - *state->requested_lease = requested_lease; - } + if (requested_lease) + state->requested_lease = tal_steal(state, requested_lease); /* BOLT-* #2 * If the peer's revocation basepoint is unknown (e.g. @@ -3138,9 +3136,7 @@ static void rbf_local_start(struct state *state, u8 *msg) tx_state->remoteconf = state->tx_state->remoteconf; if (!fromwire_dualopend_rbf_init(tx_state, msg, - state->our_role == TX_INITIATOR ? - &tx_state->opener_funding : - &tx_state->accepter_funding, + &tx_state->opener_funding, &tx_state->feerate_per_kw_funding, &tx_state->psbt)) master_badmsg(WIRE_DUALOPEND_RBF_INIT, msg); @@ -3166,9 +3162,7 @@ static void rbf_local_start(struct state *state, u8 *msg) tx_state->tx_locktime = tx_state->psbt->tx->locktime; msg = towire_init_rbf(tmpctx, &state->channel_id, - state->our_role == TX_INITIATOR ? - tx_state->opener_funding : - tx_state->accepter_funding, + tx_state->opener_funding, tx_state->tx_locktime, tx_state->feerate_per_kw_funding); @@ -3182,9 +3176,7 @@ static void rbf_local_start(struct state *state, u8 *msg) } if (!fromwire_ack_rbf(msg, &cid, - state->our_role == TX_INITIATOR ? - &tx_state->accepter_funding : - &tx_state->opener_funding)) + &tx_state->accepter_funding)) open_err_fatal(state, "Parsing ack_rbf %s", tal_hex(tmpctx, msg)); @@ -3275,9 +3267,7 @@ static void rbf_remote_start(struct state *state, const u8 *rbf_msg) tx_state = new_tx_state(rbf_ctx); if (!fromwire_init_rbf(rbf_msg, &cid, - state->our_role == TX_INITIATOR ? - &tx_state->accepter_funding : - &tx_state->opener_funding, + &tx_state->opener_funding, &tx_state->tx_locktime, &tx_state->feerate_per_kw_funding)) open_err_fatal(state, "Parsing init_rbf %s", @@ -3298,8 +3288,6 @@ static void rbf_remote_start(struct state *state, const u8 *rbf_msg) "Last funding attempt not complete:" " missing your funding tx_sigs"); - /* FIXME: should we check for currently in progress? */ - /* Copy over the channel config info -- everything except * the reserve will be the same */ tx_state->localconf = state->tx_state->localconf; @@ -3317,11 +3305,12 @@ static void rbf_remote_start(struct state *state, const u8 *rbf_msg) /* We ask master if this is ok */ msg = towire_dualopend_got_rbf_offer(NULL, &state->channel_id, - state->our_role == TX_INITIATOR ? - tx_state->accepter_funding : - tx_state->opener_funding, + state->tx_state->opener_funding, + tx_state->opener_funding, + state->tx_state->accepter_funding, tx_state->feerate_per_kw_funding, - tx_state->tx_locktime); + tx_state->tx_locktime, + state->requested_lease); wire_sync_write(REQ_FD, take(msg)); msg = wire_sync_read(tmpctx, REQ_FD); @@ -3826,7 +3815,7 @@ int main(int argc, char *argv[]) struct fee_states *fee_states; enum side opener; u8 *msg; - struct amount_sat total_funding; + struct amount_sat total_funding, *requested_lease; struct amount_msat our_msat; const struct channel_type *type; @@ -3908,13 +3897,18 @@ int main(int argc, char *argv[]) &state->tx_state->lease_expiry, &state->tx_state->lease_commit_sig, &state->tx_state->lease_chan_max_msat, - &state->tx_state->lease_chan_max_ppt)) { + &state->tx_state->lease_chan_max_ppt, + &requested_lease)) { /*~ We only reconnect on channels that the * saved the the database (exchanged commitment sigs) */ type = default_channel_type(NULL, state->our_features, state->their_features); + + if (requested_lease) + state->requested_lease = tal_steal(state, requested_lease); + state->channel = new_initial_channel(state, &state->channel_id, &state->tx_state->funding, diff --git a/openingd/dualopend_wire.csv b/openingd/dualopend_wire.csv index d437561e2ba9..17ffe5fef75a 100644 --- a/openingd/dualopend_wire.csv +++ b/openingd/dualopend_wire.csv @@ -67,6 +67,7 @@ msgdata,dualopend_reinit,lease_expiry,u32, msgdata,dualopend_reinit,lease_commit_sig,?secp256k1_ecdsa_signature, msgdata,dualopend_reinit,lease_chan_max_msat,u32, msgdata,dualopend_reinit,lease_chan_max_ppt,u16, +msgdata,dualopend_reinit,requested_lease,?amount_sat, # dualopend->master: they offered channel, should we continue? msgtype,dualopend_got_offer,7005 @@ -99,9 +100,12 @@ msgdata,dualopend_got_offer_reply,lease_rates,?lease_rates, # dualopend->master: they offered a RBF, should we continue? msgtype,dualopend_got_rbf_offer,7500 msgdata,dualopend_got_rbf_offer,channel_id,channel_id, -msgdata,dualopend_got_rbf_offer,their_funding,amount_sat, +msgdata,dualopend_got_rbf_offer,their_last_funding,amount_sat, +msgdata,dualopend_got_rbf_offer,their_curr_funding,amount_sat, +msgdata,dualopend_got_rbf_offer,our_last_funding,amount_sat, msgdata,dualopend_got_rbf_offer,funding_feerate_per_kw,u32, msgdata,dualopend_got_rbf_offer,locktime,u32, +msgdata,dualopend_got_rbf_offer,requested_lease,?amount_sat, # master->dualopend: reply back with our funding info/contribs msgtype,dualopend_got_rbf_offer_reply,7505 @@ -176,7 +180,7 @@ msgdata,dualopend_opener_init,local_shutdown_wallet_index,?u32, msgdata,dualopend_opener_init,feerate_per_kw,u32, 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,requested_sats,?amount_sat, msgdata,dualopend_opener_init,blockheight,u32, msgdata,dualopend_opener_init,dry_run,bool, # must go last because embedded tu32 diff --git a/plugins/funder.c b/plugins/funder.c index 0dbc7fcff928..d60169cab464 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -272,11 +272,17 @@ struct open_info { struct node_id id; struct amount_sat our_funding; struct amount_sat their_funding; + + /* If this is an RBF, we'll have this */ + struct amount_sat *their_last_funding; + struct amount_sat *our_last_funding; + struct amount_sat channel_max; u64 funding_feerate_perkw; u32 locktime; u32 lease_blockheight; u32 node_blockheight; + struct amount_sat requested_lease; }; @@ -284,6 +290,8 @@ static struct open_info *new_open_info(const tal_t *ctx) { struct open_info *info = tal(ctx, struct open_info); + info->their_last_funding = NULL; + info->our_last_funding = NULL; info->requested_lease = AMOUNT_SAT(0); info->lease_blockheight = 0; info->node_blockheight = 0; @@ -456,6 +464,7 @@ listfunds_success(struct command *cmd, funding_err = calculate_our_funding(current_policy, info->id, info->their_funding, + info->our_last_funding, available_funds, info->channel_max, info->requested_lease, @@ -572,21 +581,15 @@ json_openchannel2_call(struct command *cmd, err, json_tok_full_len(params), json_tok_full(buf, params)); - err = json_scan(tmpctx, buf, params, - "{openchannel2:{" - "requested_lease_msat:%" - ",lease_blockheight_start:%" - ",node_blockheight:%}}", - JSON_SCAN(json_to_msat_as_sats, &info->requested_lease), - JSON_SCAN(json_to_u32, &info->node_blockheight), - JSON_SCAN(json_to_u32, &info->lease_blockheight)); - - /* These aren't necessarily included */ - if (err) { - info->requested_lease = AMOUNT_SAT(0); - info->node_blockheight = 0; - info->lease_blockheight = 0; - } + /* Channel lease info isn't necessarily included, ignore any err */ + json_scan(tmpctx, buf, params, + "{openchannel2:{" + "requested_lease_msat:%" + ",lease_blockheight_start:%" + ",node_blockheight:%}}", + JSON_SCAN(json_to_msat_as_sats, &info->requested_lease), + JSON_SCAN(json_to_u32, &info->lease_blockheight), + JSON_SCAN(json_to_u32, &info->node_blockheight)); /* We don't fund anything that's above or below our feerate */ if (info->funding_feerate_perkw < feerate_our_min @@ -670,11 +673,15 @@ json_rbf_channel_call(struct command *cmd, const char *err; struct out_req *req; + info->their_last_funding = tal(info, struct amount_sat); + info->our_last_funding = tal(info, struct amount_sat); err = json_scan(tmpctx, buf, params, "{rbf_channel:" "{id:%" ",channel_id:%" + ",their_last_funding_msat:%" ",their_funding_msat:%" + ",our_last_funding_msat:%" ",funding_feerate_per_kw:%" ",feerate_our_max:%" ",feerate_our_min:%" @@ -682,7 +689,12 @@ json_rbf_channel_call(struct command *cmd, ",channel_max_msat:%}}", JSON_SCAN(json_to_node_id, &info->id), JSON_SCAN(json_to_channel_id, &info->cid), - JSON_SCAN(json_to_msat_as_sats, &info->their_funding), + JSON_SCAN(json_to_msat_as_sats, + info->their_last_funding), + JSON_SCAN(json_to_msat_as_sats, + &info->their_funding), + JSON_SCAN(json_to_msat_as_sats, + info->our_last_funding), JSON_SCAN(json_to_u64, &info->funding_feerate_perkw), JSON_SCAN(json_to_u64, &feerate_our_max), JSON_SCAN(json_to_u64, &feerate_our_min), @@ -695,6 +707,13 @@ json_rbf_channel_call(struct command *cmd, err, json_tok_full_len(params), json_tok_full(buf, params)); + /* Lease info isn't necessarily included, ignore any err */ + /* FIXME: blockheights?? */ + json_scan(tmpctx, buf, params, + "{rbf_channel:{" + "requested_lease_msat:%}}", + JSON_SCAN(json_to_msat_as_sats, &info->requested_lease)); + /* We don't fund anything that's above or below our feerate */ if (info->funding_feerate_perkw < feerate_our_min || info->funding_feerate_perkw > feerate_our_max) { diff --git a/plugins/funder_policy.c b/plugins/funder_policy.c index 5584cfd58cd0..59f1744c25f7 100644 --- a/plugins/funder_policy.c +++ b/plugins/funder_policy.c @@ -208,6 +208,7 @@ const char * calculate_our_funding(struct funder_policy *policy, struct node_id id, struct amount_sat their_funding, + struct amount_sat *our_last_funding, struct amount_sat available_funds, struct amount_sat channel_max, struct amount_sat requested_lease, @@ -309,6 +310,22 @@ calculate_our_funding(struct funder_policy *policy, if (amount_sat_greater(*our_funding, net_available_funds)) *our_funding = net_available_funds; + /* Are we putting in less than last time + it's a lease? + * Return an error as a convenience to the buyer */ + if (our_last_funding && !amount_sat_zero(requested_lease)) { + if (amount_sat_less(*our_funding, *our_last_funding) + && amount_sat_less(*our_funding, requested_lease)) { + return tal_fmt(tmpctx, "New amount (%s) is less than" + " last (%s); peer requested a lease (%s)", + type_to_string(tmpctx, struct amount_sat, + our_funding), + type_to_string(tmpctx, struct amount_sat, + our_last_funding), + type_to_string(tmpctx, struct amount_sat, + &requested_lease)); + } + } + /* Is our_funding less than our per-channel minimum? * if so, don't fund */ if (amount_sat_less(*our_funding, policy->per_channel_min)) { diff --git a/plugins/funder_policy.h b/plugins/funder_policy.h index 8b38ec51c1a8..c89da3d25800 100644 --- a/plugins/funder_policy.h +++ b/plugins/funder_policy.h @@ -79,6 +79,7 @@ const char * calculate_our_funding(struct funder_policy *policy, struct node_id id, struct amount_sat their_funding, + struct amount_sat *our_last_funding, struct amount_sat available_funds, struct amount_sat channel_max, struct amount_sat lease_request, diff --git a/plugins/test/run-funder_policy.c b/plugins/test/run-funder_policy.c index a91a5867615e..a9a96eaf14d8 100644 --- a/plugins/test/run-funder_policy.c +++ b/plugins/test/run-funder_policy.c @@ -29,6 +29,7 @@ void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) struct test_case { struct amount_sat their_funds; struct amount_sat available_funds; + struct amount_sat *our_last_funds; struct amount_sat channel_max; struct amount_sat lease_request; @@ -43,6 +44,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(100000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -65,6 +67,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(500), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -87,6 +90,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(6000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -109,6 +113,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(2500), .available_funds = AMOUNT_SAT(6000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -131,6 +136,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(2500), .available_funds = AMOUNT_SAT(5000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -153,6 +159,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(2500), .available_funds = AMOUNT_SAT(3000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -175,6 +182,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(2500), .available_funds = AMOUNT_SAT(5000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -197,6 +205,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(5000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .policy = { .opt = FIXED, @@ -220,6 +229,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(5000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(5500), .lease_request = AMOUNT_SAT(0), .policy = { @@ -242,6 +252,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(500), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(10000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -264,6 +275,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(1000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(10000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -286,6 +298,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5001), .available_funds = AMOUNT_SAT(5000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(10000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -308,6 +321,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(1000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(10000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -330,6 +344,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(999), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(10000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -352,6 +367,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(5000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(5000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -374,6 +390,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(5000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -396,6 +413,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(100000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(980), .policy = { @@ -418,6 +436,7 @@ struct test_case cases[] = { { .their_funds = AMOUNT_SAT(5000), .available_funds = AMOUNT_SAT(100000), + .our_last_funds = NULL, .channel_max = AMOUNT_SAT(11000), .lease_request = AMOUNT_SAT(0), .policy = { @@ -451,6 +470,7 @@ static void check_fuzzing(struct test_case fuzzcase) for (size_t i = 0; i < 100; i++) { calculate_our_funding(&fuzzcase.policy, id, fuzzcase.their_funds, + fuzzcase.our_last_funds, fuzzcase.available_funds, fuzzcase.channel_max, fuzzcase.lease_request, @@ -484,6 +504,7 @@ int main(int argc, const char *argv[]) err = calculate_our_funding(policy, id, AMOUNT_SAT(50000), + NULL, AMOUNT_SAT(50000), AMOUNT_SAT(100000), AMOUNT_SAT(100000), @@ -494,6 +515,7 @@ int main(int argc, const char *argv[]) for (i = 0; i < ARRAY_SIZE(cases); i++) { err = calculate_our_funding(&cases[i].policy, id, cases[i].their_funds, + cases[i].our_last_funds, cases[i].available_funds, cases[i].channel_max, cases[i].lease_request, @@ -528,6 +550,7 @@ int main(int argc, const char *argv[]) for (i = 0; i < 100 * flips; i++) { calculate_our_funding(&flipcase.policy, id, flipcase.their_funds, + flipcase.our_last_funds, flipcase.available_funds, flipcase.channel_max, flipcase.lease_request, From c6470faae3a3984594ee816f76e6f08e2c56cbd5 Mon Sep 17 00:00:00 2001 From: niftynei Date: Tue, 4 Oct 2022 14:51:52 -0500 Subject: [PATCH 07/13] funder: rm quote that makes nifty cringe every time she sees it --- plugins/funder.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/funder.c b/plugins/funder.c index d60169cab464..abf6ccebd2df 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -2,11 +2,6 @@ * your policy for accepting/dual-funding incoming * v2 channel-open requests. * - * "They say marriages are made in Heaven. - * But so is funder and lightning." - * - Clint Eastwood - * (because funder rhymes with thunder) - * */ #include "config.h" #include From 3d8466ed2ee9f27fbc26268277ccef0e1b990538 Mon Sep 17 00:00:00 2001 From: niftynei Date: Wed, 5 Oct 2022 13:09:19 -0500 Subject: [PATCH 08/13] funder: save utxos of signed txs to datastore We're going to need to know what utxos we used if we RBF this channel; so we serialize our inputs and save them to the datastore under the channel_id. --- plugins/funder.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/plugins/funder.c b/plugins/funder.c index abf6ccebd2df..45e2be2f7800 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -128,6 +128,83 @@ command_hook_cont_psbt(struct command *cmd, struct wally_psbt *psbt) return command_finished(cmd, response); } +static struct command_result * +datastore_add_fail(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct wally_psbt *signed_psbt) +{ + /* Oops, something's broken */ + plugin_log(cmd->plugin, LOG_BROKEN, + "`datastore` add failed: %*.s", + json_tok_full_len(error), + json_tok_full(buf, error)); + + return command_hook_cont_psbt(cmd, signed_psbt); +} + +static struct command_result * +datastore_add_success(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct wally_psbt *signed_psbt) +{ + const char *key, *err; + + err = json_scan(tmpctx, buf, result, + "{key:%}", + JSON_SCAN_TAL(cmd, json_strdup, &key)); + + if (err) + plugin_err(cmd->plugin, + "`datastore` payload did not scan. %s: %*.s", + err, json_tok_full_len(result), + json_tok_full(buf, result)); + + /* We saved the infos! */ + plugin_log(cmd->plugin, LOG_DBG, + "Saved utxos for channel (%s) to datastore", + key); + + return command_hook_cont_psbt(cmd, signed_psbt); +} + +static struct command_result * +remember_channel_utxos(struct command *cmd, + struct pending_open *open, + struct wally_psbt *signed_psbt) +{ + struct out_req *req; + u8 *utxos_bin; + char *chan_key = tal_fmt(cmd, "funder/%s", + type_to_string(cmd, struct channel_id, + &open->channel_id)); + + req = jsonrpc_request_start(cmd->plugin, cmd, + "datastore", + &datastore_add_success, + &datastore_add_fail, + signed_psbt); + + utxos_bin = tal_arr(cmd, u8, 0); + for (size_t i = 0; i < signed_psbt->tx->num_inputs; i++) { + struct bitcoin_outpoint outpoint; + + /* Don't save peer's UTXOS */ + if (!psbt_input_is_ours(&signed_psbt->inputs[i])) + continue; + + wally_tx_input_get_outpoint(&signed_psbt->tx->inputs[i], + &outpoint); + towire_bitcoin_outpoint(&utxos_bin, &outpoint); + } + json_add_string(req->js, "key", chan_key); + /* We either update the existing or add a new one, nbd */ + json_add_string(req->js, "mode", "create-or-replace"); + json_add_hex(req->js, "hex", utxos_bin, tal_bytelen(utxos_bin)); + return send_outreq(cmd->plugin, req); +} + static struct command_result * signpsbt_done(struct command *cmd, const char *buf, @@ -135,6 +212,7 @@ signpsbt_done(struct command *cmd, struct pending_open *open) { struct wally_psbt *signed_psbt; + struct command_result *res; const char *err; plugin_log(cmd->plugin, LOG_DBG, @@ -151,11 +229,15 @@ signpsbt_done(struct command *cmd, err, json_tok_full_len(result), json_tok_full(buf, result)); - /* This finishes the open (successfully!) */ + /* Save the list of utxos to the datastore! We'll need + * them again if we rbf */ + res = remember_channel_utxos(cmd, open, signed_psbt); + + /* The in-flight open is done, let's clean it up! */ list_del_from(&pending_opens, &open->list); tal_free(open); - return command_hook_cont_psbt(cmd, signed_psbt); + return res; } static struct command_result * From 0fbf1b5616af98cb044f891c6026c25f343662d2 Mon Sep 17 00:00:00 2001 From: niftynei Date: Wed, 5 Oct 2022 14:23:42 -0500 Subject: [PATCH 09/13] funder: pull out previous input list from datastore on RBF It'd be nice to know which utxos we used previously, so we can rebuild a transaction using them! --- plugins/funder.c | 116 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/plugins/funder.c b/plugins/funder.c index 45e2be2f7800..ff9ca01ccd53 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -361,6 +361,9 @@ struct open_info { u32 node_blockheight; struct amount_sat requested_lease; + + /* List of previously-used utxos */ + struct bitcoin_outpoint **prev_outs; }; static struct open_info *new_open_info(const tal_t *ctx) @@ -372,6 +375,7 @@ static struct open_info *new_open_info(const tal_t *ctx) info->requested_lease = AMOUNT_SAT(0); info->lease_blockheight = 0; info->node_blockheight = 0; + info->prev_outs = NULL; return info; } @@ -612,7 +616,7 @@ json_openchannel2_call(struct command *cmd, const char *buf, const jsmntok_t *params) { - struct open_info *info = tal(cmd, struct open_info); + struct open_info *info = new_open_info(cmd); struct amount_msat max_htlc_inflight, htlc_minimum; u64 commitment_feerate_perkw, feerate_our_max, feerate_our_min; @@ -739,6 +743,99 @@ json_openchannel2_call(struct command *cmd, return send_outreq(cmd->plugin, req); } +static struct command_result * +datastore_list_fail(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct open_info *info) +{ + struct out_req *req; + + /* Oops, something's broken */ + plugin_log(cmd->plugin, LOG_BROKEN, + "`datastore` list failed: %*.s", + json_tok_full_len(error), + json_tok_full(buf, error)); + + /* Figure out what our funds are... same flow + * as with openchannel2 callback. */ + req = jsonrpc_request_start(cmd->plugin, cmd, + "listfunds", + &listfunds_success, + &listfunds_failed, + info); + return send_outreq(cmd->plugin, req); +} + +static struct command_result * +datastore_list_success(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct open_info *info) +{ + struct out_req *req; + const char *key, *err; + const u8 *utxos_bin; + size_t len, i; + const jsmntok_t *ds_arr_tok, *ds_result; + + ds_arr_tok = json_get_member(buf, result, "datastore"); + assert(ds_arr_tok->type == JSMN_ARRAY); + + /* There should only be one result */ + utxos_bin = NULL; + json_for_each_arr(i, ds_result, ds_arr_tok) { + err = json_scan(tmpctx, buf, ds_result, + "{key:%,hex:%}", + JSON_SCAN_TAL(cmd, json_strdup, &key), + JSON_SCAN_TAL(cmd, json_tok_bin_from_hex, + &utxos_bin)); + + if (err) + plugin_err(cmd->plugin, + "`listdatastore` payload did" + " not scan. %s: %*.s", + err, json_tok_full_len(result), + json_tok_full(buf, result)); + + /* We found the prev utxo list */ + plugin_log(cmd->plugin, LOG_DBG, + "Saved utxos for channel (%s)" + " pulled from datastore", key); + + /* There should only be one result */ + break; + } + + /* Resurrect outpoints from stashed binary */ + len = tal_bytelen(utxos_bin); + while (len > 0) { + struct bitcoin_outpoint *outpoint = + tal(info, struct bitcoin_outpoint); + fromwire_bitcoin_outpoint(&utxos_bin, + &len, outpoint); + /* Cursor gets set to null if above fails */ + if (!utxos_bin) + plugin_err(cmd->plugin, + "Unable to parse saved utxos: %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + + if (!info->prev_outs) + info->prev_outs = + tal_arr(info, struct bitcoin_outpoint *, 0); + + tal_arr_expand(&info->prev_outs, outpoint); + } + + req = jsonrpc_request_start(cmd->plugin, cmd, + "listfunds", + &listfunds_success, + &listfunds_failed, + info); + return send_outreq(cmd->plugin, req); +} + /* Peer has asked us to RBF */ static struct command_result * json_rbf_channel_call(struct command *cmd, @@ -747,7 +844,7 @@ json_rbf_channel_call(struct command *cmd, { struct open_info *info = new_open_info(cmd); u64 feerate_our_max, feerate_our_min; - const char *err; + const char *err, *chan_key; struct out_req *req; info->their_last_funding = tal(info, struct amount_sat); @@ -805,15 +902,16 @@ json_rbf_channel_call(struct command *cmd, return command_hook_success(cmd); } - /* Figure out what our funds are... same flow - * as with openchannel2 callback. We assume that THEY - * will use the same inputs, so we use whatever we want here */ + /* Fetch out previous utxos from the datastore */ req = jsonrpc_request_start(cmd->plugin, cmd, - "listfunds", - &listfunds_success, - &listfunds_failed, + "listdatastore", + &datastore_list_success, + &datastore_list_fail, info); - + chan_key = tal_fmt(cmd, "funder/%s", + type_to_string(cmd, struct channel_id, + &info->cid)); + json_add_string(req->js, "key", chan_key); return send_outreq(cmd->plugin, req); } From edc907efa11df5470285a53e46478d5728f27bb1 Mon Sep 17 00:00:00 2001 From: niftynei Date: Wed, 5 Oct 2022 15:21:07 -0500 Subject: [PATCH 10/13] funder: use previous outputs in count towards available funding Still need to use them to build the PSBT for the rbf however. --- plugins/funder.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/plugins/funder.c b/plugins/funder.c index ff9ca01ccd53..5be686186700 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -475,6 +475,17 @@ static struct command_result *param_msat_as_sat(struct command *cmd, "should be a millisatoshi amount"); } +static bool previously_reserved(struct bitcoin_outpoint **prev_outs, + struct bitcoin_outpoint *out) +{ + for (size_t i = 0; i < tal_count(prev_outs); i++) { + if (bitcoin_outpoint_eq(prev_outs[i], out)) + return true; + } + + return false; +} + static struct command_result * listfunds_success(struct command *cmd, const char *buf, @@ -497,6 +508,7 @@ listfunds_success(struct command *cmd, available_funds = AMOUNT_SAT(0); json_for_each_arr(i, tok, outputs_tok) { struct amount_sat val; + struct bitcoin_outpoint out; bool is_reserved, is_p2sh; char *status; const char *err; @@ -504,10 +516,14 @@ listfunds_success(struct command *cmd, err = json_scan(tmpctx, buf, tok, "{amount_msat:%" ",status:%" - ",reserved:%}", + ",reserved:%" + ",txid:%" + ",output:%}", JSON_SCAN(json_to_msat_as_sats, &val), JSON_SCAN_TAL(cmd, json_strdup, &status), - JSON_SCAN(json_to_bool, &is_reserved)); + JSON_SCAN(json_to_bool, &is_reserved), + JSON_SCAN(json_to_txid, &out.txid), + JSON_SCAN(json_to_number, &out.n)); if (err) plugin_err(cmd->plugin, "`listfunds` payload did not scan. %s: %*.s", @@ -524,8 +540,9 @@ listfunds_success(struct command *cmd, est_fee = amount_tx_fee(info->funding_feerate_perkw, bitcoin_tx_input_weight(is_p2sh, 110)); - /* we skip reserved funds */ - if (is_reserved) + /* we skip reserved funds that aren't in our previous + * inputs list! */ + if (is_reserved && !previously_reserved(info->prev_outs, &out)) continue; /* we skip unconfirmed+spent funds */ From 6e0711396720604a0810c9d63d36849f369efd6d Mon Sep 17 00:00:00 2001 From: niftynei Date: Wed, 5 Oct 2022 16:02:52 -0500 Subject: [PATCH 11/13] funder: use utxopsbt to build psbt for RBFs We use the saved previous outputs (plus maybe some new ones?) to build a psbt for an RBF request. RBFs utxo reuse is now working so we can unfail the test (and update it to reflect that the lease sticks around through an RBF cycle). Changelog-Fixed: Plugins: `funder` now honors lease requests across RBFs --- plugins/funder.c | 101 ++++++++++++++++++++++++++++++++++++------ tests/test_opening.py | 5 +-- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/plugins/funder.c b/plugins/funder.c index 5be686186700..69d0bfd87d73 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -486,18 +486,66 @@ static bool previously_reserved(struct bitcoin_outpoint **prev_outs, return false; } +struct funder_utxo { + struct bitcoin_outpoint out; + struct amount_sat val; +}; + +static struct out_req * +build_utxopsbt_request(struct command *cmd, + struct open_info *info, + struct amount_sat requested_funds, + struct amount_sat committed_funds, + struct funder_utxo **avail_utxos) +{ + struct out_req *req; + + req = jsonrpc_request_start(cmd->plugin, cmd, + "utxopsbt", + &psbt_funded, + &psbt_fund_failed, + info); + /* Add every prev_out */ + json_array_start(req->js, "utxos"); + for (size_t i = 0; i < tal_count(info->prev_outs); i++) + json_add_outpoint(req->js, NULL, info->prev_outs[i]); + + /* Next add available utxos until we surpass the + * requested funds goal */ + /* FIXME: Update `utxopsbt` to automatically add more inputs? */ + for (size_t i = 0; i < tal_count(avail_utxos); i++) { + /* If we've already hit our goal, break */ + if (amount_sat_greater_eq(committed_funds, requested_funds)) + break; + + /* Add this output to the UTXO */ + json_add_outpoint(req->js, NULL, &avail_utxos[i]->out); + + /* Account for it */ + if (!amount_sat_add(&committed_funds, committed_funds, + avail_utxos[i]->val)) + /* This should really never happen */ + plugin_err(cmd->plugin, "overflow adding committed"); + } + json_array_end(req->js); + return req; +} + static struct command_result * listfunds_success(struct command *cmd, const char *buf, const jsmntok_t *result, struct open_info *info) { - struct amount_sat available_funds, est_fee; + struct amount_sat available_funds, committed_funds, est_fee; const jsmntok_t *outputs_tok, *tok; struct out_req *req; size_t i; const char *funding_err; + /* We only use this for RBFs, when there's a prev_outs list */ + struct funder_utxo **avail_utxos = tal_arr(cmd, struct funder_utxo *, 0); + outputs_tok = json_get_member(buf, result, "outputs"); if (!outputs_tok) plugin_err(cmd->plugin, @@ -506,24 +554,25 @@ listfunds_success(struct command *cmd, json_tok_full(buf, result)); available_funds = AMOUNT_SAT(0); + committed_funds = AMOUNT_SAT(0); json_for_each_arr(i, tok, outputs_tok) { - struct amount_sat val; - struct bitcoin_outpoint out; + struct funder_utxo *utxo; bool is_reserved, is_p2sh; char *status; const char *err; + utxo = tal(cmd, struct funder_utxo); err = json_scan(tmpctx, buf, tok, "{amount_msat:%" ",status:%" ",reserved:%" ",txid:%" ",output:%}", - JSON_SCAN(json_to_msat_as_sats, &val), + JSON_SCAN(json_to_msat_as_sats, &utxo->val), JSON_SCAN_TAL(cmd, json_strdup, &status), JSON_SCAN(json_to_bool, &is_reserved), - JSON_SCAN(json_to_txid, &out.txid), - JSON_SCAN(json_to_number, &out.n)); + JSON_SCAN(json_to_txid, &utxo->out.txid), + JSON_SCAN(json_to_number, &utxo->out.n)); if (err) plugin_err(cmd->plugin, "`listfunds` payload did not scan. %s: %*.s", @@ -542,7 +591,8 @@ listfunds_success(struct command *cmd, /* we skip reserved funds that aren't in our previous * inputs list! */ - if (is_reserved && !previously_reserved(info->prev_outs, &out)) + if (is_reserved && + !previously_reserved(info->prev_outs, &utxo->out)) continue; /* we skip unconfirmed+spent funds */ @@ -551,12 +601,25 @@ listfunds_success(struct command *cmd, /* Don't include outputs that can't cover their weight; * subtract the fee for this utxo out of the utxo */ - if (!amount_sat_sub(&val, val, est_fee)) + if (!amount_sat_sub(&utxo->val, utxo->val, est_fee)) continue; - if (!amount_sat_add(&available_funds, available_funds, val)) + if (!amount_sat_add(&available_funds, available_funds, + utxo->val)) plugin_err(cmd->plugin, "`listfunds` overflowed output values"); + + /* If this is an RBF, we keep track of available utxos */ + if (info->prev_outs) { + /* if not previously reserved, it's committed */ + if (!previously_reserved(info->prev_outs, &utxo->out)) + tal_arr_expand(&avail_utxos, utxo); + else if (!amount_sat_add(&committed_funds, + committed_funds, utxo->val)) + plugin_err(cmd->plugin, + "`listfunds` overflowed" + " committed output values"); + } } funding_err = calculate_our_funding(current_policy, @@ -585,11 +648,20 @@ listfunds_success(struct command *cmd, type_to_string(tmpctx, struct amount_sat, &info->their_funding)); - req = jsonrpc_request_start(cmd->plugin, cmd, - "fundpsbt", - &psbt_funded, - &psbt_fund_failed, - info); + /* If there's prevouts, we compose a psbt with those first, + * then add more funds for anything missing */ + if (info->prev_outs) { + req = build_utxopsbt_request(cmd, info, + info->our_funding, + committed_funds, + avail_utxos); + json_add_bool(req->js, "reservedok", true); + } else + req = jsonrpc_request_start(cmd->plugin, cmd, + "fundpsbt", + &psbt_funded, + &psbt_fund_failed, + info); json_add_string(req->js, "satoshi", type_to_string(tmpctx, struct amount_sat, &info->our_funding)); @@ -597,6 +669,7 @@ listfunds_success(struct command *cmd, tal_fmt(tmpctx, "%"PRIu64"%s", info->funding_feerate_perkw, feerate_style_name(FEERATE_PER_KSIPA))); + /* Our startweight is zero because we're freeriding on their open * transaction ! */ json_add_num(req->js, "startweight", 0); diff --git a/tests/test_opening.py b/tests/test_opening.py index c1b280694010..bdf4caae181a 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -343,7 +343,6 @@ def test_v2_rbf_single(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') -@pytest.mark.xfail def test_v2_rbf_liquidity_ad(node_factory, bitcoind, chainparams): opts = {'funder-policy': 'match', 'funder-policy-mod': 100, @@ -409,8 +408,8 @@ def test_v2_rbf_liquidity_ad(node_factory, bitcoind, chainparams): # This should be the accepter's amount fundings = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['funding'] - # FIXME: The lease goes away :( - assert Millisatoshi(0) == Millisatoshi(fundings['remote_funds_msat']) + # The lease is still there! + assert Millisatoshi(amount * 1000) == fundings['remote_funds_msat'] wait_for(lambda: [c['active'] for c in l1.rpc.listchannels(l1.get_channel_scid(l2))['channels']] == [True, True]) From 2adb06ce18e686566a7645bb6ad59bd4e45cc325 Mon Sep 17 00:00:00 2001 From: niftynei Date: Fri, 7 Oct 2022 14:49:58 -0500 Subject: [PATCH 12/13] funder: filter prev-outs such that we only use still unspent ones If for some reason a utxo we used previously is no longer 'unspent', we shouldn't use it for the next transaction. --- plugins/funder.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/plugins/funder.c b/plugins/funder.c index 69d0bfd87d73..5691b2d4293b 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -475,15 +475,16 @@ static struct command_result *param_msat_as_sat(struct command *cmd, "should be a millisatoshi amount"); } -static bool previously_reserved(struct bitcoin_outpoint **prev_outs, - struct bitcoin_outpoint *out) +static struct bitcoin_outpoint * +previously_reserved(struct bitcoin_outpoint **prev_outs, + struct bitcoin_outpoint *out) { for (size_t i = 0; i < tal_count(prev_outs); i++) { if (bitcoin_outpoint_eq(prev_outs[i], out)) - return true; + return prev_outs[i]; } - return false; + return NULL; } struct funder_utxo { @@ -494,6 +495,7 @@ struct funder_utxo { static struct out_req * build_utxopsbt_request(struct command *cmd, struct open_info *info, + struct bitcoin_outpoint **prev_outs, struct amount_sat requested_funds, struct amount_sat committed_funds, struct funder_utxo **avail_utxos) @@ -507,8 +509,8 @@ build_utxopsbt_request(struct command *cmd, info); /* Add every prev_out */ json_array_start(req->js, "utxos"); - for (size_t i = 0; i < tal_count(info->prev_outs); i++) - json_add_outpoint(req->js, NULL, info->prev_outs[i]); + for (size_t i = 0; i < tal_count(prev_outs); i++) + json_add_outpoint(req->js, NULL, prev_outs[i]); /* Next add available utxos until we surpass the * requested funds goal */ @@ -540,6 +542,7 @@ listfunds_success(struct command *cmd, struct amount_sat available_funds, committed_funds, est_fee; const jsmntok_t *outputs_tok, *tok; struct out_req *req; + struct bitcoin_outpoint **avail_prev_outs; size_t i; const char *funding_err; @@ -555,9 +558,11 @@ listfunds_success(struct command *cmd, available_funds = AMOUNT_SAT(0); committed_funds = AMOUNT_SAT(0); + avail_prev_outs = tal_arr(info, struct bitcoin_outpoint *, 0); json_for_each_arr(i, tok, outputs_tok) { struct funder_utxo *utxo; bool is_reserved, is_p2sh; + struct bitcoin_outpoint *prev_out; char *status; const char *err; @@ -589,10 +594,12 @@ listfunds_success(struct command *cmd, est_fee = amount_tx_fee(info->funding_feerate_perkw, bitcoin_tx_input_weight(is_p2sh, 110)); + /* Did we use this utxo on a previous attempt? */ + prev_out = previously_reserved(info->prev_outs, &utxo->out); + /* we skip reserved funds that aren't in our previous * inputs list! */ - if (is_reserved && - !previously_reserved(info->prev_outs, &utxo->out)) + if (is_reserved && !prev_out) continue; /* we skip unconfirmed+spent funds */ @@ -612,13 +619,21 @@ listfunds_success(struct command *cmd, /* If this is an RBF, we keep track of available utxos */ if (info->prev_outs) { /* if not previously reserved, it's committed */ - if (!previously_reserved(info->prev_outs, &utxo->out)) + if (!prev_out) { tal_arr_expand(&avail_utxos, utxo); - else if (!amount_sat_add(&committed_funds, - committed_funds, utxo->val)) + continue; + } + + if (!amount_sat_add(&committed_funds, + committed_funds, utxo->val)) plugin_err(cmd->plugin, "`listfunds` overflowed" " committed output values"); + + /* We also keep a second list of utxos, + * as it's possible some utxos got spent + * between last attempt + this one! */ + tal_arr_expand(&avail_prev_outs, prev_out); } } @@ -652,6 +667,7 @@ listfunds_success(struct command *cmd, * then add more funds for anything missing */ if (info->prev_outs) { req = build_utxopsbt_request(cmd, info, + avail_prev_outs, info->our_funding, committed_funds, avail_utxos); From ebcc865aafe0bb1217b38a7128466fa91b208eb0 Mon Sep 17 00:00:00 2001 From: niftynei Date: Fri, 7 Oct 2022 15:37:06 -0500 Subject: [PATCH 13/13] funder: cleanup datastore on state-change/channel failure Let's not leave old state hanging around! Note that this fires for pretty much every/any channel (even if we're not the opener). --- plugins/funder.c | 90 ++++++++++++++++++++++++++++++++++++++++++- tests/test_opening.py | 9 +++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/plugins/funder.c b/plugins/funder.c index 5691b2d4293b..9707f1fe1bb2 100644 --- a/plugins/funder.c +++ b/plugins/funder.c @@ -128,6 +128,31 @@ command_hook_cont_psbt(struct command *cmd, struct wally_psbt *psbt) return command_finished(cmd, response); } +static struct command_result * +datastore_del_fail(struct command *cmd, + const char *buf, + const jsmntok_t *error, + void *data UNUSED) +{ + /* Eh, ok fine */ + return notification_handled(cmd); +} + +static struct command_result * +datastore_del_success(struct command *cmd, + const char *buf, + const jsmntok_t *result, + void *data UNUSED) +{ + /* Cool we deleted some stuff */ + plugin_log(cmd->plugin, LOG_DBG, + "`datastore` del succeeded: %*.s", + json_tok_full_len(result), + json_tok_full(buf, result)); + + return notification_handled(cmd); +} + static struct command_result * datastore_add_fail(struct command *cmd, const char *buf, @@ -1047,6 +1072,64 @@ static struct command_result *json_disconnect(struct command *cmd, return notification_handled(cmd); } +static struct command_result * +delete_channel_from_datastore(struct command *cmd, + struct channel_id *cid) +{ + const struct out_req *req; + + /* Fetch out previous utxos from the datastore. + * If we were clever, we'd have some way of tracking + * channels that we actually might have data for + * but this is much easier */ + req = jsonrpc_request_start(cmd->plugin, cmd, + "deldatastore", + &datastore_del_success, + &datastore_del_fail, + NULL); + json_add_string(req->js, "key", + tal_fmt(cmd, "funder/%s", + type_to_string(cmd, struct channel_id, cid))); + return send_outreq(cmd->plugin, req); +} + +static struct command_result *json_channel_state_changed(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct channel_id cid; + const char *err, *old_state, *new_state; + + err = json_scan(tmpctx, buf, params, + "{channel_state_changed:" + "{channel_id:%" + ",old_state:%" + ",new_state:%}}", + JSON_SCAN(json_to_channel_id, &cid), + JSON_SCAN_TAL(cmd, json_strdup, &old_state), + JSON_SCAN_TAL(cmd, json_strdup, &new_state)); + + if (err) + plugin_err(cmd->plugin, + "`channel_state_changed` notification payload did" + " not scan %s: %.*s", + err, json_tok_full_len(params), + json_tok_full(buf, params)); + + /* Moving out of "awaiting lockin", + * means we clean up the datastore */ + /* FIXME: splicing state? */ + if (!streq(old_state, "DUALOPEND_AWAITING_LOCKIN") + && !streq(old_state, "CHANNELD_AWAITING_LOCKIN")) + return notification_handled(cmd); + + plugin_log(cmd->plugin, LOG_DBG, + "Cleaning up datastore for channel_id %s", + type_to_string(tmpctx, struct channel_id, &cid)); + + return delete_channel_from_datastore(cmd, &cid); +} + static struct command_result *json_channel_open_failed(struct command *cmd, const char *buf, const jsmntok_t *params) @@ -1074,7 +1157,8 @@ static struct command_result *json_channel_open_failed(struct command *cmd, if (open) unreserve_psbt(open); - return notification_handled(cmd); + /* Also clean up datastore for this channel */ + return delete_channel_from_datastore(cmd, &cid); } static void json_add_policy(struct json_stream *stream, @@ -1427,6 +1511,10 @@ const struct plugin_notification notifs[] = { "disconnect", json_disconnect, }, + { + "channel_state_changed", + json_channel_state_changed, + }, }; static char *option_channel_base(const char *arg, struct funder_policy *policy) diff --git a/tests/test_opening.py b/tests/test_opening.py index bdf4caae181a..fa02825714ac 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -398,14 +398,23 @@ def test_v2_rbf_liquidity_ad(node_factory, bitcoind, chainparams): funding_feerate=next_feerate) update = l1.rpc.openchannel_update(chan_id, bump['psbt']) assert update['commitments_secured'] + # Sign our inputs, and continue signed_psbt = l1.rpc.signpsbt(update['psbt'])['signed_psbt'] l1.rpc.openchannel_signed(chan_id, signed_psbt) + # There's data in the datastore now (l2 only) + assert l1.rpc.listdatastore() == {'datastore': []} + only_one(l2.rpc.listdatastore("funder/{}".format(chan_id))['datastore']) + # what happens when the channel opens? bitcoind.generate_block(6) l1.daemon.wait_for_log('to CHANNELD_NORMAL') + # Datastore should be cleaned up! + assert l1.rpc.listdatastore() == {'datastore': []} + assert l2.rpc.listdatastore() == {'datastore': []} + # This should be the accepter's amount fundings = only_one(only_one(l1.rpc.listpeers()['peers'])['channels'])['funding'] # The lease is still there!