Serialize coinjoin events
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Models/SerializationTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Models/SerializationTests.cs
@@ -138,7 +138,8 @@ public class SerializationTests
AssertSerialization(RoundState.FromRound(round));
var state = round.Assert<ConstructionState>();
- round.CoinjoinState = new SigningState(state.Parameters, state.Inputs, state.Outputs);
+ state = state.AddInput(CreateCoin());
+ round.CoinjoinState = new SigningState(state.Parameters, state.Events);
AssertSerialization(RoundState.FromRound(round));
}
@@ -184,4 +185,10 @@ public class SerializationTests
new(
new[] { MAC.ComputeMAC(IssuerKey, Points.First(), Scalars.First()) },
new[] { new Proof(new GroupElementVector(Points.Take(2)), new ScalarVector(Scalars.Take(2))) });
+
+ private static Coin CreateCoin()
+ {
+ using var key = new Key();
+ return WabiSabiFactory.CreateCoin(key);
+ }
}

ADD
WalletWasabi/WabiSabi/Models/Serialization/CoinJoinEventJsonConverter.cs
+using WalletWasabi.WabiSabi.Models.MultipartyTransaction;
+
+namespace WalletWasabi.WabiSabi.Models.Serialization;
+
+public class CoinJoinEventJsonConverter : GenericInterfaceJsonConverter<IEvent>
+{
+ public CoinJoinEventJsonConverter() : base(new[] { typeof(InputAdded), typeof(OutputAdded) })
+ {
+ }
+}

ADD
WalletWasabi/WabiSabi/Models/Serialization/GenericInterfaceJsonConverter.cs
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace WalletWasabi.WabiSabi.Models.Serialization;
+
+public class GenericInterfaceJsonConverter<T> : JsonConverter<T>
+{
+ // This converter is a bit unusual because we need to add a new property to the
+ // serialized json string but the converter is called recursively and fails with
+ // an "Self referencing loop" exception.
+ // The workaround is detect it and prevent reentering by setting CanRead and
+ // CanWrite to false immediately after entering.
+ // see: https://github.com/JamesNK/Newtonsoft.Json/issues/386
+ [ThreadStatic]
+ private static bool IsReading;
+
+ [ThreadStatic]
+ private static bool IsWriting;
+
+ public GenericInterfaceJsonConverter(IEnumerable<Type> types)
+ {
+ Types = types;
+ }
+
+ public override bool CanWrite => !IsWriting;
+
+ public override bool CanRead => !IsReading;
+
+ public IEnumerable<Type> Types { get; }
+
+ public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
+ {
+ try
+ {
+ IsWriting = true;
+ if (value is not null)
+ {
+ var stateTypeName = value.GetType().Name;
+ var jObject = (JObject)JToken.FromObject(value, serializer);
+ jObject.AddFirst(new JProperty("Type", stateTypeName));
+ jObject.WriteTo(writer);
+ }
+ }
+ finally
+ {
+ IsWriting = false;
+ }
+ }
+
+ public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ try
+ {
+ IsReading = true;
+
+ var jsonObject = JObject.Load(reader);
+ var typeName = jsonObject.Value<string>("Type");
+ var stateType = Types.Single(t => t.Name == typeName);
+ return (T?)serializer.Deserialize(jsonObject.CreateReader(), stateType);
+ }
+ finally
+ {
+ IsReading = false;
+ }
+ }
+}

MODIFY
WalletWasabi/WabiSabi/Models/Serialization/JsonSerializationOptions.cs
WalletWasabi/WabiSabi/Models/Serialization/JsonSerializationOptions.cs
@@ -26,7 +26,8 @@ public class JsonSerializationOptions
new MultipartyTransactionStateJsonConverter(),
new ExtPubKeyJsonConverter(),
new TimeSpanJsonConverter(),
- new CoinJsonConverter()
+ new CoinJsonConverter(),
+ new CoinJoinEventJsonConverter(),
}
};
public static readonly JsonSerializationOptions Default = new();

MODIFY
WalletWasabi/WabiSabi/Models/Serialization/MultipartyTransactionStateJsonConverter.cs
WalletWasabi/WabiSabi/Models/Serialization/MultipartyTransactionStateJsonConverter.cs
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
using WalletWasabi.WabiSabi.Models.MultipartyTransaction;
namespace WalletWasabi.WabiSabi.Models.Serialization;
-public class MultipartyTransactionStateJsonConverter : JsonConverter
+public class MultipartyTransactionStateJsonConverter : GenericInterfaceJsonConverter<MultipartyTransactionState>
{
- // This converter is a bit unusual because we need to add a new property to the
- // serialized json string but the converter is called recursively and fails with
- // an "Self referencing loop" exception.
- // The workaround is detect it and prevent reentering by setting CanRead and
- // CanWrite to false immediately after entering.
- // see: https://github.com/JamesNK/Newtonsoft.Json/issues/386
- [ThreadStatic]
- private static bool IsReading;
-
- [ThreadStatic]
- private static bool IsWriting;
-
- public override bool CanWrite => !IsWriting;
-
- public override bool CanRead => !IsReading;
-
- public override bool CanConvert(Type objectType)
- {
- return typeof(MultipartyTransactionState).IsAssignableFrom(objectType);
- }
-
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- try
- {
- IsWriting = true;
-
- var stateTypeName = value switch
- {
- ConstructionState => "Constructing",
- SigningState => "Signing",
- _ => throw new InvalidOperationException("")
- };
- var jObject = (JObject)JToken.FromObject(value, serializer);
- jObject.AddFirst(new JProperty("State", stateTypeName));
- jObject.WriteTo(writer);
- }
- finally
- {
- IsWriting = false;
- }
- }
-
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- try
+ public MultipartyTransactionStateJsonConverter() : base(new [] { typeof(ConstructionState), typeof(SigningState) })
{
- IsReading = true;
-
- var jsonObject = JObject.Load(reader);
- var stateType = jsonObject.Value<string>("State") switch
- {
- "Constructing" => typeof(ConstructionState),
- "Signing" => typeof(SigningState),
- _ => throw new InvalidOperationException("")
- };
- return serializer.Deserialize(jsonObject.CreateReader(), stateType);
- }
- finally
- {
- IsReading = false;
- }
}
}

Serialize coinjoin events