From 1c7d4df1e4d9a78962626d3c75fcadeed883a457 Mon Sep 17 00:00:00 2001 From: Shuai Date: Tue, 8 Mar 2022 16:37:37 +0800 Subject: [PATCH] Fix OOM --- neo3-gui/neo3-gui.tests/neo3-gui.tests.csproj | 2 +- .../Common/Analyzers/BlockAnalyzer.cs | 2 +- .../neo3-gui/Common/ByteArrayValueComparer.cs | 31 ++++ .../neo3-gui/Common/Consoles/MainService.cs | 2 +- .../neo3-gui/Common/Json/JObjectConverter.cs | 16 +- .../Storage/LevelDBModules/LevelDbContext.cs | 10 +- .../Storage/LevelDBModules/WriteBatch.cs | 5 +- neo3-gui/neo3-gui/Helpers.cs | 138 +++++++++--------- .../ApiServices/ContractApiService.cs | 8 +- .../ApiServices/TransactionApiService.cs | 12 +- 10 files changed, 146 insertions(+), 80 deletions(-) create mode 100644 neo3-gui/neo3-gui/Common/ByteArrayValueComparer.cs diff --git a/neo3-gui/neo3-gui.tests/neo3-gui.tests.csproj b/neo3-gui/neo3-gui.tests/neo3-gui.tests.csproj index db787ad4..3c920462 100644 --- a/neo3-gui/neo3-gui.tests/neo3-gui.tests.csproj +++ b/neo3-gui/neo3-gui.tests/neo3-gui.tests.csproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 neo3_gui.tests false diff --git a/neo3-gui/neo3-gui/Common/Analyzers/BlockAnalyzer.cs b/neo3-gui/neo3-gui/Common/Analyzers/BlockAnalyzer.cs index 7b20d20b..bcd22456 100644 --- a/neo3-gui/neo3-gui/Common/Analyzers/BlockAnalyzer.cs +++ b/neo3-gui/neo3-gui/Common/Analyzers/BlockAnalyzer.cs @@ -90,7 +90,7 @@ private void AnalysisAppExecuteResult(Blockchain.ApplicationExecuted appExec) execResult.GasConsumed = appExec.GasConsumed; try { - execResult.ResultStack = appExec.Stack.Select(q => q.ToContractParameter().ToJson()).ToArray(); + execResult.ResultStack = appExec.Stack.Select(q => q.ToJObject()).ToArray(); } catch (InvalidOperationException) { diff --git a/neo3-gui/neo3-gui/Common/ByteArrayValueComparer.cs b/neo3-gui/neo3-gui/Common/ByteArrayValueComparer.cs new file mode 100644 index 00000000..b5d692bd --- /dev/null +++ b/neo3-gui/neo3-gui/Common/ByteArrayValueComparer.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Common +{ + public class ByteArrayValueComparer : IEqualityComparer + { + + public static readonly ByteArrayValueComparer Default = new(); + + public bool Equals(byte[] x, byte[] y) + { + return x.AsSpan().SequenceEqual(y.AsSpan()); + } + + public int GetHashCode(byte[] obj) + { + unchecked + { + int hash = 17; + foreach (byte element in obj) + hash = hash * 31 + element; + return hash; + } + } + + } +} diff --git a/neo3-gui/neo3-gui/Common/Consoles/MainService.cs b/neo3-gui/neo3-gui/Common/Consoles/MainService.cs index a8199421..6bcc07d6 100644 --- a/neo3-gui/neo3-gui/Common/Consoles/MainService.cs +++ b/neo3-gui/neo3-gui/Common/Consoles/MainService.cs @@ -313,7 +313,7 @@ private bool OnInvokeCommand(string[] args) { Console.WriteLine($"VM State: {engine.State}"); Console.WriteLine($"Gas Consumed: {engine.GasConsumed}"); - Console.WriteLine($"Evaluation Stack: {new JArray(engine.ResultStack.Select(p => p.ToContractParameter().ToJson()))}"); + Console.WriteLine($"Evaluation Stack: {new JArray(engine.ResultStack.Select(p => p.ToJObject()))}"); Console.WriteLine(); if (engine.State.HasFlag(VMState.FAULT)) { diff --git a/neo3-gui/neo3-gui/Common/Json/JObjectConverter.cs b/neo3-gui/neo3-gui/Common/Json/JObjectConverter.cs index 96b66e0c..d5ed72b4 100644 --- a/neo3-gui/neo3-gui/Common/Json/JObjectConverter.cs +++ b/neo3-gui/neo3-gui/Common/Json/JObjectConverter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Text.Json; @@ -11,10 +12,15 @@ namespace Neo.Common.Json { public class JObjectConverter : JsonConverter { + public const int MaxJsonLength = 10 * 1024 * 1024; public override JObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using JsonDocument document = JsonDocument.ParseValue(ref reader); - var text= document.RootElement.Clone().ToString(); + var text = document.RootElement.Clone().ToString(); + if (!text.StartsWith("{")) + { + return (JString)text; + } return JObject.Parse(text); } @@ -47,11 +53,15 @@ public override void Write(Utf8JsonWriter writer, JObject value, JsonSerializerO if (pair.Value is null) writer.WriteNullValue(); else - Write(writer, pair.Value,options); + Write(writer, pair.Value, options); } writer.WriteEndObject(); break; - + } + + if (writer.BytesCommitted > MaxJsonLength) + { + throw new InvalidCastException("json is too long to write!"); } } } diff --git a/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/LevelDbContext.cs b/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/LevelDbContext.cs index 73320605..4b6894e0 100644 --- a/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/LevelDbContext.cs +++ b/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/LevelDbContext.cs @@ -105,7 +105,15 @@ public void SaveTxExecuteLog(ExecuteResultInfo log) { if (log != null && log.TxId != null) { - writeBatch.Put(ExecuteLogKey(log.TxId), log.SerializeJsonBytes()); + try + { + writeBatch.Put(ExecuteLogKey(log.TxId), log.SerializeJsonBytes()); + } + catch (InvalidCastException e) + { + log.ResultStack = e.ToString(); + writeBatch.Put(ExecuteLogKey(log.TxId), log.SerializeJsonBytes()); + } } } diff --git a/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/WriteBatch.cs b/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/WriteBatch.cs index 31a6ed9c..7a8905d4 100644 --- a/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/WriteBatch.cs +++ b/neo3-gui/neo3-gui/Common/Storage/LevelDBModules/WriteBatch.cs @@ -27,7 +27,10 @@ public void Delete(byte[] key) public void Put(byte[] key, byte[] value) { - Native.leveldb_writebatch_put(handle, key, (UIntPtr)key.Length, value, (UIntPtr)value.Length); + if (value != null) + { + Native.leveldb_writebatch_put(handle, key, (UIntPtr)key.Length, value, (UIntPtr)value.Length); + } } } } diff --git a/neo3-gui/neo3-gui/Helpers.cs b/neo3-gui/neo3-gui/Helpers.cs index 977c3bba..f961be92 100644 --- a/neo3-gui/neo3-gui/Helpers.cs +++ b/neo3-gui/neo3-gui/Helpers.cs @@ -45,6 +45,7 @@ using Buffer = Neo.VM.Types.Buffer; using VmArray = Neo.VM.Types.Array; using JsonSerializer = System.Text.Json.JsonSerializer; +using Pointer = Neo.VM.Types.Pointer; namespace Neo { @@ -388,6 +389,26 @@ public static byte[] SerializeJsonBytes(this T obj) return JsonSerializer.SerializeToUtf8Bytes(obj, SerializeOptions); } + + /// + /// serialize to utf8 json bytes, more performance than to json string + /// + /// + /// + /// + public static byte[] SerializeJsonBytesSafely(this T obj) + { + try + { + return SerializeJsonBytes(obj); + } + catch (Exception e) + { + Console.WriteLine(e); + return null; + } + } + /// /// serialize to utf8 json string /// @@ -1281,91 +1302,59 @@ public static string GetExMessage(this Exception ex) return msg; } + /// - /// Converts the to a . + /// Convert StackItem to JObject /// - /// The to convert. - /// The converted . - public static ContractParameter ToContractParameter(this StackItem item, List<(StackItem, ContractParameter)> context = null) + /// + /// + public static JObject ToJObject(this StackItem item) { - if (item is null) throw new ArgumentNullException(nameof(item)); + return ToJson(item, null); + } + - ContractParameter parameter = null; + private static JObject ToJson(StackItem item, HashSet context) + { + JObject json = new(); + json["type"] = item.Type; switch (item) { case VmArray array: - if (context is null) - context = new List<(StackItem, ContractParameter)>(); - else - (_, parameter) = context.FirstOrDefault(p => ReferenceEquals(p.Item1, item)); - if (parameter is null) - { - parameter = new ContractParameter { Type = ContractParameterType.Array }; - context.Add((item, parameter)); - parameter.Value = array.Select(p => ToContractParameter(p, context)).ToList(); - } + context ??= new HashSet(ReferenceEqualityComparer.Instance); + if (!context.Add(array)) throw new InvalidOperationException(); + json["value"] = new JArray(array.Select(p => ToJson(p, context))); break; - case Map map: - if (context is null) - context = new List<(StackItem, ContractParameter)>(); - else - (_, parameter) = context.FirstOrDefault(p => ReferenceEquals(p.Item1, item)); - if (parameter is null) - { - parameter = new ContractParameter { Type = ContractParameterType.Map }; - context.Add((item, parameter)); - parameter.Value = map.Select(p => - new KeyValuePair(ToContractParameter(p.Key, context), - ToContractParameter(p.Value, context))).ToList(); - } + case Boolean boolean: + json["value"] = boolean.GetBoolean(); break; - case Boolean _: - parameter = new ContractParameter - { - Type = ContractParameterType.Boolean, - Value = item.GetBoolean() - }; + case Buffer buffer: + json["value"] = buffer.InnerBuffer.ToBase64String(); break; - case ByteString array: - parameter = new ContractParameter - { - Type = ContractParameterType.ByteArray, - Value = array.GetSpan().ToArray() - }; + case ByteString _: + json["value"] = item.GetSpan().ToBase64String(); break; - case Buffer array: - parameter = new ContractParameter - { - Type = ContractParameterType.ByteArray, - Value = array.GetSpan().ToArray() - }; - break; - case Integer i: - parameter = new ContractParameter - { - Type = ContractParameterType.Integer, - Value = i.GetInteger() - }; + case Integer integer: + json["value"] = integer.GetInteger().ToString(); break; - case InteropInterface _: - parameter = new ContractParameter + case Map map: + context ??= new HashSet(ReferenceEqualityComparer.Instance); + if (!context.Add(map)) throw new InvalidOperationException(); + json["value"] = new JArray(map.Select(p => { - Type = ContractParameterType.InteropInterface - }; + JObject item = new(); + item["key"] = ToJson(p.Key, context); + item["value"] = ToJson(p.Value, context); + return item; + })); break; - case Null _: - parameter = new ContractParameter - { - Type = ContractParameterType.Any - }; + case Pointer pointer: + json["value"] = pointer.Position; break; - default: - throw new ArgumentException($"StackItemType({item.Type}) is not supported to ContractParameter."); } - return parameter; + return json; } - /// /// /检查Nep Token @@ -1423,5 +1412,20 @@ public static AssetType CheckNepAsset(this ContractState contract) } return AssetType.None; } + + + + private static readonly ConcurrentDictionary _strCache = new(ByteArrayValueComparer.Default); + + public static string ToBase64String(this byte[] data) + { + return _strCache.GetOrAdd(data, Convert.ToBase64String); + } + + + public static string ToBase64String(this ReadOnlySpan data) + { + return data.ToArray().ToBase64String(); + } } } diff --git a/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs b/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs index b24a0ef1..e468dcb2 100644 --- a/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs +++ b/neo3-gui/neo3-gui/Services/ApiServices/ContractApiService.cs @@ -239,7 +239,7 @@ public async Task InvokeContract(InvokeContractParameterModel para) } catch (Exception e) { - return Error(ErrorCode.InvalidPara); + return Error(ErrorCode.InvalidPara, e.GetExMessage()); } var signers = new List(); @@ -261,11 +261,11 @@ public async Task InvokeContract(InvokeContractParameterModel para) var result = new InvokeResultModel(); result.VmState = engine.State; result.GasConsumed = new BigDecimal((BigInteger)engine.GasConsumed, NativeContract.GAS.Decimals); - result.ResultStack = engine.ResultStack.Select(p => JStackItem.FromJson(p.ToContractParameter().ToJson())).ToList(); + result.ResultStack = engine.ResultStack.Select(p => JStackItem.FromJson(p.ToJObject())).ToList(); result.Notifications = engine.Notifications?.Select(ConvertToEventModel).ToList(); if (engine.State.HasFlag(VMState.FAULT)) { - return Error(ErrorCode.EngineFault); + return Error(ErrorCode.EngineFault, engine.FaultException?.ToString()); } if (!para.SendTx) { @@ -310,7 +310,7 @@ private static InvokeEventValueModel ConvertToEventModel(NotifyEventArgs notify) if (eventMeta?.Parameters.Any() == true) { var json = new Dictionary(); - for (var i = 0; i < eventMeta.Parameters.Length; i++) + for (var i = 0; i < eventMeta.Parameters.Length && i < notify.State.Count; i++) { var p = eventMeta.Parameters[i]; json[p.Name] = ConvertValue(notify.State[i], p.Type); diff --git a/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs b/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs index bfc9605e..10ee3b73 100644 --- a/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs +++ b/neo3-gui/neo3-gui/Services/ApiServices/TransactionApiService.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Akka.Actor; using Neo.Common; using Neo.Common.Consoles; using Neo.Common.Storage; @@ -144,6 +146,14 @@ public async Task RemoveUnconfirmTransaction(UInt256 txId) return true; } + + public async Task SendTransaction(string txRaw) + { + Transaction tx = Convert.FromBase64String(txRaw).AsSerializable(); + Blockchain.RelayResult reason = Program.Starter.NeoSystem.Blockchain.Ask(tx).Result; + return tx.ToJson(null); + } + /// /// query all transactions(on chain) ///