diff --git a/CHANGES.md b/CHANGES.md index fba8f6e001..9bc230804a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -47,6 +47,8 @@ To be released. `BlockHeader` constructor. [[#931], [#935]] - Added `HashDigest`-typed `preEvaluationHash` parameter to `Block()` constructor. [[#931], [#935]] + - Replaced `SerializationInfoExtensions.GetValueOrDefault()` to + `SerializationInfoExtensions.TryGetValue()`. [[#940]] ### Backward-incompatible network protocol changes @@ -172,6 +174,7 @@ To be released. [#933]: https://github.com/planetarium/libplanet/pull/933 [#935]: https://github.com/planetarium/libplanet/pull/935 [#936]: https://github.com/planetarium/libplanet/pull/936 +[#940]: https://github.com/planetarium/libplanet/pull/940 [sleep mode]: https://en.wikipedia.org/wiki/Sleep_mode diff --git a/Libplanet.Tests/Action/InsufficientBalanceExceptionTest.cs b/Libplanet.Tests/Action/InsufficientBalanceExceptionTest.cs new file mode 100644 index 0000000000..5e06d6a735 --- /dev/null +++ b/Libplanet.Tests/Action/InsufficientBalanceExceptionTest.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using Libplanet.Action; +using Xunit; + +namespace Libplanet.Tests.Action +{ + public class InsufficientBalanceExceptionTest + { + [Fact] + public void Serializable() + { + var minter = new Address(new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }); + var account = new Address(new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + }); + + var currency = new Currency("PLT", minter); + var exc = new InsufficientBalanceException(account, currency, 99, "for testing"); + + var formatter = new BinaryFormatter(); + using (var ms = new MemoryStream()) + { + formatter.Serialize(ms, exc); + + ms.Seek(0, SeekOrigin.Begin); + var deserialized = (InsufficientBalanceException)formatter.Deserialize(ms); + Assert.Equal("for testing", deserialized.Message); + Assert.Equal(account, deserialized.Address); + Assert.Equal(currency.Hash, deserialized.Currency.Hash); + Assert.Equal(99, deserialized.Balance); + } + } + } +} diff --git a/Libplanet.Tests/Action/UnexpectedlyTerminatedActionExceptionTest.cs b/Libplanet.Tests/Action/UnexpectedlyTerminatedActionExceptionTest.cs index a6b2dee28c..ab4dd413ef 100644 --- a/Libplanet.Tests/Action/UnexpectedlyTerminatedActionExceptionTest.cs +++ b/Libplanet.Tests/Action/UnexpectedlyTerminatedActionExceptionTest.cs @@ -1,7 +1,10 @@ using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; using Libplanet.Action; +using Libplanet.Tests.Common.Action; +using Libplanet.Tx; using Xunit; namespace Libplanet.Tests.Action @@ -12,8 +15,23 @@ public class UnexpectedlyTerminatedActionExceptionTest public void Serializable() { var innerExc = new Exception("inner"); + var blockHash = new HashDigest( + TestUtils.GetRandomBytes(HashDigest.Size) + ); + long blockIndex = 100; + var txId = new TxId(TestUtils.GetRandomBytes(TxId.Size)); + var action = new Sleep() + { + ZoneId = 1, + }; + var exc = new UnexpectedlyTerminatedActionException( - default, default, null, null, "for testing", innerExc + blockHash, + blockIndex, + txId, + action, + "for testing", + innerExc ); var formatter = new BinaryFormatter(); @@ -26,6 +44,13 @@ public void Serializable() Assert.Equal("for testing", deserialized.Message); Assert.IsType(deserialized.InnerException); Assert.Equal(innerExc.Message, deserialized.InnerException.Message); + + Assert.Equal(blockHash, deserialized.BlockHash); + Assert.Equal(blockIndex, deserialized.BlockIndex); + Assert.Equal(txId, deserialized.TxId); + + Assert.IsType(deserialized.Action); + Assert.Equal(action.PlainValue, deserialized.Action.PlainValue); } } } diff --git a/Libplanet.Tests/CurrencyTest.cs b/Libplanet.Tests/CurrencyTest.cs index 4a6442e639..e60be23be8 100644 --- a/Libplanet.Tests/CurrencyTest.cs +++ b/Libplanet.Tests/CurrencyTest.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Immutable; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; using Libplanet.Crypto; using Xunit; @@ -107,5 +109,21 @@ public void String() Currency currency = new Currency(ticker: "GOLD", minter: AddressA); Assert.Equal("GOLD (2ce0b92ff1c5e5631d73370ad2c45920c1ba9755)", currency.ToString()); } + + [Fact] + public void Serializable() + { + var currency = new Currency(ticker: "GOLD", minter: AddressA); + + var formatter = new BinaryFormatter(); + using (var ms = new MemoryStream()) + { + formatter.Serialize(ms, currency); + ms.Seek(0, SeekOrigin.Begin); + + var deserialized = (Currency)formatter.Deserialize(ms); + Assert.Equal(currency.Hash, deserialized.Hash); + } + } } } diff --git a/Libplanet/Action/InsufficientBalanceException.cs b/Libplanet/Action/InsufficientBalanceException.cs index 7bf85a333c..9647874310 100644 --- a/Libplanet/Action/InsufficientBalanceException.cs +++ b/Libplanet/Action/InsufficientBalanceException.cs @@ -1,6 +1,8 @@ #nullable enable using System; using System.Numerics; +using System.Runtime.Serialization; +using Libplanet.Serialization; namespace Libplanet.Action { @@ -36,6 +38,14 @@ public InsufficientBalanceException( Balance = balance; } + private InsufficientBalanceException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + Address = info.GetValue
(nameof(Address)); + Balance = info.GetValue(nameof(Balance)); + Currency = info.GetValue(nameof(Currency)); + } + /// /// The owner of the insufficient . /// @@ -50,5 +60,14 @@ public InsufficientBalanceException( /// The account's current balance of the . /// public BigInteger Balance { get; } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + + info.AddValue(nameof(Address), Address); + info.AddValue(nameof(Balance), Balance); + info.AddValue(nameof(Currency), Currency); + } } } diff --git a/Libplanet/Action/UnexpectedlyTerminatedActionException.cs b/Libplanet/Action/UnexpectedlyTerminatedActionException.cs index a81bb8b059..4dfa316aa6 100644 --- a/Libplanet/Action/UnexpectedlyTerminatedActionException.cs +++ b/Libplanet/Action/UnexpectedlyTerminatedActionException.cs @@ -1,8 +1,11 @@ using System; using System.Runtime.Serialization; using System.Security.Cryptography; +using Bencodex; +using Bencodex.Types; using Libplanet.Blockchain.Policies; using Libplanet.Blocks; +using Libplanet.Serialization; using Libplanet.Tx; namespace Libplanet.Action @@ -55,6 +58,30 @@ StreamingContext context ) : base(info, context) { + if (info.TryGetValue(nameof(BlockHash), out byte[] blockHash)) + { + BlockHash = new HashDigest(blockHash); + } + + if (info.TryGetValue(nameof(BlockIndex), out long blockIndex)) + { + BlockIndex = blockIndex; + } + + if (info.TryGetValue(nameof(TxId), out byte[] txId)) + { + TxId = new TxId(txId); + } + + if (info.TryGetValue($"{nameof(Action)}_type", out string actionType)) + { + var values = (Dictionary)new Codec().Decode( + info.GetValue($"{nameof(Action)}_values") + ); + + Action = (IAction)Activator.CreateInstance(Type.GetType(actionType, true, true)); + Action?.LoadPlainValue(values); + } } /// @@ -80,5 +107,31 @@ StreamingContext context /// The object which threw an exception. /// public IAction Action { get; } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + + if (BlockHash is HashDigest blockHash) + { + info.AddValue(nameof(BlockHash), blockHash.ToByteArray()); + } + + if (BlockIndex is long blockIndex) + { + info.AddValue(nameof(BlockIndex), blockIndex); + } + + if (TxId is TxId txId) + { + info.AddValue(nameof(TxId), txId.ToByteArray()); + } + + if (!(Action is null)) + { + info.AddValue($"{nameof(Action)}_type", Action.GetType().AssemblyQualifiedName); + info.AddValue($"{nameof(Action)}_values", new Codec().Encode(Action.PlainValue)); + } + } } } diff --git a/Libplanet/Currency.cs b/Libplanet/Currency.cs index 17d1e71b8d..cfc3935ee3 100644 --- a/Libplanet/Currency.cs +++ b/Libplanet/Currency.cs @@ -1,13 +1,16 @@ #nullable enable using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.Contracts; using System.IO; using System.Linq; +using System.Runtime.Serialization; using System.Security.Cryptography; using Bencodex; using Bencodex.Types; using Libplanet.Action; +using Libplanet.Serialization; namespace Libplanet { @@ -17,7 +20,8 @@ namespace Libplanet /// each value represents such currencies as USD (US Dollar) or /// EUR (Euro), not values like $100 or €100. /// - public readonly struct Currency + [Serializable] + public readonly struct Currency : ISerializable { /// /// The ticker symbol, e.g., "USD". @@ -80,6 +84,22 @@ public Currency(string ticker, Address? minter) { } + private Currency(SerializationInfo info, StreamingContext context) + { + Ticker = info.GetValue(nameof(Ticker)); + + if (info.TryGetValue(nameof(Minters), out List minters)) + { + Minters = minters.Select(m => new Address(m)).ToImmutableHashSet(); + } + else + { + Minters = default; + } + + Hash = GetHash(); + } + /// /// Returns true if and only if the given is allowed /// to mint or burn assets of this currency. @@ -90,6 +110,16 @@ public Currency(string ticker, Address? minter) [Pure] public bool AllowsToMint(Address address) => Minters is null || Minters.Contains(address); + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(nameof(Ticker), Ticker); + + if (Minters is IImmutableSet
minters) + { + info.AddValue(nameof(Minters), minters.Select(m => m.ToByteArray()).ToList()); + } + } + [Pure] public override string ToString() => $"{Ticker} ({Hash})"; diff --git a/Libplanet/Serialization/SerializationInfoExtensions.cs b/Libplanet/Serialization/SerializationInfoExtensions.cs index 8790de6c51..2d24c6c973 100644 --- a/Libplanet/Serialization/SerializationInfoExtensions.cs +++ b/Libplanet/Serialization/SerializationInfoExtensions.cs @@ -9,18 +9,20 @@ public static T GetValue(this SerializationInfo info, string name) return (T)info.GetValue(name, typeof(T)); } - public static T GetValueOrDefault( + public static bool TryGetValue( this SerializationInfo serializationInfo, string name, - T defaultValue) + out T value) { try { - return serializationInfo.GetValue(name); + value = serializationInfo.GetValue(name); + return true; } catch (SerializationException) { - return defaultValue; + value = default; + return false; } } }