Improve labeling
MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -827,7 +827,7 @@ namespace WalletWasabi.Tests
Assert.True(res2.Fee > Money.Satoshis(2 * 100)); // since there is a sanity check of 2sat/vb in the server
Assert.InRange(res2.FeePercentOfSent, 0, 1);
Assert.Single(res2.SpentCoins);
- Assert.Equal(key.P2wpkhScript, res2.SpentCoins.Single().ScriptPubKey);
+ Assert.Equal(key2.P2wpkhScript, res2.SpentCoins.Single().ScriptPubKey);
Assert.Equal(Money.Coins(1m), res2.SpentCoins.Single().Amount);
Assert.False(res2.SpendsUnconfirmed);
@@ -1085,7 +1085,8 @@ namespace WalletWasabi.Tests
#region Labeling
- res = wallet.BuildTransaction(password, new PaymentIntent(receive, MoneyRequest.CreateAllRemaining(), "my label"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);
+ Script receive2 = wallet.GetReceiveKey("").P2wpkhScript;
+ res = wallet.BuildTransaction(password, new PaymentIntent(receive2, MoneyRequest.CreateAllRemaining(), "my label"), FeeStrategy.SevenDaysConfirmationTargetStrategy, allowUnconfirmed: true);
Assert.Single(res.InnerWalletOutputs);
Assert.Equal("my label", res.InnerWalletOutputs.Single().Label);
@@ -1610,13 +1611,13 @@ namespace WalletWasabi.Tests
Assert.Single(wallet.Coins);
- // Test mixin
var operations = new PaymentIntent(
new DestinationRequest(key.P2wpkhScript, Money.Coins(0.01m)),
new DestinationRequest(new Key().ScriptPubKey, Money.Coins(0.01m)),
new DestinationRequest(new Key().ScriptPubKey, Money.Coins(0.01m)));
var tx1Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwoHoursConfirmationTargetStrategy, allowUnconfirmed: true);
- Assert.Equal(1, tx1Res.OuterWalletOutputs.Single(x => x.ScriptPubKey == key.P2wpkhScript).AnonymitySet);
+ Assert.Equal(2, tx1Res.InnerWalletOutputs.Count());
+ Assert.Equal(2, tx1Res.OuterWalletOutputs.Count());
// Spend the unconfirmed coin (send it to ourself)
operations = new PaymentIntent(key.PubKey.WitHash.ScriptPubKey, Money.Coins(0.5m));

ADD
WalletWasabi/Helpers/LabelBuilder.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace WalletWasabi.Helpers
+{
+ public class LabelBuilder
+ {
+ public List<string> Labels { get; } = new List<string>();
+
+ public LabelBuilder(params string[] labels)
+ {
+ labels = labels ?? new string[] { };
+ foreach (var label in labels)
+ {
+ Add(label);
+ }
+ }
+
+ public void Add(string label)
+ {
+ var parts = Guard.Correct(label).Split(',', StringSplitOptions.RemoveEmptyEntries);
+ foreach (var corrected in parts.Select(Guard.Correct))
+ {
+ if (corrected == "")
+ {
+ return;
+ }
+
+ if (!Labels.Contains(label))
+ {
+ Labels.Add(label);
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Join(", ", Labels);
+ }
+ }
+}

MODIFY
WalletWasabi/Models/SmartCoin.cs
WalletWasabi/Models/SmartCoin.cs
@@ -329,7 +329,6 @@ namespace WalletWasabi.Models
ScriptPubKey = Guard.NotNull(nameof(scriptPubKey), scriptPubKey);
Amount = Guard.NotNull(nameof(amount), amount);
Height = height;
- Label = Guard.Correct(label);
SpentOutputs = Guard.NotNullOrEmpty(nameof(spentOutputs), spentOutputs);
IsReplaceable = replaceable;
AnonymitySet = Guard.InRangeAndNotNull(nameof(anonymitySet), anonymitySet, 1, int.MaxValue);
@@ -343,6 +342,9 @@ namespace WalletWasabi.Models
HdPubKey = pubKey;
+ var labelBuilder = new LabelBuilder(label, pubKey?.Label);
+ Label = labelBuilder.ToString();
+
SetConfirmed();
SetUnspent();
SetIsBanned();

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -657,7 +657,8 @@ namespace WalletWasabi.Services
}
var localEndPoint = ServiceConfiguration.BitcoinCoreEndPoint;
- var localNode = await Node.ConnectAsync(Network, localEndPoint, nodeConnectionParameters);
+ using (var localNode = await Node.ConnectAsync(Network, localEndPoint, nodeConnectionParameters))
+ {
try
{
Logger.LogInfo<WalletService>($"TCP Connection succeeded, handshaking...");
@@ -686,6 +687,7 @@ namespace WalletWasabi.Services
}
}
}
+ }
Block blockFromLocalNode = null;
// Should timeout faster. Not sure if it should ever fail though. Maybe let's keep like this later for remote node connection.
@@ -991,7 +993,6 @@ namespace WalletWasabi.Services
var psbt = builder.BuildPSBT(false);
- string changeLabel = null;
var spentCoins = psbt.Inputs.Select(txin => smartCoinsByOutpoint[txin.PrevOut]).ToArray();
var realToSend = payments.Requests.Select(t => (label: t.Label,
@@ -1000,18 +1001,6 @@ namespace WalletWasabi.Services
.Where(i => i.amount != null)
.ToArray();
- if (changeHdPubKey != null)
- {
- var sb = new StringBuilder();
- foreach (var item in realToSend)
- {
- var corrected = Guard.Correct(item.label);
- sb.Append($"{corrected}, ");
- }
- changeLabel = sb.ToString().Trim(',', ' ');
- changeHdPubKey.SetLabel(changeLabel, KeyManager);
- }
-
if (!psbt.TryGetFee(out var fee))
{
throw new InvalidOperationException($"Impossible to get the fees of the PSBT, this should never happen.");
@@ -1089,25 +1078,49 @@ namespace WalletWasabi.Services
}
}
+ var labelBuilder = new LabelBuilder();
+ foreach (var label in payments.Requests.Select(x => x.Label))
+ {
+ labelBuilder.Add(label);
+ }
var outerWalletOutputs = new List<SmartCoin>();
var innerWalletOutputs = new List<SmartCoin>();
for (var i = 0U; i < tx.Outputs.Count; i++)
{
TxOut output = tx.Outputs[i];
var anonset = (tx.GetAnonymitySet(i) + spentCoins.Min(x => x.AnonymitySet)) - 1; // Minus 1, because count own only once.
- var foundKey = KeyManager.GetKeys(KeyState.Clean).FirstOrDefault(x => output.ScriptPubKey == x.P2wpkhScript);
+ var foundKey = KeyManager.GetKeyForScriptPubKey(output.ScriptPubKey);
var coin = new SmartCoin(tx.GetHash(), i, output.ScriptPubKey, output.Value, tx.Inputs.ToTxoRefs().ToArray(), Height.Unknown, tx.RBF, anonset, isLikelyCoinJoinOutput: false, pubKey: foundKey);
+ labelBuilder.Add(coin.Label); // foundKey's label is already added to the coinlabel.
- if (foundKey != null)
+ if (foundKey is null)
+ {
+ outerWalletOutputs.Add(coin);
+ }
+ else
{
- coin.Label = changeLabel;
innerWalletOutputs.Add(coin);
}
+ }
+
+ foreach (var coin in outerWalletOutputs.Concat(innerWalletOutputs))
+ {
+ var foundPaymentRequest = payments.Requests.FirstOrDefault(x => x.Destination.ScriptPubKey == coin.ScriptPubKey);
+
+ // If change then we concatenate all the labels.
+ if (foundPaymentRequest is null) // Then it's autochange.
+ {
+ coin.Label = labelBuilder.ToString();
+ }
else
{
- outerWalletOutputs.Add(coin);
+ coin.Label = new LabelBuilder(coin.Label, foundPaymentRequest.Label).ToString();
}
+
+ var foundKey = KeyManager.GetKeyForScriptPubKey(coin.ScriptPubKey);
+ foundKey?.SetLabel(coin.Label); // The foundkeylabel has already been added previously, so no need to concatenate.
}
+
Logger.LogInfo<WalletService>($"Transaction is successfully built: {tx.GetHash()}.");
var sign = !KeyManager.IsWatchOnly;
var spendsUnconfirmed = spentCoins.Any(c => !c.Confirmed);
@@ -1292,7 +1305,13 @@ namespace WalletWasabi.Services
Parallel.ForEach(unspentCoins, new ParallelOptions { MaxDegreeOfParallelism = simultaneousThread }, coin =>
{
- var result = string.Join(", ", GetClusters(coin, new List<SmartCoin>(), lookupScriptPubKey, lookupSpenderTransactionId, lookupTransactionId).Select(x => x.Label).Distinct());
+ var result = string.Join(
+ ", ",
+ GetClusters(coin, new List<SmartCoin>(), lookupScriptPubKey, lookupSpenderTransactionId, lookupTransactionId)
+ .SelectMany(x => x.Label
+ .Split(',', StringSplitOptions.RemoveEmptyEntries)
+ .Select(y => y.Trim()))
+ .Distinct());
coin.SetClusters(result);
});
}

Improve labeling