From 0c2e626c693c1f010eded0a80d7a12c43a44b55a Mon Sep 17 00:00:00 2001 From: amackillop Date: Fri, 24 Apr 2026 20:11:11 -0700 Subject: [PATCH] Expose scoring parameters in JS bindings The ldk-node revision we now depend on supports configuring ProbabilisticScorer parameters. Pass them through the NAPI layer so JS consumers can tune routing behavior. All fields from ProbabilisticScoringFeeParameters and ProbabilisticScoringDecayParameters are available as optional overrides in MdkNodeOptions. Unset fields keep their LDK defaults. Manual node penalties use a Record with hex node IDs as keys. --- Cargo.toml | 2 +- index.d.ts | 16 +++++++++++ src/lib.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6a3a863..767f268 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ bitcoin-payment-instructions = { version = "0.5.0", default-features = false, fe "http", ] } # Branch: https://github.com/moneydevkit/ldk-node/commits/lsp-0.7.0_accept-underpaying-htlcs_with_timing_logs -ldk-node = { default-features = false, git = "https://github.com/moneydevkit/ldk-node.git", rev = "e935695c6ea9b33efee4cf579207ec84b40ce71f" } +ldk-node = { default-features = false, git = "https://github.com/moneydevkit/ldk-node.git", rev = "5baa1f83a13407818b069b1f990157c8761eb982" } #ldk-node = { path = "../ldk-node" } napi = { version = "2", features = ["napi4"] } diff --git a/index.d.ts b/index.d.ts index 251dc64..7159235 100644 --- a/index.d.ts +++ b/index.d.ts @@ -15,6 +15,21 @@ export declare function generateMnemonic(): string * Runs in ~1ms vs ~4s for full node construction. */ export declare function deriveNodeId(mnemonicStr: string, networkStr: string): string +export interface ScoringParamOverrides { + basePenaltyMsat?: number + basePenaltyAmountMultiplierMsat?: number + liquidityPenaltyMultiplierMsat?: number + liquidityPenaltyAmountMultiplierMsat?: number + historicalLiquidityPenaltyMultiplierMsat?: number + historicalLiquidityPenaltyAmountMultiplierMsat?: number + antiProbingPenaltyMsat?: number + consideredImpossiblePenaltyMsat?: number + linearSuccessProbability?: boolean + probingDiversityPenaltyMsat?: number + liquidityOffsetHalfLifeSecs?: number + historicalNoUpdatesHalfLifeSecs?: number + manualNodePenalties?: Record +} export interface MdkNodeOptions { network: string mdkApiKey: string @@ -24,6 +39,7 @@ export interface MdkNodeOptions { mnemonic: string lspNodeId: string lspAddress: string + scoringParamOverrides?: ScoringParamOverrides } export interface PaymentMetadata { bolt11: string diff --git a/src/lib.rs b/src/lib.rs index d5b6505..1f0a022 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,9 +28,13 @@ use napi::{ threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; +use ldk_node::lightning::routing::gossip::NodeId; +use ldk_node::lightning::routing::scoring::{ + ProbabilisticScoringDecayParameters, ProbabilisticScoringFeeParameters, +}; use ldk_node::logger::{LogLevel, LogRecord, LogWriter}; use ldk_node::{ - Builder, Event, Node, + Builder, Event, Node, ProbabilisticScoringParameters, bip39::Mnemonic, bitcoin::{ Network, @@ -263,6 +267,24 @@ fn derive_vss_identifier(mnemonic: &Mnemonic) -> String { sha256::Hash::hash(mnemonic_phrase.as_bytes()).to_string() } +#[napi(object)] +#[derive(Default)] +pub struct ScoringParamOverrides { + pub base_penalty_msat: Option, + pub base_penalty_amount_multiplier_msat: Option, + pub liquidity_penalty_multiplier_msat: Option, + pub liquidity_penalty_amount_multiplier_msat: Option, + pub historical_liquidity_penalty_multiplier_msat: Option, + pub historical_liquidity_penalty_amount_multiplier_msat: Option, + pub anti_probing_penalty_msat: Option, + pub considered_impossible_penalty_msat: Option, + pub linear_success_probability: Option, + pub probing_diversity_penalty_msat: Option, + pub liquidity_offset_half_life_secs: Option, + pub historical_no_updates_half_life_secs: Option, + pub manual_node_penalties: Option>, +} + #[napi(object)] pub struct MdkNodeOptions { pub network: String, @@ -273,6 +295,7 @@ pub struct MdkNodeOptions { pub mnemonic: String, pub lsp_node_id: String, pub lsp_address: String, + pub scoring_param_overrides: Option, } #[napi(object)] @@ -381,6 +404,63 @@ impl MdkNode { builder.set_custom_logger(logger); builder.set_liquidity_source_lsps4(lsp_node_id, lsp_address); + if let Some(scoring) = options.scoring_param_overrides { + let mut fee_params = ProbabilisticScoringFeeParameters::default(); + if let Some(v) = scoring.base_penalty_msat { + fee_params.base_penalty_msat = v as u64; + } + if let Some(v) = scoring.base_penalty_amount_multiplier_msat { + fee_params.base_penalty_amount_multiplier_msat = v as u64; + } + if let Some(v) = scoring.liquidity_penalty_multiplier_msat { + fee_params.liquidity_penalty_multiplier_msat = v as u64; + } + if let Some(v) = scoring.liquidity_penalty_amount_multiplier_msat { + fee_params.liquidity_penalty_amount_multiplier_msat = v as u64; + } + if let Some(v) = scoring.historical_liquidity_penalty_multiplier_msat { + fee_params.historical_liquidity_penalty_multiplier_msat = v as u64; + } + if let Some(v) = scoring.historical_liquidity_penalty_amount_multiplier_msat { + fee_params.historical_liquidity_penalty_amount_multiplier_msat = v as u64; + } + if let Some(v) = scoring.anti_probing_penalty_msat { + fee_params.anti_probing_penalty_msat = v as u64; + } + if let Some(v) = scoring.considered_impossible_penalty_msat { + fee_params.considered_impossible_penalty_msat = v as u64; + } + if let Some(v) = scoring.linear_success_probability { + fee_params.linear_success_probability = v; + } + if let Some(v) = scoring.probing_diversity_penalty_msat { + fee_params.probing_diversity_penalty_msat = v as u64; + } + if let Some(penalties) = scoring.manual_node_penalties { + for (node_id_str, penalty) in penalties { + let node_id = NodeId::from_str(&node_id_str).map_err(|e| { + napi::Error::from_reason(format!("Invalid node_id {}: {}", node_id_str, e)) + })?; + fee_params + .manual_node_penalties + .insert(node_id, penalty as u64); + } + } + + let mut decay_params = ProbabilisticScoringDecayParameters::default(); + if let Some(v) = scoring.liquidity_offset_half_life_secs { + decay_params.liquidity_offset_half_life = Duration::from_secs(v as u64); + } + if let Some(v) = scoring.historical_no_updates_half_life_secs { + decay_params.historical_no_updates_half_life = Duration::from_secs(v as u64); + } + + builder.set_scoring_params(ProbabilisticScoringParameters { + fee_params, + decay_params, + }); + } + let vss_headers = HashMap::from([( "Authorization".to_string(), format!("Bearer {}", options.mdk_api_key),