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