Skip to content

Commit 4970065

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 4462cf3 commit 4970065

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

lightning/src/ln/resource_manager.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
use bitcoin::hashes::{sha256d::Hash as Sha256dHash, Hash};
22
use core::time::Duration;
33
use hashbrown::hash_map::Entry;
4+
use std::time::{SystemTime, UNIX_EPOCH};
45

56
use crate::prelude::{new_hash_map, HashMap};
67

8+
#[derive(Clone, PartialEq, Eq, Debug)]
9+
enum BucketAssigned {
10+
General,
11+
Congestion,
12+
Protected,
13+
}
14+
715
struct GeneralBucket {
816
/// Our SCID
917
scid: u64,
@@ -230,6 +238,156 @@ impl BucketResources {
230238
}
231239
}
232240

241+
#[derive(Debug, Clone)]
242+
struct PendingHTLC {
243+
incoming_channel: u64,
244+
incoming_amount_msat: u64,
245+
fee: u64,
246+
outgoing_channel: u64,
247+
outgoing_accountable: bool,
248+
added_at_unix_seconds: u64,
249+
in_flight_risk: u64,
250+
bucket: BucketAssigned,
251+
}
252+
253+
#[derive(Debug, PartialEq, Eq, Hash)]
254+
struct HtlcRef {
255+
incoming_channel_id: u64,
256+
htlc_id: u64,
257+
}
258+
259+
struct Channel {
260+
/// The reputation this channel has accrued as an outgoing link.
261+
outgoing_reputation: DecayingAverage,
262+
263+
/// The revenue this channel has earned us as an incoming link.
264+
incoming_revenue: RevenueAverage,
265+
266+
/// Pending HTLCs as an outgoing channel.
267+
pending_htlcs: HashMap<HtlcRef, PendingHTLC>,
268+
269+
general_bucket: GeneralBucket,
270+
congestion_bucket: BucketResources,
271+
last_congestion_misuse: HashMap<u64, u64>,
272+
protected_bucket: BucketResources,
273+
}
274+
275+
impl Channel {
276+
fn new(
277+
scid: u64, max_htlc_value_in_flight_msat: u64, max_accepted_htlcs: u16,
278+
general_bucket_pct: u8, congestion_bucket_pct: u8, protected_bucket_pct: u8,
279+
window: Duration, window_count: u8,
280+
) -> Self {
281+
let general_bucket_slots_allocated = max_accepted_htlcs * general_bucket_pct as u16 / 100;
282+
let general_bucket_liquidity_allocated =
283+
max_htlc_value_in_flight_msat * general_bucket_pct as u64 / 100;
284+
285+
let congestion_bucket_slots_allocated =
286+
max_accepted_htlcs * congestion_bucket_pct as u16 / 100;
287+
let congestion_bucket_liquidity_allocated =
288+
max_htlc_value_in_flight_msat * congestion_bucket_pct as u64 / 100;
289+
290+
let protected_bucket_slots_allocated =
291+
max_accepted_htlcs * protected_bucket_pct as u16 / 100;
292+
let protected_bucket_liquidity_allocated =
293+
max_htlc_value_in_flight_msat * protected_bucket_pct as u64 / 100;
294+
295+
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
296+
Channel {
297+
outgoing_reputation: DecayingAverage::new(now, window * window_count.into()),
298+
incoming_revenue: RevenueAverage::new(window, window_count, now),
299+
pending_htlcs: new_hash_map(),
300+
general_bucket: GeneralBucket::new(
301+
scid,
302+
general_bucket_slots_allocated,
303+
general_bucket_liquidity_allocated,
304+
),
305+
congestion_bucket: BucketResources::new(
306+
congestion_bucket_slots_allocated,
307+
congestion_bucket_liquidity_allocated,
308+
),
309+
last_congestion_misuse: new_hash_map(),
310+
protected_bucket: BucketResources::new(
311+
protected_bucket_slots_allocated,
312+
protected_bucket_liquidity_allocated,
313+
),
314+
}
315+
}
316+
317+
fn general_available(
318+
&mut self, incoming_amount_msat: u64, outgoing_channel_id: u64, salt: Option<[u8; 32]>,
319+
) -> Result<bool, ()> {
320+
Ok(self.general_bucket.can_add_htlc(outgoing_channel_id, incoming_amount_msat, salt)?)
321+
}
322+
323+
fn congestion_eligible(
324+
&mut self, pending_htlcs_in_congestion: bool, incoming_amount_msat: u64,
325+
outgoing_channel_id: u64, revenue_window: Duration, at_timestamp: u64,
326+
) -> bool {
327+
!pending_htlcs_in_congestion
328+
&& self.can_add_htlc_congestion(
329+
outgoing_channel_id,
330+
incoming_amount_msat,
331+
revenue_window,
332+
at_timestamp,
333+
)
334+
}
335+
336+
fn misused_congestion(&mut self, channel_id: u64, misuse_timestamp: u64) {
337+
self.last_congestion_misuse.insert(channel_id, misuse_timestamp);
338+
}
339+
340+
// Returns whether the outgoing channel has misused the congestion bucket during our last
341+
// revenue window (two weeks by default).
342+
fn has_misused_congestion(
343+
&mut self, outgoing_scid: u64, at_timestamp: u64, revenue_window: Duration,
344+
) -> bool {
345+
match self.last_congestion_misuse.entry(outgoing_scid) {
346+
Entry::Vacant(_) => false,
347+
Entry::Occupied(last_misuse) => {
348+
// If the last misuse of the congestion bucket was over 2 weeks ago, remove
349+
// the entry.
350+
debug_assert!(at_timestamp >= *last_misuse.get());
351+
let since_last_misuse = Duration::from_secs(at_timestamp - last_misuse.get());
352+
if since_last_misuse < revenue_window {
353+
return true;
354+
} else {
355+
last_misuse.remove();
356+
return false;
357+
}
358+
},
359+
}
360+
}
361+
362+
fn can_add_htlc_congestion(
363+
&mut self, channel_id: u64, htlc_amount_msat: u64, revenue_window: Duration,
364+
at_timestamp: u64,
365+
) -> bool {
366+
let congestion_resources_available =
367+
self.congestion_bucket.resources_available(htlc_amount_msat);
368+
let misused_congestion =
369+
self.has_misused_congestion(channel_id, at_timestamp, revenue_window);
370+
371+
let below_slot_limit = htlc_amount_msat
372+
<= self.congestion_bucket.liquidity_allocated
373+
/ self.congestion_bucket.slots_allocated as u64;
374+
375+
congestion_resources_available && !misused_congestion && below_slot_limit
376+
}
377+
378+
fn sufficient_reputation(
379+
&mut self, in_flight_htlc_risk: u64, outgoing_reputation: i64,
380+
outgoing_in_flight_risk: u64, at_timestamp: u64,
381+
) -> Result<bool, ()> {
382+
let incoming_revenue_threshold = self.incoming_revenue.value_at_timestamp(at_timestamp)?;
383+
384+
Ok(outgoing_reputation
385+
.saturating_sub(i64::try_from(outgoing_in_flight_risk).unwrap_or(i64::MAX))
386+
.saturating_sub(i64::try_from(in_flight_htlc_risk).unwrap_or(i64::MAX))
387+
>= incoming_revenue_threshold)
388+
}
389+
}
390+
233391
struct DecayingAverage {
234392
value: i64,
235393
last_updated: u64,

0 commit comments

Comments
 (0)