@@ -315,6 +315,51 @@ impl Default for ChannelHandshakeLimits {
315315 }
316316}
317317
318+ /// Options for how to set the max dust HTLC exposure allowed on a channel. See
319+ /// [`ChannelConfig::max_dust_htlc_exposure_msat`] for details.
320+ #[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
321+ pub enum MaxDustHTLCExposure {
322+ /// This sets a fixed limit on the total dust exposure in millisatoshis. Setting this too low
323+ /// may prevent the sending or receipt of low-value HTLCs on high-traffic nodes, however this
324+ /// limit is very important to prevent stealing of large amounts of dust HTLCs by miners
325+ /// through [fee griefing
326+ /// attacks](https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-May/002714.html).
327+ ///
328+ /// Note that if the feerate increases significantly, without a manual increase
329+ /// to this maximum the channel may be unable to send/receive HTLCs between the maximum dust
330+ /// exposure and the new minimum value for HTLCs to be economically viable to claim.
331+ FixedLimitMsat ( u64 ) ,
332+ /// This sets a multiplier on the estimated high priority feerate (sats/KW, as obtained from
333+ /// [`FeeEstimator`]) to determine the maximum allowed dust exposure. If this variant is used
334+ /// then the maximum dust exposure in millisatoshis is calculated as:
335+ /// `high_priority_feerate_per_kw * value`. For example, with our default value
336+ /// `FeeRateMultiplier(5000)`:
337+ ///
338+ /// - For the minimum fee rate of 1 sat/vByte (250 sat/KW, although the minimum
339+ /// defaults to 253 sats/KW for rounding, see [`FeeEstimator`]), the max dust exposure would
340+ /// be 253 * 5000 = 1,265,000 msats.
341+ /// - For a fee rate of 30 sat/vByte (7500 sat/KW), the max dust exposure would be
342+ /// 7500 * 5000 = 37,500,000 msats.
343+ ///
344+ /// This allows the maximum dust exposure to automatically scale with fee rate changes.
345+ ///
346+ /// Note, if you're using a third-party fee estimator, this may leave you more exposed to a
347+ /// fee griefing attack, where your fee estimator may purposely overestimate the fee rate,
348+ /// causing you to accept more dust HTLCs than you would otherwise.
349+ ///
350+ /// # Backwards Compatibility
351+ /// This variant only became available in LDK 0.0.116, so if you downgrade to a prior version
352+ /// by default this will be set to a [`Self::FixedLimitMsat`] of 5,000,000 msat.
353+ ///
354+ /// [`FeeEstimator`]: crate::chain::chaininterface::FeeEstimator
355+ FeeRateMultiplier ( u64 ) ,
356+ }
357+
358+ impl_writeable_tlv_based_enum ! ( MaxDustHTLCExposure , ;
359+ ( 1 , FixedLimitMsat ) ,
360+ ( 3 , FeeRateMultiplier ) ,
361+ ) ;
362+
318363/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
319364/// with our counterparty.
320365#[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
@@ -374,13 +419,11 @@ pub struct ChannelConfig {
374419 /// (specifically the zero fee HTLC transaction variant), this threshold no longer takes into
375420 /// account the HTLC transaction fee as it is zero.
376421 ///
377- /// This limit is applied for sent, forwarded, and received HTLCs and limits the total
378- /// exposure across all three types per-channel. Setting this too low may prevent the
379- /// sending or receipt of low-value HTLCs on high-traffic nodes, and this limit is very
380- /// important to prevent stealing of dust HTLCs by miners.
422+ /// The selected limit is applied for sent, forwarded, and received HTLCs and limits the total
423+ /// exposure across all three types per-channel.
381424 ///
382- /// Default value: 5_000_000 msat .
383- pub max_dust_htlc_exposure_msat : u64 ,
425+ /// Default value: [`MaxDustHTLCExposure::FeeRateMultiplier`] with a multiplier of 5000 .
426+ pub max_dust_htlc_exposure : MaxDustHTLCExposure ,
384427 /// The additional fee we're willing to pay to avoid waiting for the counterparty's
385428 /// `to_self_delay` to reclaim funds.
386429 ///
@@ -451,7 +494,7 @@ impl ChannelConfig {
451494 self . cltv_expiry_delta = cltv_expiry_delta;
452495 }
453496 if let Some ( max_dust_htlc_exposure_msat) = update. max_dust_htlc_exposure_msat {
454- self . max_dust_htlc_exposure_msat = max_dust_htlc_exposure_msat;
497+ self . max_dust_htlc_exposure = max_dust_htlc_exposure_msat;
455498 }
456499 if let Some ( force_close_avoidance_max_fee_satoshis) = update. force_close_avoidance_max_fee_satoshis {
457500 self . force_close_avoidance_max_fee_satoshis = force_close_avoidance_max_fee_satoshis;
@@ -466,32 +509,75 @@ impl Default for ChannelConfig {
466509 forwarding_fee_proportional_millionths : 0 ,
467510 forwarding_fee_base_msat : 1000 ,
468511 cltv_expiry_delta : 6 * 12 , // 6 blocks/hour * 12 hours
469- max_dust_htlc_exposure_msat : 5_000_000 ,
512+ max_dust_htlc_exposure : MaxDustHTLCExposure :: FeeRateMultiplier ( 5000 ) ,
470513 force_close_avoidance_max_fee_satoshis : 1000 ,
471514 accept_underpaying_htlcs : false ,
472515 }
473516 }
474517}
475518
476- impl_writeable_tlv_based ! ( ChannelConfig , {
477- ( 0 , forwarding_fee_proportional_millionths, required) ,
478- ( 1 , accept_underpaying_htlcs, ( default_value, false ) ) ,
479- ( 2 , forwarding_fee_base_msat, required) ,
480- ( 4 , cltv_expiry_delta, required) ,
481- ( 6 , max_dust_htlc_exposure_msat, required) ,
482- // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
483- // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
484- // the next required type of 10, which if seen by the old serialization will always fail.
485- ( 10 , force_close_avoidance_max_fee_satoshis, required) ,
486- } ) ;
519+ impl crate :: util:: ser:: Writeable for ChannelConfig {
520+ fn write < W : crate :: util:: ser:: Writer > ( & self , writer : & mut W ) -> Result < ( ) , crate :: io:: Error > {
521+ let max_dust_htlc_exposure_msat_fixed_limit = match self . max_dust_htlc_exposure {
522+ MaxDustHTLCExposure :: FixedLimitMsat ( limit) => limit,
523+ MaxDustHTLCExposure :: FeeRateMultiplier ( _) => 5_000_000 ,
524+ } ;
525+ write_tlv_fields ! ( writer, {
526+ ( 0 , self . forwarding_fee_proportional_millionths, required) ,
527+ ( 1 , self . accept_underpaying_htlcs, ( default_value, false ) ) ,
528+ ( 2 , self . forwarding_fee_base_msat, required) ,
529+ ( 3 , self . max_dust_htlc_exposure, required) ,
530+ ( 4 , self . cltv_expiry_delta, required) ,
531+ ( 6 , max_dust_htlc_exposure_msat_fixed_limit, required) ,
532+ // ChannelConfig serialized this field with a required type of 8 prior to the introduction of
533+ // LegacyChannelConfig. To make sure that serialization is not compatible with this one, we use
534+ // the next required type of 10, which if seen by the old serialization will always fail.
535+ ( 10 , self . force_close_avoidance_max_fee_satoshis, required) ,
536+ } ) ;
537+ Ok ( ( ) )
538+ }
539+ }
540+
541+ impl crate :: util:: ser:: Readable for ChannelConfig {
542+ fn read < R : crate :: io:: Read > ( reader : & mut R ) -> Result < Self , crate :: ln:: msgs:: DecodeError > {
543+ let mut forwarding_fee_proportional_millionths = 0 ;
544+ let mut accept_underpaying_htlcs = false ;
545+ let mut forwarding_fee_base_msat = 1000 ;
546+ let mut cltv_expiry_delta = 6 * 12 ;
547+ let mut max_dust_htlc_exposure_msat = None ;
548+ let mut max_dust_htlc_exposure_enum = None ;
549+ let mut force_close_avoidance_max_fee_satoshis = 1000 ;
550+ read_tlv_fields ! ( reader, {
551+ ( 0 , forwarding_fee_proportional_millionths, required) ,
552+ ( 1 , accept_underpaying_htlcs, ( default_value, false ) ) ,
553+ ( 2 , forwarding_fee_base_msat, required) ,
554+ ( 3 , max_dust_htlc_exposure_enum, option) ,
555+ ( 4 , cltv_expiry_delta, required) ,
556+ // Has always been written, but became optionally read in 0.0.116
557+ ( 6 , max_dust_htlc_exposure_msat, option) ,
558+ ( 10 , force_close_avoidance_max_fee_satoshis, required) ,
559+ } ) ;
560+ let max_dust_htlc_fixed_limit = max_dust_htlc_exposure_msat. unwrap_or ( 5_000_000 ) ;
561+ let max_dust_htlc_exposure_msat = max_dust_htlc_exposure_enum
562+ . unwrap_or ( MaxDustHTLCExposure :: FixedLimitMsat ( max_dust_htlc_fixed_limit) ) ;
563+ Ok ( Self {
564+ forwarding_fee_proportional_millionths,
565+ accept_underpaying_htlcs,
566+ forwarding_fee_base_msat,
567+ cltv_expiry_delta,
568+ max_dust_htlc_exposure : max_dust_htlc_exposure_msat,
569+ force_close_avoidance_max_fee_satoshis,
570+ } )
571+ }
572+ }
487573
488574/// A parallel struct to [`ChannelConfig`] to define partial updates.
489575#[ allow( missing_docs) ]
490576pub struct ChannelConfigUpdate {
491577 pub forwarding_fee_proportional_millionths : Option < u32 > ,
492578 pub forwarding_fee_base_msat : Option < u32 > ,
493579 pub cltv_expiry_delta : Option < u16 > ,
494- pub max_dust_htlc_exposure_msat : Option < u64 > ,
580+ pub max_dust_htlc_exposure_msat : Option < MaxDustHTLCExposure > ,
495581 pub force_close_avoidance_max_fee_satoshis : Option < u64 > ,
496582}
497583
@@ -513,7 +599,7 @@ impl From<ChannelConfig> for ChannelConfigUpdate {
513599 forwarding_fee_proportional_millionths : Some ( config. forwarding_fee_proportional_millionths ) ,
514600 forwarding_fee_base_msat : Some ( config. forwarding_fee_base_msat ) ,
515601 cltv_expiry_delta : Some ( config. cltv_expiry_delta ) ,
516- max_dust_htlc_exposure_msat : Some ( config. max_dust_htlc_exposure_msat ) ,
602+ max_dust_htlc_exposure_msat : Some ( config. max_dust_htlc_exposure ) ,
517603 force_close_avoidance_max_fee_satoshis : Some ( config. force_close_avoidance_max_fee_satoshis ) ,
518604 }
519605 }
@@ -546,12 +632,17 @@ impl Default for LegacyChannelConfig {
546632
547633impl crate :: util:: ser:: Writeable for LegacyChannelConfig {
548634 fn write < W : crate :: util:: ser:: Writer > ( & self , writer : & mut W ) -> Result < ( ) , crate :: io:: Error > {
635+ let max_dust_htlc_exposure_msat_fixed_limit = match self . options . max_dust_htlc_exposure {
636+ MaxDustHTLCExposure :: FixedLimitMsat ( limit) => limit,
637+ MaxDustHTLCExposure :: FeeRateMultiplier ( _) => 5_000_000 ,
638+ } ;
549639 write_tlv_fields ! ( writer, {
550640 ( 0 , self . options. forwarding_fee_proportional_millionths, required) ,
551- ( 1 , self . options . max_dust_htlc_exposure_msat , ( default_value , 5_000_000 ) ) ,
641+ ( 1 , max_dust_htlc_exposure_msat_fixed_limit , required ) ,
552642 ( 2 , self . options. cltv_expiry_delta, required) ,
553643 ( 3 , self . options. force_close_avoidance_max_fee_satoshis, ( default_value, 1000 ) ) ,
554644 ( 4 , self . announced_channel, required) ,
645+ ( 5 , self . options. max_dust_htlc_exposure, required) ,
555646 ( 6 , self . commit_upfront_shutdown_pubkey, required) ,
556647 ( 8 , self . options. forwarding_fee_base_msat, required) ,
557648 } ) ;
@@ -562,25 +653,32 @@ impl crate::util::ser::Writeable for LegacyChannelConfig {
562653impl crate :: util:: ser:: Readable for LegacyChannelConfig {
563654 fn read < R : crate :: io:: Read > ( reader : & mut R ) -> Result < Self , crate :: ln:: msgs:: DecodeError > {
564655 let mut forwarding_fee_proportional_millionths = 0 ;
565- let mut max_dust_htlc_exposure_msat = 5_000_000 ;
656+ let mut max_dust_htlc_exposure_msat_fixed_limit = None ;
566657 let mut cltv_expiry_delta = 0 ;
567658 let mut force_close_avoidance_max_fee_satoshis = 1000 ;
568659 let mut announced_channel = false ;
569660 let mut commit_upfront_shutdown_pubkey = false ;
570661 let mut forwarding_fee_base_msat = 0 ;
662+ let mut max_dust_htlc_exposure_enum = None ;
571663 read_tlv_fields ! ( reader, {
572664 ( 0 , forwarding_fee_proportional_millionths, required) ,
573- ( 1 , max_dust_htlc_exposure_msat, ( default_value, 5_000_000u64 ) ) ,
665+ // Has always been written, but became optionally read in 0.0.116
666+ ( 1 , max_dust_htlc_exposure_msat_fixed_limit, option) ,
574667 ( 2 , cltv_expiry_delta, required) ,
575668 ( 3 , force_close_avoidance_max_fee_satoshis, ( default_value, 1000u64 ) ) ,
576669 ( 4 , announced_channel, required) ,
670+ ( 5 , max_dust_htlc_exposure_enum, option) ,
577671 ( 6 , commit_upfront_shutdown_pubkey, required) ,
578672 ( 8 , forwarding_fee_base_msat, required) ,
579673 } ) ;
674+ let max_dust_htlc_exposure_msat_fixed_limit =
675+ max_dust_htlc_exposure_msat_fixed_limit. unwrap_or ( 5_000_000 ) ;
676+ let max_dust_htlc_exposure_msat = max_dust_htlc_exposure_enum
677+ . unwrap_or ( MaxDustHTLCExposure :: FixedLimitMsat ( max_dust_htlc_exposure_msat_fixed_limit) ) ;
580678 Ok ( Self {
581679 options : ChannelConfig {
582680 forwarding_fee_proportional_millionths,
583- max_dust_htlc_exposure_msat,
681+ max_dust_htlc_exposure : max_dust_htlc_exposure_msat,
584682 cltv_expiry_delta,
585683 force_close_avoidance_max_fee_satoshis,
586684 forwarding_fee_base_msat,
0 commit comments