diff --git a/lightningd/Makefile b/lightningd/Makefile index 11686eae9..0f74f5af8 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -75,6 +75,7 @@ LIGHTNINGD_SRC := \ lightningd/gossip_msg.c \ lightningd/hsm_control.c \ lightningd/htlc_end.c \ + lightningd/htlc_set.c \ lightningd/invoice.c \ lightningd/io_loop_with_timers.c \ lightningd/json.c \ diff --git a/lightningd/htlc_set.c b/lightningd/htlc_set.c new file mode 100644 index 000000000..fc59c64a5 --- /dev/null +++ b/lightningd/htlc_set.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include + +#if EXPERIMENTAL_FEATURES +/* If an HTLC times out, we need to free entire set, since we could be processing + * it in invoice.c right now. */ +static void htlc_set_hin_destroyed(struct htlc_in *hin, + struct htlc_set *set) +{ + for (size_t i = 0; i < tal_count(set->htlcs); i++) { + if (set->htlcs[i] == hin) { + /* Don't try to re-fail this HTLC! */ + tal_arr_remove(&set->htlcs, i); + /* Kind of the correct failure code. */ + htlc_set_fail(set, WIRE_MPP_TIMEOUT); + return; + } + } + abort(); +} + +static void destroy_htlc_set(struct htlc_set *set, + struct htlc_set_map *map) +{ + htlc_set_map_del(map, set); +} + +/* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * - MUST fail all HTLCs in the HTLC set after some reasonable + * timeout. + * - SHOULD use `mpp_timeout` for the failure message. + */ +static void timeout_htlc_set(struct htlc_set *set) +{ + htlc_set_fail(set, WIRE_MPP_TIMEOUT); +} +#endif /* EXPERIMENTAL_FEATURES */ + +void htlc_set_fail(struct htlc_set *set, enum onion_type failcode) +{ + for (size_t i = 0; i < tal_count(set->htlcs); i++) { +#if EXPERIMENTAL_FEATURES + /* Don't remove from set */ + tal_del_destructor2(set->htlcs[i], htlc_set_hin_destroyed, set); +#endif + fail_htlc(set->htlcs[i], failcode); + } + tal_free(set); +} + +void htlc_set_fulfill(struct htlc_set *set, const struct preimage *preimage) +{ + for (size_t i = 0; i < tal_count(set->htlcs); i++) { +#if EXPERIMENTAL_FEATURES + /* Don't remove from set */ + tal_del_destructor2(set->htlcs[i], htlc_set_hin_destroyed, set); +#endif + fulfill_htlc(set->htlcs[i], preimage); + } + tal_free(set); +} + +static struct htlc_set *new_htlc_set(struct lightningd *ld, + struct htlc_in *hin, + struct amount_msat total_msat) +{ + struct htlc_set *set; + + set = tal(ld, struct htlc_set); + set->total_msat = total_msat; + set->payment_hash = hin->payment_hash; + set->so_far = AMOUNT_MSAT(0); + set->htlcs = tal_arr(set, struct htlc_in *, 1); + set->htlcs[0] = hin; + +#if EXPERIMENTAL_FEATURES + /* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * - MUST fail all HTLCs in the HTLC set after some reasonable + * timeout. + * - SHOULD wait for at least 60 seconds after the initial + * HTLC. + */ + notleak(new_reltimer(ld->timers, set, time_from_sec(70), + timeout_htlc_set, set)); + htlc_set_map_add(&ld->htlc_sets, set); + tal_add_destructor2(set, destroy_htlc_set, &ld->htlc_sets); +#endif + return set; +} + +void htlc_set_add(struct lightningd *ld, + struct htlc_in *hin, + struct amount_msat total_msat, + const struct secret *payment_secret) +{ + struct htlc_set *set; + const struct invoice_details *details; + + /* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * The final node: + * - MUST fail the HTLC if dictated by Requirements under + * [Failure Messages](#failure-messages) + * - Note: "amount paid" specified there is the `total_msat` field. + */ + details = invoice_check_payment(tmpctx, ld, &hin->payment_hash, + total_msat, payment_secret); + if (!details) { + fail_htlc(hin, WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS); + return; + } + +#if !EXPERIMENTAL_FEATURES + /* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * - if it does not support `basic_mpp`: + * - MUST fail the HTLC if `total_msat` is not exactly equal to + * `amt_to_forward`. + */ + if (!amount_msat_eq(hin->msat, total_msat)) { + fail_htlc(hin, WIRE_FINAL_INCORRECT_HTLC_AMOUNT); + return; + } + + /* We create a transient set which just has one entry. */ + set = new_htlc_set(ld, hin, total_msat); +#else + /* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * - otherwise, if it supports `basic_mpp`: + * - MUST add it to the HTLC set corresponding to that `payment_hash`. + * - if the total `amount_msat` of this HTLC set equals `total_msat`: + * - SHOULD fulfill all HTLCs in the HTLC set + */ + set = htlc_set_map_get(&ld->htlc_sets, &hin->payment_hash); + if (!set) + set = new_htlc_set(ld, hin, total_msat); + else + tal_arr_expand(&set->htlcs, hin); + + /* Remove from set should hin get destroyed somehow */ + tal_add_destructor2(hin, htlc_set_hin_destroyed, set); + + /* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * - SHOULD fail the entire HTLC set if `total_msat` is not + * the same for all HTLCs in the set. + */ + if (!amount_msat_eq(total_msat, set->total_msat)) { + log_unusual(ld->log, "Failing HTLC set %s:" + " total_msat %s new htlc total %s", + type_to_string(tmpctx, struct sha256, + &set->payment_hash), + type_to_string(tmpctx, struct amount_msat, + &set->total_msat), + type_to_string(tmpctx, struct amount_msat, + &total_msat)); + htlc_set_fail(set, WIRE_FINAL_INCORRECT_HTLC_AMOUNT); + return; + } +#endif /* EXPERIMENTAL_FEATURES */ + + /* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * - if the total `amount_msat` of this HTLC set equals `total_msat`: + * - SHOULD fulfill all HTLCs in the HTLC set + */ + if (!amount_msat_add(&set->so_far, set->so_far, hin->msat)) { + log_unusual(ld->log, "Failing HTLC set %s:" + " overflow adding %s+%s", + type_to_string(tmpctx, struct sha256, + &set->payment_hash), + type_to_string(tmpctx, struct amount_msat, + &set->so_far), + type_to_string(tmpctx, struct amount_msat, + &hin->msat)); + htlc_set_fail(set, WIRE_FINAL_INCORRECT_HTLC_AMOUNT); + return; + } + + if (amount_msat_eq(set->so_far, total_msat)) { + /* FIXME: hand to invoice_try_pay! */ + tal_free(set); + return; + } + + /* BOLT-9441a66faad63edc8cd89860b22fbf24a86f0dcd #4: + * - otherwise, if the total `amount_msat` of this HTLC set is less than + * `total_msat`: + * - MUST NOT fulfill any HTLCs in the HTLC set */ +} diff --git a/lightningd/htlc_set.h b/lightningd/htlc_set.h new file mode 100644 index 000000000..11e57adcb --- /dev/null +++ b/lightningd/htlc_set.h @@ -0,0 +1,55 @@ +#ifndef LIGHTNING_LIGHTNINGD_HTLC_SET_H +#define LIGHTNING_LIGHTNINGD_HTLC_SET_H +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + +struct htlc_in; +struct lightningd; + +/* Set of incoming HTLCs for multi-part-payments */ +struct htlc_set { + struct amount_msat total_msat, so_far; + struct sha256 payment_hash; + struct htlc_in **htlcs; +}; + +static inline const struct sha256 *keyof_htlc_set(const struct htlc_set *set) +{ + return &set->payment_hash; +} + +static inline size_t hash_payment_hash(const struct sha256 *payment_hash) +{ + return siphash24(siphash_seed(), payment_hash, sizeof(&payment_hash)); +} + +static inline bool htlc_set_eq(const struct htlc_set *set, + const struct sha256 *payment_hash) +{ + return sha256_eq(payment_hash, &set->payment_hash); +} + +HTABLE_DEFINE_TYPE(struct htlc_set, + keyof_htlc_set, + hash_payment_hash, + htlc_set_eq, + htlc_set_map); + +/* Handles hin: if it completes a set, hands that to invoice_try_pay */ +void htlc_set_add(struct lightningd *ld, + struct htlc_in *hin, + struct amount_msat total_msat, + const struct secret *payment_secret); + +/* Fail every htlc in the set: frees set */ +void htlc_set_fail(struct htlc_set *set, enum onion_type failcode); + +/* Fulfill every htlc in the set: frees set */ +void htlc_set_fulfill(struct htlc_set *set, const struct preimage *preimage); +#endif /* LIGHTNING_LIGHTNINGD_HTLC_SET_H */ diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 876cc9834..156de206a 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -161,6 +161,12 @@ static struct lightningd *new_lightningd(const tal_t *ctx) htlc_in_map_init(&ld->htlcs_in); htlc_out_map_init(&ld->htlcs_out); +#if EXPERIMENTAL_FEATURES + /*~ For multi-part payments, we need to keep some incoming payments + * in limbo until we get all the parts, or we time them out. */ + htlc_set_map_init(&ld->htlc_sets); +#endif /* EXPERIMENTAL_FEATURES */ + /*~ We have a multi-entry log-book infrastructure: we define a 100MB log * book to hold all the entries (and trims as necessary), and multiple * log objects which each can write into it, each with a unique diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 7e1bfc85d..d8a8b172d 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -162,6 +163,11 @@ struct lightningd { struct htlc_in_map htlcs_in; struct htlc_out_map htlcs_out; +#if EXPERIMENTAL_FEATURES + /* Sets of HTLCs we are holding onto for MPP. */ + struct htlc_set_map htlc_sets; +#endif + struct wallet *wallet; /* Outstanding waitsendpay commands. */