-
Notifications
You must be signed in to change notification settings - Fork 4
Add Rebalance data model #506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| /* | ||
| * NodeGuard | ||
| * Copyright (C) 2023 Elenpay | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Affero General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU Affero General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Affero General Public License | ||
| * along with this program. If not, see http://www.gnu.org/licenses/. | ||
| * | ||
| */ | ||
|
|
||
| using System.ComponentModel.DataAnnotations.Schema; | ||
| using NBitcoin; | ||
|
|
||
| namespace NodeGuard.Data.Models; | ||
|
|
||
| public enum RebalanceStatus | ||
| { | ||
| Pending, | ||
| Probing, | ||
| InFlight, | ||
| Succeeded, | ||
| Failed, | ||
| NoRoute, | ||
| Timeout, | ||
| InsufficientBalance, | ||
| ExceededFeeLimit | ||
| } | ||
|
|
||
| public class Rebalance : Entity | ||
| { | ||
| public int NodeId { get; set; } | ||
| public Node? Node { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Pubkey of the source node, stored for simpler analytics | ||
| /// </summary> | ||
| public string? SourceNodePubKey { get; set; } | ||
|
|
||
| public RebalanceStatus Status { get; set; } | ||
|
|
||
| public bool IsManual { get; set; } | ||
|
|
||
| public int AttemptNumber { get; set; } = 1; | ||
|
|
||
| /// <summary> | ||
| /// The amount the user originally requested to rebalance. | ||
| /// </summary> | ||
| public long RequestedAmountSats { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The amount actually used for the payment. May be lower than RequestedAmountSats | ||
| /// when the prober had to reduce the amount to find a viable route. | ||
| /// </summary> | ||
| public long SatsAmount { get; set; } | ||
|
|
||
| [NotMapped] | ||
| public decimal Amount => new Money(SatsAmount, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC); | ||
|
|
||
| [NotMapped] | ||
| public decimal RequestedAmount => new Money(RequestedAmountSats, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC); | ||
|
|
||
| /// <summary> | ||
| /// Maximum fee as percentage of <see cref="SatsAmount"/> (0.05 = 0.05%). | ||
| /// Escalates on retry per <see cref="RetryMaxFeePct"/> or Constants. | ||
| /// </summary> | ||
| public double MaxFeePct { get; set; } | ||
| /// <summary> | ||
| /// Maximum fee percent done in retries, falling to a user-supplied value or defaulting to Constants a retry is needed. | ||
| /// </summary> | ||
| public double? RetryMaxFeePct { get; set; } | ||
| public long? FeePaidSats { get; set; } | ||
|
|
||
| public long? FeePaidMsat { get; set; } | ||
|
|
||
| [NotMapped] | ||
| public long? EffectivePpm => FeePaidMsat.HasValue && SatsAmount > 0 | ||
| ? FeePaidMsat.Value * 1_000L / SatsAmount | ||
| : (long?)null; | ||
|
|
||
| [NotMapped] | ||
| public Money FeePaid => new Money(FeePaidSats ?? 0, MoneyUnit.Satoshi); | ||
|
|
||
| public int? SourceChannelId { get; set; } | ||
| public Channel? SourceChannel { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// LND chan_id of the source channel at the time of the request. | ||
| /// </summary> | ||
| public ulong? SourceChanIdLnd { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Last-hop peer pubkey which constrains the receiving PEER of the circular payment — not the receiving channel. | ||
| /// Lightning nodes choose which channel between this node and that peer to use as the last hop, so theres no technical guarantee which channel will be used, but in practice this is sufficient to balance out source channels. | ||
| /// When null, LND picks any inbound peer that satisfies the cost cap. | ||
| /// </summary> | ||
| public string? TargetPubkey { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Persisted for forensic / proof-of-payment lookup; intentionally not exposed via gRPC. | ||
| /// </summary> | ||
| public string? PreimageHex { get; set; } | ||
|
|
||
| public string? UserRequestorId { get; set; } | ||
| public ApplicationUser? UserRequestor { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Pathfinding/payment timeout we passed to SendPaymentV2. | ||
| /// </summary> | ||
| public int TimeoutSeconds { get; set; } = 60; | ||
|
|
||
| /// <summary> | ||
| /// Multiplier applied to the probe amount after each failure. Range: (0, 1) exclusive. | ||
| /// 0.5 = halve (default); 0.8 = next try is 20% smaller (finer-grained, more iterations); | ||
| /// 0.3 = next try is 70% smaller (gives up faster). When null, the runtime falls back | ||
| /// to <c>Constants.REBALANCE_PROBE_BACKOFF_RATIO</c>. | ||
| /// </summary> | ||
| public double? ProbeBackoffRatio { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Maximum number of attempts (including the first try). When null, falls back to | ||
| /// <c>Constants.REBALANCE_MAX_ATTEMPTS</c> | ||
| /// </summary> | ||
| public int? MaxAttempts { get; set; } | ||
|
|
||
|
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* | ||
| * NodeGuard | ||
| * Copyright (C) 2023 Elenpay | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Affero General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU Affero General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Affero General Public License | ||
| * along with this program. If not, see http://www.gnu.org/licenses/. | ||
| * | ||
| */ | ||
|
|
||
| using NBitcoin; | ||
| using NodeGuard.Data.Models; | ||
|
|
||
| namespace NodeGuard.Data.Repositories.Interfaces; | ||
|
|
||
| public interface IRebalanceRepository | ||
| { | ||
| Task<Rebalance?> GetById(int id); | ||
|
|
||
| Task<(List<Rebalance> rebalances, int totalCount)> GetPaginatedAsync( | ||
| int pageNumber, | ||
| int pageSize, | ||
| RebalanceStatus? status = null, | ||
| int? nodeId = null, | ||
| int? sourceChannelId = null, | ||
| string? userId = null, | ||
| bool? isManual = null, | ||
| DateTimeOffset? fromDate = null, | ||
| DateTimeOffset? toDate = null); | ||
|
|
||
| /// <summary> | ||
| /// Get all rebalances currently in flight (Pending/Probing/InFlight). Used by the | ||
| /// monitor job for stale-state cleanup after process restart. | ||
| /// </summary> | ||
| Task<List<Rebalance>> GetAllInFlight(); | ||
|
|
||
| Task<(bool, string?)> AddAsync(Rebalance rebalance); | ||
|
|
||
| (bool, string?) Update(Rebalance rebalance); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /* | ||
| * NodeGuard | ||
| * Copyright (C) 2023 Elenpay | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Affero General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU Affero General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Affero General Public License | ||
| * along with this program. If not, see http://www.gnu.org/licenses/. | ||
| * | ||
| */ | ||
|
|
||
| using Microsoft.EntityFrameworkCore; | ||
| using NBitcoin; | ||
| using NodeGuard.Data.Models; | ||
| using NodeGuard.Data.Repositories.Interfaces; | ||
|
|
||
| namespace NodeGuard.Data.Repositories; | ||
|
|
||
| public class RebalanceRepository : IRebalanceRepository | ||
| { | ||
| private readonly IRepository<Rebalance> _repository; | ||
| private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory; | ||
|
|
||
| public RebalanceRepository(IRepository<Rebalance> repository, IDbContextFactory<ApplicationDbContext> dbContextFactory) | ||
| { | ||
| _repository = repository; | ||
| _dbContextFactory = dbContextFactory; | ||
| } | ||
|
|
||
| public async Task<Rebalance?> GetById(int id) | ||
| { | ||
| await using var context = await _dbContextFactory.CreateDbContextAsync(); | ||
|
|
||
| return await context.Rebalances | ||
| .Include(r => r.Node) | ||
| .Include(r => r.SourceChannel) | ||
| .Include(r => r.UserRequestor) | ||
| .SingleOrDefaultAsync(r => r.Id == id); | ||
| } | ||
|
|
||
| public async Task<(List<Rebalance> rebalances, int totalCount)> GetPaginatedAsync( | ||
| int pageNumber, | ||
| int pageSize, | ||
| RebalanceStatus? status = null, | ||
| int? nodeId = null, | ||
| int? sourceChannelId = null, | ||
| string? userId = null, | ||
| bool? isManual = null, | ||
| DateTimeOffset? fromDate = null, | ||
| DateTimeOffset? toDate = null) | ||
| { | ||
| await using var context = await _dbContextFactory.CreateDbContextAsync(); | ||
|
|
||
| var query = context.Rebalances | ||
| .Include(r => r.Node) | ||
| .Include(r => r.SourceChannel) | ||
| .ThenInclude(c => c!.SourceNode) | ||
| .Include(r => r.SourceChannel) | ||
| .ThenInclude(c => c!.DestinationNode) | ||
| .Include(r => r.UserRequestor) | ||
| .AsQueryable(); | ||
|
|
||
| if (status.HasValue) | ||
| query = query.Where(r => r.Status == status.Value); | ||
|
|
||
| if (nodeId.HasValue) | ||
| query = query.Where(r => r.NodeId == nodeId.Value); | ||
|
|
||
| if (sourceChannelId.HasValue) | ||
| query = query.Where(r => r.SourceChannelId == sourceChannelId.Value); | ||
|
|
||
| if (!string.IsNullOrEmpty(userId)) | ||
| query = query.Where(r => r.UserRequestorId == userId); | ||
|
|
||
| if (isManual.HasValue) | ||
| query = query.Where(r => r.IsManual == isManual.Value); | ||
|
|
||
| if (fromDate.HasValue) | ||
| query = query.Where(r => r.CreationDatetime >= fromDate.Value); | ||
|
|
||
| if (toDate.HasValue) | ||
| query = query.Where(r => r.CreationDatetime <= toDate.Value); | ||
|
|
||
| query = query.OrderByDescending(r => r.CreationDatetime); | ||
|
|
||
| var totalCount = await query.CountAsync(); | ||
|
|
||
| var rebalances = await query | ||
| .Skip((pageNumber - 1) * pageSize) | ||
| .Take(pageSize) | ||
| .ToListAsync(); | ||
|
|
||
| return (rebalances, totalCount); | ||
| } | ||
|
|
||
| public async Task<List<Rebalance>> GetAllInFlight() | ||
| { | ||
| await using var context = await _dbContextFactory.CreateDbContextAsync(); | ||
|
|
||
| return await context.Rebalances | ||
| .Where(r => r.Status == RebalanceStatus.Pending | ||
| || r.Status == RebalanceStatus.Probing | ||
| || r.Status == RebalanceStatus.InFlight) | ||
| .ToListAsync(); | ||
| } | ||
|
|
||
| public async Task<(bool, string?)> AddAsync(Rebalance rebalance) | ||
| { | ||
| await using var context = await _dbContextFactory.CreateDbContextAsync(); | ||
|
|
||
| rebalance.SetCreationDatetime(); | ||
| rebalance.SetUpdateDatetime(); | ||
|
|
||
| return await _repository.AddAsync(rebalance, context); | ||
| } | ||
|
|
||
| public (bool, string?) Update(Rebalance rebalance) | ||
| { | ||
| using var context = _dbContextFactory.CreateDbContext(); | ||
|
|
||
| rebalance.SetUpdateDatetime(); | ||
|
|
||
| return _repository.Update(rebalance, context); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.