Plugin Architecture
The BlockNode is designed for extensibility through a robust plugin system. All major features are implemented as plugins, allowing developers to add, modify, or replace functionality easily.
Plugin Architecture
Base Interface: All plugins implement the
BlockNodePlugininterface, which defines lifecycle methods (init,start,stop) and configuration hooks.Messaging Facility: The
BlockMessagingFacilityis a special plugin responsible for distributing events (block items or notifications) to registered handlers (other plugins/components).This facility is how plugins communicate data and events between plugins.
This facility may be replaced, but a single implementation must be present in any Block Node.
Service Loading: Plugins are discovered and loaded dynamically at startup using a JPMS a service loader.
Plugin Lifecycle
Discovery: On startup, the
BlockNodeAppscans for available plugins implementingBlockNodePluginusing JPMS ServiceLoader.Initialization: Each plugin is initialized with the application context and appropriate service routing builders.
Start: Plugins are started, enabling them to register for events, process events, and interact with other components.
Stop: On shutdown, plugins are stopped gracefully.
Adding a New Plugin
To add a new plugin:
Implement the
BlockNodePlugininterface in your module.Optionally, implement specialized interfaces (e.g.,
BlockProviderPlugin,HistoricalBlockFacilityorBlockMessagingFacility) if your plugin replaces or extends block management and messaging capabilities.Optionally, implement additional interfaces (e.g., a protobuf
ServiceInterface) for additional capabilities.
Example Plugin Structure
Gradle Build Configuration
Plugin jars are built and assembled through Gradle configurations defined in block-node/app/build.gradle.kts.
blockNodePlugins Configuration
The blockNodePlugins configuration in the app module is the authoritative list of all Block Node plugins. It is both consumable (other projects like E2E suites can depend on it) and resolvable (Gradle can resolve it into actual jar files). Transitive dependencies (gRPC, Helidon, Swirlds libraries, etc.) are included automatically.
When adding a new plugin, update two places in block-node/app/build.gradle.kts:
Add the project dependency to the
blockNodePluginsconfiguration independencies.Add the module to
testModuleInfoso integration tests can load it.
Core vs Plugin Jar Filtering
The OCI image contains only core runtime jars (mainModuleInfo dependencies). Plugin jars and their transitive dependencies are kept separate. At build time, tasks like prepareDockerPlugins (for local Docker) and prepareTestPlugins (in the suites module) resolve the full plugin classpath, then filter out any jars already present in the core runtime to avoid duplicates.
How Other Projects Consume Plugins
The E2E suites module (tools-and-tests/suites/build.gradle.kts) declares two local configurations:
appCoreRuntime— resolves the app's core jars for exclusion filtering.testPlugins— resolves plugin jars from the app'sblockNodePluginsconfiguration.
The prepareTestPlugins task subtracts core jars from plugin jars and copies the result into a directory that gets mounted into E2E test containers.
Deployment Configuration
The Block Node OCI image is published without plugins. Plugins are downloaded during deployment based on Helm chart configuration. This allows operators to select exactly which plugins to load.
For deployment configuration, including pre-defined profiles and custom plugin selection, see:
Last updated