Skip to content

Commit 6076d57

Browse files
committed
Add Channel to resource manager
The Channel struct introduced here has the core information that will be used by the resource manager to make forwarding decisions on HTLCs: - Reputation that this channel has accrued as an outgoing link in HTLC forwards. - Revenue (forwarding fees) that the channel has earned us as an incoming link. - Pending HTLCs this channel is currently holding as an outgoing link. - Bucket resources that are currently in use in general, congestion and protected.
1 parent 05d2ec7 commit 6076d57

1 file changed

Lines changed: 201 additions & 0 deletions

File tree

lightning/src/ln/resource_manager.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ use crate::{
2020
/// The minimum number of slots required for the general bucket to function.
2121
const MIN_GENERAL_BUCKET_SLOTS: u16 = 5;
2222

23+
/// The minimum number of accepted HTLCs required for a channel to be added to the resource
24+
/// manager. With the default bucket allocations of 40%/20%/40% (general/congestion/protected),
25+
/// the general bucket (40%) needs at least 5 usable HTLC slots to function effectively. To
26+
/// ensure the general bucket gets 5 slots at its 40% share, we need at least 12 total HTLC
27+
/// slots.
28+
const MIN_ACCEPTED_HTLCS: u16 = 12;
29+
30+
/// The minimum `max_htlc_value_in_flight_msat` required for a channel to be added to the resource
31+
/// manager. This corresponds to the default `min_funding_satoshis` of 1000 in
32+
/// [`crate::util::config::ChannelHandshakeLimits`], which is the smallest channel size LDK will
33+
/// accept.
34+
const MIN_MAX_IN_FLIGHT_MSAT: u64 = 1_000_000;
35+
36+
#[derive(Clone, PartialEq, Eq, Debug)]
37+
enum BucketAssigned {
38+
General,
39+
Congestion,
40+
Protected,
41+
}
42+
2343
struct GeneralBucket {
2444
/// Our SCID
2545
scid: u64,
@@ -260,6 +280,187 @@ impl BucketResources {
260280
}
261281
}
262282

283+
struct BucketAllocations {
284+
general_slots: u16,
285+
general_liquidity: u64,
286+
congestion_slots: u16,
287+
congestion_liquidity: u64,
288+
protected_slots: u16,
289+
protected_liquidity: u64,
290+
}
291+
292+
fn bucket_allocations(
293+
max_accepted_htlcs: u16, max_htlc_value_in_flight_msat: u64, general_pct: u8,
294+
congestion_pct: u8,
295+
) -> BucketAllocations {
296+
let general_slots = (max_accepted_htlcs as f64 * general_pct as f64 / 100.0).round() as u16;
297+
let general_liquidity =
298+
(max_htlc_value_in_flight_msat as f64 * general_pct as f64 / 100.0).round() as u64;
299+
300+
let congestion_slots =
301+
(max_accepted_htlcs as f64 * congestion_pct as f64 / 100.0).round() as u16;
302+
let congestion_liquidity =
303+
(max_htlc_value_in_flight_msat as f64 * congestion_pct as f64 / 100.0).round() as u64;
304+
305+
let protected_slots = max_accepted_htlcs - general_slots - congestion_slots;
306+
let protected_liquidity =
307+
max_htlc_value_in_flight_msat - general_liquidity - congestion_liquidity;
308+
309+
BucketAllocations {
310+
general_slots,
311+
general_liquidity,
312+
congestion_slots,
313+
congestion_liquidity,
314+
protected_slots,
315+
protected_liquidity,
316+
}
317+
}
318+
319+
#[derive(Debug, Clone)]
320+
struct PendingHTLC {
321+
incoming_amount_msat: u64,
322+
fee: u64,
323+
outgoing_accountable: bool,
324+
added_at_unix_seconds: u64,
325+
in_flight_risk: u64,
326+
bucket: BucketAssigned,
327+
}
328+
329+
#[derive(Debug, PartialEq, Eq, Hash)]
330+
struct HtlcRef {
331+
incoming_channel_id: u64,
332+
htlc_id: u64,
333+
}
334+
335+
struct Channel {
336+
/// The reputation this channel has accrued as an outgoing link.
337+
outgoing_reputation: DecayingAverage,
338+
339+
/// The revenue this channel has earned us as an incoming link.
340+
incoming_revenue: AggregatedWindowAverage,
341+
342+
/// HTLC Ref incoming channel -> pending HTLC outgoing.
343+
/// It tracks all the pending HTLCs where this channel is the outgoing link.
344+
pending_htlcs: HashMap<HtlcRef, PendingHTLC>,
345+
346+
general_bucket: GeneralBucket,
347+
congestion_bucket: BucketResources,
348+
/// SCID -> unix seconds timestamp
349+
/// Tracks which channels have misused the congestion bucket and the unix timestamp.
350+
last_congestion_misuse: HashMap<u64, u64>,
351+
protected_bucket: BucketResources,
352+
}
353+
354+
impl Channel {
355+
fn new(
356+
scid: u64, bucket_allocations: BucketAllocations, reputation_window: Duration,
357+
revenue_week_avg_duration: Duration, timestamp_unix_secs: u64,
358+
) -> Result<Self, ()> {
359+
const REVENUE_WEEK_MULTIPLIER: u8 = 6;
360+
361+
Ok(Channel {
362+
outgoing_reputation: DecayingAverage::new(timestamp_unix_secs, reputation_window),
363+
incoming_revenue: AggregatedWindowAverage::new(
364+
revenue_week_avg_duration,
365+
REVENUE_WEEK_MULTIPLIER,
366+
timestamp_unix_secs,
367+
),
368+
pending_htlcs: new_hash_map(),
369+
general_bucket: GeneralBucket::new(
370+
scid,
371+
bucket_allocations.general_slots,
372+
bucket_allocations.general_liquidity,
373+
)?,
374+
congestion_bucket: BucketResources::new(
375+
bucket_allocations.congestion_slots,
376+
bucket_allocations.congestion_liquidity,
377+
),
378+
last_congestion_misuse: new_hash_map(),
379+
protected_bucket: BucketResources::new(
380+
bucket_allocations.protected_slots,
381+
bucket_allocations.protected_liquidity,
382+
),
383+
})
384+
}
385+
386+
fn general_available<ES: EntropySource>(
387+
&mut self, incoming_amount_msat: u64, outgoing_channel_id: u64, entropy_source: &ES,
388+
) -> Result<bool, ()> {
389+
Ok(self.general_bucket.can_add_htlc(
390+
outgoing_channel_id,
391+
incoming_amount_msat,
392+
entropy_source,
393+
)?)
394+
}
395+
396+
fn congestion_eligible(
397+
&mut self, pending_htlcs_in_congestion: bool, incoming_amount_msat: u64,
398+
outgoing_channel_id: u64, at_timestamp: u64,
399+
) -> Result<bool, ()> {
400+
Ok(!pending_htlcs_in_congestion
401+
&& self.can_add_htlc_congestion(
402+
outgoing_channel_id,
403+
incoming_amount_msat,
404+
at_timestamp,
405+
)?)
406+
}
407+
408+
fn misused_congestion(&mut self, channel_id: u64, misuse_timestamp: u64) {
409+
self.last_congestion_misuse.insert(channel_id, misuse_timestamp);
410+
}
411+
412+
// Returns whether the outgoing channel has misused the congestion bucket in the last two
413+
// weeks.
414+
fn has_misused_congestion(
415+
&mut self, outgoing_scid: u64, at_timestamp: u64,
416+
) -> Result<bool, ()> {
417+
match self.last_congestion_misuse.entry(outgoing_scid) {
418+
Entry::Vacant(_) => Ok(false),
419+
Entry::Occupied(last_misuse) => {
420+
if at_timestamp < *last_misuse.get() {
421+
return Err(());
422+
}
423+
// If the last misuse of the congestion bucket was over more than two
424+
// weeks ago, remove the entry.
425+
const TWO_WEEKS: u64 = 2016 * 10 * 60;
426+
let since_last_misuse = at_timestamp - last_misuse.get();
427+
if since_last_misuse < TWO_WEEKS {
428+
return Ok(true);
429+
} else {
430+
last_misuse.remove();
431+
return Ok(false);
432+
}
433+
},
434+
}
435+
}
436+
437+
fn can_add_htlc_congestion(
438+
&mut self, channel_id: u64, htlc_amount_msat: u64, at_timestamp: u64,
439+
) -> Result<bool, ()> {
440+
let congestion_resources_available =
441+
self.congestion_bucket.resources_available(htlc_amount_msat);
442+
let misused_congestion = self.has_misused_congestion(channel_id, at_timestamp)?;
443+
444+
let below_liquidity_limit = htlc_amount_msat
445+
<= self.congestion_bucket.liquidity_allocated
446+
/ self.congestion_bucket.slots_allocated as u64;
447+
448+
Ok(congestion_resources_available && !misused_congestion && below_liquidity_limit)
449+
}
450+
451+
fn sufficient_reputation(
452+
&mut self, in_flight_htlc_risk: u64, outgoing_reputation: i64,
453+
outgoing_in_flight_risk: u64, at_timestamp: u64,
454+
) -> bool {
455+
let incoming_revenue_threshold = self.incoming_revenue.value_at_timestamp(at_timestamp);
456+
457+
outgoing_reputation
458+
.saturating_sub(i64::try_from(outgoing_in_flight_risk).unwrap_or(i64::MAX))
459+
.saturating_sub(i64::try_from(in_flight_htlc_risk).unwrap_or(i64::MAX))
460+
>= incoming_revenue_threshold
461+
}
462+
}
463+
263464
/// A weighted average that decays over a specified window.
264465
///
265466
/// It enables tracking of historical behavior without storing individual data points.

0 commit comments

Comments
 (0)