From bdcf82375ca9354695ed1959b00f1c9bee72c37a Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Tue, 28 Sep 2021 14:03:08 -0500
Subject: [PATCH 1/9] lightningd: new option for htlc dust limit

To reduce the surface area of amount of a channel balance that can be
eaten up as htlc dust, we introduce a new config
'--max-dust-htlc-exposure-msat', which sets the max amount that any
channel's balance can be added as dust

Changelog-Added: config: new option --max-dust-htlc-exposure-msat, which limits the total amount of sats to be allowed as dust on a channel
---
 channeld/test/run-full_channel.c    |  2 ++
 common/channel_config.c             |  2 ++
 common/channel_config.h             |  6 ++++++
 devtools/mkcommit.c                 |  1 +
 doc/lightning-listconfigs.7.md      |  3 ++-
 doc/schemas/listconfigs.schema.json |  4 ++++
 lightningd/lightningd.h             |  5 +++++
 lightningd/opening_common.c         |  3 +++
 lightningd/options.c                | 20 ++++++++++++++++++++
 openingd/dualopend.c                |  3 +++
 openingd/openingd.c                 |  3 +++
 wallet/db.c                         |  3 +++
 wallet/wallet.c                     | 12 ++++++++----
 13 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/channeld/test/run-full_channel.c b/channeld/test/run-full_channel.c
index b82dfa5d5f90..addd5f564ede 100644
--- a/channeld/test/run-full_channel.c
+++ b/channeld/test/run-full_channel.c
@@ -406,6 +406,8 @@ int main(int argc, const char *argv[])
 
 	local_config->max_htlc_value_in_flight = AMOUNT_MSAT(-1ULL);
 	remote_config->max_htlc_value_in_flight = AMOUNT_MSAT(-1ULL);
+	local_config->max_dust_htlc_exposure_msat = AMOUNT_MSAT(-1ULL);
+	remote_config->max_dust_htlc_exposure_msat = AMOUNT_MSAT(-1ULL);
 	local_config->channel_reserve = AMOUNT_SAT(0);
 	remote_config->channel_reserve = AMOUNT_SAT(0);
 	local_config->htlc_minimum = AMOUNT_MSAT(0);
diff --git a/common/channel_config.c b/common/channel_config.c
index 5b8627c76639..c4e991f4987e 100644
--- a/common/channel_config.c
+++ b/common/channel_config.c
@@ -9,6 +9,7 @@ void towire_channel_config(u8 **pptr, const struct channel_config *config)
 	towire_amount_msat(pptr, config->htlc_minimum);
 	towire_u16(pptr, config->to_self_delay);
 	towire_u16(pptr, config->max_accepted_htlcs);
+	towire_amount_msat(pptr, config->max_dust_htlc_exposure_msat);
 }
 
 void fromwire_channel_config(const u8 **ptr, size_t *max,
@@ -20,4 +21,5 @@ void fromwire_channel_config(const u8 **ptr, size_t *max,
 	config->htlc_minimum = fromwire_amount_msat(ptr, max);
 	config->to_self_delay = fromwire_u16(ptr, max);
 	config->max_accepted_htlcs = fromwire_u16(ptr, max);
+	config->max_dust_htlc_exposure_msat = fromwire_amount_msat(ptr, max);
 }
diff --git a/common/channel_config.h b/common/channel_config.h
index 7078c83dda67..fb74a4998c01 100644
--- a/common/channel_config.h
+++ b/common/channel_config.h
@@ -71,6 +71,12 @@ struct channel_config {
 	 * similarly, `max_accepted_htlcs` limits the number of outstanding
 	 * HTLCs the other node can offer. */
 	u16 max_accepted_htlcs;
+
+	/* BOLT-TBD #X
+	 *
+	 * maximum dust exposure allowed for this channel
+	 */
+	struct amount_msat max_dust_htlc_exposure_msat;
 };
 
 void towire_channel_config(u8 **pptr, const struct channel_config *config);
diff --git a/devtools/mkcommit.c b/devtools/mkcommit.c
index 2c3cdeada08f..f6afbf32d402 100644
--- a/devtools/mkcommit.c
+++ b/devtools/mkcommit.c
@@ -149,6 +149,7 @@ static int parse_config(char *argv[],
 	config->max_htlc_value_in_flight = AMOUNT_MSAT(-1ULL);
 	config->htlc_minimum = AMOUNT_MSAT(0);
 	config->max_accepted_htlcs = 483;
+	config->max_dust_htlc_exposure_msat = AMOUNT_MSAT(-1ULL);
 
 	config->to_self_delay = atoi(argv[argnum]);
 	argnum++;
diff --git a/doc/lightning-listconfigs.7.md b/doc/lightning-listconfigs.7.md
index 352bd1c409ae..52ee273eb5bf 100644
--- a/doc/lightning-listconfigs.7.md
+++ b/doc/lightning-listconfigs.7.md
@@ -71,6 +71,7 @@ On success, an object is returned, containing:
 - **rescan** (integer, optional): `rescan` field from config or cmdline, or default
 - **fee-per-satoshi** (u32, optional): `fee-per-satoshi` field from config or cmdline, or default
 - **max-concurrent-htlcs** (u32, optional): `max-concurrent-htlcs` field from config or cmdline, or default
+- **max-dust-htlc-exposure-msat** (msat, optional): `max-dust-htlc-exposure-mast` field from config or cmdline, or default
 - **min-capacity-sat** (u64, optional): `min-capacity-sat` field from config or cmdline, or default
 - **addr** (string, optional): `addr` field from config or cmdline (can be more than one)
 - **announce-addr** (string, optional): `announce-addr` field from config or cmdline (can be more than one)
@@ -206,4 +207,4 @@ RESOURCES
 ---------
 
 Main web site: <https://github.com/ElementsProject/lightning>
-[comment]: # ( SHA256STAMP:47c067588120e0f9a71206313685cebb2a8c515e9b04b688b202d2772c8f8146)
+[comment]: # ( SHA256STAMP:71a911b67203f75e7c1f717be611f505713fce4e8113fc4a84c89bc50730d2bf)
diff --git a/doc/schemas/listconfigs.schema.json b/doc/schemas/listconfigs.schema.json
index 9f910292f9f1..ca24ba76983c 100644
--- a/doc/schemas/listconfigs.schema.json
+++ b/doc/schemas/listconfigs.schema.json
@@ -183,6 +183,10 @@
 	    "type": "u32",
 	    "description": "`max-concurrent-htlcs` field from config or cmdline, or default"
 	},
+	"max-dust-htlc-exposure-msat": {
+	    "type": "msat",
+	    "description": "`max-dust-htlc-exposure-mast` field from config or cmdline, or default"
+	},
 	"min-capacity-sat": {
 	    "type": "u64",
 	    "description": "`min-capacity-sat` field from config or cmdline, or default"
diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h
index 532841dcab7e..553f10a785b4 100644
--- a/lightningd/lightningd.h
+++ b/lightningd/lightningd.h
@@ -7,6 +7,8 @@
 #include <sys/stat.h>
 #include <wallet/wallet.h>
 
+struct amount_msat;
+
 /* Various adjustable things. */
 struct config {
 	/* How long do we want them to lock up their funds? (blocks) */
@@ -31,6 +33,9 @@ struct config {
 	/* htlcs per channel */
 	u32 max_concurrent_htlcs;
 
+	/* Max amount of dust allowed per channel */
+	struct amount_msat max_dust_htlc_exposure_msat;
+
 	/* How long between changing commit and sending COMMIT message. */
 	u32 commit_time_ms;
 
diff --git a/lightningd/opening_common.c b/lightningd/opening_common.c
index 9cd3872e9657..d203aed8d2a4 100644
--- a/lightningd/opening_common.c
+++ b/lightningd/opening_common.c
@@ -132,6 +132,9 @@ void channel_config(struct lightningd *ld,
 	ours->dust_limit = chainparams->dust_limit;
 	ours->max_htlc_value_in_flight = AMOUNT_MSAT(UINT64_MAX);
 
+	ours->max_dust_htlc_exposure_msat
+		= ld->config.max_dust_htlc_exposure_msat;
+
 	/* Don't care */
 	ours->htlc_minimum = AMOUNT_MSAT(0);
 
diff --git a/lightningd/options.c b/lightningd/options.c
index ef65f9dd26b4..d8431c13c542 100644
--- a/lightningd/options.c
+++ b/lightningd/options.c
@@ -10,6 +10,7 @@
 #include <common/features.h>
 #include <common/hsm_encryption.h>
 #include <common/json_command.h>
+#include <common/json_helpers.h>
 #include <common/json_tok.h>
 #include <common/param.h>
 #include <common/type_to_string.h>
@@ -671,6 +672,9 @@ static const struct config testnet_config = {
 	/* Testnet blockspace is free. */
 	.max_concurrent_htlcs = 483,
 
+	/* Max amount of dust allowed per channel (50ksat) */
+	.max_dust_htlc_exposure_msat = AMOUNT_MSAT(50000000),
+
 	/* Be aggressive on testnet. */
 	.cltv_expiry_delta = 6,
 	.cltv_final = 10,
@@ -717,6 +721,9 @@ static const struct config mainnet_config = {
 	/* While up to 483 htlcs are possible we do 30 by default (as eclair does) to save blockspace */
 	.max_concurrent_htlcs = 30,
 
+	/* Max amount of dust allowed per channel (50ksat) */
+	.max_dust_htlc_exposure_msat = AMOUNT_MSAT(50000000),
+
 	/* BOLT #2:
 	 *
 	 * 1. the `cltv_expiry_delta` for channels, `3R+2G+2S`: if in doubt, a
@@ -842,6 +849,14 @@ static char *opt_start_daemon(struct lightningd *ld)
 	errx(1, "Died with signal %u", WTERMSIG(exitcode));
 }
 
+static char *opt_set_msat(const char *arg, struct amount_msat *amt)
+{
+	if (!parse_amount_msat(amt, arg, strlen(arg)))
+		return tal_fmt(NULL, "Unable to parse millisatoshi '%s'", arg);
+
+	return NULL;
+}
+
 static char *opt_set_wumbo(struct lightningd *ld)
 {
 	feature_set_or(ld->our_features,
@@ -1005,6 +1020,9 @@ static void register_opts(struct lightningd *ld)
 	opt_register_arg("--max-concurrent-htlcs", opt_set_u32, opt_show_u32,
 			 &ld->config.max_concurrent_htlcs,
 			 "Number of HTLCs one channel can handle concurrently. Should be between 1 and 483");
+	opt_register_arg("--max-dust-htlc-exposure-msat", opt_set_msat,
+			 NULL, &ld->config.max_dust_htlc_exposure_msat,
+			 "Max HTLC amount that can be trimmed");
 	opt_register_arg("--min-capacity-sat", opt_set_u64, opt_show_u64,
 			 &ld->config.min_capacity_sat,
 			 "Minimum capacity in satoshis for accepting channels");
@@ -1496,6 +1514,8 @@ static void add_config(struct lightningd *ld,
 			   || opt->cb_arg == (void *)plugin_opt_flag_set) {
 			/* FIXME: We actually treat it as if they specified
 			 * --plugin for each one, so ignore these */
+		} else if (opt->cb_arg == (void *)opt_set_msat) {
+			json_add_amount_msat_only(response, name0, ld->config.max_dust_htlc_exposure_msat);
 #if EXPERIMENTAL_FEATURES
 		} else if (opt->cb_arg == (void *)opt_set_accept_extra_tlv_types) {
                         /* TODO Actually print the option */
diff --git a/openingd/dualopend.c b/openingd/dualopend.c
index 022c52e7312c..e3eb065282b2 100644
--- a/openingd/dualopend.c
+++ b/openingd/dualopend.c
@@ -134,6 +134,9 @@ static struct tx_state *new_tx_state(const tal_t *ctx)
 	tx_state->lease_chan_max_msat = 0;
 	tx_state->lease_chan_max_ppt = 0;
 
+	/* no max_htlc_dust_exposure on remoteconf, we exclusively use the local's */
+	tx_state->remoteconf.max_dust_htlc_exposure_msat = AMOUNT_MSAT(0);
+
 	for (size_t i = 0; i < NUM_TX_MSGS; i++)
 		tx_state->tx_msg_count[i] = 0;
 
diff --git a/openingd/openingd.c b/openingd/openingd.c
index 78280f6bcab6..c8d2cc0d286d 100644
--- a/openingd/openingd.c
+++ b/openingd/openingd.c
@@ -1429,6 +1429,9 @@ int main(int argc, char *argv[])
 	memset(&state->channel_id, 0, sizeof(state->channel_id));
 	state->channel = NULL;
 
+	/* Default this to zero, we only ever look at the local */
+	state->remoteconf.max_dust_htlc_exposure_msat = AMOUNT_MSAT(0);
+
 	/*~ We set these to NULL, meaning no requirements on shutdown */
 	state->upfront_shutdown_script[LOCAL]
 		= state->upfront_shutdown_script[REMOTE]
diff --git a/wallet/db.c b/wallet/db.c
index 5c884cb16d4e..1b6c8f02d611 100644
--- a/wallet/db.c
+++ b/wallet/db.c
@@ -852,6 +852,9 @@ static struct migration dbmigrations[] = {
 	 " shared_secret=NULL,"
 	 " localfailmsg=NULL"
 	 " WHERE (hstate=9 OR hstate=19);"), NULL},
+    /* We default to 50k sats */
+    {SQL("ALTER TABLE channel_configs ADD max_dust_htlc_exposure_msat BIGINT DEFAULT 50000000"), NULL},
+    {SQL("ALTER TABLE channel_htlcs ADD fail_immediate INTEGER DEFAULT 0"), NULL},
 };
 
 /* Leak tracking. */
diff --git a/wallet/wallet.c b/wallet/wallet.c
index d483e1ee75c1..56180e16633e 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -1711,7 +1711,8 @@ static void wallet_channel_config_save(struct wallet *w,
 					"  channel_reserve_satoshis=?,"
 					"  htlc_minimum_msat=?,"
 					"  to_self_delay=?,"
-					"  max_accepted_htlcs=?"
+					"  max_accepted_htlcs=?,"
+					"  max_dust_htlc_exposure_msat=?"
 					" WHERE id=?;"));
 	db_bind_amount_sat(stmt, 0, &cc->dust_limit);
 	db_bind_amount_msat(stmt, 1, &cc->max_htlc_value_in_flight);
@@ -1719,7 +1720,8 @@ static void wallet_channel_config_save(struct wallet *w,
 	db_bind_amount_msat(stmt, 3, &cc->htlc_minimum);
 	db_bind_int(stmt, 4, cc->to_self_delay);
 	db_bind_int(stmt, 5, cc->max_accepted_htlcs);
-	db_bind_u64(stmt, 6, cc->id);
+	db_bind_amount_msat(stmt, 6, &cc->max_dust_htlc_exposure_msat);
+	db_bind_u64(stmt, 7, cc->id);
 	db_exec_prepared_v2(take(stmt));
 }
 
@@ -1731,7 +1733,8 @@ bool wallet_channel_config_load(struct wallet *w, const u64 id,
 	const char *query = SQL(
 	    "SELECT id, dust_limit_satoshis, max_htlc_value_in_flight_msat, "
 	    "channel_reserve_satoshis, htlc_minimum_msat, to_self_delay, "
-	    "max_accepted_htlcs FROM channel_configs WHERE id= ? ;");
+	    "max_accepted_htlcs, max_dust_htlc_exposure_msat"
+	    " FROM channel_configs WHERE id= ? ;");
 	struct db_stmt *stmt = db_prepare_v2(w->db, query);
 	db_bind_u64(stmt, 0, id);
 	db_query_prepared(stmt);
@@ -1746,7 +1749,8 @@ bool wallet_channel_config_load(struct wallet *w, const u64 id,
 	db_column_amount_msat(stmt, col++, &cc->htlc_minimum);
 	cc->to_self_delay = db_column_int(stmt, col++);
 	cc->max_accepted_htlcs = db_column_int(stmt, col++);
-	assert(col == 7);
+	db_column_amount_msat(stmt, col++, &cc->max_dust_htlc_exposure_msat);
+	assert(col == 8);
 	tal_free(stmt);
 	return ok;
 }

From 2d7a87476044937c3b3e14a03c17b330d491a733 Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Wed, 6 Oct 2021 12:39:08 -0500
Subject: [PATCH 2/9] tests: bump the ceiling for dust, fixes failures for
 liquid

Liquid's threshold for dust is a bit higher, so we bump up the max limit
here so we can actually get the whole MPP'd payment sent over the wire
---
 tests/test_pay.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/test_pay.py b/tests/test_pay.py
index 4b58a32a5658..7197516ec964 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -3415,7 +3415,8 @@ def test_pay_peer(node_factory, bitcoind):
      v  /
      l3
     """
-    l1, l2, l3 = node_factory.get_nodes(3)
+    # Set the dust exposure higher, this gets triggered on liquid
+    l1, l2, l3 = node_factory.get_nodes(3, opts={'max-dust-htlc-exposure-msat': '100000sat'})
     node_factory.join_nodes([l1, l2])
     node_factory.join_nodes([l1, l3])
     node_factory.join_nodes([l3, l2], wait_for_announce=True)

From abfcbcec6c4634891f9c1f6231f990e88cb4ecb1 Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Tue, 28 Sep 2021 14:08:11 -0500
Subject: [PATCH 3/9] htlcs: add flag to 'fail immediately'

If we're over the dust limit, we fail it immediatey *after* commiting
it, but we need a way to signal this throughout the lifecycle, so we add
it to htlc_in struct and persist it through to the database.

If it's supposed to be failed, we fail after the commit cycle is
completed.
---
 channeld/channeld.c              | 10 ++++++++--
 channeld/channeld_htlc.h         |  3 +++
 channeld/full_channel.c          | 11 +++++++----
 channeld/full_channel.h          |  7 ++++++-
 channeld/test/run-full_channel.c |  4 ++--
 common/htlc_wire.c               |  2 ++
 common/htlc_wire.h               |  1 +
 lightningd/htlc_end.c            |  4 +++-
 lightningd/htlc_end.h            |  5 ++++-
 lightningd/peer_htlcs.c          | 10 +++++++++-
 wallet/wallet.c                  | 10 ++++++++--
 11 files changed, 53 insertions(+), 14 deletions(-)

diff --git a/channeld/channeld.c b/channeld/channeld.c
index 8a3b56e8e829..5eb0fb26c28f 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -738,7 +738,11 @@ static void handle_peer_add_htlc(struct peer *peer, const u8 *msg)
 #endif
 	add_err = channel_add_htlc(peer->channel, REMOTE, id, amount,
 				   cltv_expiry, &payment_hash,
-				   onion_routing_packet, blinding, &htlc, NULL);
+				   onion_routing_packet, blinding, &htlc, NULL,
+				   /* We don't immediately fail incoming htlcs,
+				    * instead we wait and fail them after
+				    * they've been committed */
+				   false);
 	if (add_err != CHANNEL_ERR_ADD_OK)
 		peer_failed_warn(peer->pps, &peer->channel_id,
 				 "Bad peer_add_htlc: %s",
@@ -1468,6 +1472,7 @@ static void marshall_htlc_info(const tal_t *ctx,
 				ecdh(a.blinding, &a.blinding_ss);
 			} else
 				a.blinding = NULL;
+			a.fail_immediate = htlc->fail_immediate;
 			tal_arr_expand(added, a);
 		} else if (htlc->state == RCVD_REMOVE_COMMIT) {
 			if (htlc->r) {
@@ -3299,7 +3304,8 @@ static void handle_offer_htlc(struct peer *peer, const u8 *inmsg)
 
 	e = channel_add_htlc(peer->channel, LOCAL, peer->htlc_id,
 			     amount, cltv_expiry, &payment_hash,
-			     onion_routing_packet, take(blinding), NULL, &htlc_fee);
+			     onion_routing_packet, take(blinding), NULL,
+			     &htlc_fee, true);
 	status_debug("Adding HTLC %"PRIu64" amount=%s cltv=%u gave %s",
 		     peer->htlc_id,
 		     type_to_string(tmpctx, struct amount_msat, &amount),
diff --git a/channeld/channeld_htlc.h b/channeld/channeld_htlc.h
index fcb4e0841eb8..3a40416f6c1c 100644
--- a/channeld/channeld_htlc.h
+++ b/channeld/channeld_htlc.h
@@ -28,6 +28,9 @@ struct htlc {
 
 	/* Blinding (optional). */
 	struct pubkey *blinding;
+
+	/* Should we immediately fail this htlc? */
+	bool fail_immediate;
 };
 
 static inline bool htlc_has(const struct htlc *h, int flag)
diff --git a/channeld/full_channel.c b/channeld/full_channel.c
index 1ae08ec79909..59eb23acdbc6 100644
--- a/channeld/full_channel.c
+++ b/channeld/full_channel.c
@@ -493,7 +493,8 @@ static enum channel_add_err add_htlc(struct channel *channel,
 				     const struct pubkey *blinding TAKES,
 				     struct htlc **htlcp,
 				     bool enforce_aggregate_limits,
-				     struct amount_sat *htlc_fee)
+				     struct amount_sat *htlc_fee,
+				     bool err_immediate_failures)
 {
 	struct htlc *htlc, *old;
 	struct amount_msat msat_in_htlcs, committed_msat, adding_msat, removing_msat;
@@ -508,6 +509,7 @@ static enum channel_add_err add_htlc(struct channel *channel,
 	htlc->id = id;
 	htlc->amount = amount;
 	htlc->state = state;
+	htlc->fail_immediate = false;
 
 	htlc->rhash = *payment_hash;
 	if (blinding)
@@ -768,7 +770,8 @@ enum channel_add_err channel_add_htlc(struct channel *channel,
 				      const u8 routing[TOTAL_PACKET_SIZE(ROUTING_INFO_SIZE)],
 				      const struct pubkey *blinding TAKES,
 				      struct htlc **htlcp,
-				      struct amount_sat *htlc_fee)
+				      struct amount_sat *htlc_fee,
+				      bool err_immediate_failures)
 {
 	enum htlc_state state;
 
@@ -786,7 +789,7 @@ enum channel_add_err channel_add_htlc(struct channel *channel,
 
 	return add_htlc(channel, state, id, amount, cltv_expiry,
 			payment_hash, routing, blinding,
-			htlcp, true, htlc_fee);
+			htlcp, true, htlc_fee, err_immediate_failures);
 }
 
 struct htlc *channel_get_htlc(struct channel *channel, enum side sender, u64 id)
@@ -1392,7 +1395,7 @@ bool channel_force_htlcs(struct channel *channel,
 			     &htlcs[i]->payment_hash,
 			     htlcs[i]->onion_routing_packet,
 			     htlcs[i]->blinding,
-			     &htlc, false, NULL);
+			     &htlc, false, NULL, false);
 		if (e != CHANNEL_ERR_ADD_OK) {
 			status_broken("%s HTLC %"PRIu64" failed error %u",
 				     htlc_state_owner(htlcs[i]->state) == LOCAL
diff --git a/channeld/full_channel.h b/channeld/full_channel.h
index b9f59e9c1da6..ebfb4f856c76 100644
--- a/channeld/full_channel.h
+++ b/channeld/full_channel.h
@@ -103,6 +103,10 @@ u32 actual_feerate(const struct channel *channel,
  * @routing: routing information (copied)
  * @blinding: optional blinding information for this HTLC.
  * @htlcp: optional pointer for resulting htlc: filled in if and only if CHANNEL_ERR_NONE.
+ * @err_immediate_failures: in some cases (dusty htlcs) we want to immediately
+ *                          fail the htlc; for peer incoming don't want to
+ *                          error, but rather mark it as failed and fail after
+ *                          it's been committed to (so set this to false)
  *
  * If this returns CHANNEL_ERR_NONE, the fee htlc was added and
  * the output amounts adjusted accordingly.  Otherwise nothing
@@ -117,7 +121,8 @@ enum channel_add_err channel_add_htlc(struct channel *channel,
 				      const u8 routing[TOTAL_PACKET_SIZE(ROUTING_INFO_SIZE)],
 				      const struct pubkey *blinding TAKES,
 				      struct htlc **htlcp,
-				      struct amount_sat *htlc_fee);
+				      struct amount_sat *htlc_fee,
+				      bool err_immediate_failures);
 
 /**
  * channel_get_htlc: find an HTLC
diff --git a/channeld/test/run-full_channel.c b/channeld/test/run-full_channel.c
index addd5f564ede..1212f596f9b6 100644
--- a/channeld/test/run-full_channel.c
+++ b/channeld/test/run-full_channel.c
@@ -165,7 +165,7 @@ static const struct htlc **include_htlcs(struct channel *channel, enum side side
 		memset(&preimage, i, sizeof(preimage));
 		sha256(&hash, &preimage, sizeof(preimage));
 		e = channel_add_htlc(channel, sender, i, msatoshi, 500+i, &hash,
-				     dummy_routing, NULL, NULL, NULL);
+				     dummy_routing, NULL, NULL, NULL, true);
 		assert(e == CHANNEL_ERR_ADD_OK);
 		htlcs[i] = channel_get_htlc(channel, sender, i);
 	}
@@ -257,7 +257,7 @@ static void send_and_fulfill_htlc(struct channel *channel,
 	sha256(&rhash, &r, sizeof(r));
 
 	assert(channel_add_htlc(channel, sender, 1337, msatoshi, 900, &rhash,
-				dummy_routing, NULL, NULL, NULL)
+				dummy_routing, NULL, NULL, NULL, true)
 	       == CHANNEL_ERR_ADD_OK);
 
 	changed_htlcs = tal_arr(channel, const struct htlc *, 0);
diff --git a/common/htlc_wire.c b/common/htlc_wire.c
index e82f27ed4dba..801c8124bf64 100644
--- a/common/htlc_wire.c
+++ b/common/htlc_wire.c
@@ -78,6 +78,7 @@ void towire_added_htlc(u8 **pptr, const struct added_htlc *added)
 		towire_secret(pptr, &added->blinding_ss);
 	} else
 		towire_bool(pptr, false);
+	towire_bool(pptr, added->fail_immediate);
 }
 
 void towire_existing_htlc(u8 **pptr, const struct existing_htlc *existing)
@@ -171,6 +172,7 @@ void fromwire_added_htlc(const u8 **cursor, size_t *max,
 		fromwire_secret(cursor, max, &added->blinding_ss);
 	} else
 		added->blinding = NULL;
+	added->fail_immediate = fromwire_bool(cursor, max);
 }
 
 struct existing_htlc *fromwire_existing_htlc(const tal_t *ctx,
diff --git a/common/htlc_wire.h b/common/htlc_wire.h
index 73a1236f8333..0779dbfec1c8 100644
--- a/common/htlc_wire.h
+++ b/common/htlc_wire.h
@@ -15,6 +15,7 @@ struct added_htlc {
 	struct sha256 payment_hash;
 	u32 cltv_expiry;
 	u8 onion_routing_packet[TOTAL_PACKET_SIZE(ROUTING_INFO_SIZE)];
+	bool fail_immediate;
 
 	/* If this is non-NULL, secret is the resulting shared secret */
 	struct pubkey *blinding;
diff --git a/lightningd/htlc_end.c b/lightningd/htlc_end.c
index b5589c2e573a..461d77d17a18 100644
--- a/lightningd/htlc_end.c
+++ b/lightningd/htlc_end.c
@@ -130,7 +130,8 @@ struct htlc_in *new_htlc_in(const tal_t *ctx,
 			    const struct secret *shared_secret TAKES,
 			    const struct pubkey *blinding TAKES,
 			    const struct secret *blinding_ss,
-			    const u8 *onion_routing_packet)
+			    const u8 *onion_routing_packet,
+			    bool fail_immediate)
 {
 	struct htlc_in *hin = tal(ctx, struct htlc_in);
 
@@ -141,6 +142,7 @@ struct htlc_in *new_htlc_in(const tal_t *ctx,
 	hin->cltv_expiry = cltv_expiry;
 	hin->payment_hash = *payment_hash;
 	hin->status = NULL;
+	hin->fail_immediate = fail_immediate;
 	if (shared_secret)
 		hin->shared_secret = tal_dup(hin, struct secret, shared_secret);
 	else
diff --git a/lightningd/htlc_end.h b/lightningd/htlc_end.h
index f6a4fd9fccb6..91bd6315b3eb 100644
--- a/lightningd/htlc_end.h
+++ b/lightningd/htlc_end.h
@@ -52,6 +52,8 @@ struct htlc_in {
 	struct secret blinding_ss;
 	/* true if we supplied the preimage */
 	bool *we_filled;
+	/* true if we immediately fail the htlc (too much dust) */
+	bool fail_immediate;
 
 	/* A simple text annotation shown in `listpeers` */
 	char *status;
@@ -154,7 +156,8 @@ struct htlc_in *new_htlc_in(const tal_t *ctx,
 			    const struct secret *shared_secret TAKES,
 			    const struct pubkey *blinding TAKES,
 			    const struct secret *blinding_ss,
-			    const u8 *onion_routing_packet);
+			    const u8 *onion_routing_packet,
+			    bool fail_immediate);
 
 /* You need to set the ID, then connect_htlc_out this! */
 struct htlc_out *new_htlc_out(const tal_t *ctx,
diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c
index e937b8105e6c..9e9456595194 100644
--- a/lightningd/peer_htlcs.c
+++ b/lightningd/peer_htlcs.c
@@ -1171,6 +1171,13 @@ static bool peer_accepted_htlc(const tal_t *ctx,
 		goto fail;
 	}
 
+	if (hin->fail_immediate && htlc_in_update_state(channel, hin, RCVD_ADD_ACK_REVOCATION)) {
+		log_debug(channel->log, "failing immediately, as requested");
+		/* Failing the htlc, typically done because of htlc dust */
+		*failmsg = towire_temporary_node_failure(ctx);
+		goto fail;
+	}
+
 	if (!replay && !htlc_in_update_state(channel, hin, RCVD_ADD_ACK_REVOCATION)) {
 		*failmsg = towire_temporary_node_failure(ctx);
 		goto fail;
@@ -1863,7 +1870,8 @@ static bool channel_added_their_htlc(struct channel *channel,
 			  added->cltv_expiry, &added->payment_hash,
 			  op ? &shared_secret : NULL,
 			  added->blinding, &added->blinding_ss,
-			  added->onion_routing_packet);
+			  added->onion_routing_packet,
+			  added->fail_immediate);
 
 	/* Save an incoming htlc to the wallet */
 	wallet_htlc_save_in(ld->wallet, channel, hin);
diff --git a/wallet/wallet.c b/wallet/wallet.c
index 56180e16633e..1492f358a433 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -2317,8 +2317,9 @@ void wallet_htlc_save_in(struct wallet *wallet,
 				 " shared_secret,"
 				 " routing_onion,"
 				 " received_time,"
-				 " min_commit_num) VALUES "
-				 "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
+				 " min_commit_num, "
+				 " fail_immediate) VALUES "
+				 "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
 
 	db_bind_u64(stmt, 0, chan->dbid);
 	db_bind_u64(stmt, 1, in->key.id);
@@ -2345,6 +2346,8 @@ void wallet_htlc_save_in(struct wallet *wallet,
 	db_bind_u64(stmt, 11, min_unsigned(chan->next_index[LOCAL]-1,
 					   chan->next_index[REMOTE]-1));
 
+	db_bind_int(stmt, 12, in->fail_immediate);
+
 	db_exec_prepared_v2(stmt);
 	in->dbid = db_last_insert_id_v2(take(stmt));
 }
@@ -2555,6 +2558,8 @@ static bool wallet_stmt2htlc_in(struct channel *channel,
 	} else
 		in->we_filled = NULL;
 
+	in->fail_immediate = db_column_int(stmt, 14);
+
 	return ok;
 }
 
@@ -2683,6 +2688,7 @@ bool wallet_htlcs_load_in_for_channel(struct wallet *wallet,
 					     ", shared_secret"
 					     ", received_time"
 					     ", we_filled"
+					     ", fail_immediate"
 					     " FROM channel_htlcs"
 					     " WHERE direction= ?"
 					     " AND channel_id= ?"

From 7cd7971c40bd2889620478a60c9e6f682c5ac74e Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Tue, 28 Sep 2021 14:12:01 -0500
Subject: [PATCH 4/9] dusty-htlcs: enforce limit on dusty htlcs

for every new added htlc, check that adding it won't go over our 'dust
budget' (which assumes a slightly higher than current feerate, as this
prevents sudden feerate changes from overshooting our dust budget)

note that if the feerate changes surpass the limits we've set, we
immediately fail the channel.
---
 channeld/channeld.c           |  12 ++++
 channeld/commit_tx.c          |  16 +++++
 channeld/commit_tx.h          |  20 +++++++
 channeld/full_channel.c       | 106 +++++++++++++++++++++++++++++++++-
 channeld/full_channel.h       |  12 ++++
 channeld/full_channel_error.h |   2 +
 common/htlc_trim.c            |  12 ++++
 common/htlc_trim.h            |   3 +
 8 files changed, 182 insertions(+), 1 deletion(-)

diff --git a/channeld/channeld.c b/channeld/channeld.c
index 5eb0fb26c28f..0de935bf0600 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -1292,6 +1292,14 @@ static void send_commit(struct peer *peer)
 		if (feerate_changes_done(peer->channel->fee_states, false)) {
 			u8 *msg;
 
+			/* Is this feerate update going to push the committed
+			 * htlcs over our allowed dust limits? */
+			if (!htlc_dust_ok(peer->channel, feerate_target, REMOTE)
+			   || !htlc_dust_ok(peer->channel, feerate_target, LOCAL))
+				/* We fail the channel. Oops */
+				peer_failed_err(peer->pps, &peer->channel_id,
+						"Too much dust to update fee");
+
 			if (!channel_update_feerate(peer->channel, feerate_target))
 				status_failed(STATUS_FAIL_INTERNAL_ERROR,
 					      "Could not afford feerate %u"
@@ -3360,6 +3368,10 @@ static void handle_offer_htlc(struct peer *peer, const u8 *inmsg)
 		failwiremsg = towire_temporary_channel_failure(inmsg, get_local_channel_update(inmsg, peer));
 		failstr = "Too many HTLCs";
 		goto failed;
+	case CHANNEL_ERR_DUST_FAILURE:
+		failwiremsg = towire_temporary_channel_failure(inmsg, get_local_channel_update(inmsg, peer));
+		failstr = "HTLC too dusty, allowed dust limit reached";
+		goto failed;
 	}
 	/* Shouldn't return anything else! */
 	abort();
diff --git a/channeld/commit_tx.c b/channeld/commit_tx.c
index 3eec184401b7..9c2f7499f7ae 100644
--- a/channeld/commit_tx.c
+++ b/channeld/commit_tx.c
@@ -35,6 +35,22 @@ size_t commit_tx_num_untrimmed(const struct htlc **htlcs,
 	return n;
 }
 
+bool commit_tx_amount_trimmed(const struct htlc **htlcs,
+			      u32 feerate_per_kw,
+			      struct amount_sat dust_limit,
+			      bool option_anchor_outputs,
+			      enum side side,
+			      struct amount_msat *amt)
+{
+	for (size_t i = 0; i < tal_count(htlcs); i++) {
+		if (trim(htlcs[i], feerate_per_kw, dust_limit,
+			 option_anchor_outputs, side))
+			if (!amount_msat_add(amt, *amt, htlcs[i]->amount))
+				return false;
+	}
+	return true;
+}
+
 static void add_offered_htlc_out(struct bitcoin_tx *tx, size_t n,
 				 const struct htlc *htlc,
 				 const struct keyset *keyset,
diff --git a/channeld/commit_tx.h b/channeld/commit_tx.h
index 1de552ef61a8..fc2db029de40 100644
--- a/channeld/commit_tx.h
+++ b/channeld/commit_tx.h
@@ -24,6 +24,26 @@ size_t commit_tx_num_untrimmed(const struct htlc **htlcs,
 			       bool option_anchor_outputs,
 			       enum side side);
 
+/**
+ * commit_tx_amount_trimmed: what's the sum of trimmed htlc amounts?
+ * @htlcs: tal_arr of HTLCs
+ * @feerate_per_kw: feerate to use
+ * @dust_limit: dust limit below which to trim outputs.
+ * @option_anchor_outputs: does option_anchor_outputs apply to this channel?
+ * @side: from which side's point of view
+ * @amt: returned, total value trimmed from this commitment
+ *
+ * We need @side because HTLC fees are different for offered and
+ * received HTLCs.
+ *
+ * Returns false if unable to calculate amount trimmed.
+ */
+bool commit_tx_amount_trimmed(const struct htlc **htlcs,
+			      u32 feerate_per_kw,
+			      struct amount_sat dust_limit,
+			      bool option_anchor_outputs,
+			      enum side side,
+			      struct amount_msat *amt);
 /**
  * commit_tx: create (unsigned) commitment tx to spend the funding tx output
  * @ctx: context to allocate transaction and @htlc_map from.
diff --git a/channeld/full_channel.c b/channeld/full_channel.c
index 59eb23acdbc6..4fb9fc59bc53 100644
--- a/channeld/full_channel.c
+++ b/channeld/full_channel.c
@@ -7,6 +7,7 @@
 #include <common/blockheight_states.h>
 #include <common/features.h>
 #include <common/fee_states.h>
+#include <common/htlc_trim.h>
 #include <common/htlc_tx.h>
 #include <common/htlc_wire.h>
 #include <common/keyset.h>
@@ -423,6 +424,37 @@ static struct amount_sat fee_for_htlcs(const struct channel *channel,
 	return commit_tx_base_fee(feerate, untrimmed, option_anchor_outputs);
 }
 
+static bool htlc_dust(const struct channel *channel,
+		      const struct htlc **committed,
+		      const struct htlc **adding,
+		      const struct htlc **removing,
+		      enum side side,
+		      u32 feerate,
+		      struct amount_msat *trim_total)
+{
+	struct amount_sat dust_limit = channel->config[side].dust_limit;
+	bool option_anchor_outputs = channel_has(channel, OPT_ANCHOR_OUTPUTS);
+	struct amount_msat trim_rmvd = AMOUNT_MSAT(0);
+
+	if (!commit_tx_amount_trimmed(committed, feerate,
+				      dust_limit,
+				      option_anchor_outputs,
+				      side, trim_total))
+		return false;
+	if (!commit_tx_amount_trimmed(adding, feerate,
+				      dust_limit,
+				      option_anchor_outputs,
+				      side, trim_total))
+		return false;
+	if (!commit_tx_amount_trimmed(removing, feerate,
+				      dust_limit,
+				      option_anchor_outputs,
+				      side, &trim_rmvd))
+		return false;
+
+	return amount_msat_sub(trim_total, *trim_total, trim_rmvd);
+}
+
 /*
  * There is a corner case where the opener can spend so much that the
  * non-opener can't add any non-dust HTLCs (since the opener would
@@ -497,12 +529,14 @@ static enum channel_add_err add_htlc(struct channel *channel,
 				     bool err_immediate_failures)
 {
 	struct htlc *htlc, *old;
-	struct amount_msat msat_in_htlcs, committed_msat, adding_msat, removing_msat;
+	struct amount_msat msat_in_htlcs, committed_msat,
+			   adding_msat, removing_msat, htlc_dust_amt;
 	enum side sender = htlc_state_owner(state), recipient = !sender;
 	const struct htlc **committed, **adding, **removing;
 	const struct channel_view *view;
 	size_t htlc_count;
 	bool option_anchor_outputs = channel_has(channel, OPT_ANCHOR_OUTPUTS);
+	u32 feerate, feerate_ceil;
 
 	htlc = tal(tmpctx, struct htlc);
 
@@ -753,6 +787,42 @@ static enum channel_add_err add_htlc(struct channel *channel,
 		}
 	}
 
+	htlc_dust_amt = AMOUNT_MSAT(0);
+	feerate = channel_feerate(channel, recipient);
+	/* Note that we check for trimmed htlcs at an
+	 * *accelerated* rate, so that future feerate changes
+	 * don't suddenly surprise us */
+	feerate_ceil = htlc_trim_feerate_ceiling(feerate);
+
+	if (!htlc_dust(channel, committed,
+		       adding, removing, recipient,
+		       feerate_ceil, &htlc_dust_amt))
+		return CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED;
+
+	if (amount_msat_greater(htlc_dust_amt,
+				channel->config[LOCAL].max_dust_htlc_exposure_msat)) {
+		htlc->fail_immediate = true;
+		if (err_immediate_failures)
+			return CHANNEL_ERR_DUST_FAILURE;
+	}
+
+
+	/* Also check the sender, as they'll eventually have the same
+	 * constraint */
+	htlc_dust_amt = AMOUNT_MSAT(0);
+	feerate = channel_feerate(channel, sender);
+	feerate_ceil = htlc_trim_feerate_ceiling(feerate);
+	if (!htlc_dust(channel, committed, adding,
+		       removing, sender, feerate_ceil,
+		       &htlc_dust_amt))
+		return CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED;
+
+	if (amount_msat_greater(htlc_dust_amt,
+				channel->config[LOCAL].max_dust_htlc_exposure_msat)) {
+		htlc->fail_immediate = true;
+		if (err_immediate_failures)
+			return CHANNEL_ERR_DUST_FAILURE;
+	}
 	dump_htlc(htlc, "NEW:");
 	htlc_map_add(channel->htlcs, tal_steal(channel, htlc));
 	if (htlcp)
@@ -1109,6 +1179,37 @@ u32 approx_max_feerate(const struct channel *channel)
 	return avail.satoshis / weight * 1000; /* Raw: once-off reverse feerate*/
 }
 
+/* Is the sum of trimmed htlcs, as this new feerate, above our
+ * max allowed htlc dust limit? */
+static struct amount_msat htlc_calculate_dust(const struct channel *channel,
+					      u32 feerate_per_kw,
+					      enum side side)
+{
+	const struct htlc **committed, **adding, **removing;
+	struct amount_msat acc_dust = AMOUNT_MSAT(0);
+
+	gather_htlcs(tmpctx, channel, side,
+		     &committed, &removing, &adding);
+
+	htlc_dust(channel, committed, adding, removing,
+		  side, feerate_per_kw, &acc_dust);
+
+	return acc_dust;
+}
+
+bool htlc_dust_ok(const struct channel *channel,
+		  u32 feerate_per_kw,
+		  enum side side)
+{
+	struct amount_msat total_dusted;
+
+	total_dusted = htlc_calculate_dust(channel, feerate_per_kw, side);
+
+	return amount_msat_greater_eq(
+		channel->config[LOCAL].max_dust_htlc_exposure_msat,
+		total_dusted);
+}
+
 bool can_opener_afford_feerate(const struct channel *channel, u32 feerate_per_kw)
 {
 	struct amount_sat needed, fee;
@@ -1180,6 +1281,9 @@ bool channel_update_feerate(struct channel *channel, u32 feerate_per_kw)
 	if (!can_opener_afford_feerate(channel, feerate_per_kw))
 		return false;
 
+	if (!htlc_dust_ok(channel, feerate_per_kw, REMOTE))
+		return false;
+
 	status_debug("Setting %s feerate to %u",
 		     side_to_str(!channel->opener), feerate_per_kw);
 
diff --git a/channeld/full_channel.h b/channeld/full_channel.h
index ebfb4f856c76..6f674a75f2c0 100644
--- a/channeld/full_channel.h
+++ b/channeld/full_channel.h
@@ -180,6 +180,18 @@ u32 approx_max_feerate(const struct channel *channel);
  */
 bool can_opener_afford_feerate(const struct channel *channel, u32 feerate);
 
+/**
+ * htlc_dust_ok: will this feerate keep our dusted htlc's beneath
+ * 		 the updated feerate?
+ *
+ * @channel: The channel state
+ * @feerate_per_kw: new feerate to test ok'ness for
+ * @side: which side's htlcs to verify
+ */
+bool htlc_dust_ok(const struct channel *channel,
+		  u32 feerate_per_kw,
+		  enum side side);
+
 /**
  * channel_update_feerate: Change fee rate on non-opener side.
  * @channel: The channel
diff --git a/channeld/full_channel_error.h b/channeld/full_channel_error.h
index 49ca03a39e1a..05deffb869e1 100644
--- a/channeld/full_channel_error.h
+++ b/channeld/full_channel_error.h
@@ -20,6 +20,8 @@ enum channel_add_err {
 	CHANNEL_ERR_HTLC_BELOW_MINIMUM,
 	/* HTLC would push past max_accepted_htlcs */
 	CHANNEL_ERR_TOO_MANY_HTLCS,
+	/* HTLC would push dusted-htlcs above max_dust_htlc_exposure_msat */
+	CHANNEL_ERR_DUST_FAILURE,
 };
 
 enum channel_remove_err {
diff --git a/common/htlc_trim.c b/common/htlc_trim.c
index acd2f83740f1..e47c5fdb0be8 100644
--- a/common/htlc_trim.c
+++ b/common/htlc_trim.c
@@ -42,3 +42,15 @@ bool htlc_is_trimmed(enum side htlc_owner,
 		return true;
 	return amount_msat_less_sat(htlc_amount, htlc_min);
 }
+
+/*  Minimum amount of headroom we should use for
+ *  anticipated feerate adjustments */
+#define HTLC_FEE_MIN_RANGE 2530
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+u32 htlc_trim_feerate_ceiling(u32 feerate_per_kw)
+{
+	/* Add the greater of 1.25x or 2530 sat/kw */
+	return max(feerate_per_kw + feerate_per_kw / 4,
+		   feerate_per_kw + HTLC_FEE_MIN_RANGE);
+}
diff --git a/common/htlc_trim.h b/common/htlc_trim.h
index 113834cfd9c9..0044de45c00b 100644
--- a/common/htlc_trim.h
+++ b/common/htlc_trim.h
@@ -12,4 +12,7 @@ bool htlc_is_trimmed(enum side htlc_owner,
 		     enum side side,
 		     bool option_anchor_outputs);
 
+/* Calculate the our htlc-trimming buffer feerate
+ * (max(25%, 10s/vbyte) above feerate_per_kw) */
+u32 htlc_trim_feerate_ceiling(u32 feerate_per_kw);
 #endif /* LIGHTNING_COMMON_HTLC_TRIM_H */

From a22a684e7d9dec007c512390c1c50a778348f1e6 Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Fri, 1 Oct 2021 17:17:38 -0500
Subject: [PATCH 5/9] tests: check for incoming + outgoing dust limits

---
 tests/test_pay.py | 107 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/tests/test_pay.py b/tests/test_pay.py
index 7197516ec964..a604b8809b7b 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -2436,6 +2436,113 @@ def test_lockup_drain(node_factory, bitcoind):
         l2.pay(l1, total // 2)
 
 
+@pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs")
+def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams):
+    """ Try to hit the 'too much dust' limit, should fail the HTLC """
+    feerate = 10000
+
+    # elements txs are bigger so they become dusty faster
+    max_dust_limit_sat = 100000 if chainparams['elements'] else 50000
+    non_dust_htlc_val_sat = 20000 if chainparams['elements'] else 10000
+    htlc_val_sat = 10000 if chainparams['elements'] else 5000
+
+    l1, l2 = node_factory.line_graph(2, opts={'may_reconnect': True,
+                                              'feerates': (feerate, feerate, feerate, feerate),
+                                              'max-dust-htlc-exposure-msat': '{}sat'.format(max_dust_limit_sat),
+                                              'allow_warning': True})
+
+    # l2 holds all of l1's htlcs hostage
+    l2.rpc.dev_ignore_htlcs(id=l1.info['id'], ignore=True)
+
+    # l2's max dust limit is set to 100k
+    htlc_val_msat = htlc_val_sat * 1000
+    num_dusty_htlcs = max_dust_limit_sat // htlc_val_sat
+
+    # add a some non-dusty htlcs, these will fail when we raise the dust limit
+    route = l1.rpc.getroute(l2.info['id'], non_dust_htlc_val_sat * 1000, 1)['route']
+    for i in range(0, 3):
+        inv = l2.rpc.invoice((non_dust_htlc_val_sat * 1000), str(i + 100), str(i + 100))
+        l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
+        l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs')
+        res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])
+        assert res['status'] == 'pending'
+
+    # add some dusty-htlcs
+    route = l1.rpc.getroute(l2.info['id'], htlc_val_msat, 1)['route']
+    for i in range(0, num_dusty_htlcs):
+        inv = l2.rpc.invoice(htlc_val_msat, str(i), str(i))
+        l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
+        l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs')
+        res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])
+        assert res['status'] == 'pending'
+
+    # one more should tip it over, and return a payment failure
+    inv = l2.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs), str(num_dusty_htlcs))
+    l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
+    l1.daemon.wait_for_log('CHANNEL_ERR_DUST_FAILURE')
+    wait_for(lambda: only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])['status'] == 'failed')
+
+    # but we can still add a non dust htlc
+    route = l1.rpc.getroute(l2.info['id'], non_dust_htlc_val_sat * 1000, 1)['route']
+    inv = l2.rpc.invoice((10000 * 1000), str(120), str(120))
+    l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
+    l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs')
+    res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])
+    assert res['status'] == 'pending'
+
+    # Ok, adjust our feerate upward, so the non-dust htlcs are now dust
+    # note that this is above the buffer we've been keeping, so the channel
+    # should automatically fail
+    l1.set_feerates([feerate * 2] * 4, False)
+    l1.restart()
+
+    # the channel should fail -- too much dust
+    inv = l2.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs + 1), str(num_dusty_htlcs + 1))
+    with pytest.raises(RpcError, match=r'WIRE_UNKNOWN_NEXT_PEER'):
+        l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
+
+
+@pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs")
+def test_htlc_too_dusty_incoming(node_factory, bitcoind):
+    """ Try to hit the 'too much dust' limit, should fail the HTLC """
+    feerate = 30000
+    l1, l2, l3 = node_factory.line_graph(3, opts=[{'may_reconnect': True,
+                                                   'feerates': (feerate, feerate, feerate, feerate),
+                                                   'max-dust-htlc-exposure-msat': '200000sat'},
+                                                  {'may_reconnect': True,
+                                                   'feerates': (feerate, feerate, feerate, feerate),
+                                                   'max-dust-htlc-exposure-msat': '100000sat',
+                                                   'fee-base': 0,
+                                                   'fee-per-satoshi': 0},
+                                                  {'max-dust-htlc-exposure-msat': '500000sat'}],
+                                         wait_for_announce=True)
+
+    # on the l2->l3, and l3 holds all the htlcs hostage
+    # have l3 hold onto all the htlcs and not fulfill them
+    l3.rpc.dev_ignore_htlcs(id=l2.info['id'], ignore=True)
+
+    # l2's max dust limit is set to 100k
+    max_dust_limit_sat = 100000
+    htlc_val_sat = 10000
+    htlc_val_msat = htlc_val_sat * 1000
+    num_dusty_htlcs = max_dust_limit_sat // htlc_val_sat
+    route = l1.rpc.getroute(l3.info['id'], htlc_val_msat, 1)['route']
+
+    # l1 sends as much money as it can
+    for i in range(0, num_dusty_htlcs):
+        inv = l3.rpc.invoice(htlc_val_msat, str(i), str(i))
+        l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
+        l3.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs')
+        res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])
+        assert res['status'] == 'pending'
+
+    # one more should tip it over, and return a payment failure
+    inv = l3.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs), str(num_dusty_htlcs))
+    l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
+    l2.daemon.wait_for_log('failing immediately, as requested')
+    wait_for(lambda: only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])['status'] == 'failed')
+
+
 def test_error_returns_blockheight(node_factory, bitcoind):
     """Test that incorrect_or_unknown_payment_details returns block height"""
     l1, l2 = node_factory.line_graph(2)

From 778e7d707b41d76d0ea0d2012ff826dea9031f53 Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Mon, 4 Oct 2021 14:06:53 -0500
Subject: [PATCH 6/9] channeld: add in RFC notes for
 max_htlc_dust_exposure_msat

And update some behavior to check both sides on receipt of a
update_fee, as per the proposed spec.

https://github.com/lightningnetwork/lightning-rfc/pull/919
---
 channeld/channeld.c     | 20 ++++++++++++++++++++
 channeld/full_channel.c | 22 +++++++++++++++++++++-
 common/htlc_trim.c      |  6 +++++-
 3 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/channeld/channeld.c b/channeld/channeld.c
index 0de935bf0600..27610241a1a1 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -1292,6 +1292,20 @@ static void send_commit(struct peer *peer)
 		if (feerate_changes_done(peer->channel->fee_states, false)) {
 			u8 *msg;
 
+			/* BOLT-919 #2:
+			 *
+			 * A sending node:
+			 * - if the `dust_balance_on_counterparty_tx` at the
+			 *   new `dust_buffer_feerate` is superior to
+			 *   `max_dust_htlc_exposure_msat`:
+			 *   - MAY NOT send `update_fee`
+			 *   - MAY fail the channel
+			 * - if the `dust_balance_on_holder_tx` at the
+			 *   new `dust_buffer_feerate` is superior to
+			 *   the `max_dust_htlc_exposure_msat`:
+			 *   - MAY NOT send `update_fee`
+			 *   - MAY fail the channel
+			 */
 			/* Is this feerate update going to push the committed
 			 * htlcs over our allowed dust limits? */
 			if (!htlc_dust_ok(peer->channel, feerate_target, REMOTE)
@@ -3369,6 +3383,12 @@ static void handle_offer_htlc(struct peer *peer, const u8 *inmsg)
 		failstr = "Too many HTLCs";
 		goto failed;
 	case CHANNEL_ERR_DUST_FAILURE:
+		/* BOLT-919 #2:
+		 * - upon an outgoing HTLC:
+		 *   - if a HTLC's `amount_msat` is inferior the counterparty's...
+		 *   - SHOULD NOT send this HTLC
+		 *   - SHOULD fail this HTLC if it's forwarded
+		 */
 		failwiremsg = towire_temporary_channel_failure(inmsg, get_local_channel_update(inmsg, peer));
 		failstr = "HTLC too dusty, allowed dust limit reached";
 		goto failed;
diff --git a/channeld/full_channel.c b/channeld/full_channel.c
index 4fb9fc59bc53..6e1b39c87af3 100644
--- a/channeld/full_channel.c
+++ b/channeld/full_channel.c
@@ -801,6 +801,18 @@ static enum channel_add_err add_htlc(struct channel *channel,
 
 	if (amount_msat_greater(htlc_dust_amt,
 				channel->config[LOCAL].max_dust_htlc_exposure_msat)) {
+		/* BOLT-919 #2:
+		 * A node:
+		 * - upon an incoming HTLC:
+		 *   - if a HTLC's `amount_msat` is inferior to the
+		 *   counterparty's `dust_limit_satoshis` plus the HTLC-timeout fee
+		 *   at the `dust_buffer_feerate`: ...
+		 *   - SHOULD fail this HTLC once it's committed
+		 *   - SHOULD NOT reveal a preimage for this HTLC
+		*/
+		/* Note: Marking this as 'fail_immediate' and
+		 * NOT returning an ERR will fail this HTLC
+		 * once it's committed */
 		htlc->fail_immediate = true;
 		if (err_immediate_failures)
 			return CHANNEL_ERR_DUST_FAILURE;
@@ -1281,7 +1293,15 @@ bool channel_update_feerate(struct channel *channel, u32 feerate_per_kw)
 	if (!can_opener_afford_feerate(channel, feerate_per_kw))
 		return false;
 
-	if (!htlc_dust_ok(channel, feerate_per_kw, REMOTE))
+	/* BOLT-919 #2:
+	 * - if the `dust_balance_on_holder_tx` at the
+	 *   new `dust_buffer_feerate` is superior to
+	 *   the `max_dust_htlc_exposure_msat`:
+	 *   ...
+	 *   - MAY fail the channel
+	 */
+	if (!htlc_dust_ok(channel, feerate_per_kw, REMOTE) ||
+	    !htlc_dust_ok(channel, feerate_per_kw, LOCAL))
 		return false;
 
 	status_debug("Setting %s feerate to %u",
diff --git a/common/htlc_trim.c b/common/htlc_trim.c
index e47c5fdb0be8..dccfd26d993f 100644
--- a/common/htlc_trim.c
+++ b/common/htlc_trim.c
@@ -50,7 +50,11 @@ bool htlc_is_trimmed(enum side htlc_owner,
 
 u32 htlc_trim_feerate_ceiling(u32 feerate_per_kw)
 {
-	/* Add the greater of 1.25x or 2530 sat/kw */
+	/* BOLT-919 #2:
+	 *
+	 * `dust_buffer_feerate` is defined as the maximum
+	 * of either 2530 sats per kWU or 125% of the
+	 * current `feerate_per_kw`. */
 	return max(feerate_per_kw + feerate_per_kw / 4,
 		   feerate_per_kw + HTLC_FEE_MIN_RANGE);
 }

From 1ee58351301c173e525c15c1bd7c6d154b51f37c Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Wed, 6 Oct 2021 12:37:09 -0500
Subject: [PATCH 7/9] dusty htlcs: don't fail the channel, make it error a
 whole bunch

Let's make this a softer launch by just warning on the channel til the
feerates go back down.

You can also 'fix' this by upping your dust limit with
the `max-dust-htlc-exposure-msat` config.
---
 channeld/channeld.c | 8 ++++----
 tests/test_pay.py   | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/channeld/channeld.c b/channeld/channeld.c
index 27610241a1a1..8d26ad1ac9b4 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -1309,10 +1309,10 @@ static void send_commit(struct peer *peer)
 			/* Is this feerate update going to push the committed
 			 * htlcs over our allowed dust limits? */
 			if (!htlc_dust_ok(peer->channel, feerate_target, REMOTE)
-			   || !htlc_dust_ok(peer->channel, feerate_target, LOCAL))
-				/* We fail the channel. Oops */
-				peer_failed_err(peer->pps, &peer->channel_id,
-						"Too much dust to update fee");
+			    || !htlc_dust_ok(peer->channel, feerate_target, LOCAL))
+				peer_failed_warn(peer->pps, &peer->channel_id,
+						"Too much dust to update fee (Desired"
+						" feerate update %d)", feerate_target);
 
 			if (!channel_update_feerate(peer->channel, feerate_target))
 				status_failed(STATUS_FAIL_INTERNAL_ERROR,
diff --git a/tests/test_pay.py b/tests/test_pay.py
index a604b8809b7b..dd972de38f15 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -2496,9 +2496,9 @@ def test_htlc_too_dusty_outgoing(node_factory, bitcoind, chainparams):
     l1.set_feerates([feerate * 2] * 4, False)
     l1.restart()
 
-    # the channel should fail -- too much dust
+    # the channel should start warning -- too much dust
     inv = l2.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs + 1), str(num_dusty_htlcs + 1))
-    with pytest.raises(RpcError, match=r'WIRE_UNKNOWN_NEXT_PEER'):
+    with pytest.raises(RpcError, match=r'WIRE_TEMPORARY_CHANNEL_FAILURE'):
         l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret'])
 
 

From a8c061e499c04335396c6cbdd5eb9eaaa9d74d8a Mon Sep 17 00:00:00 2001
From: niftynei <niftynei@gmail.com>
Date: Thu, 7 Oct 2021 13:03:38 -0500
Subject: [PATCH 8/9] tests: raise dust limit on mpp test

Fails liquid-regtest otherwise; liquid tends to hit the dust limit
earlier than non-liquid tx, and MPP exacerbates this by divvying up
payments into dusty bits then attempting to shove them through the same
channel, hitting the dust max. The MPP then fails as not all the parts
were able to arrive at their destination.
---
 tests/test_pay.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/tests/test_pay.py b/tests/test_pay.py
index dd972de38f15..7a8b5b6f0c6a 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -3570,7 +3570,8 @@ def test_mpp_presplit(node_factory):
 
     l1, l2, l3 = node_factory.line_graph(
         3, fundamount=10**8, wait_for_announce=True,
-        opts={'wumbo': None}
+        opts={'wumbo': None,
+              'max-dust-htlc-exposure-msat': '500000sat'}
     )
 
     inv = l3.rpc.invoice(amt, 'lbl', 'desc')['bolt11']

From 8f68dbae4680549101eda5bf5df42c4c7943c28d Mon Sep 17 00:00:00 2001
From: Christian Decker <decker.christian@gmail.com>
Date: Fri, 22 Oct 2021 18:02:57 +0200
Subject: [PATCH 9/9] pytest: Create less dust in `test_delpay_payment_split`

We were triggering the dust panic.
---
 tests/test_pay.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/tests/test_pay.py b/tests/test_pay.py
index 7a8b5b6f0c6a..e7a0145b6dc6 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -3781,17 +3781,16 @@ def test_delpay_payment_split(node_factory, bitcoind):
     Test behavior of delpay with an MPP
     """
     MPP_TARGET_SIZE = 10**7  # Taken from libpluin-pay.c
-    amt = 5 * MPP_TARGET_SIZE
+    amt = 4 * MPP_TARGET_SIZE
 
     l1, l2, l3 = node_factory.line_graph(3, fundamount=10**5,
                                          wait_for_announce=True)
-
     inv = l3.rpc.invoice(amt, 'lbl', 'desc')
     l1.rpc.pay(inv['bolt11'])
 
     assert len(l1.rpc.listpays()['pays']) == 1
     delpay_result = l1.rpc.delpay(inv['payment_hash'], 'complete')['payments']
-    assert len(delpay_result) >= 5
+    assert len(delpay_result) >= 4
     assert len(l1.rpc.listpays()['pays']) == 0