Add heuristic fee engine and related services#533
Conversation
…e and LightningService
…ting states; add corresponding interfaces and tests
…fee management and automated rebalancing
There was a problem hiding this comment.
Pull request overview
Introduces the Phase 1 “foundation + signal” components for a heuristic routing engine, adding persisted routing/fee state models, a scheduled job to compute per-channel signals from forwarding history + live channel data, and UI/config surfaces to stage nodes for future Phase 2/3 actuation.
Changes:
- Add
ChannelRoutingState/ChannelFeeStateentities + repositories and EF migration to persist routing-engine state. - Add
TargetRatioReevaluationJobplusPeerCategorizationService(hysteresis categorization + EWMA smoothing) to compute targets and peer flow categories. - Extend services/UI/config:
GetInfo/block height retrieval, routing-engine constants, and node settings UI for dynamic fees/rebalancing.
Reviewed changes
Copilot reviewed 31 out of 32 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/NodeGuard.Tests/Services/PeerCategorizationServiceTests.cs | Unit tests for categorization hysteresis + target/EMA smoothing math. |
| test/NodeGuard.Tests/Helpers/ChannelOwnershipHelperTests.cs | Tests ownership dedup logic for managed↔managed channels. |
| test/NodeGuard.Tests/Helpers/BlockHeightHelperTests.cs | Tests SCID funding-height/age parsing and alias/zero handling. |
| test/NodeGuard.Tests/Data/Repositories/RebalanceRepositoryRoutingEngineTests.cs | Tests new rebalance in-flight count and budget-consumption calculations. |
| test/NodeGuard.Tests/Data/Repositories/ChannelRoutingStateRepositoryTests.cs | Tests routing-state upsert semantics and filtering by node pubkey. |
| test/NodeGuard.Tests/Data/Repositories/ChannelFlowAnalyticsRepositoryTests.cs | Tests settled-only, windowed per-channel forwarding aggregations. |
| src/Services/PeerCategorizationService.cs | Adds categorization decision record + core categorization/target smoothing logic. |
| src/Services/LightningService.cs | Adds GetBlockHeight to expose chain tip via LND GetInfo. |
| src/Services/LightningClientService.cs | Adds GetInfo RPC wrapper with error handling/logging. |
| src/Program.cs | Registers new repositories/service and schedules the new Quartz job/trigger. |
| src/Pages/Nodes.razor | Adds routing-engine configuration fields (dynamic fees/rebalance budgets/dry-run) to node UI. |
| src/Migrations/ApplicationDbContextModelSnapshot.cs | Updates EF snapshot with routing-engine entities and new columns/defaults. |
| src/Migrations/20260707155251_AddRoutingEngineFoundation.Designer.cs | Migration designer for routing-engine foundation schema changes. |
| src/Migrations/20260707155251_AddRoutingEngineFoundation.cs | Adds new node/channel columns and creates ChannelRoutingStates / ChannelFeeStates tables. |
| src/Jobs/TargetRatioReevaluationJob.cs | Implements Phase 1 job to compute/store routing signals and target ratios per channel. |
| src/Helpers/Constants.cs | Adds routing-engine constants and env var parsing. |
| src/Helpers/ChannelOwnershipHelper.cs | Adds helper to decide managed-node channel “ownership” for dedup. |
| src/Helpers/BlockHeightHelper.cs | Adds helper to parse funding height and channel age from SCID. |
| src/Data/Repositories/RebalanceRepository.cs | Adds in-flight count and consumed-fees calculations for routing-engine budgeting. |
| src/Data/Repositories/Interfaces/IRebalanceRepository.cs | Declares new rebalance query methods for Phase 3 budgeting/caps. |
| src/Data/Repositories/Interfaces/IChannelRoutingStateRepository.cs | New repository interface for routing-state persistence/upsert. |
| src/Data/Repositories/Interfaces/IChannelFlowAnalyticsRepository.cs | New interface for settled-only forwarding flow aggregations. |
| src/Data/Repositories/Interfaces/IChannelFeeStateRepository.cs | New interface for fee-state persistence/upsert (Phase 2). |
| src/Data/Repositories/ChannelRoutingStateRepository.cs | Implements routing-state get/upsert logic keyed by ChannelId. |
| src/Data/Repositories/ChannelFlowAnalyticsRepository.cs | Implements settled-only forwarding aggregations over ForwardingHtlcEvents. |
| src/Data/Repositories/ChannelFeeStateRepository.cs | Implements fee-state get/upsert logic keyed by ChannelId. |
| src/Data/Models/Rebalance.cs | Adds ReservedFeeSats for in-flight budget accounting. |
| src/Data/Models/Node.cs | Adds routing-engine node-level feature flags and budget/cap settings. |
| src/Data/Models/ChannelRoutingState.cs | Adds routing-state entity + PeerFlowCategory enum. |
| src/Data/Models/ChannelFeeState.cs | Adds fee-state entity for future fee engine (baseline/last-applied/circuit breaker). |
| src/Data/Models/Channel.cs | Adds per-channel opt-out flag IsDynamicFeeEnabled (default true). |
| src/Data/ApplicationDbContext.cs | Wires up new DbSets, 1:1 mappings, and default values for new flags. |
Files not reviewed (1)
- src/Migrations/20260707155251_AddRoutingEngineFoundation.Designer.cs: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Fee reserved for this in-flight rebalance so its spend counts against the node budget | ||
| /// before <see cref="FeePaidSats"/> settles (Phase 3 in-flight budget accounting). | ||
| /// </summary> | ||
| public long? ReservedFeeSats { get; set; } |
There was a problem hiding this comment.
I dont fully get this field, if the rebalance is in pending already you know the amount it's counted into the budget?
| /// Per-channel fee-engine state (1:1 with <see cref="Channel"/>). Declared in the Phase 1 | ||
| /// migration so Phase 2 needs no schema change; rows are not created or populated until the |
There was a problem hiding this comment.
I dont like this "phase 1" comments coming from the LLM, They should be todo or pure comments outside the summary as this can get obsolete if not changed
There was a problem hiding this comment.
yes, will remove all this
|
|
||
| /// <summary> | ||
| /// Circuit-breaker counter: incremented on each consecutive failure to apply a fee update, | ||
| /// reset to 0 on success. | ||
| /// </summary> | ||
| public int ConsecutiveFailures { get; set; } = 0; |
There was a problem hiding this comment.
not really, just a feature, will remove it
| /// <summary> | ||
| /// Proportional gain mapping net-flow to target drift: target_goal = 0.5 + clamp(K·netFlowRatio, ±maxDrift). | ||
| /// Default 0.70. Env: ROUTING_ENGINE_TARGET_K. | ||
| /// </summary> | ||
| public static double ROUTING_ENGINE_TARGET_K = 0.70; |
There was a problem hiding this comment.
Can you simplify what's this field about?
|
|
||
| /// <summary> | ||
| /// Maximum drift of target_goal away from 0.5 (reached at |netFlowRatio| = 0.5). Default 0.35, | ||
| /// giving a target_goal range of [0.15, 0.85]. Env: ROUTING_ENGINE_TARGET_MAX_DRIFT. | ||
| /// </summary> | ||
| public static double ROUTING_ENGINE_TARGET_MAX_DRIFT = 0.35; | ||
|
|
||
| /// <summary> | ||
| /// EWMA smoothing factor folding target_goal into the stored TargetLocalRatio. Default 0.10 | ||
| /// (~10 cycles to converge). Env: ROUTING_ENGINE_TARGET_ALPHA. | ||
| /// </summary> | ||
| public static double ROUTING_ENGINE_TARGET_ALPHA = 0.10; | ||
|
|
||
| /// <summary> | ||
| /// EWMA smoothing factor for EmaLocalRatio (~24h effective window at 30-min sampling). | ||
| /// Default 0.04. Env: ROUTING_ENGINE_FEE_EMA_ALPHA. | ||
| /// </summary> | ||
| public static double ROUTING_ENGINE_FEE_EMA_ALPHA = 0.04; | ||
|
|
||
| /// <summary> | ||
| /// Consecutive divergent cycles required before a category flip commits (anti-flap hysteresis). | ||
| /// Default 3. Env: ROUTING_ENGINE_CATEGORY_FLIP_HYSTERESIS_CYCLES. | ||
| /// </summary> | ||
| public static int ROUTING_ENGINE_CATEGORY_FLIP_HYSTERESIS_CYCLES = 3; |
There was a problem hiding this comment.
Same, clearer summaries help and examples also
There was a problem hiding this comment.
I have added clarifying formulas to the alpha factors for the target and simpler descriptions
| if (channel.Status == Channel.ChannelStatus.Open && channel.ChanId != 0) | ||
| { |
There was a problem hiding this comment.
Also check if the channel is active/enabled? Check Availability on channels
There was a problem hiding this comment.
added here:
NodeGuard/src/Jobs/TargetRatioReevaluationJob.cs
Lines 154 to 158 in a706fec
| <Field> | ||
| <FieldLabel>Restore Baseline on Disable</FieldLabel> | ||
| <FieldHelp>When dynamic fees are turned off, restore each channel's captured fee baseline in one final write. When off, last-set fees are frozen in place.</FieldHelp> | ||
| <Check TValue="bool" Checked="@_selectedNodeForLiquidity.RestoreFeeBaselineOnDisable" | ||
| CheckedChanged="@((value) => _selectedNodeForLiquidity.RestoreFeeBaselineOnDisable = value)"> | ||
| Restore fee baseline on disable | ||
| </Check> | ||
| </Field> |
There was a problem hiding this comment.
I dont think this has any benefit tbh
There was a problem hiding this comment.
Okay, I thought this was a good idea. If a node is disabled, you will have to change fees for every single channel, and this solves it.
There was a problem hiding this comment.
I mean what's the issue leaving old fees as they are?
There was a problem hiding this comment.
we would leave them mid strategy instead in a market avg fee. No strong in this opinion though
… repository and remove unused repository interfaces
…lated documentation
…model and related migrations
Introduce foundational components for a heuristic routing engine, including new methods for LightningClientService, migrations for routing engine tables, and repositories for channel fee and routing states. Implement helper classes for block height and channel ownership, along with tests to ensure functionality. This update enhances dynamic fee management and automated rebalancing capabilities.