From d3cbf592af6e26433905a8769dbcbf531491a82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20A=2EP?= <53834183+Jossec101@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:25:17 +0100 Subject: [PATCH] Fix outputs for sweep transactions (#399) * Added consts for sweep min tx amount in sats, conf target * Fix creating dust utxos and 100K utxos, now we require a min amount to be swept out of the LND hot wallet, this shall ensure that there's more than a 100K utxo in the wallet * bump to 25M sats minimum default sweep tx --- src/Helpers/Constants.cs | 13 +++++++++++++ src/Jobs/SweepNodeWalletsJob.cs | 34 ++++++++++++++++----------------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/Helpers/Constants.cs b/src/Helpers/Constants.cs index 046ec18c..fdfa1592 100644 --- a/src/Helpers/Constants.cs +++ b/src/Helpers/Constants.cs @@ -75,6 +75,7 @@ public class Constants public static readonly decimal MAXIMUM_WITHDRAWAL_BTC_AMOUNT = 21_000_000; public static readonly int TRANSACTION_CONFIRMATION_MINIMUM_BLOCKS; public static readonly long ANCHOR_CLOSINGS_MINIMUM_SATS; + public static readonly long MINIMUM_SWEEP_TRANSACTION_AMOUNT_SATS = 25_000_000; //25M sats public static readonly string DEFAULT_DERIVATION_PATH = "48'/1'"; public static readonly int SESSION_TIMEOUT_MILLISECONDS = 3_600_000; @@ -86,6 +87,11 @@ public class Constants /// public static decimal MAX_TX_FEE_RATIO = 0.5m; + /// + /// The target number of confirmations blocks (fee rate) for the sweep transaction + /// + public static int SWEEP_CONF_TARGET = 6; + public const string IsFrozenTag = "frozen"; public const string IsManuallyFrozenTag = "manually_frozen"; @@ -217,6 +223,13 @@ static Constants() var anchorClosingMinSats = GetEnvironmentalVariableOrThrowIfNotTesting("ANCHOR_CLOSINGS_MINIMUM_SATS"); if (anchorClosingMinSats != null) ANCHOR_CLOSINGS_MINIMUM_SATS = long.Parse(anchorClosingMinSats); // Check https://github.com/lightningnetwork/lnd/issues/6505#issuecomment-1120364460 to understand, we need 100K+ to support anchor channel closings + var sweepConfTarget = Environment.GetEnvironmentVariable("SWEEP_CONF_TARGET"); + if (sweepConfTarget != null) SWEEP_CONF_TARGET = int.Parse(sweepConfTarget); + + var minSweepTransactionAmount = Environment.GetEnvironmentVariable("MINIMUM_SWEEP_TRANSACTION_AMOUNT_SATS"); + if (minSweepTransactionAmount != null) MINIMUM_SWEEP_TRANSACTION_AMOUNT_SATS = long.Parse(minSweepTransactionAmount); + + DEFAULT_DERIVATION_PATH = GetEnvironmentalVariableOrThrowIfNotTesting("DEFAULT_DERIVATION_PATH") ?? DEFAULT_DERIVATION_PATH; var timeout = Environment.GetEnvironmentVariable("SESSION_TIMEOUT_MILLISECONDS"); diff --git a/src/Jobs/SweepNodeWalletsJob.cs b/src/Jobs/SweepNodeWalletsJob.cs index 19d5f818..ca5a72d9 100644 --- a/src/Jobs/SweepNodeWalletsJob.cs +++ b/src/Jobs/SweepNodeWalletsJob.cs @@ -44,7 +44,7 @@ public SweepNodeWalletsJob(ILogger logger, INBXplorerService nbXplorerService, ILightningClientService lightningClientService) { - + _logger = logger; _nodeRepository = nodeRepository; _walletRepository = walletRepository; @@ -66,7 +66,7 @@ public async Task Execute(IJobExecutionContext context) #region Local functions async Task SweepFunds(Node node, Wallet wallet, Lightning.LightningClient lightningClient - ,List utxos) + , List utxos) { if (node == null) throw new ArgumentNullException(nameof(node)); if (wallet == null) throw new ArgumentNullException(nameof(wallet)); @@ -81,9 +81,9 @@ async Task SweepFunds(Node node, Wallet wallet, Lightning.LightningClient lightn if (node.ChannelAdminMacaroon != null) { var lndChangeAddress = await lightningClient.NewAddressAsync(new NewAddressRequest - { - Type = AddressType.UnusedWitnessPubkeyHash - }, + { + Type = AddressType.UnusedWitnessPubkeyHash + }, new Metadata { { @@ -92,21 +92,21 @@ async Task SweepFunds(Node node, Wallet wallet, Lightning.LightningClient lightn }); var totalSatsAvailable = utxos.Sum(x => x.AmountSat); - if (returningAddress != null && lndChangeAddress != null && utxos.Any() && totalSatsAvailable > requiredAnchorChannelClosingAmount) + if (returningAddress != null && lndChangeAddress != null && utxos.Any() && totalSatsAvailable > Constants.MINIMUM_SWEEP_TRANSACTION_AMOUNT_SATS) { - var sweepedFundsAmount = (long)((totalSatsAvailable - requiredAnchorChannelClosingAmount) * 0.9); // We should let requiredAnchorChannelClosingAmount sats as a UTXO in in the hot wallet for channel closings + // We need to maintain onchain balance to be at least RequiredAnchorChannelClosingAmount but also we apply a 10% buffer to pay for this sweep fees and let some more money on the wallet + var sweepedFundsAmount = (long)((totalSatsAvailable - requiredAnchorChannelClosingAmount) * 0.9); var sendManyResponse = await lightningClient.SendManyAsync(new SendManyRequest() - { - AddrToAmount = + { + AddrToAmount = { {returningAddress.Address.ToString(), sweepedFundsAmount}, //Sweeped funds - {lndChangeAddress.Address, requiredAnchorChannelClosingAmount}, }, - MinConfs = 1, - Label = $"Hot wallet Sweep tx on {DateTime.UtcNow.ToString("O")} to walletId:{wallet.Id}", - SpendUnconfirmed = false, - TargetConf = 1 // 1 for now TODO Maybe this can be set as a global env var for all the Target blocks of the FM.. - }, + MinConfs = 6, + Label = $"Hot wallet Sweep tx on {DateTime.UtcNow.ToString("O")} to walletId:{wallet.Id}", + SpendUnconfirmed = false, + TargetConf = Constants.SWEEP_CONF_TARGET + }, new Metadata { { @@ -157,9 +157,9 @@ async Task SweepFunds(Node node, Wallet wallet, Lightning.LightningClient lightn try { - + var client = _lightningClientService.GetLightningClient(node.Endpoint); - + var unspentResponse = await client.ListUnspentAsync(new ListUnspentRequest { MinConfs = 1, MaxConfs = Int32.MaxValue }, new Metadata { {