Skip to content

Commit

Permalink
Merge pull request #430 from getlipa/feature/cap-routing-fees
Browse files Browse the repository at this point in the history
Restrict routing fees to reasonable values
  • Loading branch information
danielgranhao authored Jun 27, 2023
2 parents ee33bfd + 149e4e8 commit a3def9b
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 16 deletions.
24 changes: 19 additions & 5 deletions eel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod invoice;
mod logger;
mod random;
mod rapid_sync_client;
mod router;
mod storage_persister;
mod task_manager;
mod test_utils;
Expand Down Expand Up @@ -55,6 +56,8 @@ use crate::task_manager::{PeriodConfig, RestartIfFailedPeriod, TaskManager, Task
use crate::tx_broadcaster::TxBroadcaster;
use crate::types::{ChainMonitor, ChannelManager, PeerManager, RapidGossipSync, Router, TxSync};

pub use crate::router::MaxRoutingFeeMode;
use crate::router::{FeeLimitingRouter, SimpleMaxRoutingFeeStrategy};
use bitcoin::hashes::hex::ToHex;
pub use bitcoin::Network;
use cipher::consts::U32;
Expand Down Expand Up @@ -116,6 +119,7 @@ pub struct LightningNode {
peer_manager: Arc<PeerManager>,
task_manager: Arc<Mutex<TaskManager>>,
data_store: Arc<Mutex<DataStore>>,
max_routing_fee_strategy: Arc<SimpleMaxRoutingFeeStrategy>,
}

impl LightningNode {
Expand Down Expand Up @@ -196,11 +200,15 @@ impl LightningNode {
));

// Step 13: Initialize the Router
let router = Arc::new(Router::new(
Arc::clone(&graph),
Arc::clone(&logger),
keys_manager.get_secure_random_bytes(),
Arc::clone(&scorer),
let max_routing_fee_strategy = Arc::new(SimpleMaxRoutingFeeStrategy::new(21_000, 50));
let router = Arc::new(FeeLimitingRouter::new(
Router::new(
Arc::clone(&graph),
Arc::clone(&logger),
keys_manager.get_secure_random_bytes(),
Arc::clone(&scorer),
),
Arc::clone(&max_routing_fee_strategy),
));

// (needed when using Electrum or BIP 157/158)
Expand Down Expand Up @@ -339,6 +347,7 @@ impl LightningNode {
peer_manager,
task_manager,
data_store,
max_routing_fee_strategy,
})
}

Expand Down Expand Up @@ -418,6 +427,11 @@ impl LightningNode {
invoice::decode_invoice(&invoice, network)
}

pub fn get_payment_max_routing_fee_mode(&self, amount_msat: u64) -> MaxRoutingFeeMode {
self.max_routing_fee_strategy
.get_payment_max_fee_mode(amount_msat)
}

pub fn pay_invoice(&self, invoice: String, metadata: String) -> PayResult<()> {
let network = self.config.lock().unwrap().network;
let invoice =
Expand Down
140 changes: 140 additions & 0 deletions eel/src/router.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use crate::types::Router;
use lightning::ln::channelmanager::{ChannelDetails, PaymentId};
use lightning::ln::msgs::{ErrorAction, LightningError};
use lightning::ln::PaymentHash;
use lightning::routing::router::{InFlightHtlcs, Route, RouteParameters};
use secp256k1::PublicKey;
use std::sync::Arc;

pub enum MaxRoutingFeeMode {
Relative { max_fee_permyriad: u16 },
Absolute { max_fee_msat: u64 },
}

pub(crate) struct SimpleMaxRoutingFeeStrategy {
min_max_fee_msat: u64,
max_relative_fee_permyriad: u16,
}

impl SimpleMaxRoutingFeeStrategy {
pub fn new(min_max_fee_msat: u64, max_relative_fee_permyriad: u16) -> Self {
SimpleMaxRoutingFeeStrategy {
min_max_fee_msat,
max_relative_fee_permyriad,
}
}

pub fn get_payment_max_fee_mode(&self, payment_amount_msat: u64) -> MaxRoutingFeeMode {
let threshold = self.min_max_fee_msat * 10_000 / self.max_relative_fee_permyriad as u64;

if payment_amount_msat > threshold {
MaxRoutingFeeMode::Relative {
max_fee_permyriad: self.max_relative_fee_permyriad,
}
} else {
MaxRoutingFeeMode::Absolute {
max_fee_msat: self.min_max_fee_msat,
}
}
}

pub fn compute_max_fee_msat(&self, payment_amount_msat: u64) -> u64 {
match self.get_payment_max_fee_mode(payment_amount_msat) {
MaxRoutingFeeMode::Relative { max_fee_permyriad } => {
payment_amount_msat * max_fee_permyriad as u64 / 10000
}
MaxRoutingFeeMode::Absolute { max_fee_msat } => max_fee_msat,
}
}
}

pub(crate) struct FeeLimitingRouter {
inner: Router,
max_fee_strategy: Arc<SimpleMaxRoutingFeeStrategy>,
}

impl FeeLimitingRouter {
pub fn new(router: Router, max_fee_strategy: Arc<SimpleMaxRoutingFeeStrategy>) -> Self {
FeeLimitingRouter {
inner: router,
max_fee_strategy,
}
}
}

impl lightning::routing::router::Router for FeeLimitingRouter {
fn find_route(
&self,
payer: &PublicKey,
route_params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>,
inflight_htlcs: &InFlightHtlcs,
) -> Result<Route, LightningError> {
let max_fee_msat = self
.max_fee_strategy
.compute_max_fee_msat(route_params.final_value_msat);

let route = self
.inner
.find_route(payer, route_params, first_hops, inflight_htlcs)?;
let route_fees = route.get_total_fees();
if route_fees > max_fee_msat {
Err(LightningError {
err: format!("Route's fees exceed maximum allowed - max allowed: {max_fee_msat} - route's fees {route_fees}"),
action: ErrorAction::IgnoreError,
})
} else {
Ok(route)
}
}

fn find_route_with_id(
&self,
payer: &PublicKey,
route_params: &RouteParameters,
first_hops: Option<&[&ChannelDetails]>,
inflight_htlcs: &InFlightHtlcs,
payment_hash: PaymentHash,
payment_id: PaymentId,
) -> Result<Route, LightningError> {
let max_fee_msat = self
.max_fee_strategy
.compute_max_fee_msat(route_params.final_value_msat);

let route = self.inner.find_route_with_id(
payer,
route_params,
first_hops,
inflight_htlcs,
payment_hash,
payment_id,
)?;
let route_fees = route.get_total_fees();
if route_fees > max_fee_msat {
Err(LightningError {
err: format!("Route's fees exceed maximum allowed - max allowed: {max_fee_msat} - route's fees {route_fees}"),
action: ErrorAction::IgnoreError,
})
} else {
Ok(route)
}
}
}

#[cfg(test)]
mod tests {
use crate::router::SimpleMaxRoutingFeeStrategy;

#[test]
fn test_simple_max_routing_fee_strategy() {
let max_fee_strategy = SimpleMaxRoutingFeeStrategy::new(21000, 50);

assert_eq!(max_fee_strategy.compute_max_fee_msat(0), 21_000);
assert_eq!(max_fee_strategy.compute_max_fee_msat(21_000), 21_000);
assert_eq!(max_fee_strategy.compute_max_fee_msat(4199_000), 21_000);
assert_eq!(max_fee_strategy.compute_max_fee_msat(4200_000), 21_000);
assert_eq!(max_fee_strategy.compute_max_fee_msat(4201_000), 21_005);
assert_eq!(max_fee_strategy.compute_max_fee_msat(4399_000), 21_995);
assert_eq!(max_fee_strategy.compute_max_fee_msat(4400_000), 22_000);
}
}
11 changes: 5 additions & 6 deletions eel/src/storage_persister.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::encryption_symmetric::{decrypt, encrypt};
use crate::errors::*;
use crate::interfaces::RemoteStorage;
use crate::types::{
ChainMonitor, ChannelManager, ChannelManagerReadArgs, NetworkGraph, Router, Scorer,
};
use crate::types::{ChainMonitor, ChannelManager, ChannelManagerReadArgs, NetworkGraph, Scorer};
use crate::LightningLogger;
use std::cmp::Ordering;

use crate::router::FeeLimitingRouter;
use crate::tx_broadcaster::TxBroadcaster;
use bitcoin::hash_types::BlockHash;
use bitcoin::hashes::hex::ToHex;
Expand All @@ -20,7 +19,7 @@ use lightning::chain::keysinterface::{
};
use lightning::chain::transaction::OutPoint;
use lightning::chain::{ChannelMonitorUpdateStatus, Watch};
use lightning::ln::channelmanager::{ChainParameters, SimpleArcChannelManager};
use lightning::ln::channelmanager::ChainParameters;
use lightning::routing::router;
use lightning::routing::scoring::{ProbabilisticScoringParameters, WriteableScore};
use lightning::util::config::UserConfig;
Expand Down Expand Up @@ -253,7 +252,7 @@ impl StoragePersister {
keys_manager: Arc<KeysManager>,
fee_estimator: Arc<crate::FeeEstimator>,
logger: Arc<LightningLogger>,
router: Arc<Router>,
router: Arc<FeeLimitingRouter>,
channel_monitors: Vec<&mut ChannelMonitor<InMemorySigner>>,
user_config: UserConfig,
chain_params: ChainParameters,
Expand All @@ -275,7 +274,7 @@ impl StoragePersister {

match local_channel_manager {
None => {
let channel_manager = SimpleArcChannelManager::new(
let channel_manager = ChannelManager::new(
fee_estimator,
chain_monitor,
broadcaster,
Expand Down
16 changes: 12 additions & 4 deletions eel/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use crate::logger::LightningLogger;
use crate::storage_persister::StoragePersister;
use crate::tx_broadcaster::TxBroadcaster;

use crate::router::FeeLimitingRouter;
use lightning::chain::chainmonitor::ChainMonitor as LdkChainMonitor;
use lightning::chain::keysinterface::{InMemorySigner, KeysManager};
use lightning::ln::channelmanager::SimpleArcChannelManager;
use lightning::ln::peer_handler::IgnoringMessageHandler;
use lightning::routing::router::DefaultRouter;
use lightning::routing::scoring::ProbabilisticScorer;
Expand All @@ -24,8 +24,16 @@ pub(crate) type ChainMonitor = LdkChainMonitor<
Arc<StoragePersister>,
>;

pub(crate) type ChannelManager =
SimpleArcChannelManager<ChainMonitor, TxBroadcaster, FeeEstimator, LightningLogger>;
pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager<
Arc<ChainMonitor>,
Arc<TxBroadcaster>,
Arc<KeysManager>,
Arc<KeysManager>,
Arc<KeysManager>,
Arc<FeeEstimator>,
Arc<FeeLimitingRouter>,
Arc<LightningLogger>,
>;

pub(crate) type ChannelManagerReadArgs<'a> = lightning::ln::channelmanager::ChannelManagerReadArgs<
'a,
Expand All @@ -35,7 +43,7 @@ pub(crate) type ChannelManagerReadArgs<'a> = lightning::ln::channelmanager::Chan
Arc<KeysManager>,
Arc<KeysManager>,
Arc<FeeEstimator>,
Arc<Router>,
Arc<FeeLimitingRouter>,
Arc<LightningLogger>,
>;

Expand Down
44 changes: 43 additions & 1 deletion examples/3l-node/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::hinter::{CommandHint, CommandHinter};

use uniffi_lipalightninglib::{Amount, TzConfig};
use uniffi_lipalightninglib::{Amount, MaxRoutingFeeMode, TzConfig};

use bitcoin::secp256k1::PublicKey;
use chrono::offset::FixedOffset;
Expand Down Expand Up @@ -92,6 +92,11 @@ pub(crate) fn poll_for_user_input(node: &LightningNode, log_file_path: &str) {
println!("{}", message.red());
}
}
"getmaxroutingfeemode" => {
if let Err(message) = get_max_routing_fee_mode(node, &mut words) {
println!("{}", message.red());
}
}
"payinvoice" => {
if let Err(message) = pay_invoice(node, &mut words) {
println!("{}", message.red());
Expand Down Expand Up @@ -158,6 +163,10 @@ fn setup_editor(history_path: &Path) -> Editor<CommandHinter, DefaultHistory> {
"decodeinvoice <invoice>",
"decodeinvoice ",
));
hints.insert(CommandHint::new(
"getmaxroutingfeemode <payment amount in SAT>",
"getmaxroutingfeemode ",
));
hints.insert(CommandHint::new("payinvoice <invoice>", "payinvoice "));
hints.insert(CommandHint::new(
"payopeninvoice <invoice> <amount in SAT>",
Expand Down Expand Up @@ -189,6 +198,7 @@ fn help() {
println!();
println!(" invoice <amount in SAT> [description]");
println!(" decodeinvoice <invoice>");
println!(" getmaxroutingfeemode <payment amount in SAT>");
println!(" payinvoice <invoice>");
println!(" payopeninvoice <invoice> <amount in SAT>");
println!();
Expand Down Expand Up @@ -388,6 +398,38 @@ fn decode_invoice(
Ok(())
}

fn get_max_routing_fee_mode(
node: &LightningNode,
words: &mut dyn Iterator<Item = &str>,
) -> Result<(), String> {
let amount_argument = match words.next() {
Some(amount) => match amount.parse::<u64>() {
Ok(parsed) => Ok(parsed),
Err(_) => return Err("Error: SAT amount must be an integer".to_string()),
},
None => Err("The payment amount in SAT is required".to_string()),
}?;

let max_fee_strategy = node.get_payment_max_routing_fee_mode(amount_argument);

match max_fee_strategy {
MaxRoutingFeeMode::Relative { max_fee_permyriad } => {
println!(
"Max fee strategy: Relative (<= {} %)",
max_fee_permyriad as f64 / 100.0
);
}
MaxRoutingFeeMode::Absolute { max_fee_amount } => {
println!(
"Max fee strategy: Absolute (<= {})",
amount_to_string(max_fee_amount)
);
}
}

Ok(())
}

fn pay_invoice(node: &LightningNode, words: &mut dyn Iterator<Item = &str>) -> Result<(), String> {
let invoice = words
.next()
Expand Down
19 changes: 19 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ pub struct LightningNode {
core_node: eel::LightningNode,
}

pub enum MaxRoutingFeeMode {
Relative { max_fee_permyriad: u16 },
Absolute { max_fee_amount: Amount },
}

impl LightningNode {
pub fn new(config: Config, events_callback: Box<dyn EventsCallback>) -> Result<Self> {
enable_backtrace();
Expand Down Expand Up @@ -213,6 +218,20 @@ impl LightningNode {
Ok(InvoiceDetails::from_remote_invoice(invoice, &rate))
}

pub fn get_payment_max_routing_fee_mode(&self, amount_sat: u64) -> MaxRoutingFeeMode {
match self
.core_node
.get_payment_max_routing_fee_mode(amount_sat * 1000)
{
eel::MaxRoutingFeeMode::Relative { max_fee_permyriad } => {
MaxRoutingFeeMode::Relative { max_fee_permyriad }
}
eel::MaxRoutingFeeMode::Absolute { max_fee_msat } => MaxRoutingFeeMode::Absolute {
max_fee_amount: max_fee_msat.to_amount_up(&self.get_exchange_rate()),
},
}
}

pub fn pay_invoice(&self, invoice: String, metadata: String) -> PayResult<()> {
self.core_node.pay_invoice(invoice, metadata)
}
Expand Down
Loading

0 comments on commit a3def9b

Please sign in to comment.