|
| 1 | +use crate::logging::MutinyLogger; |
| 2 | +use crate::node::NetworkGraph; |
| 3 | +use crate::scorer::HubPreferentialScorer; |
| 4 | +use crate::utils::Mutex; |
| 5 | +use bitcoin::secp256k1::PublicKey; |
| 6 | +use lightning::ln::channelmanager::ChannelDetails; |
| 7 | +use lightning::ln::features::ChannelFeatures; |
| 8 | +use lightning::ln::msgs::LightningError; |
| 9 | +use lightning::routing::gossip::NodeId; |
| 10 | +use lightning::routing::router::{ |
| 11 | + BlindedTail, DefaultRouter, InFlightHtlcs, Path, Payee, Route, RouteHop, RouteParameters, |
| 12 | + Router, |
| 13 | +}; |
| 14 | +use lightning::routing::scoring::ProbabilisticScoringFeeParameters; |
| 15 | +use lightning::util::ser::Writeable; |
| 16 | +use log::{info, warn}; |
| 17 | +use std::sync::Arc; |
| 18 | + |
| 19 | +type LdkRouter = DefaultRouter< |
| 20 | + Arc<NetworkGraph>, |
| 21 | + Arc<MutinyLogger>, |
| 22 | + Arc<Mutex<HubPreferentialScorer>>, |
| 23 | + ProbabilisticScoringFeeParameters, |
| 24 | + HubPreferentialScorer, |
| 25 | +>; |
| 26 | + |
| 27 | +pub struct MutinyRouter { |
| 28 | + network_graph: Arc<NetworkGraph>, |
| 29 | + lsp_key: Option<PublicKey>, |
| 30 | + router: LdkRouter, |
| 31 | +} |
| 32 | + |
| 33 | +impl MutinyRouter { |
| 34 | + pub fn new( |
| 35 | + network_graph: Arc<NetworkGraph>, |
| 36 | + lsp_key: Option<PublicKey>, |
| 37 | + logger: Arc<MutinyLogger>, |
| 38 | + random_seed_bytes: [u8; 32], |
| 39 | + scorer: Arc<Mutex<HubPreferentialScorer>>, |
| 40 | + score_params: ProbabilisticScoringFeeParameters, |
| 41 | + ) -> Self { |
| 42 | + let router = DefaultRouter::new( |
| 43 | + network_graph.clone(), |
| 44 | + logger, |
| 45 | + random_seed_bytes, |
| 46 | + scorer, |
| 47 | + score_params, |
| 48 | + ); |
| 49 | + |
| 50 | + Self { |
| 51 | + network_graph, |
| 52 | + lsp_key, |
| 53 | + router, |
| 54 | + } |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +impl Router for MutinyRouter { |
| 59 | + fn find_route( |
| 60 | + &self, |
| 61 | + payer: &PublicKey, |
| 62 | + route_params: &RouteParameters, |
| 63 | + first_hops: Option<&[&ChannelDetails]>, |
| 64 | + inflight_htlcs: InFlightHtlcs, |
| 65 | + ) -> Result<Route, LightningError> { |
| 66 | + match &route_params.payment_params.payee { |
| 67 | + Payee::Clear { .. } => { |
| 68 | + self.router |
| 69 | + .find_route(payer, route_params, first_hops, inflight_htlcs) |
| 70 | + } |
| 71 | + Payee::Blinded { |
| 72 | + route_hints, |
| 73 | + features: _, |
| 74 | + } => { |
| 75 | + // if we have no LSP, then handle normally |
| 76 | + if self.lsp_key.is_none() { |
| 77 | + return self |
| 78 | + .router |
| 79 | + .find_route(payer, route_params, first_hops, inflight_htlcs); |
| 80 | + } |
| 81 | + |
| 82 | + let (blinded_info, blinded_path) = route_hints.first().unwrap(); |
| 83 | + let graph_lock = self.network_graph.read_only(); |
| 84 | + let lsp_node_id = NodeId::from_pubkey(&self.lsp_key.unwrap()); |
| 85 | + let node_info = graph_lock.node(&lsp_node_id).unwrap(); |
| 86 | + |
| 87 | + let amt = route_params.final_value_msat; |
| 88 | + |
| 89 | + // first our channel with enough capacity |
| 90 | + let first_hops = first_hops.unwrap_or(&[]); |
| 91 | + let first = first_hops |
| 92 | + .iter() |
| 93 | + .find_map(|c| { |
| 94 | + if c.outbound_capacity_msat >= amt { |
| 95 | + Some(c) |
| 96 | + } else { |
| 97 | + None |
| 98 | + } |
| 99 | + }) |
| 100 | + .unwrap(); |
| 101 | + |
| 102 | + let channel_features = |
| 103 | + ChannelFeatures::from_be_bytes(first.counterparty.features.encode()); |
| 104 | + |
| 105 | + let scid = scid_from_parts(467591, 1, 0); |
| 106 | + warn!("scid: {}", scid); |
| 107 | + |
| 108 | + let cltv_expiry_delta = first.config.unwrap().cltv_expiry_delta; |
| 109 | + let hops = vec![ |
| 110 | + RouteHop { |
| 111 | + pubkey: self.lsp_key.unwrap(), |
| 112 | + node_features: node_info |
| 113 | + .announcement_info |
| 114 | + .as_ref() |
| 115 | + .unwrap() |
| 116 | + .features |
| 117 | + .clone(), |
| 118 | + short_channel_id: first.get_outbound_payment_scid().unwrap(), |
| 119 | + channel_features: channel_features.clone(), |
| 120 | + fee_msat: 0, // 0 for own channel |
| 121 | + cltv_expiry_delta: 0, // 0 for own channel |
| 122 | + maybe_announced_channel: false, |
| 123 | + }, |
| 124 | + RouteHop { |
| 125 | + pubkey: blinded_path.introduction_node_id, |
| 126 | + node_features: node_info |
| 127 | + .announcement_info |
| 128 | + .as_ref() |
| 129 | + .unwrap() |
| 130 | + .features |
| 131 | + .clone(), |
| 132 | + short_channel_id: 17112782831943311000, // fixme |
| 133 | + channel_features, |
| 134 | + fee_msat: 10_000, // put high value just to try |
| 135 | + cltv_expiry_delta: cltv_expiry_delta as u32, |
| 136 | + maybe_announced_channel: false, |
| 137 | + }, |
| 138 | + ]; |
| 139 | + |
| 140 | + let blinded_tail = Some(BlindedTail { |
| 141 | + hops: blinded_path.blinded_hops.clone(), |
| 142 | + blinding_point: blinded_path.blinding_point, |
| 143 | + excess_final_cltv_expiry_delta: blinded_info.cltv_expiry_delta as u32, |
| 144 | + final_value_msat: amt, |
| 145 | + }); |
| 146 | + |
| 147 | + let path = Path { hops, blinded_tail }; |
| 148 | + |
| 149 | + Ok(Route { |
| 150 | + paths: vec![path], |
| 151 | + route_params: Some(route_params.clone()), |
| 152 | + }) |
| 153 | + } |
| 154 | + } |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +/// Constructs a `short_channel_id` using the components pieces. Results in an error |
| 159 | +/// if the block height, tx index, or vout index overflow the maximum sizes. |
| 160 | +pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> u64 { |
| 161 | + (block << 40) | (tx_index << 16) | vout_index |
| 162 | +} |
0 commit comments