This commit is contained in:
Kukks
2023-02-23 12:39:43 +01:00
parent 6a83de4ee6
commit 1a44b774aa
31 changed files with 99 additions and 579 deletions

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plugins\BTCPayServer.Plugins.Wabisabi\BTCPayServer.Plugins.Wabisabi.csproj" />
<ProjectReference Include="..\submodules\walletwasabi\WalletWasabi\WalletWasabi.csproj" />
</ItemGroup>
</Project>

View File

@@ -45,6 +45,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.PluginPacker",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.NIP05", "Plugins\BTCPayServer.Plugins.NIP05\BTCPayServer.Plugins.NIP05.csproj", "{362D2175-9632-418E-95B1-5D638C52ECA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Tests", "submodules\btcpayserver\BTCPayServer.Tests\BTCPayServer.Tests.csproj", "{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Tests", "BTCPayServer.Plugins.Tests\BTCPayServer.Plugins.Tests.csproj", "{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -213,6 +217,22 @@ Global
{362D2175-9632-418E-95B1-5D638C52ECA6}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
{362D2175-9632-418E-95B1-5D638C52ECA6}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
{362D2175-9632-418E-95B1-5D638C52ECA6}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Release|Any CPU.Build.0 = Release|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Altcoins-Debug|Any CPU.ActiveCfg = Altcoins-Debug|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{4146B6DF-7BEE-4BD0-B6B1-77E7630A1B81}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Release|Any CPU.Build.0 = Release|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}

View File

@@ -15,8 +15,10 @@ using Microsoft.Extensions.Options;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletWasabi.Affiliation;
using WalletWasabi.Affiliation.Models;
using WalletWasabi.Affiliation.Models.CoinjoinRequest;
using WalletWasabi.Affiliation.Models.CoinJoinNotification;
using WalletWasabi.Affiliation.Serialization;
namespace BTCPayServer.Plugins.Wabisabi.AffiliateServer;
@@ -84,8 +86,8 @@ public class AffiliateServerController:Controller
[AllowAnonymous]
[HttpPost("get_coinjoin_request")]
public async Task<IActionResult> GetCoinjoinRequest([FromBody] GetCoinjoinRequestRequest request)
[HttpPost("notify_coinjoin")]
public async Task<IActionResult> GetCoinjoinRequest()
{
var settings = await _settingsRepository.GetSettingAsync<WabisabiAffiliateSettings>();
@@ -93,11 +95,13 @@ public class AffiliateServerController:Controller
{
return NotFound();
}
var keyB = Encoders.Hex.DecodeData(settings.SigningKey);
ecdsa.ImportSubjectPublicKeyInfo(keyB.AsSpan(), out _);
Payload payload = new(new Header(), request.Body);
using var reader = new StreamReader(Request.Body);
var request =
AffiliateServerHttpApiClient.Deserialize<CoinJoinNotificationRequest>(await reader.ReadToEndAsync());
Payload payload = new(Header.Instance, request.Body);
try
{
@@ -109,8 +113,9 @@ public class AffiliateServerController:Controller
await System.IO.File.AppendAllLinesAsync(path, new[] {JObject.FromObject(request).ToString(Formatting.None).Replace(Environment.NewLine, "")}, Encoding.UTF8);
return Ok(new GetCoinjoinRequestResponse(Array.Empty<byte>()));
var response = new CoinJoinNotificationResponse(Array.Empty<byte>());
return Json(response, AffiliationJsonSerializationOptions.Settings);
}
catch (Exception e)
{

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using NBitcoin;
using WalletWasabi.WabiSabi.Models;
namespace WalletWasabi.Affiliation.Models;
public record AffiliateInformation
(
ImmutableArray<AffiliationFlag> RunningAffiliateServers,
ImmutableDictionary<string, ImmutableDictionary<AffiliationFlag, byte[]>> CoinjoinRequests
)
{
public static readonly AffiliateInformation Empty = new(ImmutableArray<AffiliationFlag>.Empty, ImmutableDictionary<string, ImmutableDictionary<AffiliationFlag, byte[]>>.Empty);
}

View File

@@ -1,42 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using WalletWasabi.Affiliation.Serialization;
using System.Text;
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
public record Body
{
public Body(IEnumerable<Input> inputs, IEnumerable<Output> outputs, long slip44CoinType, decimal feeRate, long noFeeThreshold, long minRegistrableAmount, long timestamp)
{
Inputs = inputs;
Outputs = outputs;
Slip44CoinType = slip44CoinType;
FeeRate = feeRate;
NoFeeThreshold = noFeeThreshold;
MinRegistrableAmount = minRegistrableAmount;
Timestamp = timestamp;
}
[JsonProperty(PropertyName = "inputs")]
public IEnumerable<Input> Inputs { get; }
[JsonProperty(PropertyName = "outputs")]
public IEnumerable<Output> Outputs { get; }
[JsonProperty(PropertyName = "slip44_coin_type")]
public long Slip44CoinType { get; }
[JsonProperty(PropertyName = "fee_rate")]
[JsonConverter(typeof(AffiliationFeeRateJsonConverter))]
public decimal FeeRate { get; }
[JsonProperty(PropertyName = "no_fee_threshold")]
public long NoFeeThreshold { get; }
[JsonProperty(PropertyName = "min_registrable_amount")]
public long MinRegistrableAmount { get; }
[JsonProperty(PropertyName = "timestamp")]
public long Timestamp { get; }
}

View File

@@ -1,12 +0,0 @@
using Newtonsoft.Json;
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
public record Header
{
[JsonProperty(PropertyName = "title")]
public static readonly string Title = "payment request";
[JsonProperty(PropertyName = "version")]
public static readonly int Version = 1;
}

View File

@@ -1,62 +0,0 @@
using System;
using NBitcoin;
using Newtonsoft.Json;
using WalletWasabi.Affiliation.Serialization;
using WalletWasabi.WabiSabi.Models;
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
public record Input
{
public Input(Outpoint prevout, byte[] scriptPubkey, bool isAffiliated, bool isNoFee)
{
Prevout = prevout;
ScriptPubkey = scriptPubkey;
IsAffiliated = isAffiliated;
IsNoFee = isNoFee;
if (isNoFee && isAffiliated)
{
Logging.Logger.LogWarning($"Detected input with redundant affiliation flag: {Convert.ToHexString(prevout.Hash)}, {prevout.Index}");
}
}
[JsonProperty(PropertyName = "prevout")]
public Outpoint Prevout { get; }
[JsonProperty(PropertyName = "script_pubkey")]
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
public byte[] ScriptPubkey { get; }
[JsonProperty(PropertyName = "is_affiliated")]
public bool IsAffiliated { get; }
[JsonProperty(PropertyName = "is_no_fee")]
public bool IsNoFee { get; }
public static Input FromAffiliateInput(AffiliateInput affiliateInput, AffiliationFlag affiliationFlag)
{
return new Input(Outpoint.FromOutPoint(affiliateInput.Prevout), affiliateInput.ScriptPubKey.ToBytes(), affiliateInput.AffiliationFlag == affiliationFlag, affiliateInput.IsNoFee);
}
}
public record AffiliateInput
{
public AffiliateInput(OutPoint prevout, Script scriptPubKey, AffiliationFlag affiliationFlag, bool isNoFee)
{
Prevout = prevout;
ScriptPubKey = scriptPubKey;
AffiliationFlag = affiliationFlag;
IsNoFee = isNoFee;
}
public AffiliateInput(Coin coin, AffiliationFlag affiliationFlag, bool isNoFee)
: this(coin.Outpoint, coin.ScriptPubKey, affiliationFlag, isNoFee)
{
}
public OutPoint Prevout { get; }
public Script ScriptPubKey { get; }
public AffiliationFlag AffiliationFlag { get; }
public bool IsNoFee { get; }
}

View File

@@ -1,26 +0,0 @@
using NBitcoin;
using Newtonsoft.Json;
using WalletWasabi.Affiliation.Serialization;
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
public record Outpoint
{
public Outpoint(byte[] hash, long index)
{
Hash = hash;
Index = index;
}
[JsonProperty(PropertyName = "hash")]
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
public byte[] Hash { get; }
[JsonProperty(PropertyName = "index")]
public long Index { get; }
public static Outpoint FromOutPoint(OutPoint outPoint)
{
return new Outpoint(outPoint.Hash.ToBytes(lendian: true), outPoint.N);
}
}

View File

@@ -1,26 +0,0 @@
using NBitcoin;
using Newtonsoft.Json;
using WalletWasabi.Affiliation.Serialization;
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
public record Output
{
public Output(long amount, byte[] script_pubkey)
{
Amount = amount;
ScriptPubkey = script_pubkey;
}
[JsonProperty(PropertyName = "amount")]
public long Amount { get; }
[JsonProperty(PropertyName = "script_pubkey")]
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
public byte[] ScriptPubkey { get; }
public static Output FromTxOut(TxOut txOut)
{
return new Output(txOut.Value, txOut.ScriptPubKey.ToBytes());
}
}

View File

@@ -1,25 +0,0 @@
using Newtonsoft.Json;
using WalletWasabi.Affiliation.Serialization;
using System.Text;
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
public record Payload
{
public Payload(Header header, Body body)
{
Header = header;
Body = body;
}
[JsonProperty(PropertyName = "header")]
public Header Header { get; }
[JsonProperty(PropertyName = "body")]
public Body Body { get; }
public byte[] GetCanonicalSerialization()
{
return Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(this, CanonicalJsonSerializationOptions.Settings));
}
}

View File

@@ -1,16 +0,0 @@
using Newtonsoft.Json;
using WalletWasabi.Affiliation.Serialization;
using WalletWasabi.Affiliation.Models.CoinjoinRequest;
namespace WalletWasabi.Affiliation.Models;
public record GetCoinjoinRequestRequest
{
[JsonProperty(PropertyName = "signature")]
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
public byte[] Signature { get; }
[JsonProperty(PropertyName = "body")]
public Body Body { get; }
}

View File

@@ -1,14 +0,0 @@
using Newtonsoft.Json;
namespace WalletWasabi.Affiliation.Models;
public class GetCoinjoinRequestResponse
{
[JsonProperty(PropertyName = "coinjoin_request")]
public byte[] CoinjoinRequest;
public GetCoinjoinRequestResponse(byte[] coinjoinRequest)
{
CoinjoinRequest = coinjoinRequest;
}
}

View File

@@ -1,3 +0,0 @@
namespace WalletWasabi.Affiliation.Models;
public record StatusRequest();

View File

@@ -1,3 +0,0 @@
namespace WalletWasabi.Affiliation.Models;
public record StatusResponse();

View File

@@ -1,24 +0,0 @@
using System;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
using WalletWasabi.Helpers;
namespace WalletWasabi.Affiliation.Serialization;
public class AffiliationByteArrayJsonConverter : JsonConverter<byte[]>
{
public override byte[]? ReadJson(JsonReader reader, Type objectType, byte[]? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value is string serialized)
{
return Convert.FromHexString(serialized);
}
throw new JsonSerializationException("Cannot deserialize object.");
}
public override void WriteJson(JsonWriter writer, byte[]? value, JsonSerializer serializer)
{
Guard.NotNull(nameof(value), value);
writer.WriteValue(Convert.ToHexString(value).ToLower());
}
}

View File

@@ -1,42 +0,0 @@
using System;
using Newtonsoft.Json;
namespace WalletWasabi.Affiliation.Serialization;
public class AffiliationFeeRateJsonConverter : JsonConverter<decimal>
{
public static readonly decimal Base = 1e-8m;
private static long EncodeDecimal(decimal value)
{
return (long)decimal.Round(value / Base);
}
private static decimal DecodeDecimal(long value)
{
return value * Base;
}
private static bool IsEncodable(decimal value)
{
return DecodeDecimal(EncodeDecimal(value)) == value;
}
public override decimal ReadJson(JsonReader reader, Type objectType, decimal existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value is long number)
{
return DecodeDecimal(number);
}
throw new JsonSerializationException("Cannot deserialize object.");
}
public override void WriteJson(JsonWriter writer, decimal value, JsonSerializer serializer)
{
if (!IsEncodable(value))
{
throw new ArgumentException("Decimal cannot be unambiguously encodable.", nameof(value));
}
writer.WriteValue(EncodeDecimal(value));
}
}

View File

@@ -1,40 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using WalletWasabi.WabiSabi.Models;
namespace WalletWasabi.Affiliation.Serialization;
public class AffiliationFlagConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string)
{
return new AffiliationFlag((string)value);
}
throw new NotSupportedException();
}
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (destinationType == typeof(string))
{
if (value is AffiliationFlag)
{
return ((AffiliationFlag)value).Name;
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}

View File

@@ -1,15 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace WalletWasabi.Affiliation.Serialization;
public static class AffiliationJsonSerializationOptions
{
public static readonly List<JsonConverter> Converters = new()
{
new AffiliationByteArrayJsonConverter(),
new AffiliationFeeRateJsonConverter()
};
public static readonly JsonSerializerSettings Settings = new() { Converters = Converters };
}

View File

@@ -1,56 +0,0 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Collections.Generic;
using System.Linq;
namespace WalletWasabi.Affiliation.Serialization;
public static class CanonicalJsonSerializationOptions
{
/// <summary>
/// JSON settings that enforces JSON's objects' properties' to be serialized in alphabetical order.
/// </summary>
public static readonly JsonSerializerSettings Settings = new()
{
ContractResolver = new OrderedContractResolver(),
Converters = AffiliationJsonSerializationOptions.Converters,
// Intentionally enforced default value.
Formatting = Formatting.None
};
/// <seealso href="https://stackoverflow.com/a/11309106/3744182"/>
private class OrderedContractResolver : DefaultContractResolver
{
private static bool IsValidCharacter(char c)
{
return char.IsAscii(c) && ((char.IsLetter(c) && char.IsLower(c)) || char.IsDigit(c) || c == '_');
}
private static bool IsValidPropertyName(string name)
{
return name.All(IsValidCharacter);
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IEnumerable<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
foreach (JsonProperty property in properties)
{
if (property.PropertyName is null)
{
throw new JsonSerializationException("Property name is not set");
}
if (!IsValidPropertyName(property.PropertyName))
{
throw new JsonSerializationException("Object property contains an invalid character.");
}
}
return properties.OrderBy(p => p.PropertyName, StringComparer.Ordinal).ToList();
}
}
}

View File

@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using Microsoft.Extensions.Logging;
using NBitcoin;
using WalletWasabi.Blockchain.TransactionOutputs;
@@ -83,29 +81,6 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
new Dictionary<AnonsetType, int>() {{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}},
_wallet.ConsolidationMode, liquidityClue);
_logger.LogInformation(solution.ToString());
// SubsetSolution bestSolution = null;
// for (int i = 0; i < 100; i++)
// {
// var minCoins = new Dictionary<AnonsetType, int>();
// if (_wallet.RedCoinIsolation)
// {
// minCoins.Add(AnonsetType.Red, 1);
// }
// var solution = SelectCoinsInternal(utxoSelectionParameters, coinCandidates, payments, Random.Shared.Next(10,31),
// minCoins, new Dictionary<AnonsetType, int>()
// {
//
// {AnonsetType.Red, 1},
// {AnonsetType.Orange, 1},
// {AnonsetType.Green, 1}
// },_wallet.ConsolidationMode, liquidityClue);
// if (bestSolution is null || solution.Score() > bestSolution.Score())
// {
// bestSolution = solution;
// }
// }
// _logger.LogInformation(bestSolution.ToString());
// return bestSolution.Coins.ToImmutableList();
return solution.Coins.ToImmutableList();
}
@@ -115,7 +90,6 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
Dictionary<AnonsetType, int> maxPerType, Dictionary<AnonsetType, int> idealMinimumPerType,
bool consolidationMode, Money liquidityClue)
{
var stopwatch = Stopwatch.StartNew();
// Sort the coins by their anon score and then by descending order their value, and then slightly randomize in 2 ways:
//attempt to shift coins that comes from the same tx AND also attempt to shift coins based on percentage probability
var remainingCoins = SlightlyShiftOrder(RandomizeCoins(
@@ -130,25 +104,6 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
if (remainingCoins.All(coin => coin.CoinColor(_wallet.AnonScoreTarget) == AnonsetType.Green) &&
!remainingPendingPayments.Any())
{
// var decidedAmt = Random.Shared.Next(10, maxCoins);
// // all the coins are mixed and we have no payments to do..
// //if we are trying to reduce our utxoset, and we
// if (consolidationMode && remainingCoins.Count >= decidedAmt)
// {
//
// for (int i = 0; i < decidedAmt; i++)
// {
//
// var anonsetOrderedCoin =
// remainingCoins.OrderBy(coin => coin.AnonymitySet).BiasedRandomElement(70);
// solution.Coins.Add(anonsetOrderedCoin);
// remainingCoins.Remove(anonsetOrderedCoin);
// }
// }
// else
// {
//still good to have a chance to proceed with a join to reduce timing analysis
var rand = Random.Shared.Next(1, 1001);
if (rand > _wallet.WabisabiStoreSettings.ExtraJoinProbability)
{
@@ -158,7 +113,6 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
_logger.LogInformation(
"All coins are private and we have no pending payments but will join just to reduce timing analysis");
//}
}
while (remainingCoins.Any())
@@ -246,9 +200,6 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
}
}
}
stopwatch.Stop();
solution.TimeElapsed = stopwatch.Elapsed;
return solution;
}
@@ -316,7 +267,7 @@ public enum AnonsetType
Green
}
public class SubsetSolution : IEquatable<SubsetSolution>
public class SubsetSolution
{
private readonly UtxoSelectionParameters _utxoSelectionParameters;
@@ -326,8 +277,6 @@ public class SubsetSolution : IEquatable<SubsetSolution>
TotalPaymentsGross = totalPaymentsGross;
AnonsetTarget = anonsetTarget;
}
public TimeSpan TimeElapsed { get; set; }
public List<SmartCoin> Coins { get; set; } = new();
public List<PendingPayment> HandledPayments { get; set; } = new();
@@ -346,49 +295,6 @@ public class SubsetSolution : IEquatable<SubsetSolution>
public decimal LeftoverValue => TotalValue - TotalPaymentCost;
public decimal Score()
{
var score = 0m;
decimal ComputeCoinScore(List<SmartCoin> coins)
{
var w = 0m;
foreach (var smartCoin in coins)
{
var val = smartCoin.EffectiveValue(_utxoSelectionParameters.MiningFeeRate,
_utxoSelectionParameters.CoordinationFeeRate).ToDecimal(MoneyUnit.BTC);
if (smartCoin.AnonymitySet <= 0)
{
w += val;
}
else
{
w += val / (decimal)smartCoin.AnonymitySet;
}
}
return w; // / (coins.Count == 0 ? 1 : coins.Count);
}
decimal ComputePaymentScore(List<PendingPayment> pendingPayments)
{
return TotalPaymentsGross == 0 ? 100 : (pendingPayments.Count / (decimal)TotalPaymentsGross) * 100;
}
score += ComputeCoinScore(Coins);
score += ComputePaymentScore(HandledPayments);
return score;
}
public string GetId()
{
return string.Join("-",
Coins.OrderBy(coin => coin.Outpoint).Select(coin => coin.Outpoint.ToString())
.Concat(HandledPayments.OrderBy(arg => arg.Value).Select(p => p.Value.ToString())));
}
public override string ToString()
{
var sb = new StringBuilder();
@@ -402,7 +308,7 @@ public class SubsetSolution : IEquatable<SubsetSolution>
sc.TryGetValue(AnonsetType.Orange, out var ocoins);
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} score: {Score()} Compute time: {TimeElapsed} ");
$"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}");
sb.AppendLine(
$"Used coins: {string.Join(", ", Coins.Select(coin => coin.Outpoint + " " + coin.Amount.ToString() + " A" + coin.AnonymitySet))}");
if (HandledPayments.Any())
@@ -410,18 +316,6 @@ public class SubsetSolution : IEquatable<SubsetSolution>
return sb.ToString();
}
public bool Equals(SubsetSolution? other)
{
return GetId() == other?.GetId();
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((SubsetSolution)obj);
}
private static decimal GetAnonLoss<TCoin>(IEnumerable<TCoin> coins)
where TCoin : SmartCoin

View File

@@ -643,12 +643,11 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
PaymentFailed = PaymentFailed(data.Id),
PaymentSucceeded = PaymentSucceeded(data.Id),
};
}).Where(payment => payment is not null).ToArray();
return await Task.WhenAll(payouts);
}).ToArray();
return (await Task.WhenAll(payouts)).Where(payment => payment is not null).ToArray();
}
catch (Exception e)
{
Console.WriteLine(e);
return Array.Empty<PendingPayment>();
}
}

View File

@@ -3,6 +3,7 @@ using BTCPayServer.Abstractions.Services;
using BTCPayServer.Configuration;
using BTCPayServer.Services;
using Microsoft.Extensions.DependencyInjection;
using WalletWasabi.Affiliation;
using WalletWasabi.WabiSabi.Models.Serialization;
namespace WalletWasabi.Backend.Controllers;
@@ -16,8 +17,12 @@ public static class CoordinatorExtensions
services.AddTransient(provider =>
{
var s = provider.GetRequiredService<WabisabiCoordinatorService>();
if (!s.Started)
{
return null;
}
return new WabiSabiController(s.IdempotencyRequestCache, s.WabiSabiCoordinator.Arena,
s.WabiSabiCoordinator.CoinJoinFeeRateStatStore);
s.WabiSabiCoordinator.CoinJoinFeeRateStatStore, s.WabiSabiCoordinator.AffiliationManager);
});
services.AddHostedService((sp) => sp.GetRequiredService<WabisabiCoordinatorService>());

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using WalletWasabi.Affiliation;
using WalletWasabi.Backend.Filters;
using WalletWasabi.Cache;
using WalletWasabi.WabiSabi.Backend.PostRequests;
@@ -23,17 +24,19 @@ namespace WalletWasabi.Backend.Controllers;
[Produces("application/json")]
public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
{
public WabiSabiController(IdempotencyRequestCache idempotencyRequestCache, Arena arena, CoinJoinFeeRateStatStore coinJoinFeeRateStatStore)
public WabiSabiController(IdempotencyRequestCache idempotencyRequestCache, Arena arena, CoinJoinFeeRateStatStore coinJoinFeeRateStatStore, AffiliationManager affiliationManager)
{
IdempotencyRequestCache = idempotencyRequestCache;
Arena = arena;
CoinJoinFeeRateStatStore = coinJoinFeeRateStatStore;
AffiliationManager = affiliationManager;
}
private static TimeSpan RequestTimeout { get; } = TimeSpan.FromMinutes(5);
private IdempotencyRequestCache IdempotencyRequestCache { get; }
private Arena Arena { get; }
private CoinJoinFeeRateStatStore CoinJoinFeeRateStatStore { get; }
private AffiliationManager AffiliationManager { get; }
[HttpPost("status")]
public async Task<RoundStateResponse> GetStatusAsync(RoundStateRequest request, CancellationToken cancellationToken)
@@ -41,7 +44,8 @@ public class WabiSabiController : ControllerBase, IWabiSabiApiRequestHandler
var before = DateTimeOffset.UtcNow;
var response = await Arena.GetStatusAsync(request, cancellationToken);
var medians = CoinJoinFeeRateStatStore.GetDefaultMedians();
var ret = new RoundStateResponse(response.RoundStates, medians);
var affiliateInformation = AffiliationManager.GetAffiliateInformation();
var ret = new RoundStateResponse(response.RoundStates, medians, affiliateInformation);
var duration = DateTimeOffset.UtcNow - before;
RequestTimeStatista.Instance.Add("status", duration);

View File

@@ -26,6 +26,7 @@ using NBXplorer;
using NBXplorer.Models;
using Newtonsoft.Json.Linq;
using NNostr.Client;
using WalletWasabi.Affiliation;
using WalletWasabi.Bases;
using WalletWasabi.BitcoinCore.Rpc;
using WalletWasabi.Cache;
@@ -47,6 +48,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
public readonly IdempotencyRequestCache IdempotencyRequestCache;
public bool Started => HostedServices.IsStartAllAsyncStarted;
private HostedServices HostedServices { get; } = new();
public WabiSabiCoordinator WabiSabiCoordinator { get; private set; }
@@ -63,6 +65,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
_httpClientFactory = httpClientFactory;
IdempotencyRequestCache = new(memoryCache);
}
private WabisabiCoordinatorSettings cachedSettings;

View File

@@ -64,11 +64,13 @@
}
<div class="row">
<h2 class="">Coinjoin configuration</h2>
<a href="https://github.com/Kukks/BTCPayServerPlugins/blob/master/Plugins/BTCPayServer.Plugins.Wabisabi/readme.md" class="ms-1" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span></div>
<div class="d-flex">
<h2 class="">Coinjoin configuration</h2>
<a href="https://github.com/Kukks/BTCPayServerPlugins/blob/master/Plugins/BTCPayServer.Plugins.Wabisabi/readme.md" class="ms-1" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</div>
</div>
<form method="post">
@{
var wallet = await WalletProvider.GetWalletAsync(storeId);
@@ -121,8 +123,8 @@
<div id="advanced" class="@(Model.PlebMode ? "d-none" : "")">
<div class="form-group">
<label asp-for="AnonymitySetTarget" class="form-label">Use Anon score model</label>
<input type="number" class="form-control" asp-for="AnonymitySetTarget" placeholder="target anon score">
<label asp-for="AnonymitySetTarget" class="form-label">Anon score target</label>
<input type="number" class="form-control" asp-for="AnonymitySetTarget" placeholder="target anon score" min="0">
<p class="text-muted">Scores your coinjoined utxos based on how many other utxos in the coinjoin (and other previous coinjoin rounds) had the same value.<br/> Anonset score computation is not an exact science, and when using coordinators with massive liquidity, is not that important as all rounds (past, present, future) contribute to your privacy.</p>
</div>
@@ -350,10 +352,10 @@
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings">Coordinator runner</a>
<a asp-controller="WabisabiStore" asp-action="ListCoinjoins" class="btn btn-secondary mt-2" asp-route-storeId="@storeId">Coinjoins</a>
<button name="command" type="submit" value="discover" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings">Discover coordinators over Nostr</button>
<a class="btn btn-secondary mt-2" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discrete payments - Coming soon</a>
<a class="btn btn-secondary mt-2" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discreet payments - Coming soon</a>
</form>
</form>
@if (ViewBag.DiscoveredCoordinators is List<DiscoveredCoordinator> discoveredCoordinators)
{
@@ -364,6 +366,7 @@
}
<partial name="Wabisabi/AddManualCoordinator" model="@(new DiscoveredCoordinator())"/>
@section PageFootContent {
<partial name="_ValidationScriptsPartial"/>
}

View File

@@ -3,4 +3,7 @@
@addTagHelper *, BTCPayServer.Abstractions
@addTagHelper *, BTCPayServer.TagHelpers
@addTagHelper *, BTCPayServer.Views.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, BTCPayServer

View File

@@ -1,41 +1,25 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using BTCPayServer.Client.Models;
using BTCPayServer.Common;
using BTCPayServer.Data.Data;
using BTCPayServer.Payments;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.PayoutProcessors;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using WalletWasabi.Backend.Controllers;
using WalletWasabi.Logging;
using WalletWasabi.WabiSabi.Client;
using WalletWasabi.WabiSabi.Models.Serialization;
using LogLevel = WalletWasabi.Logging.LogLevel;
namespace BTCPayServer.Plugins.Wabisabi;
@@ -131,7 +115,7 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
return new[] {new PaymentMethodId("BTC", PaymentTypes.BTCLike)};
}
public Task<IHostedService> ConstructProcessor(PayoutProcessorData settings)
public Task<IHostedService> ConstructProcessor(Data.PayoutProcessorData settings)
{
return Task.FromResult<IHostedService>(new ShellSerice());
}

View File

@@ -1,15 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client;
using BTCPayServer.Data.Data;
using BTCPayServer.Data;
using BTCPayServer.PayoutProcessors;
using BTCPayServer.Services;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json.Linq;
using WalletWasabi.WabiSabi.Client;
namespace BTCPayServer.Plugins.Wabisabi
{

View File

@@ -91,6 +91,7 @@ namespace BTCPayServer.Plugins.Wabisabi
var actualCommand = pieces[0];
var commandIndex = pieces.Length > 1 ? pieces[1] : null;
var coordinator = pieces.Length > 2 ? pieces[2] : null;
vm.AnonymitySetTarget = Math.Max(2, vm.AnonymitySetTarget);
var coord = vm.Settings.SingleOrDefault(settings => settings.Coordinator == coordinator);
ModelState.Clear();