diff --git a/src/ApplicationLogs/LogReader.cs b/src/ApplicationLogs/LogReader.cs index 71a9253f0..c6aaede8d 100644 --- a/src/ApplicationLogs/LogReader.cs +++ b/src/ApplicationLogs/LogReader.cs @@ -175,8 +175,13 @@ private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IRe foreach (var appExec in applicationExecutedList.Where(p => p.Transaction != null)) { var txJson = TxLogToJson(appExec); + if (RpcServer.LogEvents.TryGetValue(appExec.Transaction.Hash, out var list)) + { + txJson["logs"] = new JArray(list.Select(q => new JString(q.Message))); + } Put(appExec.Transaction.Hash.ToArray(), Neo.Utility.StrictUTF8.GetBytes(txJson.ToString())); } + RpcServer.LogEvents.Clear(); //processing log for block var blockJson = BlockLogToJson(block, applicationExecutedList); diff --git a/src/RpcServer/RpcServer.SmartContract.cs b/src/RpcServer/RpcServer.SmartContract.cs index cf22c2780..d2c9d37c9 100644 --- a/src/RpcServer/RpcServer.SmartContract.cs +++ b/src/RpcServer/RpcServer.SmartContract.cs @@ -121,6 +121,8 @@ private JObject GetInvokeResult(byte[] script, Signer[] signers = null, Witness[ lock (sessions) sessions.Add(id, session); } + if (session.Engine.ScriptContainer is Transaction tx && LogEvents.ContainsKey(tx.Hash)) + json["logs"] = new JArray(LogEvents[tx.Hash].Select(p => new JString(p.Message))); return json; } diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index ccc581441..7c29f085b 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.DependencyInjection; -using Neo.IO; using Neo.Json; using Neo.Network.P2P; using System; @@ -28,6 +27,9 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; namespace Neo.Plugins { @@ -39,6 +41,14 @@ public partial class RpcServer : IDisposable private RpcServerSettings settings; private readonly NeoSystem system; private readonly LocalNode localNode; + /// + /// Public interface of _logEvents. + /// + public static Dictionary> LogEvents { get; } = new(); + /// + /// Maximum number of events to be logged per contract + /// + private static int _maxLogEvents = 20; public RpcServer(NeoSystem system, RpcServerSettings settings) { @@ -47,6 +57,14 @@ public RpcServer(NeoSystem system, RpcServerSettings settings) localNode = system.LocalNode.Ask(new LocalNode.GetInstance()).Result; RegisterMethods(this); Initialize_SmartContract(); + _maxLogEvents = settings.MaxLogEvents; + ApplicationEngine.Log += OnContractLogEvent; + Blockchain.Committed += OnCommitted; + } + + private void OnCommitted(NeoSystem system, Block block) + { + LogEvents.Clear(); } private bool CheckAuth(HttpContext context) @@ -92,8 +110,28 @@ private static JObject CreateResponse(JToken id) return response; } + // It is potentially possible to have dos attack by sending a lot of transactions and logs. + // To prevent this, we limit the number of logs to be logged per contract. + // If the number of logs is greater than MAX_LOG_EVENTS, we remove the oldest log. + private static void OnContractLogEvent(object _, LogEventArgs e) + { + if (e.ScriptContainer is not Transaction tx) return; + if (!LogEvents.TryGetValue(tx.Hash, out var _logs)) + { + _logs = new Queue(); + LogEvents.Add(tx.Hash, _logs); + } + if (LogEvents[tx.Hash].Count >= _maxLogEvents) + { + _logs.Dequeue(); + } + _logs.Enqueue(e); + } + public void Dispose() { + Blockchain.Committed -= OnCommitted; + ApplicationEngine.Log -= OnContractLogEvent; Dispose_SmartContract(); if (host != null) { diff --git a/src/RpcServer/Settings.cs b/src/RpcServer/Settings.cs index 8bf8d0c95..8be8438f0 100644 --- a/src/RpcServer/Settings.cs +++ b/src/RpcServer/Settings.cs @@ -42,6 +42,7 @@ public record RpcServerSettings public long MaxFee { get; init; } public int MaxIteratorResultItems { get; init; } public int MaxStackSize { get; init; } + public int MaxLogEvents { get; init; } public string[] DisabledMethods { get; init; } public bool SessionEnabled { get; init; } public TimeSpan SessionExpirationTime { get; init; } @@ -57,6 +58,7 @@ public record RpcServerSettings TrustedAuthorities = Array.Empty(), MaxIteratorResultItems = 100, MaxStackSize = ushort.MaxValue, + MaxLogEvents = 20, DisabledMethods = Array.Empty(), MaxConcurrentConnections = 40, SessionEnabled = false, @@ -77,6 +79,7 @@ public record RpcServerSettings MaxFee = (long)new BigDecimal(section.GetValue("MaxFee", Default.MaxFee), NativeContract.GAS.Decimals).Value, MaxIteratorResultItems = section.GetValue("MaxIteratorResultItems", Default.MaxIteratorResultItems), MaxStackSize = section.GetValue("MaxStackSize", Default.MaxStackSize), + MaxLogEvents = section.GetValue("MaxLogEvents", Default.MaxLogEvents), DisabledMethods = section.GetSection("DisabledMethods").GetChildren().Select(p => p.Get()).ToArray(), MaxConcurrentConnections = section.GetValue("MaxConcurrentConnections", Default.MaxConcurrentConnections), SessionEnabled = section.GetValue("SessionEnabled", Default.SessionEnabled), diff --git a/src/RpcServer/config.json b/src/RpcServer/config.json index 89bc7f578..8a6c6f49c 100644 --- a/src/RpcServer/config.json +++ b/src/RpcServer/config.json @@ -15,6 +15,7 @@ "MaxConcurrentConnections": 40, "MaxIteratorResultItems": 100, "MaxStackSize": 65535, + "MaxLogEvents": 20, "DisabledMethods": [ "openwallet" ], "SessionEnabled": false, "SessionExpirationTime": 60