Neo Plugin Architecture: How to Extend Node Functionality with Custom Plugins
The Neo plugin architecture is a lightweight, reflection-based framework that automatically discovers DLLs from a Plugins folder and manages their lifecycle through the abstract Plugin base class, enabling developers to extend node functionality without modifying core blockchain code.
The neo-project/neo repository implements a modular plugin system designed for runtime extensibility. Understanding the Neo plugin architecture is essential for implementing custom consensus rules, monitoring agents, or RPC extensions while isolating failures through configurable exception policies.
Core Components of the Plugin Framework
The architecture centers on the Neo.Plugins namespace, where the base Plugin class orchestrates discovery, registration, and inter-plugin communication.
The Plugin Base Class
All extensions must inherit from the abstract Plugin class defined in src/Neo/Plugins/Plugin.cs. This base class provides the infrastructure for automatic registration and lifecycle management.
When a plugin is instantiated, the constructor automatically adds the instance to the static Plugin.Plugins list (line 33) and initializes a logger (lines 107-110). The framework discovers plugins by scanning the PluginsDirectory (lines 38-40), which defaults to a Plugins folder relative to the executable.
Key virtual methods for customization include:
Configure()(lines 14-18): Called during construction to load plugin-specific JSON configuration viaGetConfiguration().OnSystemLoaded(NeoSystem system)(lines 60-62): Invoked afterNeoSysteminitializes core services like the blockchain and P2P networking.OnMessage(object message)(lines 52-55): Handles inter-plugin messages sent viaPlugin.SendMessage().
Configuration and Settings Interfaces
For strongly-typed configuration, implement the IPluginSettings interface from src/Neo/Plugins/IPluginSettings.cs. This interface exposes the ExceptionPolicy property (lines 16-17), allowing plugins to declare their preferred failure handling strategy.
Plugins access configuration through the protected GetConfiguration() method, which returns the "PluginConfiguration" section from the plugin's JSON config file.
Exception Handling Policies
The UnhandledExceptionPolicy enum in src/Neo/Plugins/UnhandledExceptionPolicy.cs defines three failure modes (lines 15-19):
Ignore: Log the exception and continue execution.StopPlugin: Disable the specific plugin but keep the node running.StopNode: Shut down the entire node process.
Set this policy via the ExceptionPolicy property override or through IPluginSettings to control isolation boundaries.
System Integration and Lifecycle
The NeoSystem class in src/Neo/NeoSystem.cs triggers plugin loading during static initialization. The static constructor calls Plugin.LoadPlugins() (lines 12-13), which scans the Plugins directory and instantiates all non-abstract Plugin subclasses.
After constructing core actors (blockchain, memory pool, local node), NeoSystem iterates through Plugin.Plugins and invokes OnSystemLoaded(this) (lines 53-55), signaling that plugins may now interact with node services.
Implementing a Custom Plugin
Creating a custom plugin involves inheriting from the base class, implementing lifecycle hooks, and deploying to the correct directory structure.
Project Structure and Deployment
Compile your plugin as a .NET class library targeting the same framework as the Neo node. Deploy the output DLL to:
<neo-node-exe>/Plugins/<YourPluginName>/
Place an optional config.json in the same folder. The framework monitors this directory; changes require a node restart to reload assemblies.
Basic Plugin Implementation
Here is a minimal plugin that logs initialization and responds to inter-plugin messages:
using Neo.Plugins;
using Neo.SmartContract.Native;
using Neo;
using System;
public class HelloWorldPlugin : Plugin
{
protected override void Configure()
{
var config = GetConfiguration();
Logger.Information("HelloWorldPlugin configured with: {Config}", config.Value);
}
protected internal override void OnSystemLoaded(NeoSystem system)
{
Logger.Information("HelloWorldPlugin attached to NeoSystem at height {Height}",
NativeContract.Ledger.CurrentHash(system.StoreView));
}
protected override bool OnMessage(object message)
{
if (message is string s && s == "ping")
{
Logger.Information("Received ping, sending pong.");
return true; // Stop propagation
}
return false; // Allow other plugins to process
}
public override void Dispose()
{
Logger.Information("HelloWorldPlugin disposing.");
base.Dispose();
}
}
This example demonstrates the required inheritance pattern, logger usage through the base class Logger property, and proper message handling semantics.
Handling Configuration
For plugins requiring structured configuration, implement IPluginSettings and bind to a JSON section:
using Microsoft.Extensions.Configuration;
using Neo.Plugins;
public class AnalyticsSettings : IPluginSettings
{
public static AnalyticsSettings Current { get; private set; } = new();
public UnhandledExceptionPolicy ExceptionPolicy { get; set; } = UnhandledExceptionPolicy.StopPlugin;
public string Endpoint { get; set; } = "http://localhost:8080";
public int BatchSize { get; set; } = 100;
public static void Load(IConfigurationSection section)
{
section.Bind(Current);
}
}
public class AnalyticsPlugin : Plugin
{
protected internal override UnhandledExceptionPolicy ExceptionPolicy =>
AnalyticsSettings.Current.ExceptionPolicy;
protected override void Configure()
{
AnalyticsSettings.Load(GetConfiguration());
Logger.Information("AnalyticsPlugin configured. Endpoint: {Endpoint}, BatchSize: {BatchSize}",
AnalyticsSettings.Current.Endpoint, AnalyticsSettings.Current.BatchSize);
}
}
This pattern provides type-safe access to configuration values while exposing the exception policy to the framework.
Inter-Plugin Communication
Plugins communicate via the static Plugin.SendMessage method, which iterates through loaded plugins and invokes OnMessage until one returns true:
// Broadcasting a message from any plugin or service
bool wasHandled = Plugin.SendMessage(new {
Type = "BlockProcessed",
Height = 1000,
Hash = "0x..."
});
The implementation in src/Neo/Plugins/Plugin.cs (lines 68-104) ensures that the first plugin handling the message stops further propagation, enabling efficient event subscription patterns.
Key Source Files
Understanding the Neo plugin architecture requires familiarity with these specific files in the neo-project/neo repository:
| File | Purpose | Location |
|---|---|---|
Plugin.cs |
Abstract base class defining the plugin lifecycle, discovery mechanism, and messaging API. | src/Neo/Plugins/Plugin.cs |
IPluginSettings.cs |
Interface for strongly-typed configuration exposing the ExceptionPolicy property. |
src/Neo/Plugins/IPluginSettings.cs |
UnhandledExceptionPolicy.cs |
Enum defining failure modes: Ignore, StopPlugin, StopNode. |
src/Neo/Plugins/UnhandledExceptionPolicy.cs |
NeoSystem.cs |
Core node class that triggers plugin loading and lifecycle callbacks. | src/Neo/NeoSystem.cs |
TestPlugin.cs |
Reference implementation demonstrating configuration loading and event handling. | tests/Neo.UnitTests/Plugins/TestPlugin.cs |
Summary
- The Neo plugin architecture centers on the abstract
Pluginclass insrc/Neo/Plugins/Plugin.cs, providing automatic registration, configuration loading, and lifecycle management. - Plugins are discovered at runtime by scanning the
Pluginsdirectory for DLLs containing non-abstract implementations of the base class. - Key extension points include
Configure()for JSON configuration,OnSystemLoaded(NeoSystem)for service integration, andOnMessage(object)for inter-plugin communication. - The
UnhandledExceptionPolicyenum controls failure isolation, allowing exceptions to either stop the plugin (StopPlugin), halt the node (StopNode), or be ignored. - Implementation requires inheriting from
Plugin, deploying to thePluginsfolder, and optionally implementingIPluginSettingsfor strongly-typed configuration.
Frequently Asked Questions
What is the base class for all Neo plugins?
All custom plugins must inherit from the abstract Plugin class defined in src/Neo/Plugins/Plugin.cs. This base class handles automatic registration via its constructor (lines 107-110), provides access to the logging infrastructure through the Logger property, and defines virtual methods for configuration and lifecycle management that subclasses override to implement custom functionality.
How does the Neo node discover and load plugins?
The NeoSystem class triggers plugin discovery during its static constructor by calling Plugin.LoadPlugins() (lines 12-13 in src/Neo/NeoSystem.cs). This method scans the Plugins directory (defined in Plugin.cs lines 38-40), loads each DLL assembly, and uses reflection to instantiate every non-abstract class deriving from Plugin (lines 89-100 in Plugin.cs).
What lifecycle hooks are available for plugin initialization?
The primary lifecycle hooks include Configure(), called during construction to load JSON configuration (lines 14-18 in Plugin.cs); OnSystemLoaded(NeoSystem system), invoked after core services are ready (lines 53-55 in NeoSystem.cs); and OnMessage(object message), which handles inter-plugin communication (lines 52-55 in Plugin.cs). Additionally, developers can override Dispose() for cleanup when the node shuts down.
How do plugins handle configuration and error policies?
Plugins load configuration by overriding Configure() and calling GetConfiguration(), which returns the "PluginConfiguration" section from the plugin's JSON config file. For strongly-typed settings, implement IPluginSettings (defined in src/Neo/Plugins/IPluginSettings.cs) to expose the ExceptionPolicy property. This policy, defined in UnhandledExceptionPolicy.cs, controls whether unhandled exceptions stop only the plugin (StopPlugin), halt the entire node (StopNode), or are ignored.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →