diff --git a/BTCPayServer.Plugins.Tests/BTCPayServer.Plugins.Tests.csproj b/BTCPayServer.Plugins.Tests/BTCPayServer.Plugins.Tests.csproj new file mode 100644 index 0000000..8d99cda --- /dev/null +++ b/BTCPayServer.Plugins.Tests/BTCPayServer.Plugins.Tests.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + enable + enable + + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/BTCPayServerPlugins.sln b/BTCPayServerPlugins.sln index 8927b83..ba4fe8b 100644 --- a/BTCPayServerPlugins.sln +++ b/BTCPayServerPlugins.sln @@ -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} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/AffiliateServerController.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/AffiliateServerController.cs index 0ffc586..be176ac 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/AffiliateServerController.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/AffiliateServerController.cs @@ -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 GetCoinjoinRequest([FromBody] GetCoinjoinRequestRequest request) + [HttpPost("notify_coinjoin")] + public async Task GetCoinjoinRequest() { var settings = await _settingsRepository.GetSettingAsync(); @@ -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(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())); - + var response = new CoinJoinNotificationResponse(Array.Empty()); + return Json(response, AffiliationJsonSerializationOptions.Settings); + } catch (Exception e) { diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/AffiliateInformation.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/AffiliateInformation.cs deleted file mode 100644 index ce77d5c..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/AffiliateInformation.cs +++ /dev/null @@ -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 RunningAffiliateServers, - ImmutableDictionary> CoinjoinRequests -) -{ - public static readonly AffiliateInformation Empty = new(ImmutableArray.Empty, ImmutableDictionary>.Empty); -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Body.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Body.cs deleted file mode 100644 index 76ee0ab..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Body.cs +++ /dev/null @@ -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 inputs, IEnumerable 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 Inputs { get; } - - [JsonProperty(PropertyName = "outputs")] - public IEnumerable 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; } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Header.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Header.cs deleted file mode 100644 index 74b0a32..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Header.cs +++ /dev/null @@ -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; -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Input.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Input.cs deleted file mode 100644 index 48f4c56..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Input.cs +++ /dev/null @@ -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; } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Outpoint.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Outpoint.cs deleted file mode 100644 index 776880d..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Outpoint.cs +++ /dev/null @@ -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); - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Output.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Output.cs deleted file mode 100644 index a08e4e8..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Output.cs +++ /dev/null @@ -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()); - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Payload.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Payload.cs deleted file mode 100644 index bf17802..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/CoinjoinRequest/Payload.cs +++ /dev/null @@ -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)); - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/GetCoinjoinRequestRequest.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/GetCoinjoinRequestRequest.cs deleted file mode 100644 index eded271..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/GetCoinjoinRequestRequest.cs +++ /dev/null @@ -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; } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/GetCoinjoinRequestResponse.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/GetCoinjoinRequestResponse.cs deleted file mode 100644 index f14859c..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/GetCoinjoinRequestResponse.cs +++ /dev/null @@ -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; - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/StatusRequest.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/StatusRequest.cs deleted file mode 100644 index a38f203..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/StatusRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace WalletWasabi.Affiliation.Models; - -public record StatusRequest(); diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/StatusResponse.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/StatusResponse.cs deleted file mode 100644 index 924d3b4..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Models/StatusResponse.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace WalletWasabi.Affiliation.Models; - -public record StatusResponse(); diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationByteArrayJsonConverter.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationByteArrayJsonConverter.cs deleted file mode 100644 index 13bb0a5..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationByteArrayJsonConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using NBitcoin.DataEncoders; -using Newtonsoft.Json; -using WalletWasabi.Helpers; - -namespace WalletWasabi.Affiliation.Serialization; - -public class AffiliationByteArrayJsonConverter : JsonConverter -{ - 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()); - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationFeeRateJsonConverter.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationFeeRateJsonConverter.cs deleted file mode 100644 index 485b4a4..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationFeeRateJsonConverter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace WalletWasabi.Affiliation.Serialization; - -public class AffiliationFeeRateJsonConverter : JsonConverter -{ - 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)); - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationFlagConverter.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationFlagConverter.cs deleted file mode 100644 index 07d8041..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationFlagConverter.cs +++ /dev/null @@ -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); - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationJsonSerializationOptions.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationJsonSerializationOptions.cs deleted file mode 100644 index f2a2a42..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/AffiliationJsonSerializationOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace WalletWasabi.Affiliation.Serialization; - -public static class AffiliationJsonSerializationOptions -{ - public static readonly List Converters = new() - { - new AffiliationByteArrayJsonConverter(), - new AffiliationFeeRateJsonConverter() - }; - - public static readonly JsonSerializerSettings Settings = new() { Converters = Converters }; -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/CanonicalJsonSerializationOptions.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/CanonicalJsonSerializationOptions.cs deleted file mode 100644 index 3418fe8..0000000 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/AffiliateServer/Serialization/CanonicalJsonSerializationOptions.cs +++ /dev/null @@ -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 -{ - /// - /// JSON settings that enforces JSON's objects' properties' to be serialized in alphabetical order. - /// - public static readonly JsonSerializerSettings Settings = new() - { - ContractResolver = new OrderedContractResolver(), - Converters = AffiliationJsonSerializationOptions.Converters, - - // Intentionally enforced default value. - Formatting = Formatting.None - }; - - /// - 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 CreateProperties(Type type, MemberSerialization memberSerialization) - { - IEnumerable 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(); - } - } -} diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs index b16b89c..dcb5322 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs @@ -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.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(); - // if (_wallet.RedCoinIsolation) - // { - // minCoins.Add(AnonsetType.Red, 1); - // } - // var solution = SelectCoinsInternal(utxoSelectionParameters, coinCandidates, payments, Random.Shared.Next(10,31), - // minCoins, new Dictionary() - // { - // - // {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 maxPerType, Dictionary 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 +public class SubsetSolution { private readonly UtxoSelectionParameters _utxoSelectionParameters; @@ -326,8 +277,6 @@ public class SubsetSolution : IEquatable TotalPaymentsGross = totalPaymentsGross; AnonsetTarget = anonsetTarget; } - - public TimeSpan TimeElapsed { get; set; } public List Coins { get; set; } = new(); public List HandledPayments { get; set; } = new(); @@ -346,49 +295,6 @@ public class SubsetSolution : IEquatable public decimal LeftoverValue => TotalValue - TotalPaymentCost; - public decimal Score() - { - var score = 0m; - - decimal ComputeCoinScore(List 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 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 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 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(IEnumerable coins) where TCoin : SmartCoin diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs index 5947dea..186fce6 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs @@ -643,12 +643,11 @@ public async Task> 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(); } } diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/CoordinatorExtensions.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/CoordinatorExtensions.cs index 4cba256..8fa4a15 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/CoordinatorExtensions.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/CoordinatorExtensions.cs @@ -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(); + 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()); diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabiSabiController.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabiSabiController.cs index 5c5299b..117bfdd 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabiSabiController.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabiSabiController.cs @@ -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 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); diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs index f28ef73..0a23522 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs @@ -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; diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiStore/UpdateWabisabiStoreSettings.cshtml b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiStore/UpdateWabisabiStoreSettings.cshtml index 0207447..ab8150d 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiStore/UpdateWabisabiStoreSettings.cshtml +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/WabisabiStore/UpdateWabisabiStoreSettings.cshtml @@ -64,11 +64,13 @@ }
-

Coinjoin configuration

- - - -
+
+

Coinjoin configuration

+ + + +
+
@{ var wallet = await WalletProvider.GetWalletAsync(storeId); @@ -121,8 +123,8 @@
- - + +

Scores your coinjoined utxos based on how many other utxos in the coinjoin (and other previous coinjoin rounds) had the same value.
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.

@@ -350,10 +352,10 @@ Coordinator runner Coinjoins -Enable Discrete payments - Coming soon +Enable Discreet payments - Coming soon - + @if (ViewBag.DiscoveredCoordinators is List discoveredCoordinators) { @@ -364,6 +366,7 @@ } + @section PageFootContent { } diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/_ViewImports.cshtml b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/_ViewImports.cshtml index 3ee3edb..cf06ff9 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/_ViewImports.cshtml +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/_ViewImports.cshtml @@ -3,4 +3,7 @@ @addTagHelper *, BTCPayServer.Abstractions @addTagHelper *, BTCPayServer.TagHelpers @addTagHelper *, BTCPayServer.Views.TagHelpers -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, BTCPayServer \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs index 6300d37..04be45c 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiPlugin.cs @@ -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 ConstructProcessor(PayoutProcessorData settings) + public Task ConstructProcessor(Data.PayoutProcessorData settings) { return Task.FromResult(new ShellSerice()); } diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs index 9bf6b2b..b897366 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiService.cs @@ -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 { diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs index 9778b0b..bc7b656 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/WabisabiStoreController.cs @@ -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(); diff --git a/submodules/btcpayserver b/submodules/btcpayserver index 99299ba..e6a157a 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit 99299ba06fba954bced3e4c36fa823b39fa48f0c +Subproject commit e6a157a1016a0b9600a6c44c6a1f394a5a80ea69 diff --git a/submodules/walletwasabi b/submodules/walletwasabi index 77021fc..1123357 160000 --- a/submodules/walletwasabi +++ b/submodules/walletwasabi @@ -1 +1 @@ -Subproject commit 77021fc2cab35f19194b20bb2e8db2996c99a6a1 +Subproject commit 112335770901a5e1d1eb484451243626bb0dae51