From 3c02309f104d46e8436cb2ffd97b38d6325d8c4e Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 00:35:54 +0800 Subject: [PATCH 01/20] catch plugin exceptions. --- src/Neo/Ledger/Blockchain.cs | 63 ++++++++++++++++++++++++++++++++++-- src/Neo/Plugins/Plugin.cs | 10 +++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index ff9c8e29d8..620a6a1956 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -16,6 +16,7 @@ using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.Plugins; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; @@ -468,10 +469,10 @@ private void Persist(Block block) Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); } - Committing?.Invoke(system, block, snapshot, all_application_executed); + InvokeCommitting(system, block, snapshot, all_application_executed); snapshot.Commit(); } - Committed?.Invoke(system, block); + InvokeCommitted(system, block); system.MemPool.UpdatePoolForBlockPersisted(block, system.StoreView); extensibleWitnessWhiteList = null; block_cache.Remove(block.PrevHash); @@ -480,6 +481,64 @@ private void Persist(Block block) Debug.Assert(header.Index == block.Index); } + private void InvokeCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + var handlers = Committing?.GetInvocationList(); + if (handlers == null) return; + + foreach (var @delegate in handlers) + { + var handler = (CommittingHandler)@delegate; + try + { + handler(system, block, snapshot, applicationExecutedList); + } + catch (Exception ex) + { + if (handler.Target is Plugin) + { + // Log the exception and continue with the next handler + Utility.Log(nameof(handler.Target), LogLevel.Error, ex); + } + else + { + // Rethrow the exception if the handler is not from a plugin + Console.WriteLine($"Exception in committing handler: {ex.Message}"); + throw; + } + } + } + } + + private void InvokeCommitted(NeoSystem system, Block block) + { + var handlers = Committed?.GetInvocationList(); + if (handlers == null) return; + + foreach (var @delegate in handlers) + { + var handler = (CommittedHandler)@delegate; + try + { + handler(system, block); + } + catch (Exception ex) + { + if (handler.Target is Plugin) + { + // Log the exception and continue with the next handler + Utility.Log(nameof(handler.Target), LogLevel.Error, ex); + } + else + { + // Rethrow the exception if the handler is not from a plugin + Console.WriteLine($"Exception in committed handler: {ex.Message}"); + throw; + } + } + } + } + /// /// Gets a object used for creating the actor. /// diff --git a/src/Neo/Plugins/Plugin.cs b/src/Neo/Plugins/Plugin.cs index 248301af56..3eefd4e7da 100644 --- a/src/Neo/Plugins/Plugin.cs +++ b/src/Neo/Plugins/Plugin.cs @@ -229,7 +229,15 @@ protected internal virtual void OnSystemLoaded(NeoSystem system) /// if the is handled by a plugin; otherwise, . public static bool SendMessage(object message) { - return Plugins.Any(plugin => plugin.OnMessage(message)); + try + { + return Plugins.Any(plugin => plugin.OnMessage(message)); + } + catch (Exception ex) + { + Utility.Log(nameof(Plugin), LogLevel.Error, ex); + return false; + } } } } From e426ed6f755801ba12e4d2ee5ef0835c145c19e2 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 00:50:11 +0800 Subject: [PATCH 02/20] add UT test --- src/Neo/Ledger/Blockchain.cs | 4 ++-- tests/Neo.UnitTests/Plugins/TestPlugin.cs | 21 ++++++++++++++++++++- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 9 +++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index 620a6a1956..c2979b67a9 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -481,7 +481,7 @@ private void Persist(Block block) Debug.Assert(header.Index == block.Index); } - private void InvokeCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + internal static void InvokeCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { var handlers = Committing?.GetInvocationList(); if (handlers == null) return; @@ -510,7 +510,7 @@ private void InvokeCommitting(NeoSystem system, Block block, DataCache snapshot, } } - private void InvokeCommitted(NeoSystem system, Block block) + internal static void InvokeCommitted(NeoSystem system, Block block) { var handlers = Committed?.GetInvocationList(); if (handlers == null) return; diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index dde500b927..59d8ed98a6 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -10,13 +10,22 @@ // modifications are permitted. using Microsoft.Extensions.Configuration; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.Plugins; +using System; +using System.Collections.Generic; namespace Neo.UnitTests.Plugins { public class TestPlugin : Plugin { - public TestPlugin() : base() { } + public TestPlugin() : base() + { + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + } protected override void Configure() { } @@ -36,5 +45,15 @@ public IConfigurationSection TestGetConfiguration() } protected override bool OnMessage(object message) => true; + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + throw new NotImplementedException(); + } + + private void OnCommitted(NeoSystem system, Block block) + { + throw new NotImplementedException(); + } } } diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index e5e8cb6e49..7fc272d17f 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Ledger; using Neo.Plugins; using System; @@ -63,5 +64,13 @@ public void TestGetConfiguration() var pp = new TestPlugin(); pp.TestGetConfiguration().Key.Should().Be("PluginConfiguration"); } + + [TestMethod] + public void TestOnException() + { + var pp = new TestPlugin(); + + Blockchain.InvokeCommitted(null,null); + } } } From 5d6fa28e65ff05e0a794bb5e050bc260376efe24 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 00:52:55 +0800 Subject: [PATCH 03/20] udpate format --- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index 7fc272d17f..ed82e41cb5 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -70,7 +70,7 @@ public void TestOnException() { var pp = new TestPlugin(); - Blockchain.InvokeCommitted(null,null); + Blockchain.InvokeCommitted(null, null); } } } From 439f504f2154c41cc250ed38c46384fc4536b1b8 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 00:58:50 +0800 Subject: [PATCH 04/20] make the test more complete --- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index ed82e41cb5..46e2e4fb03 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -70,7 +70,15 @@ public void TestOnException() { var pp = new TestPlugin(); - Blockchain.InvokeCommitted(null, null); + // Call the InvokeCommitted method and ensure no exception is thrown + try + { + Blockchain.InvokeCommitted(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitted threw an exception: {ex.Message}"); + } } } } From e569fed499b52726143339fc7dadcd0b311cc6b6 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 00:59:38 +0800 Subject: [PATCH 05/20] complete the ut test --- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index 46e2e4fb03..fdf33ddfb6 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -73,6 +73,7 @@ public void TestOnException() // Call the InvokeCommitted method and ensure no exception is thrown try { + Blockchain.InvokeCommitting(null, null,null,null); Blockchain.InvokeCommitted(null, null); } catch (Exception ex) From d701dd35999f2001522d57cfdd4ee16978e5859a Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 01:01:13 +0800 Subject: [PATCH 06/20] format --- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index fdf33ddfb6..2492eebdbe 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -73,7 +73,7 @@ public void TestOnException() // Call the InvokeCommitted method and ensure no exception is thrown try { - Blockchain.InvokeCommitting(null, null,null,null); + Blockchain.InvokeCommitting(null, null, null, null); Blockchain.InvokeCommitted(null, null); } catch (Exception ex) From db1c7f01699c21084c917bb93a968ba61239af33 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 01:10:59 +0800 Subject: [PATCH 07/20] complete UT tests with NonPlugin case --- tests/Neo.UnitTests/Plugins/TestPlugin.cs | 19 +++++++++++++++++++ tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 11 ++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index 59d8ed98a6..e5400350cc 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -19,6 +19,25 @@ namespace Neo.UnitTests.Plugins { + public class TestNonPlugin + { + public TestNonPlugin() : base() + { + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + } + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + throw new NotImplementedException(); + } + + private void OnCommitted(NeoSystem system, Block block) + { + throw new NotImplementedException(); + } + } + public class TestPlugin : Plugin { public TestPlugin() : base() diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index 2492eebdbe..124ef21474 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -68,7 +68,7 @@ public void TestGetConfiguration() [TestMethod] public void TestOnException() { - var pp = new TestPlugin(); + _ = new TestPlugin(); // Call the InvokeCommitted method and ensure no exception is thrown try @@ -80,6 +80,15 @@ public void TestOnException() { Assert.Fail($"InvokeCommitted threw an exception: {ex.Message}"); } + + _ = new TestNonPlugin(); + + // Call the InvokeCommitted method and ensure exception is thrown + Assert.ThrowsException(() => + { + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); + }); } } } From 17f74efb6e879085db5bcc4bfe2bcb276a4642ac Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 08:15:28 +0800 Subject: [PATCH 08/20] async invoke --- src/Neo/Ledger/Blockchain.cs | 59 +++++++++-------------- tests/Neo.UnitTests/Plugins/TestPlugin.cs | 7 +-- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 26 +++++----- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index c2979b67a9..0d6eb4fc40 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Akka.Configuration; using Akka.IO; +using Akka.Util.Internal; using Neo.IO.Actors; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; @@ -21,10 +22,12 @@ using Neo.SmartContract.Native; using Neo.VM; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; namespace Neo.Ledger { @@ -469,10 +472,10 @@ private void Persist(Block block) Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); } - InvokeCommitting(system, block, snapshot, all_application_executed); + _ = InvokeCommittingAsync(system, block, snapshot, all_application_executed); snapshot.Commit(); } - InvokeCommitted(system, block); + _ = InvokeCommittedAsync(system, block); system.MemPool.UpdatePoolForBlockPersisted(block, system.StoreView); extensibleWitnessWhiteList = null; block_cache.Remove(block.PrevHash); @@ -481,64 +484,48 @@ private void Persist(Block block) Debug.Assert(header.Index == block.Index); } - internal static void InvokeCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + internal static async Task InvokeCommittingAsync(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { - var handlers = Committing?.GetInvocationList(); - if (handlers == null) return; + await InvokeHandlersAsync(Committing?.GetInvocationList(), h => ((CommittingHandler)h)(system, block, snapshot, applicationExecutedList)); + } - foreach (var @delegate in handlers) - { - var handler = (CommittingHandler)@delegate; - try - { - handler(system, block, snapshot, applicationExecutedList); - } - catch (Exception ex) - { - if (handler.Target is Plugin) - { - // Log the exception and continue with the next handler - Utility.Log(nameof(handler.Target), LogLevel.Error, ex); - } - else - { - // Rethrow the exception if the handler is not from a plugin - Console.WriteLine($"Exception in committing handler: {ex.Message}"); - throw; - } - } - } + internal static async Task InvokeCommittedAsync(NeoSystem system, Block block) + { + await InvokeHandlersAsync(Committed?.GetInvocationList(), h => ((CommittedHandler)h)(system, block)); } - internal static void InvokeCommitted(NeoSystem system, Block block) + private static async Task InvokeHandlersAsync(Delegate[] handlers, Action handlerAction) { - var handlers = Committed?.GetInvocationList(); if (handlers == null) return; - foreach (var @delegate in handlers) + var exceptions = new ConcurrentBag(); + var tasks = handlers.Select(handler => Task.Run(() => { - var handler = (CommittedHandler)@delegate; try { - handler(system, block); + handlerAction(handler); } catch (Exception ex) { if (handler.Target is Plugin) { // Log the exception and continue with the next handler + // Isolate the plugin exception Utility.Log(nameof(handler.Target), LogLevel.Error, ex); } else { - // Rethrow the exception if the handler is not from a plugin - Console.WriteLine($"Exception in committed handler: {ex.Message}"); - throw; + exceptions.Add(ex); } } - } + })).ToList(); + + await Task.WhenAll(tasks); + + exceptions.ForEach(e => throw e); } + /// /// Gets a object used for creating the actor. /// diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index e5400350cc..8bc058c2a2 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -21,7 +21,7 @@ namespace Neo.UnitTests.Plugins { public class TestNonPlugin { - public TestNonPlugin() : base() + public TestNonPlugin() { Blockchain.Committing += OnCommitting; Blockchain.Committed += OnCommitted; @@ -29,15 +29,16 @@ public TestNonPlugin() : base() private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { - throw new NotImplementedException(); + throw new NotImplementedException("Test exception from OnCommitting"); } private void OnCommitted(NeoSystem system, Block block) { - throw new NotImplementedException(); + throw new NotImplementedException("Test exception from OnCommitted"); } } + public class TestPlugin : Plugin { public TestPlugin() : base() diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index 124ef21474..1ad286ae9a 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -14,6 +14,7 @@ using Neo.Ledger; using Neo.Plugins; using System; +using System.Threading.Tasks; namespace Neo.UnitTests.Plugins { @@ -66,28 +67,31 @@ public void TestGetConfiguration() } [TestMethod] - public void TestOnException() + public async Task TestOnException() { - _ = new TestPlugin(); - - // Call the InvokeCommitted method and ensure no exception is thrown + // Ensure no exception is thrown try { - Blockchain.InvokeCommitting(null, null, null, null); - Blockchain.InvokeCommitted(null, null); + await Blockchain.InvokeCommittingAsync(null, null, null, null); + await Blockchain.InvokeCommittedAsync(null, null); } catch (Exception ex) { - Assert.Fail($"InvokeCommitted threw an exception: {ex.Message}"); + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); } + // Register TestNonPlugin that throws exceptions _ = new TestNonPlugin(); - // Call the InvokeCommitted method and ensure exception is thrown - Assert.ThrowsException(() => + // Ensure exception is thrown + await Assert.ThrowsExceptionAsync(async () => + { + await Blockchain.InvokeCommittingAsync(null, null, null, null); + }); + + await Assert.ThrowsExceptionAsync(async () => { - Blockchain.InvokeCommitting(null, null, null, null); - Blockchain.InvokeCommitted(null, null); + await Blockchain.InvokeCommittedAsync(null, null); }); } } From 3cd8df7ec689cd0a90cddab4d90d6b20f9b0fff5 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 18:40:29 +0800 Subject: [PATCH 09/20] stop plugin on exception --- src/Neo/Ledger/Blockchain.cs | 26 +++++---- src/Neo/Plugins/Plugin.cs | 54 ++++++++++++----- .../ApplicationLogs/ApplicationLogs.json | 3 +- src/Plugins/DBFTPlugin/DBFTPlugin.cs | 2 + src/Plugins/DBFTPlugin/DBFTPlugin.json | 3 +- src/Plugins/DBFTPlugin/Settings.cs | 4 +- src/Plugins/OracleService/OracleService.cs | 2 + src/Plugins/OracleService/OracleService.json | 1 + src/Plugins/OracleService/Settings.cs | 4 +- src/Plugins/RpcServer/RpcServer.json | 1 + src/Plugins/RpcServer/RpcServerPlugin.cs | 1 + src/Plugins/RpcServer/Settings.cs | 4 +- src/Plugins/StateService/Settings.cs | 4 +- src/Plugins/StateService/StatePlugin.cs | 2 + src/Plugins/StateService/StateService.json | 3 +- src/Plugins/StorageDumper/Settings.cs | 4 +- src/Plugins/StorageDumper/StorageDumper.cs | 2 +- src/Plugins/StorageDumper/StorageDumper.json | 3 +- src/Plugins/TokensTracker/TokensTracker.cs | 3 + src/Plugins/TokensTracker/TokensTracker.json | 3 +- tests/Neo.UnitTests/Plugins/TestPlugin.cs | 24 ++++++-- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 58 +++++++++++++++++++ 22 files changed, 167 insertions(+), 44 deletions(-) diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index 0d6eb4fc40..9e855ae468 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -503,20 +503,26 @@ private static async Task InvokeHandlersAsync(Delegate[] handlers, Action /// The directory containing the plugin folders. Files can be contained in any subdirectory. /// - public static readonly string PluginsDirectory = Combine(GetDirectoryName(System.AppContext.BaseDirectory), "Plugins"); + public static readonly string PluginsDirectory = + Combine(GetDirectoryName(System.AppContext.BaseDirectory), "Plugins"); private static readonly FileSystemWatcher configWatcher; @@ -67,6 +68,18 @@ public abstract class Plugin : IDisposable /// public virtual Version Version => GetType().Assembly.GetName().Version; + /// + /// If the plugin should be stopped when an exception is thrown. + /// Default is . + /// + protected internal virtual bool StopOnUnhandledException { get; init; } = true; + + /// + /// The plugin will be stopped if an exception is thrown. + /// But it also depends on . + /// + internal bool IsStopped { get; set; } + static Plugin() { if (!Directory.Exists(PluginsDirectory)) return; @@ -74,7 +87,8 @@ static Plugin() { EnableRaisingEvents = true, IncludeSubdirectories = true, - NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.Size, + NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime | + NotifyFilters.LastWrite | NotifyFilters.Size, }; configWatcher.Changed += ConfigWatcher_Changed; configWatcher.Created += ConfigWatcher_Changed; @@ -106,7 +120,8 @@ private static void ConfigWatcher_Changed(object sender, FileSystemEventArgs e) { case ".json": case ".dll": - Utility.Log(nameof(Plugin), LogLevel.Warning, $"File {e.Name} is {e.ChangeType}, please restart node."); + Utility.Log(nameof(Plugin), LogLevel.Warning, + $"File {e.Name} is {e.ChangeType}, please restart node."); break; } } @@ -119,7 +134,8 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven AssemblyName an = new(args.Name); Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name) ?? - AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == an.Name); + AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == an.Name); if (assembly != null) return assembly; string filename = an.Name + ".dll"; @@ -150,7 +166,8 @@ public virtual void Dispose() /// The content of the configuration file read. protected IConfigurationSection GetConfiguration() { - return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build().GetSection("PluginConfiguration"); + return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build() + .GetSection("PluginConfiguration"); } private static void LoadPlugin(Assembly assembly) @@ -187,6 +204,7 @@ internal static void LoadPlugins() catch { } } } + foreach (Assembly assembly in assemblies) { LoadPlugin(assembly); @@ -229,15 +247,23 @@ protected internal virtual void OnSystemLoaded(NeoSystem system) /// if the is handled by a plugin; otherwise, . public static bool SendMessage(object message) { - try - { - return Plugins.Any(plugin => plugin.OnMessage(message)); - } - catch (Exception ex) - { - Utility.Log(nameof(Plugin), LogLevel.Error, ex); - return false; - } + + return Plugins.Any(plugin => + { + try + { + return !plugin.IsStopped && + plugin.OnMessage(message); + } + catch (Exception ex) + { + if (plugin.StopOnUnhandledException) + plugin.IsStopped = true; + Utility.Log(nameof(Plugin), LogLevel.Error, ex); + return false; + } + } + ); } } } diff --git a/src/Plugins/ApplicationLogs/ApplicationLogs.json b/src/Plugins/ApplicationLogs/ApplicationLogs.json index af601bc81e..81f0e6395e 100644 --- a/src/Plugins/ApplicationLogs/ApplicationLogs.json +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.json @@ -3,7 +3,8 @@ "Path": "ApplicationLogs_{0}", "Network": 860833102, "MaxStackSize": 65535, - "Debug": false + "Debug": false, + "StopOnUnhandledException": false }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.cs b/src/Plugins/DBFTPlugin/DBFTPlugin.cs index 9ca44adc74..ca69292acf 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.cs +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.cs @@ -31,6 +31,8 @@ public class DBFTPlugin : Plugin public override string ConfigFile => System.IO.Path.Combine(RootPath, "DBFTPlugin.json"); + protected override bool StopOnUnhandledException => settings.StopOnUnhandledException; + public DBFTPlugin() { RemoteNode.MessageReceived += RemoteNode_MessageReceived; diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.json b/src/Plugins/DBFTPlugin/DBFTPlugin.json index 2e2b710ba3..35cbd95a13 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.json +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.json @@ -5,6 +5,7 @@ "AutoStart": false, "Network": 860833102, "MaxBlockSize": 2097152, - "MaxBlockSystemFee": 150000000000 + "MaxBlockSystemFee": 150000000000, + "StopOnUnhandledException": true } } diff --git a/src/Plugins/DBFTPlugin/Settings.cs b/src/Plugins/DBFTPlugin/Settings.cs index 28ad21f37a..1f37feaf16 100644 --- a/src/Plugins/DBFTPlugin/Settings.cs +++ b/src/Plugins/DBFTPlugin/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.DBFTPlugin { - public class Settings + public class Settings : PluginSettings { public string RecoveryLogs { get; } public bool IgnoreRecoveryLogs { get; } @@ -22,7 +22,7 @@ public class Settings public uint MaxBlockSize { get; } public long MaxBlockSystemFee { get; } - public Settings(IConfigurationSection section) + public Settings(IConfigurationSection section) : base(section) { RecoveryLogs = section.GetValue("RecoveryLogs", "ConsensusState"); IgnoreRecoveryLogs = section.GetValue("IgnoreRecoveryLogs", false); diff --git a/src/Plugins/OracleService/OracleService.cs b/src/Plugins/OracleService/OracleService.cs index e9787b3d4c..67dd80bd79 100644 --- a/src/Plugins/OracleService/OracleService.cs +++ b/src/Plugins/OracleService/OracleService.cs @@ -61,6 +61,8 @@ public class OracleService : Plugin public override string Description => "Built-in oracle plugin"; + protected override bool StopOnUnhandledException => Settings.Default.StopOnUnhandledException; + public override string ConfigFile => System.IO.Path.Combine(RootPath, "OracleService.json"); public OracleService() diff --git a/src/Plugins/OracleService/OracleService.json b/src/Plugins/OracleService/OracleService.json index 1ab0d93399..28d6b7bd26 100644 --- a/src/Plugins/OracleService/OracleService.json +++ b/src/Plugins/OracleService/OracleService.json @@ -6,6 +6,7 @@ "MaxOracleTimeout": 10000, "AllowPrivateHost": false, "AllowedContentTypes": [ "application/json" ], + "StopOnUnhandledException": false, "Https": { "Timeout": 5000 }, diff --git a/src/Plugins/OracleService/Settings.cs b/src/Plugins/OracleService/Settings.cs index 952ea0c27b..db93c1c400 100644 --- a/src/Plugins/OracleService/Settings.cs +++ b/src/Plugins/OracleService/Settings.cs @@ -37,7 +37,7 @@ public NeoFSSettings(IConfigurationSection section) } } - class Settings + class Settings : PluginSettings { public uint Network { get; } public Uri[] Nodes { get; } @@ -51,7 +51,7 @@ class Settings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { Network = section.GetValue("Network", 5195086u); Nodes = section.GetSection("Nodes").GetChildren().Select(p => new Uri(p.Get(), UriKind.Absolute)).ToArray(); diff --git a/src/Plugins/RpcServer/RpcServer.json b/src/Plugins/RpcServer/RpcServer.json index 8f6905dead..8583db7dde 100644 --- a/src/Plugins/RpcServer/RpcServer.json +++ b/src/Plugins/RpcServer/RpcServer.json @@ -1,5 +1,6 @@ { "PluginConfiguration": { + "StopOnUnhandledException": false, "Servers": [ { "Network": 860833102, diff --git a/src/Plugins/RpcServer/RpcServerPlugin.cs b/src/Plugins/RpcServer/RpcServerPlugin.cs index c22462d139..f59ad43a1c 100644 --- a/src/Plugins/RpcServer/RpcServerPlugin.cs +++ b/src/Plugins/RpcServer/RpcServerPlugin.cs @@ -24,6 +24,7 @@ public class RpcServerPlugin : Plugin private static readonly Dictionary> handlers = new(); public override string ConfigFile => System.IO.Path.Combine(RootPath, "RpcServer.json"); + protected override bool StopOnUnhandledException => settings.StopOnUnhandledException; protected override void Configure() { diff --git a/src/Plugins/RpcServer/Settings.cs b/src/Plugins/RpcServer/Settings.cs index ad624d9082..2cf7b72fb8 100644 --- a/src/Plugins/RpcServer/Settings.cs +++ b/src/Plugins/RpcServer/Settings.cs @@ -18,11 +18,11 @@ namespace Neo.Plugins.RpcServer { - class Settings + class Settings : PluginSettings { public IReadOnlyList Servers { get; init; } - public Settings(IConfigurationSection section) + public Settings(IConfigurationSection section) : base(section) { Servers = section.GetSection(nameof(Servers)).GetChildren().Select(p => RpcServerSettings.Load(p)).ToArray(); } diff --git a/src/Plugins/StateService/Settings.cs b/src/Plugins/StateService/Settings.cs index 8557866bc1..a425b57d7e 100644 --- a/src/Plugins/StateService/Settings.cs +++ b/src/Plugins/StateService/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.StateService { - internal class Settings + internal class Settings : PluginSettings { public string Path { get; } public bool FullState { get; } @@ -23,7 +23,7 @@ internal class Settings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { Path = section.GetValue("Path", "Data_MPT_{0}"); FullState = section.GetValue("FullState", false); diff --git a/src/Plugins/StateService/StatePlugin.cs b/src/Plugins/StateService/StatePlugin.cs index 1c28e92193..4dfc8de165 100644 --- a/src/Plugins/StateService/StatePlugin.cs +++ b/src/Plugins/StateService/StatePlugin.cs @@ -40,6 +40,8 @@ public class StatePlugin : Plugin public override string Description => "Enables MPT for the node"; public override string ConfigFile => System.IO.Path.Combine(RootPath, "StateService.json"); + protected override bool StopOnUnhandledException => Settings.Default.StopOnUnhandledException; + internal IActorRef Store; internal IActorRef Verifier; diff --git a/src/Plugins/StateService/StateService.json b/src/Plugins/StateService/StateService.json index 265436fc30..19c18b2bf7 100644 --- a/src/Plugins/StateService/StateService.json +++ b/src/Plugins/StateService/StateService.json @@ -4,7 +4,8 @@ "FullState": false, "Network": 860833102, "AutoVerify": false, - "MaxFindResultItems": 100 + "MaxFindResultItems": 100, + "StopOnUnhandledException": true }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/StorageDumper/Settings.cs b/src/Plugins/StorageDumper/Settings.cs index c2761ce6b9..e645cd7074 100644 --- a/src/Plugins/StorageDumper/Settings.cs +++ b/src/Plugins/StorageDumper/Settings.cs @@ -14,7 +14,7 @@ namespace Neo.Plugins.StorageDumper { - internal class Settings + internal class Settings : PluginSettings { /// /// Amount of storages states (heights) to be dump in a given json file @@ -32,7 +32,7 @@ internal class Settings public static Settings? Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { // Geting settings for storage changes state dumper BlockCacheSize = section.GetValue("BlockCacheSize", 1000u); diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs index d8987cdcaa..bc2c97af07 100644 --- a/src/Plugins/StorageDumper/StorageDumper.cs +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -29,7 +29,7 @@ public class StorageDumper : Plugin /// private JObject? _currentBlock; private string? _lastCreateDirectory; - + protected override bool StopOnUnhandledException => Settings.Default != null && Settings.Default.StopOnUnhandledException; public override string Description => "Exports Neo-CLI status data"; diff --git a/src/Plugins/StorageDumper/StorageDumper.json b/src/Plugins/StorageDumper/StorageDumper.json index b327c37e0c..0d9d75d29b 100644 --- a/src/Plugins/StorageDumper/StorageDumper.json +++ b/src/Plugins/StorageDumper/StorageDumper.json @@ -3,6 +3,7 @@ "BlockCacheSize": 1000, "HeightToBegin": 0, "StoragePerFolder": 100000, - "Exclude": [ -4 ] + "Exclude": [ -4 ], + "StopOnUnhandledException": false } } diff --git a/src/Plugins/TokensTracker/TokensTracker.cs b/src/Plugins/TokensTracker/TokensTracker.cs index eacd0de6e4..f20122ba30 100644 --- a/src/Plugins/TokensTracker/TokensTracker.cs +++ b/src/Plugins/TokensTracker/TokensTracker.cs @@ -29,8 +29,10 @@ public class TokensTracker : Plugin private uint _network; private string[] _enabledTrackers; private IStore _db; + private bool _stopOnUnhandledException; private NeoSystem neoSystem; private readonly List trackers = new(); + protected override bool StopOnUnhandledException => _stopOnUnhandledException; public override string Description => "Enquiries balances and transaction history of accounts through RPC"; @@ -56,6 +58,7 @@ protected override void Configure() _maxResults = config.GetValue("MaxResults", 1000u); _network = config.GetValue("Network", 860833102u); _enabledTrackers = config.GetSection("EnabledTrackers").GetChildren().Select(p => p.Value).ToArray(); + _stopOnUnhandledException = config.GetValue("StopOnUnhandledException", true); } protected override void OnSystemLoaded(NeoSystem system) diff --git a/src/Plugins/TokensTracker/TokensTracker.json b/src/Plugins/TokensTracker/TokensTracker.json index ca63183b68..c480cf6eda 100644 --- a/src/Plugins/TokensTracker/TokensTracker.json +++ b/src/Plugins/TokensTracker/TokensTracker.json @@ -4,7 +4,8 @@ "TrackHistory": true, "MaxResults": 1000, "Network": 860833102, - "EnabledTrackers": [ "NEP-11", "NEP-17" ] + "EnabledTrackers": [ "NEP-11", "NEP-17" ], + "StopOnUnhandledException": false }, "Dependency": [ "RpcServer" diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index 8bc058c2a2..76071f6c34 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -19,7 +19,16 @@ namespace Neo.UnitTests.Plugins { - public class TestNonPlugin + + internal class TestPluginSettings(IConfigurationSection section) : PluginSettings(section) + { + public static TestPluginSettings Default { get; private set; } + public static void Load(IConfigurationSection section) + { + Default = new TestPluginSettings(section); + } + } + internal class TestNonPlugin { public TestNonPlugin() { @@ -39,15 +48,22 @@ private void OnCommitted(NeoSystem system, Block block) } - public class TestPlugin : Plugin + internal class TestPlugin : Plugin { - public TestPlugin() : base() + private readonly bool _stopOnUnhandledException; + protected internal override bool StopOnUnhandledException => _stopOnUnhandledException && TestPluginSettings.Default.StopOnUnhandledException; + + public TestPlugin(bool stopOnUnhandledException = true) : base() { Blockchain.Committing += OnCommitting; Blockchain.Committed += OnCommitted; + _stopOnUnhandledException = stopOnUnhandledException; } - protected override void Configure() { } + protected override void Configure() + { + TestPluginSettings.Load(GetConfiguration()); + } public void LogMessage(string message) { diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index 1ad286ae9a..7e82bad297 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -69,6 +69,7 @@ public void TestGetConfiguration() [TestMethod] public async Task TestOnException() { + _ = new TestPlugin(); // Ensure no exception is thrown try { @@ -94,5 +95,62 @@ await Assert.ThrowsExceptionAsync(async () => await Blockchain.InvokeCommittedAsync(null, null); }); } + + [TestMethod] + public async Task TestOnPluginStopped() + { + var pp = new TestPlugin(); + Assert.AreEqual(false, pp.IsStopped); + // Ensure no exception is thrown + try + { + await Blockchain.InvokeCommittingAsync(null, null, null, null); + await Blockchain.InvokeCommittedAsync(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); + } + + Assert.AreEqual(true, pp.IsStopped); + } + + [TestMethod] + public async Task TestOnPluginStopOnException() + { + // pp will stop on exception. + var pp = new TestPlugin(); + Assert.AreEqual(false, pp.IsStopped); + // Ensure no exception is thrown + try + { + await Blockchain.InvokeCommittingAsync(null, null, null, null); + await Blockchain.InvokeCommittedAsync(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); + } + + Assert.AreEqual(true, pp.IsStopped); + + // pp2 will not stop on exception. + var pp2 = new TestPlugin(false); + Assert.AreEqual(false, pp2.IsStopped); + // Ensure no exception is thrown + try + { + await Blockchain.InvokeCommittingAsync(null, null, null, null); + await Blockchain.InvokeCommittedAsync(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); + } + + Assert.AreEqual(false, pp2.IsStopped); + + } + } } From d7799f3cd0963cf21c9f9cce5c646c85b94dc657 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 23:21:38 +0800 Subject: [PATCH 10/20] remove watcher from blockchain if uint test is done to avoid cross test data pollution. --- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 48 +++++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index 7e82bad297..b31c60667e 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -14,6 +14,7 @@ using Neo.Ledger; using Neo.Plugins; using System; +using System.Reflection; using System.Threading.Tasks; namespace Neo.UnitTests.Plugins @@ -23,6 +24,51 @@ public class UT_Plugin { private static readonly object locker = new(); + [TestInitialize] + public void TestInitialize() + { + ClearEventHandlers(); + } + + [TestCleanup] + public void TestCleanup() + { + ClearEventHandlers(); + } + + private static void ClearEventHandlers() + { + ClearEventHandler("Committing"); + ClearEventHandler("Committed"); + } + + private static void ClearEventHandler(string eventName) + { + var eventInfo = typeof(Blockchain).GetEvent(eventName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (eventInfo == null) + { + return; + } + + var fields = typeof(Blockchain).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); + foreach (var field in fields) + { + if (field.FieldType == typeof(MulticastDelegate) || field.FieldType.BaseType == typeof(MulticastDelegate)) + { + var eventDelegate = (MulticastDelegate)field.GetValue(null); + if (eventDelegate != null && field.Name.Contains(eventName)) + { + foreach (var handler in eventDelegate.GetInvocationList()) + { + eventInfo.RemoveEventHandler(null, handler); + } + break; + } + } + } + } + + [TestMethod] public void TestGetConfigFile() { @@ -149,8 +195,6 @@ public async Task TestOnPluginStopOnException() } Assert.AreEqual(false, pp2.IsStopped); - } - } } From 90405c5c17e2d79085fa54b2623fcd9224ed9e9a Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 9 Jun 2024 23:26:42 +0800 Subject: [PATCH 11/20] add missing file --- src/Neo/Plugins/PluginSettings.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/Neo/Plugins/PluginSettings.cs diff --git a/src/Neo/Plugins/PluginSettings.cs b/src/Neo/Plugins/PluginSettings.cs new file mode 100644 index 0000000000..b622afb1b5 --- /dev/null +++ b/src/Neo/Plugins/PluginSettings.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PluginSettings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; + +namespace Neo.Plugins; + +public abstract class PluginSettings(IConfigurationSection section) +{ + public bool StopOnUnhandledException { get; } = section.GetValue("StopOnUnhandledException", true); +} From 005d057fa406c9f28880411e921bc5e49c7cfef5 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 10 Jun 2024 00:05:25 +0800 Subject: [PATCH 12/20] 3 different policy on handling plugin exception --- src/Neo/Ledger/Blockchain.cs | 21 +++++++++++++----- src/Neo/Plugins/Plugin.cs | 18 +++++++++++---- src/Neo/Plugins/PluginSettings.cs | 17 +++++++++++++- .../ApplicationLogs/ApplicationLogs.json | 2 +- src/Plugins/DBFTPlugin/DBFTPlugin.cs | 2 +- src/Plugins/DBFTPlugin/DBFTPlugin.json | 2 +- src/Plugins/OracleService/OracleService.cs | 2 +- src/Plugins/OracleService/OracleService.json | 2 +- src/Plugins/RpcServer/RpcServer.json | 2 +- src/Plugins/RpcServer/RpcServerPlugin.cs | 2 +- src/Plugins/StateService/StatePlugin.cs | 2 +- src/Plugins/StateService/StateService.json | 2 +- src/Plugins/StorageDumper/StorageDumper.cs | 2 +- src/Plugins/StorageDumper/StorageDumper.json | 2 +- src/Plugins/TokensTracker/TokensTracker.cs | 11 +++++++--- src/Plugins/TokensTracker/TokensTracker.json | 2 +- tests/Neo.UnitTests/Plugins/TestPlugin.cs | 8 +++---- tests/Neo.UnitTests/Plugins/UT_Plugin.cs | 22 +++++++++++++++++-- 18 files changed, 90 insertions(+), 31 deletions(-) diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index 9e855ae468..043741ad5a 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -513,11 +513,22 @@ private static async Task InvokeHandlersAsync(Delegate[] handlers, Action. /// - protected internal virtual bool StopOnUnhandledException { get; init; } = true; + protected internal virtual UnhandledExceptionPolicy ExceptionPolicy { get; init; } = UnhandledExceptionPolicy.StopNode; /// /// The plugin will be stopped if an exception is thrown. - /// But it also depends on . + /// But it also depends on . /// internal bool IsStopped { get; set; } @@ -257,8 +257,18 @@ public static bool SendMessage(object message) } catch (Exception ex) { - if (plugin.StopOnUnhandledException) - plugin.IsStopped = true; + switch (plugin.ExceptionPolicy) + { + case UnhandledExceptionPolicy.StopNode: + throw; + case UnhandledExceptionPolicy.StopPlugin: + plugin.IsStopped = true; + break; + case UnhandledExceptionPolicy.Ignore: + break; + default: + throw new ArgumentOutOfRangeException(); + } Utility.Log(nameof(Plugin), LogLevel.Error, ex); return false; } diff --git a/src/Neo/Plugins/PluginSettings.cs b/src/Neo/Plugins/PluginSettings.cs index b622afb1b5..2e0e68ea9f 100644 --- a/src/Neo/Plugins/PluginSettings.cs +++ b/src/Neo/Plugins/PluginSettings.cs @@ -10,10 +10,25 @@ // modifications are permitted. using Microsoft.Extensions.Configuration; +using Org.BouncyCastle.Security; +using System; namespace Neo.Plugins; public abstract class PluginSettings(IConfigurationSection section) { - public bool StopOnUnhandledException { get; } = section.GetValue("StopOnUnhandledException", true); + public UnhandledExceptionPolicy ExceptionPolicy + { + get + { + var policyString = section.GetValue("UnhandledExceptionPolicy", "StopNode"); + if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) + { + + return policy; + } + + throw new InvalidParameterException($"{policyString} is not a valid UnhandledExceptionPolicy"); + } + } } diff --git a/src/Plugins/ApplicationLogs/ApplicationLogs.json b/src/Plugins/ApplicationLogs/ApplicationLogs.json index 81f0e6395e..2664665dd2 100644 --- a/src/Plugins/ApplicationLogs/ApplicationLogs.json +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.json @@ -4,7 +4,7 @@ "Network": 860833102, "MaxStackSize": 65535, "Debug": false, - "StopOnUnhandledException": false + "UnhandledExceptionPolicy": "StopPlugin" }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.cs b/src/Plugins/DBFTPlugin/DBFTPlugin.cs index ca69292acf..ca522297a8 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.cs +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.cs @@ -31,7 +31,7 @@ public class DBFTPlugin : Plugin public override string ConfigFile => System.IO.Path.Combine(RootPath, "DBFTPlugin.json"); - protected override bool StopOnUnhandledException => settings.StopOnUnhandledException; + protected override UnhandledExceptionPolicy ExceptionPolicy => settings.ExceptionPolicy; public DBFTPlugin() { diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.json b/src/Plugins/DBFTPlugin/DBFTPlugin.json index 35cbd95a13..705b2b77cb 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.json +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.json @@ -6,6 +6,6 @@ "Network": 860833102, "MaxBlockSize": 2097152, "MaxBlockSystemFee": 150000000000, - "StopOnUnhandledException": true + "UnhandledExceptionPolicy": "StopNode" } } diff --git a/src/Plugins/OracleService/OracleService.cs b/src/Plugins/OracleService/OracleService.cs index 67dd80bd79..4d6cc59efe 100644 --- a/src/Plugins/OracleService/OracleService.cs +++ b/src/Plugins/OracleService/OracleService.cs @@ -61,7 +61,7 @@ public class OracleService : Plugin public override string Description => "Built-in oracle plugin"; - protected override bool StopOnUnhandledException => Settings.Default.StopOnUnhandledException; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; public override string ConfigFile => System.IO.Path.Combine(RootPath, "OracleService.json"); diff --git a/src/Plugins/OracleService/OracleService.json b/src/Plugins/OracleService/OracleService.json index 28d6b7bd26..49bf1153b3 100644 --- a/src/Plugins/OracleService/OracleService.json +++ b/src/Plugins/OracleService/OracleService.json @@ -6,7 +6,7 @@ "MaxOracleTimeout": 10000, "AllowPrivateHost": false, "AllowedContentTypes": [ "application/json" ], - "StopOnUnhandledException": false, + "UnhandledExceptionPolicy": "Ignore", "Https": { "Timeout": 5000 }, diff --git a/src/Plugins/RpcServer/RpcServer.json b/src/Plugins/RpcServer/RpcServer.json index 8583db7dde..dc9c25b8da 100644 --- a/src/Plugins/RpcServer/RpcServer.json +++ b/src/Plugins/RpcServer/RpcServer.json @@ -1,6 +1,6 @@ { "PluginConfiguration": { - "StopOnUnhandledException": false, + "UnhandledExceptionPolicy": "Ignore", "Servers": [ { "Network": 860833102, diff --git a/src/Plugins/RpcServer/RpcServerPlugin.cs b/src/Plugins/RpcServer/RpcServerPlugin.cs index f59ad43a1c..03416c1be5 100644 --- a/src/Plugins/RpcServer/RpcServerPlugin.cs +++ b/src/Plugins/RpcServer/RpcServerPlugin.cs @@ -24,7 +24,7 @@ public class RpcServerPlugin : Plugin private static readonly Dictionary> handlers = new(); public override string ConfigFile => System.IO.Path.Combine(RootPath, "RpcServer.json"); - protected override bool StopOnUnhandledException => settings.StopOnUnhandledException; + protected override UnhandledExceptionPolicy ExceptionPolicy => settings.ExceptionPolicy; protected override void Configure() { diff --git a/src/Plugins/StateService/StatePlugin.cs b/src/Plugins/StateService/StatePlugin.cs index 4dfc8de165..69a092a173 100644 --- a/src/Plugins/StateService/StatePlugin.cs +++ b/src/Plugins/StateService/StatePlugin.cs @@ -40,7 +40,7 @@ public class StatePlugin : Plugin public override string Description => "Enables MPT for the node"; public override string ConfigFile => System.IO.Path.Combine(RootPath, "StateService.json"); - protected override bool StopOnUnhandledException => Settings.Default.StopOnUnhandledException; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; internal IActorRef Store; internal IActorRef Verifier; diff --git a/src/Plugins/StateService/StateService.json b/src/Plugins/StateService/StateService.json index 19c18b2bf7..cadd2da5fd 100644 --- a/src/Plugins/StateService/StateService.json +++ b/src/Plugins/StateService/StateService.json @@ -5,7 +5,7 @@ "Network": 860833102, "AutoVerify": false, "MaxFindResultItems": 100, - "StopOnUnhandledException": true + "UnhandledExceptionPolicy": "StopPlugin" }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs index bc2c97af07..9bbad94ce0 100644 --- a/src/Plugins/StorageDumper/StorageDumper.cs +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -29,7 +29,7 @@ public class StorageDumper : Plugin /// private JObject? _currentBlock; private string? _lastCreateDirectory; - protected override bool StopOnUnhandledException => Settings.Default != null && Settings.Default.StopOnUnhandledException; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; public override string Description => "Exports Neo-CLI status data"; diff --git a/src/Plugins/StorageDumper/StorageDumper.json b/src/Plugins/StorageDumper/StorageDumper.json index 0d9d75d29b..0c314cf262 100644 --- a/src/Plugins/StorageDumper/StorageDumper.json +++ b/src/Plugins/StorageDumper/StorageDumper.json @@ -4,6 +4,6 @@ "HeightToBegin": 0, "StoragePerFolder": 100000, "Exclude": [ -4 ], - "StopOnUnhandledException": false + "UnhandledExceptionPolicy": "Ignore" } } diff --git a/src/Plugins/TokensTracker/TokensTracker.cs b/src/Plugins/TokensTracker/TokensTracker.cs index f20122ba30..74056baa7d 100644 --- a/src/Plugins/TokensTracker/TokensTracker.cs +++ b/src/Plugins/TokensTracker/TokensTracker.cs @@ -15,6 +15,7 @@ using Neo.Persistence; using Neo.Plugins.RpcServer; using Neo.Plugins.Trackers; +using System; using System.Collections.Generic; using System.Linq; using static System.IO.Path; @@ -29,10 +30,10 @@ public class TokensTracker : Plugin private uint _network; private string[] _enabledTrackers; private IStore _db; - private bool _stopOnUnhandledException; + private UnhandledExceptionPolicy _exceptionPolicy; private NeoSystem neoSystem; private readonly List trackers = new(); - protected override bool StopOnUnhandledException => _stopOnUnhandledException; + protected override UnhandledExceptionPolicy ExceptionPolicy => _exceptionPolicy; public override string Description => "Enquiries balances and transaction history of accounts through RPC"; @@ -58,7 +59,11 @@ protected override void Configure() _maxResults = config.GetValue("MaxResults", 1000u); _network = config.GetValue("Network", 860833102u); _enabledTrackers = config.GetSection("EnabledTrackers").GetChildren().Select(p => p.Value).ToArray(); - _stopOnUnhandledException = config.GetValue("StopOnUnhandledException", true); + var policyString = config.GetValue("UnhandledExceptionPolicy", "StopNode"); + if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) + { + _exceptionPolicy = policy; + } } protected override void OnSystemLoaded(NeoSystem system) diff --git a/src/Plugins/TokensTracker/TokensTracker.json b/src/Plugins/TokensTracker/TokensTracker.json index c480cf6eda..26be559dbf 100644 --- a/src/Plugins/TokensTracker/TokensTracker.json +++ b/src/Plugins/TokensTracker/TokensTracker.json @@ -5,7 +5,7 @@ "MaxResults": 1000, "Network": 860833102, "EnabledTrackers": [ "NEP-11", "NEP-17" ], - "StopOnUnhandledException": false + "UnhandledExceptionPolicy": "Ignore" }, "Dependency": [ "RpcServer" diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index 76071f6c34..5e51220c7f 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -50,14 +50,14 @@ private void OnCommitted(NeoSystem system, Block block) internal class TestPlugin : Plugin { - private readonly bool _stopOnUnhandledException; - protected internal override bool StopOnUnhandledException => _stopOnUnhandledException && TestPluginSettings.Default.StopOnUnhandledException; + private readonly UnhandledExceptionPolicy _exceptionPolicy; + protected internal override UnhandledExceptionPolicy ExceptionPolicy => _exceptionPolicy; - public TestPlugin(bool stopOnUnhandledException = true) : base() + public TestPlugin(UnhandledExceptionPolicy exceptionPolicy = UnhandledExceptionPolicy.StopPlugin) : base() { Blockchain.Committing += OnCommitting; Blockchain.Committed += OnCommitted; - _stopOnUnhandledException = stopOnUnhandledException; + _exceptionPolicy = exceptionPolicy; } protected override void Configure() diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index b31c60667e..c48d32563f 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -68,7 +68,6 @@ private static void ClearEventHandler(string eventName) } } - [TestMethod] public void TestGetConfigFile() { @@ -181,7 +180,7 @@ public async Task TestOnPluginStopOnException() Assert.AreEqual(true, pp.IsStopped); // pp2 will not stop on exception. - var pp2 = new TestPlugin(false); + var pp2 = new TestPlugin(UnhandledExceptionPolicy.Ignore); Assert.AreEqual(false, pp2.IsStopped); // Ensure no exception is thrown try @@ -196,5 +195,24 @@ public async Task TestOnPluginStopOnException() Assert.AreEqual(false, pp2.IsStopped); } + + [TestMethod] + public async Task TestOnNodeStopOnPluginException() + { + // node will stop on pp exception. + var pp = new TestPlugin(UnhandledExceptionPolicy.StopNode); + Assert.AreEqual(false, pp.IsStopped); + await Assert.ThrowsExceptionAsync(async () => + { + await Blockchain.InvokeCommittingAsync(null, null, null, null); + }); + + await Assert.ThrowsExceptionAsync(async () => + { + await Blockchain.InvokeCommittedAsync(null, null); + }); + + Assert.AreEqual(false, pp.IsStopped); + } } } From 1da63a2289b926c19d4f23b9ac32b0db7e03c1b4 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 10 Jun 2024 00:12:31 +0800 Subject: [PATCH 13/20] add missing file --- src/Neo/Plugins/UnhandledExceptionPolicy.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/Neo/Plugins/UnhandledExceptionPolicy.cs diff --git a/src/Neo/Plugins/UnhandledExceptionPolicy.cs b/src/Neo/Plugins/UnhandledExceptionPolicy.cs new file mode 100644 index 0000000000..035e173aa3 --- /dev/null +++ b/src/Neo/Plugins/UnhandledExceptionPolicy.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UnhandledExceptionPolicy.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Plugins +{ + public enum UnhandledExceptionPolicy + { + Ignore = 0, + StopPlugin = 1, + StopNode = 2, + } +} From 850baf39baf7c676d120f6a76c91716a959eb92f Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 10 Jun 2024 00:58:12 +0800 Subject: [PATCH 14/20] fix null warning --- src/Plugins/ApplicationLogs/LogReader.cs | 1 + src/Plugins/ApplicationLogs/Settings.cs | 4 ++-- src/Plugins/StorageDumper/StorageDumper.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Plugins/ApplicationLogs/LogReader.cs b/src/Plugins/ApplicationLogs/LogReader.cs index 6a8682ab5e..296ec6de73 100644 --- a/src/Plugins/ApplicationLogs/LogReader.cs +++ b/src/Plugins/ApplicationLogs/LogReader.cs @@ -37,6 +37,7 @@ public class LogReader : Plugin public override string Name => "ApplicationLogs"; public override string Description => "Synchronizes smart contract VM executions and notifications (NotifyLog) on blockchain."; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; #region Ctor diff --git a/src/Plugins/ApplicationLogs/Settings.cs b/src/Plugins/ApplicationLogs/Settings.cs index 8f2a0da1e1..c693322503 100644 --- a/src/Plugins/ApplicationLogs/Settings.cs +++ b/src/Plugins/ApplicationLogs/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.ApplicationLogs { - internal class Settings + internal class Settings: PluginSettings { public string Path { get; } public uint Network { get; } @@ -23,7 +23,7 @@ internal class Settings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { Path = section.GetValue("Path", "ApplicationLogs_{0}"); Network = section.GetValue("Network", 5195086u); diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs index 9bbad94ce0..e73123c1f7 100644 --- a/src/Plugins/StorageDumper/StorageDumper.cs +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -29,7 +29,7 @@ public class StorageDumper : Plugin /// private JObject? _currentBlock; private string? _lastCreateDirectory; - protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default?.ExceptionPolicy??UnhandledExceptionPolicy.Ignore; public override string Description => "Exports Neo-CLI status data"; From 4e9ec4a02e645aaff5d16dbdce39f5b2f4d026a0 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 10 Jun 2024 00:58:23 +0800 Subject: [PATCH 15/20] format --- src/Plugins/ApplicationLogs/Settings.cs | 2 +- src/Plugins/StorageDumper/StorageDumper.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugins/ApplicationLogs/Settings.cs b/src/Plugins/ApplicationLogs/Settings.cs index c693322503..6a5f238272 100644 --- a/src/Plugins/ApplicationLogs/Settings.cs +++ b/src/Plugins/ApplicationLogs/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.ApplicationLogs { - internal class Settings: PluginSettings + internal class Settings : PluginSettings { public string Path { get; } public uint Network { get; } diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs index e73123c1f7..f070cc6a5e 100644 --- a/src/Plugins/StorageDumper/StorageDumper.cs +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -29,7 +29,7 @@ public class StorageDumper : Plugin /// private JObject? _currentBlock; private string? _lastCreateDirectory; - protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default?.ExceptionPolicy??UnhandledExceptionPolicy.Ignore; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default?.ExceptionPolicy ?? UnhandledExceptionPolicy.Ignore; public override string Description => "Exports Neo-CLI status data"; From baf2f30cb5e998329106947786e304fa7b492e4c Mon Sep 17 00:00:00 2001 From: Shargon Date: Mon, 10 Jun 2024 01:18:14 -0700 Subject: [PATCH 16/20] Apply suggestions from code review Clean --- src/Neo/Ledger/Blockchain.cs | 2 -- src/Neo/Plugins/PluginSettings.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index 043741ad5a..f830750d0e 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -541,8 +541,6 @@ private static async Task InvokeHandlersAsync(Delegate[] handlers, Action throw e); } - - /// /// Gets a object used for creating the actor. /// diff --git a/src/Neo/Plugins/PluginSettings.cs b/src/Neo/Plugins/PluginSettings.cs index 2e0e68ea9f..5da84f0143 100644 --- a/src/Neo/Plugins/PluginSettings.cs +++ b/src/Neo/Plugins/PluginSettings.cs @@ -24,7 +24,6 @@ public UnhandledExceptionPolicy ExceptionPolicy var policyString = section.GetValue("UnhandledExceptionPolicy", "StopNode"); if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) { - return policy; } From 77e8ea8551b00d3a91564651e423fa7f7d606bbf Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 10 Jun 2024 16:50:19 +0800 Subject: [PATCH 17/20] Update src/Neo/Plugins/PluginSettings.cs Co-authored-by: Shargon --- src/Neo/Plugins/PluginSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Plugins/PluginSettings.cs b/src/Neo/Plugins/PluginSettings.cs index 5da84f0143..3a55ee87b5 100644 --- a/src/Neo/Plugins/PluginSettings.cs +++ b/src/Neo/Plugins/PluginSettings.cs @@ -21,7 +21,7 @@ public UnhandledExceptionPolicy ExceptionPolicy { get { - var policyString = section.GetValue("UnhandledExceptionPolicy", "StopNode"); + var policyString = section.GetValue("UnhandledExceptionPolicy", nameof(UnhandledExceptionPolicy.StopNode)); if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) { return policy; From 5f11c4cca378a2019618b939fe6f21f5bc6e8ef6 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 11 Jun 2024 11:32:33 +0800 Subject: [PATCH 18/20] Update src/Neo/Plugins/PluginSettings.cs Co-authored-by: Christopher Schuchardt --- src/Neo/Plugins/PluginSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo/Plugins/PluginSettings.cs b/src/Neo/Plugins/PluginSettings.cs index 3a55ee87b5..af33e44eea 100644 --- a/src/Neo/Plugins/PluginSettings.cs +++ b/src/Neo/Plugins/PluginSettings.cs @@ -21,7 +21,7 @@ public UnhandledExceptionPolicy ExceptionPolicy { get { - var policyString = section.GetValue("UnhandledExceptionPolicy", nameof(UnhandledExceptionPolicy.StopNode)); + var policyString = section.GetValue(nameof(UnhandledExceptionPolicy), nameof(UnhandledExceptionPolicy.StopNode)); if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) { return policy; From bce7b65fec22c75a0df970edd1b73b13c44f0d35 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 16 Jun 2024 19:24:49 +0800 Subject: [PATCH 19/20] Update src/Plugins/TokensTracker/TokensTracker.cs Co-authored-by: Christopher Schuchardt --- src/Plugins/TokensTracker/TokensTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/TokensTracker/TokensTracker.cs b/src/Plugins/TokensTracker/TokensTracker.cs index 74056baa7d..5e1458acc9 100644 --- a/src/Plugins/TokensTracker/TokensTracker.cs +++ b/src/Plugins/TokensTracker/TokensTracker.cs @@ -59,7 +59,7 @@ protected override void Configure() _maxResults = config.GetValue("MaxResults", 1000u); _network = config.GetValue("Network", 860833102u); _enabledTrackers = config.GetSection("EnabledTrackers").GetChildren().Select(p => p.Value).ToArray(); - var policyString = config.GetValue("UnhandledExceptionPolicy", "StopNode"); + var policyString = config.GetValue(nameof(UnhandledExceptionPolicy), nameof(UnhandledExceptionPolicy.StopNode)); if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) { _exceptionPolicy = policy; From 10ba5e971e8ab0d4ceb977de4504bb7c67822915 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Tue, 18 Jun 2024 09:52:22 +0800 Subject: [PATCH 20/20] Update src/Plugins/TokensTracker/TokensTracker.json --- src/Plugins/TokensTracker/TokensTracker.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/TokensTracker/TokensTracker.json b/src/Plugins/TokensTracker/TokensTracker.json index 26be559dbf..dbdbecfd40 100644 --- a/src/Plugins/TokensTracker/TokensTracker.json +++ b/src/Plugins/TokensTracker/TokensTracker.json @@ -5,7 +5,7 @@ "MaxResults": 1000, "Network": 860833102, "EnabledTrackers": [ "NEP-11", "NEP-17" ], - "UnhandledExceptionPolicy": "Ignore" + "UnhandledExceptionPolicy": "StopPlugin" }, "Dependency": [ "RpcServer"