This commit is contained in:
Kukks
2024-01-08 16:02:37 +01:00
parent 9d47ac08eb
commit 62c6054927
5 changed files with 295 additions and 274 deletions

View File

@@ -11,6 +11,7 @@ using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Crypto.Randomness; using WalletWasabi.Crypto.Randomness;
using WalletWasabi.Extensions; using WalletWasabi.Extensions;
using WalletWasabi.Helpers; using WalletWasabi.Helpers;
using WalletWasabi.WabiSabi;
using WalletWasabi.WabiSabi.Backend.Rounds; using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Client; using WalletWasabi.WabiSabi.Client;
using WalletWasabi.Wallets; using WalletWasabi.Wallets;
@@ -20,12 +21,10 @@ namespace BTCPayServer.Plugins.Wabisabi;
public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
{ {
private readonly BTCPayWallet _wallet; private readonly BTCPayWallet _wallet;
private readonly ILogger _logger;
public BTCPayCoinjoinCoinSelector(BTCPayWallet wallet, ILogger logger) public BTCPayCoinjoinCoinSelector(BTCPayWallet wallet)
{ {
_wallet = wallet; _wallet = wallet;
_logger = logger;
} }
public async Task<ImmutableList<SmartCoin>> SelectCoinsAsync(IEnumerable<SmartCoin> coinCandidates, public async Task<ImmutableList<SmartCoin>> SelectCoinsAsync(IEnumerable<SmartCoin> coinCandidates,
@@ -98,12 +97,13 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
ConsolidationModeType.WhenLowFeeAndManyUTXO => isLowFee && coinCandidates.Count() > BTCPayWallet.HighAmountOfCoins, ConsolidationModeType.WhenLowFeeAndManyUTXO => isLowFee && coinCandidates.Count() > BTCPayWallet.HighAmountOfCoins,
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
Dictionary<AnonsetType, int> idealMinimumPerType = new Dictionary<AnonsetType, int>()
{{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}};
var solution = await SelectCoinsInternal(utxoSelectionParameters, coinCandidates, payments, var solution = await SelectCoinsInternal(utxoSelectionParameters, coinCandidates, payments,
Random.Shared.Next(10, 31), Random.Shared.Next(10, 31),
maxPerType, maxPerType,
new Dictionary<AnonsetType, int>() {{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}}, idealMinimumPerType,
consolidationMode, liquidityClue, secureRandom); consolidationMode, liquidityClue, secureRandom);
if (attemptingTobeParanoid && !solution.HandledPayments.Any()) if (attemptingTobeParanoid && !solution.HandledPayments.Any())
@@ -119,7 +119,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
attemptingToMixToOtherWallet = false; attemptingToMixToOtherWallet = false;
goto selectCoins; goto selectCoins;
} }
_logger.LogTrace(solution.ToString()); _wallet.LogTrace(solution.ToString());
return solution.Coins.ToImmutableList(); return solution.Coins.ToImmutableList();
} }
@@ -140,19 +140,26 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
var solution = new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget, var solution = new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget,
utxoSelectionParameters); utxoSelectionParameters);
var cv = new CoinsView(remainingCoins);
var percentage = await _wallet.GetPrivacyPercentageAsync(cv);
var fullyPrivate = await _wallet.IsWalletPrivateAsync(new CoinsView(remainingCoins)); var fullyPrivate = await _wallet.IsWalletPrivateAsync(new CoinsView(remainingCoins));
var coinjoiningOnlyForPayments = fullyPrivate && remainingPendingPayments.Any(); var coinjoiningOnlyForPayments = fullyPrivate && remainingPendingPayments.Any();
if (!consolidationMode && percentage < 1 && _wallet.ConsolidationMode != ConsolidationModeType.Never)
{
consolidationMode = true;
}
solution.ConsolidationMode = consolidationMode;
if (fullyPrivate && !coinjoiningOnlyForPayments ) if (fullyPrivate && !coinjoiningOnlyForPayments )
{ {
var rand = Random.Shared.Next(1, 1001); var rand = Random.Shared.Next(1, 1001);
if (rand > _wallet.WabisabiStoreSettings.ExtraJoinProbability) if (rand > _wallet.WabisabiStoreSettings.ExtraJoinProbability)
{ {
_logger.LogTrace($"All coins are private and we have no pending payments. Skipping join."); _wallet.LogTrace($"All coins are private and we have no pending payments. Skipping join.");
return solution; return solution;
} }
_logger.LogTrace( _wallet.LogTrace(
"All coins are private and we have no pending payments but will join just to reduce timing analysis"); "All coins are private and we have no pending payments but will join just to reduce timing analysis");
} }
@@ -234,6 +241,8 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
{ {
continue; continue;
} }
//if we have less than the max suggested output registration, we should add more coins to reach that number to avoid breaking up into too many coins? //if we have less than the max suggested output registration, we should add more coins to reach that number to avoid breaking up into too many coins?
var isLessThanMaxOutputRegistration = solution.Coins.Count < Math.Max(solution.HandledPayments.Count +1, 8); var isLessThanMaxOutputRegistration = solution.Coins.Count < Math.Max(solution.HandledPayments.Count +1, 8);
var rand = Random.Shared.Next(1, 101); var rand = Random.Shared.Next(1, 101);
@@ -253,10 +262,17 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
chance -= maxCoinCapacityPercentage; chance -= maxCoinCapacityPercentage;
} }
_logger.LogDebug($"coin selection: no payments left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} > {rand} (random 0-100) continue: {chance > rand}");
if (chance <= rand) if (chance <= rand)
{ {
if (_wallet.MinimumDenominationAmount is not null &&
Money.Coins(solution.LeftoverValue).Satoshi < _wallet.MinimumDenominationAmount)
{
_wallet.LogDebug(
$"coin selection: leftover value {solution.LeftoverValue} is less than minimum denomination amount {_wallet.MinimumDenominationAmount} so we will try to add more coins");
continue;
}
_wallet.LogDebug($"coin selection: no payments left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} > {rand} (random 0-100) continue: {chance > rand}");
break; break;
} }
@@ -265,7 +281,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
if (coinjoiningOnlyForPayments && solution.HandledPayments?.Any() is not true) if (coinjoiningOnlyForPayments && solution.HandledPayments?.Any() is not true)
{ {
_logger.LogInformation( _wallet.LogInfo(
"Attempted to coinjoin only to fulfill payments but the coin selection results yielded no handled payment."); "Attempted to coinjoin only to fulfill payments but the coin selection results yielded no handled payment.");
return new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget, return new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget,
utxoSelectionParameters); utxoSelectionParameters);
@@ -326,8 +342,6 @@ public static class SmartCoinExtensions
public static AnonsetType CoinColor(this SmartCoin coin, int anonsetTarget) public static AnonsetType CoinColor(this SmartCoin coin, int anonsetTarget)
{ {
return coin.IsPrivate(anonsetTarget)? AnonsetType.Green: coin.IsSemiPrivate(anonsetTarget)? AnonsetType.Orange: AnonsetType.Red; return coin.IsPrivate(anonsetTarget)? AnonsetType.Green: coin.IsSemiPrivate(anonsetTarget)? AnonsetType.Orange: AnonsetType.Red;
return coin.AnonymitySet <= 1 ? AnonsetType.Red :
coin.AnonymitySet >= anonsetTarget ? AnonsetType.Green : AnonsetType.Orange;
} }
} }
@@ -365,10 +379,10 @@ public class SubsetSolution
payment.ToTxOut().EffectiveCost(_utxoSelectionParameters.MiningFeeRate).ToDecimal(MoneyUnit.BTC)); payment.ToTxOut().EffectiveCost(_utxoSelectionParameters.MiningFeeRate).ToDecimal(MoneyUnit.BTC));
public decimal LeftoverValue => TotalValue - TotalPaymentCost; public decimal LeftoverValue => TotalValue - TotalPaymentCost;
public bool ConsolidationMode { get; set; }
public override string ToString() public override string ToString()
{ {
var sb = new StringBuilder();
if (!Coins.Any()) if (!Coins.Any())
{ {
return "Solution yielded no selection of coins"; return "Solution yielded no selection of coins";
@@ -378,20 +392,8 @@ public class SubsetSolution
sc.TryGetValue(AnonsetType.Green, out var gcoins); sc.TryGetValue(AnonsetType.Green, out var gcoins);
sc.TryGetValue(AnonsetType.Orange, out var ocoins); sc.TryGetValue(AnonsetType.Orange, out var ocoins);
sc.TryGetValue(AnonsetType.Red, out var rcoins); sc.TryGetValue(AnonsetType.Red, out var rcoins);
sb.AppendLine(
$"Solution total coins:{Coins.Count} R:{rcoins?.Length ?? 0} O:{ocoins?.Length ?? 0} G:{gcoins?.Length ?? 0} AL:{GetAnonLoss(Coins)} total value: {TotalValue} total payments:{TotalPaymentCost}/{TotalPaymentsGross} leftover: {LeftoverValue}");
if (HandledPayments.Any())
sb.AppendLine($"handled payments: {string.Join(", ", HandledPayments.Select(p => p.Value))} ");
return sb.ToString();
}
private static decimal GetAnonLoss<TCoin>(IEnumerable<TCoin> coins) return $"Selected {Coins.Count} ({TotalValue} BTC) ({ocoins?.Length + rcoins?.Length} not private, {gcoins?.Length ?? 0} private) coins to pay {TotalPaymentsGross} payments ({TotalPaymentCost} BTC) with {LeftoverValue} BTC leftover\n Consolidation mode:{ConsolidationMode}";
where TCoin : SmartCoin
{
double minimumAnonScore = coins.Min(x => x.AnonymitySet);
var rawSum = coins.Sum(x => x.Amount);
return coins.Sum(x =>
((decimal)x.AnonymitySet - (decimal)minimumAnonScore) * x.Amount.ToDecimal(MoneyUnit.BTC)) / rawSum;
} }
} }

View File

@@ -13,7 +13,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Coinjoin</Product> <Product>Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description> <Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.67</Version> <Version>1.0.68</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>

View File

@@ -51,7 +51,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
// public readonly IBTCPayServerClientFactory BtcPayServerClientFactory; // public readonly IBTCPayServerClientFactory BtcPayServerClientFactory;
public WabisabiStoreSettings WabisabiStoreSettings; public WabisabiStoreSettings WabisabiStoreSettings;
public readonly IUTXOLocker UtxoLocker; public readonly IUTXOLocker UtxoLocker;
public readonly ILogger Logger; // public readonly ILogger Logger;
public static readonly BlockchainAnalyzer BlockchainAnalyzer = new(); public static readonly BlockchainAnalyzer BlockchainAnalyzer = new();
public BTCPayWallet( public BTCPayWallet(
@@ -85,12 +85,12 @@ public class BTCPayWallet : IWallet, IDestinationProvider
UtxoLocker = utxoLocker; UtxoLocker = utxoLocker;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_memoryCache = memoryCache; _memoryCache = memoryCache;
Logger = loggerFactory.CreateLogger($"BTCPayWallet_{storeId}"); _logger = loggerFactory.CreateLogger($"BTCPayWallet_{storeId}");
} }
public string StoreId { get; set; } public string StoreId { get; set; }
public List<(Microsoft.Extensions.Logging.LogLevel, string)> LastLogs { get; private set; } = new(); public List<(DateTimeOffset time, Microsoft.Extensions.Logging.LogLevel level , string message)> LastLogs { get; private set; } = new();
public void Log(LogLevel logLevel, string logMessage, string callerFilePath = "", string callerMemberName = "", public void Log(LogLevel logLevel, string logMessage, string callerFilePath = "", string callerMemberName = "",
int callerLineNumber = -1) int callerLineNumber = -1)
{ {
@@ -104,12 +104,12 @@ public class BTCPayWallet : IWallet, IDestinationProvider
LogLevel.Critical => Microsoft.Extensions.Logging.LogLevel.Critical, LogLevel.Critical => Microsoft.Extensions.Logging.LogLevel.Critical,
_ => throw new ArgumentOutOfRangeException(nameof(logLevel)) _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
}; };
if(LastLogs.FirstOrDefault().Item2 != logMessage) if(LastLogs.FirstOrDefault().message != logMessage)
LastLogs.Insert(0, (ll, logMessage) ); LastLogs.Insert(0, (DateTimeOffset.Now, ll, logMessage) );
if (LastLogs.Count >= 100) if (LastLogs.Count >= 100)
LastLogs.RemoveLast(); LastLogs.RemoveLast();
Logger.Log(ll, logMessage, callerFilePath, callerMemberName, callerLineNumber); _logger.Log(ll, logMessage, callerFilePath, callerMemberName, callerLineNumber);
} }
public string WalletName => StoreId; public string WalletName => StoreId;
@@ -147,16 +147,16 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public async Task<bool> IsWalletPrivateAsync(CoinsView coins) public async Task<bool> IsWalletPrivateAsync(CoinsView coins)
{ {
var privacy= GetPrivacyPercentage(coins, AnonScoreTarget); var privacy= await GetPrivacyPercentageAsync(coins);
var mixToOtherWallet = !WabisabiStoreSettings.PlebMode && !string.IsNullOrEmpty(WabisabiStoreSettings var mixToOtherWallet = !WabisabiStoreSettings.PlebMode && !string.IsNullOrEmpty(WabisabiStoreSettings
.MixToOtherWallet); .MixToOtherWallet);
var forceConsolidate = ConsolidationMode == ConsolidationModeType.WhenLowFeeAndManyUTXO && coins.Available().Confirmed().Count() > HighAmountOfCoins; var forceConsolidate = ConsolidationMode == ConsolidationModeType.WhenLowFeeAndManyUTXO && coins.Available().Confirmed().Count() > HighAmountOfCoins;
return !BatchPayments && privacy >= 1 && !mixToOtherWallet && !forceConsolidate; return !BatchPayments && privacy >= 1 && !mixToOtherWallet && !forceConsolidate;
} }
public async Task<double> GetPrivacyPercentageAsync() public async Task<double> GetPrivacyPercentageAsync(CoinsView coins)
{ {
return GetPrivacyPercentage(await GetAllCoins(), AnonScoreTarget); return GetPrivacyPercentage(coins, AnonScoreTarget);
} }
public async Task<CoinsView> GetAllCoins() public async Task<CoinsView> GetAllCoins()
@@ -199,7 +199,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public IRoundCoinSelector GetCoinSelector() public IRoundCoinSelector GetCoinSelector()
{ {
_coinSelector??= new BTCPayCoinjoinCoinSelector(this, Logger ); _coinSelector??= new BTCPayCoinjoinCoinSelector(this );
return _coinSelector; return _coinSelector;
} }
@@ -213,9 +213,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
{ {
try try
{ {
if(await finishedCoinJoin.CoinJoinTask is not SuccessfulCoinJoinResult successfulCoinJoinResult)
var successfulCoinJoinResult = (await finishedCoinJoin.CoinJoinTask) as SuccessfulCoinJoinResult; return;
await RegisterCoinjoinTransaction(successfulCoinJoinResult, await RegisterCoinjoinTransaction(successfulCoinJoinResult,
finishedCoinJoin.CoinJoinClient.CoordinatorName); finishedCoinJoin.CoinJoinClient.CoordinatorName);
} }
@@ -410,6 +409,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
} }
private Task _savingProgress = Task.CompletedTask; private Task _savingProgress = Task.CompletedTask;
private readonly ILogger _logger;
public async Task RegisterCoinjoinTransaction(SuccessfulCoinJoinResult result, string coordinatorName) public async Task RegisterCoinjoinTransaction(SuccessfulCoinJoinResult result, string coordinatorName)
{ {
@@ -419,6 +419,13 @@ public class BTCPayWallet : IWallet, IDestinationProvider
} }
private async Task RegisterCoinjoinTransactionInternal(SuccessfulCoinJoinResult result, string coordinatorName) private async Task RegisterCoinjoinTransactionInternal(SuccessfulCoinJoinResult result, string coordinatorName)
{ {
var attempts = 0;
while (attempts < 5)
{
//wait longer between attempts
await Task.Delay(TimeSpan.FromSeconds(attempts * 3));
attempts++;
try try
{ {
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
@@ -428,7 +435,9 @@ public class BTCPayWallet : IWallet, IDestinationProvider
WellknownMetadataKeys.AccountKeyPath); WellknownMetadataKeys.AccountKeyPath);
var storeIdForutxo = WabisabiStoreSettings.PlebMode || var storeIdForutxo = WabisabiStoreSettings.PlebMode ||
string.IsNullOrEmpty(WabisabiStoreSettings.MixToOtherWallet)? StoreId: WabisabiStoreSettings.MixToOtherWallet; string.IsNullOrEmpty(WabisabiStoreSettings.MixToOtherWallet)
? StoreId
: WabisabiStoreSettings.MixToOtherWallet;
var utxoDerivationScheme = DerivationScheme; var utxoDerivationScheme = DerivationScheme;
if (storeIdForutxo != StoreId) if (storeIdForutxo != StoreId)
{ {
@@ -436,6 +445,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
var scheme = s.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC"); var scheme = s.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC");
utxoDerivationScheme = scheme.AccountDerivation; utxoDerivationScheme = scheme.AccountDerivation;
} }
List<(IndexedTxOut txout, Task<KeyPathInformation>)> scriptInfos = new(); List<(IndexedTxOut txout, Task<KeyPathInformation>)> scriptInfos = new();
@@ -443,7 +453,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
foreach (var script in result.Outputs) foreach (var script in result.Outputs)
{ {
var txout = result.UnsignedCoinJoin.Outputs.AsIndexedOutputs() var txout = result.UnsignedCoinJoin.Outputs.AsIndexedOutputs()
.Single(@out => @out.TxOut.ScriptPubKey == script.ScriptPubKey && @out.TxOut.Value == script.Value); .Single(@out =>
@out.TxOut.ScriptPubKey == script.ScriptPubKey && @out.TxOut.Value == script.Value);
//this was not a mix to self, but rather a payment //this was not a mix to self, but rather a payment
@@ -455,12 +466,18 @@ public class BTCPayWallet : IWallet, IDestinationProvider
continue; continue;
} }
var privateEnough = result.Coins.All(c => c.AnonymitySet >= WabisabiStoreSettings.AnonymitySetTarget ); var privateEnough =
scriptInfos.Add((txout, ExplorerClient.GetKeyInformationAsync(BlockchainAnalyzer.StdDenoms.Contains(txout.TxOut.Value)&& privateEnough?utxoDerivationScheme:DerivationScheme, script.ScriptPubKey))); result.Coins.All(c => c.AnonymitySet >= WabisabiStoreSettings.AnonymitySetTarget);
scriptInfos.Add((txout,
ExplorerClient.GetKeyInformationAsync(
BlockchainAnalyzer.StdDenoms.Contains(txout.TxOut.Value) && privateEnough
? utxoDerivationScheme
: DerivationScheme, script.ScriptPubKey)));
} }
await Task.WhenAll(scriptInfos.Select(t => t.Item2)); await Task.WhenAll(scriptInfos.Select(t => t.Item2));
var scriptInfos2 = scriptInfos.Where(tuple => tuple.Item2.Result is not null).ToDictionary(tuple => tuple.txout.TxOut.ScriptPubKey); var scriptInfos2 = scriptInfos.Where(tuple => tuple.Item2.Result is not null)
.ToDictionary(tuple => tuple.txout.TxOut.ScriptPubKey);
var smartTx = new SmartTransaction(result.UnsignedCoinJoin, new Height(HeightType.Unknown)); var smartTx = new SmartTransaction(result.UnsignedCoinJoin, new Height(HeightType.Unknown));
result.Coins.ForEach(coin => result.Coins.ForEach(coin =>
{ {
@@ -472,7 +489,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
{ {
if (scriptInfos2.TryGetValue(s.ScriptPubKey, out var si)) if (scriptInfos2.TryGetValue(s.ScriptPubKey, out var si))
{ {
var derivation = DerivationScheme.GetChild(si.Item2.Result.KeyPath).GetExtPubKeys().First().PubKey; var derivation = DerivationScheme.GetChild(si.Item2.Result.KeyPath).GetExtPubKeys().First()
.PubKey;
var hdPubKey = new HdPubKey(derivation, kp.Derive(si.Item2.Result.KeyPath).KeyPath, var hdPubKey = new HdPubKey(derivation, kp.Derive(si.Item2.Result.KeyPath).KeyPath,
LabelsArray.Empty, LabelsArray.Empty,
@@ -489,7 +507,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
} }
catch (Exception e) catch (Exception e)
{ {
Logger.LogError($"Failed to analyze anonsets of tx {smartTx.GetHash()}"); this.LogError($"Failed to analyze anonsets of tx {smartTx.GetHash()}");
} }
@@ -534,8 +552,10 @@ public class BTCPayWallet : IWallet, IDestinationProvider
if (result.HandledPayments.Any()) if (result.HandledPayments.Any())
{ {
attachments.AddRange(result.HandledPayments.Select(payment => new Attachment("payout", payment.Value.Identifier))); attachments.AddRange(result.HandledPayments.Select(payment =>
new Attachment("payout", payment.Value.Identifier)));
} }
List<(WalletId wallet, string id, IEnumerable<Attachment> attachments, string type )> objects = new(); List<(WalletId wallet, string id, IEnumerable<Attachment> attachments, string type )> objects = new();
objects.Add((new WalletId(StoreId, "BTC"), objects.Add((new WalletId(StoreId, "BTC"),
@@ -593,34 +613,21 @@ public class BTCPayWallet : IWallet, IDestinationProvider
stopwatch.Stop(); stopwatch.Stop();
Logger.LogInformation($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}"); this.LogInfo($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}");
_memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory"); _memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory");
break;
} }
catch (Exception e) catch (Exception e)
{ {
Logger.LogError(e, "Could not save coinjoin progress!");
this.LogError( "Could not save coinjoin progress! " + e.Message);
// ignored // ignored
}
} }
}
// public async Task UnlockUTXOs() }
// {
// var client = await BtcPayServerClientFactory.Create(null, StoreId);
// var utxos = await client.GetOnChainWalletUTXOs(StoreId, "BTC");
// var unlocked = new List<string>();
// foreach (OnChainWalletUTXOData utxo in utxos)
// {
//
// if (await UtxoLocker.TryUnlock(utxo.Outpoint))
// {
// unlocked.Add(utxo.Outpoint.ToString());
// }
// }
//
// Logger.LogTrace($"unlocked utxos: {string.Join(',', unlocked)}");
// }
public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count, bool mixedOutputs, bool privateEnough) public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count, bool mixedOutputs, bool privateEnough)
{ {

View File

@@ -13,7 +13,6 @@
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager @inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
<script src="~/Resources/chart.js" type="text/javascript"> </script> <script src="~/Resources/chart.js" type="text/javascript"> </script>
@inject IExplorerClientProvider ExplorerClientProvider
@{ @{
var available = true; var available = true;
@@ -21,6 +20,7 @@
{ {
return; return;
} }
var storeId = ScopeProvider.GetCurrentStoreId(); var storeId = ScopeProvider.GetCurrentStoreId();
Context.Items["cjlite"] = true; Context.Items["cjlite"] = true;
} }
@@ -78,11 +78,9 @@
id = coin.Outpoint.ToString(), id = coin.Outpoint.ToString(),
coinjoinInProgress = coin.CoinJoinInProgress coinjoinInProgress = coin.CoinJoinInProgress
}).OrderBy(coin => coin.isPrivate).ThenBy(coin => coin.score), }).OrderBy(coin => coin.isPrivate).ThenBy(coin => coin.score),
}; };
@if (coins.Any()) @if (coins.Any())
{ {
<script> <script>
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
@@ -261,6 +259,7 @@ updateInProgressAnimation(myChart);
}); });
</script> </script>
} }
<div class="widget store-numbers"> <div class="widget store-numbers">
@if (wallet is { }) @if (wallet is { })
@@ -289,8 +288,9 @@ updateInProgressAnimation(myChart);
@if (coins.Any()) @if (coins.Any())
{ {
<div class="d-flex justify-content-center mb-4" style="max-height: 400px; "> <canvas id="cjchart"></canvas></div> <div class="d-flex justify-content-center mb-4" style="max-height: 400px; ">
<canvas id="cjchart"></canvas>
</div>
} }
<div class="modal modal-lg fade" id="coins" data-bs-keyboard="false" tabindex="-1"> <div class="modal modal-lg fade" id="coins" data-bs-keyboard="false" tabindex="-1">
@@ -372,6 +372,7 @@ updateInProgressAnimation(myChart);
tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true) tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true)
{ {
} }
var statusMsg = coordinator.WasabiCoordinatorStatusFetcher.Connected ? $"Connected to {(coordinator.Coordinator?.ToString() ?? "local")}" : $"Not connected to {(coordinator.Coordinator?.ToString() ?? "local")}"; var statusMsg = coordinator.WasabiCoordinatorStatusFetcher.Connected ? $"Connected to {(coordinator.Coordinator?.ToString() ?? "local")}" : $"Not connected to {(coordinator.Coordinator?.ToString() ?? "local")}";
<div class="list-group-item"> <div class="list-group-item">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3"> <div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
@@ -422,7 +423,9 @@ updateInProgressAnimation(myChart);
</tr> </tr>
<tr> <tr>
<th scope="">Round id</th> <th scope="">Round id</th>
<td class="text-truncate"><vc:truncate-center text="@currentRound.Id.ToString()" classes="truncate-center-id"></vc:truncate-center></td> <td class="text-truncate">
<vc:truncate-center text="@currentRound.Id.ToString()" classes="truncate-center-id"></vc:truncate-center>
</td>
</tr> </tr>
<tr> <tr>
<th scope="">Mining feerate</th> <th scope="">Mining feerate</th>
@@ -439,7 +442,9 @@ updateInProgressAnimation(myChart);
{ {
statement += $" / {tracker.CoinJoinClient.CoinsToRegister.Count()} inputs ({tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC))} BTC)"; statement += $" / {tracker.CoinJoinClient.CoinsToRegister.Count()} inputs ({tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC))} BTC)";
} }
<tr>
var inputToolTip = tracker.CoinJoinClient.CoinsToRegister.Aggregate("", (current, coin) => current + $"{coin.Amount.ToDecimal(MoneyUnit.BTC)} BTC\n");
<tr data-bs-toggle="tooltip" title="@inputToolTip">
<th scope="">Your inputs</th> <th scope="">Your inputs</th>
<td class=""> <td class="">
<span class="w-100">@statement</span> <span class="w-100">@statement</span>
@@ -460,8 +465,10 @@ updateInProgressAnimation(myChart);
if (tracker.CoinJoinClient.OutputTxOuts is { } outputs) if (tracker.CoinJoinClient.OutputTxOuts is { } outputs)
{ {
var statement = $"{outputs.outputTxOuts.Count()} outputs ({outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC))} BTC {(outputs.batchedPayments.Any() ? $"{outputs.batchedPayments.Count()} batched payments" : "")}"; var statement = $"{outputs.outputTxOuts.Count()} outputs ({outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC))} BTC {(outputs.batchedPayments.Any() ? $"{outputs.batchedPayments.Count()} batched payments" : "")}";
var outputToolTip = outputs.outputTxOuts.Aggregate("", (current, output) => current + $"{output.Value.ToDecimal(MoneyUnit.BTC)} BTC\n");
<tr>
<tr data-bs-toggle="tooltip" title="@outputToolTip">
<th scope="">Your outputs</th> <th scope="">Your outputs</th>
<td >@statement</td> <td >@statement</td>
</tr> </tr>
@@ -470,8 +477,6 @@ updateInProgressAnimation(myChart);
</table> </table>
</div> </div>
} }
} }
</div> </div>
@@ -481,14 +486,12 @@ updateInProgressAnimation(myChart);
@if (coins.Any()) @if (coins.Any())
{ {
<button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#coins"> <button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#coins">
View coins View coins
</button> </button>
} }
@if (wallet.LastLogs.Any()) @if (wallet.LastLogs.Any())
{ {
<button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#logs"> <button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#logs">
Recent logs Recent logs
</button> </button>
@@ -499,19 +502,27 @@ updateInProgressAnimation(myChart);
<h3 class="mb-0">Coinjoin logs</h3> <h3 class="mb-0">Coinjoin logs</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body table-responsive mt-0"> <div class="modal-body mt-0">
<div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Time</th>
<th>Message</th> <th>Message</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var evt in wallet.LastLogs) @foreach (var evt in wallet.LastLogs)
{ {
string cssClass = evt.Item1 <= (LogLevel) 2 ? "info" : evt.Item1 == (LogLevel) 4 ? "warning" : "danger"; string cssClass = evt.level <= (LogLevel) 2 ? "info" : evt.level == (LogLevel) 4 ? "warning" : "danger";
<tr class="text-@cssClass"> <tr class="text-@cssClass">
<td>@evt.Item2</td> <td>
<small class="text-muted" data-timeago-unixms="@evt.time.ToUnixTimeMilliseconds()">@evt.time.ToTimeAgo()</small>
</td>
<td>
<pre>@evt.message</pre>
</td>
</tr> </tr>
} }
</tbody> </tbody>
@@ -523,6 +534,7 @@ updateInProgressAnimation(myChart);
</div> </div>
</div> </div>
</div> </div>
</div>
} }
</div> </div>