feat(plugin-api): expose Besu node version and commit hash via BesuConfiguration#10227
feat(plugin-api): expose Besu node version and commit hash via BesuConfiguration#10227alejandroGM0 wants to merge 2 commits intobesu-eth:mainfrom
Conversation
…nfiguration Plugins currently cannot access the host Besu node version at runtime. BesuVersionUtils exists in the util module but is not exposed through any plugin-api service. Add getBesuVersion() and getBesuCommitHash() to BesuConfiguration, delegating to BesuVersionUtils. This enables plugins to log diagnostics, perform runtime compatibility checks, and include version info in custom RPC responses. Signed-off-by: Alejandro <26930485+alejandroGM0@users.noreply.github.com>
599d0a8 to
3644bab
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Exposes the running Besu node’s version and git commit hash through the plugin API so plugins can query runtime node build information without relying on JSON-RPC.
Changes:
- Added
getBesuVersion()andgetBesuCommitHash()toBesuConfigurationinplugin-api - Implemented the new methods in
BesuConfigurationImplviaBesuVersionUtils - Updated plugin API change hash and added unit tests for the new getters
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java | Adds new plugin API methods to expose Besu version and commit hash. |
| plugin-api/build.gradle | Updates checkAPIChanges known hash for the API surface change. |
| app/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java | Implements the new API methods by delegating to BesuVersionUtils. |
| app/src/test/java/org/hyperledger/besu/services/BesuConfigurationImplTest.java | Adds tests to validate non-empty return values for the new methods. |
| * @return the Besu node version string, never null | ||
| */ | ||
| String getBesuVersion(); | ||
|
|
||
| /** | ||
| * Returns the git commit hash of the running Besu build. | ||
| * | ||
| * @return the short git commit hash, never null | ||
| */ | ||
| String getBesuCommitHash(); |
There was a problem hiding this comment.
Adding abstract methods to a public plugin API interface is source-breaking for any downstream code that implements BesuConfiguration (common in plugin unit tests via fakes/stubs). To reduce upgrade friction, consider making these default methods (with a documented fallback like \"UNKNOWN\") or introducing a new extended interface (e.g., BesuConfigurationV2) / dedicated service for version info so older implementers aren’t forced to update immediately.
| * @return the Besu node version string, never null | |
| */ | |
| String getBesuVersion(); | |
| /** | |
| * Returns the git commit hash of the running Besu build. | |
| * | |
| * @return the short git commit hash, never null | |
| */ | |
| String getBesuCommitHash(); | |
| * <p>The default implementation returns {@code "UNKNOWN"} to preserve source compatibility | |
| * for existing downstream implementations of this public plugin API. | |
| * | |
| * @return the Besu node version string, never null | |
| */ | |
| default String getBesuVersion() { | |
| return "UNKNOWN"; | |
| } | |
| /** | |
| * Returns the git commit hash of the running Besu build. | |
| * | |
| * <p>The default implementation returns {@code "UNKNOWN"} to preserve source compatibility | |
| * for existing downstream implementations of this public plugin API. | |
| * | |
| * @return the short git commit hash, never null | |
| */ | |
| default String getBesuCommitHash() { | |
| return "UNKNOWN"; | |
| } |
| * <p>The format follows semantic versioning: {@code "MAJOR.MINOR.PATCH"} for release builds | ||
| * (e.g., {@code "25.3.0"}) or {@code "MAJOR.MINOR.PATCH-qualifier"} for development builds | ||
| * (e.g., {@code "25.3.1-dev-ac23d311"}). | ||
| * | ||
| * <p>Available during all plugin lifecycle phases ({@code register} through {@code stop}). | ||
| * | ||
| * @return the Besu node version string, never null |
There was a problem hiding this comment.
The Javadoc promises semantic-version formatting and a non-null return, but it doesn’t document what happens when build metadata is unavailable (e.g., missing/stripped manifest properties). Please document the exact fallback value(s) (such as \"UNKNOWN\") and whether the value is guaranteed to match the documented SemVer patterns in all build variants.
| * <p>The format follows semantic versioning: {@code "MAJOR.MINOR.PATCH"} for release builds | |
| * (e.g., {@code "25.3.0"}) or {@code "MAJOR.MINOR.PATCH-qualifier"} for development builds | |
| * (e.g., {@code "25.3.1-dev-ac23d311"}). | |
| * | |
| * <p>Available during all plugin lifecycle phases ({@code register} through {@code stop}). | |
| * | |
| * @return the Besu node version string, never null | |
| * <p>When build metadata is available, the returned value follows semantic versioning: | |
| * {@code "MAJOR.MINOR.PATCH"} for release builds (e.g., {@code "25.3.0"}) or | |
| * {@code "MAJOR.MINOR.PATCH-qualifier"} for development builds (e.g., | |
| * {@code "25.3.1-dev-ac23d311"}). | |
| * | |
| * <p>If the version metadata is unavailable (for example, if manifest properties are missing | |
| * or stripped), this method returns the literal string {@code "UNKNOWN"}. That fallback value | |
| * is never null, but it is not guaranteed to match the semantic-version patterns described | |
| * above. | |
| * | |
| * <p>Available during all plugin lifecycle phases ({@code register} through {@code stop}). | |
| * | |
| * @return the Besu node version string; never null, and {@code "UNKNOWN"} if version metadata | |
| * is unavailable |
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class BesuConfigurationImplTest { | ||
|
|
||
| @Test | ||
| void getBesuVersion_returnsNonEmptyString() { | ||
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | ||
| String version = config.getBesuVersion(); | ||
| assertThat(version).isNotNull(); | ||
| assertThat(version).isNotEmpty(); | ||
| } | ||
|
|
||
| @Test | ||
| void getBesuCommitHash_returnsNonEmptyString() { | ||
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | ||
| String commit = config.getBesuCommitHash(); | ||
| assertThat(commit).isNotNull(); | ||
| assertThat(commit).isNotEmpty(); |
There was a problem hiding this comment.
These tests only assert non-null/non-empty, so they won’t catch regressions where the methods stop delegating correctly (e.g., returning a constant placeholder) or violate the documented format. Consider asserting equality with BesuVersionUtils.shortVersion() / BesuVersionUtils.commit() and (optionally) validating basic format expectations (e.g., SemVer-like version, hex-ish commit hash) to better pin down the intended contract.
| import org.junit.jupiter.api.Test; | |
| class BesuConfigurationImplTest { | |
| @Test | |
| void getBesuVersion_returnsNonEmptyString() { | |
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | |
| String version = config.getBesuVersion(); | |
| assertThat(version).isNotNull(); | |
| assertThat(version).isNotEmpty(); | |
| } | |
| @Test | |
| void getBesuCommitHash_returnsNonEmptyString() { | |
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | |
| String commit = config.getBesuCommitHash(); | |
| assertThat(commit).isNotNull(); | |
| assertThat(commit).isNotEmpty(); | |
| import org.hyperledger.besu.util.BesuVersionUtils; | |
| import org.junit.jupiter.api.Test; | |
| class BesuConfigurationImplTest { | |
| @Test | |
| void getBesuVersion_returnsShortVersion() { | |
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | |
| String version = config.getBesuVersion(); | |
| assertThat(version) | |
| .isEqualTo(BesuVersionUtils.shortVersion()) | |
| .matches("\\d+\\.\\d+\\.\\d+(?:[-+].+)?"); | |
| } | |
| @Test | |
| void getBesuCommitHash_returnsCommitHash() { | |
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | |
| String commit = config.getBesuCommitHash(); | |
| assertThat(commit) | |
| .isEqualTo(BesuVersionUtils.commit()) | |
| .matches("[0-9a-fA-F]+"); |
| * <p>The format follows semantic versioning: {@code "MAJOR.MINOR.PATCH"} for release builds | ||
| * (e.g., {@code "25.3.0"}) or {@code "MAJOR.MINOR.PATCH-qualifier"} for development builds | ||
| * (e.g., {@code "25.3.1-dev-ac23d311"}). |
There was a problem hiding this comment.
The Javadoc currently guarantees a specific version string shape (including an example that embeds what looks like a commit hash in the version). Unless BesuVersionUtils.shortVersion() is explicitly specified to always follow this exact format, this is easy to get out of sync with actual behavior. Consider loosening the contract to: (a) state it’s the output of BesuVersionUtils.shortVersion(), and/or (b) describe it as a human-readable version string that is typically SemVer for releases, without guaranteeing qualifier structure.
| * <p>The format follows semantic versioning: {@code "MAJOR.MINOR.PATCH"} for release builds | |
| * (e.g., {@code "25.3.0"}) or {@code "MAJOR.MINOR.PATCH-qualifier"} for development builds | |
| * (e.g., {@code "25.3.1-dev-ac23d311"}). | |
| * <p>This is a human-readable Besu version string. For release builds, it is typically a | |
| * semantic version such as {@code "25.3.0"}. |
| @Test | ||
| void getBesuVersion_returnsNonEmptyString() { | ||
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | ||
| String version = config.getBesuVersion(); | ||
| assertThat(version).isNotNull(); | ||
| assertThat(version).isNotEmpty(); | ||
| } | ||
|
|
||
| @Test | ||
| void getBesuCommitHash_returnsNonEmptyString() { | ||
| BesuConfigurationImpl config = new BesuConfigurationImpl(); | ||
| String commit = config.getBesuCommitHash(); | ||
| assertThat(commit).isNotNull(); | ||
| assertThat(commit).isNotEmpty(); | ||
| } |
There was a problem hiding this comment.
These tests only assert non-null/non-empty, which doesn’t verify the intended contract that BesuConfigurationImpl delegates to BesuVersionUtils. To better protect against regressions (e.g., accidental hard-coding or wiring to the wrong source), assert equality with BesuVersionUtils.shortVersion() / BesuVersionUtils.commit() and consider using isNotBlank() if whitespace-only values would be undesirable.
Loosen the Javadoc contract for getBesuVersion and getBesuCommitHash to match actual runtime behaviour and update tests to verify delegation to BesuVersionUtils. Refresh the plugin-api knownHash accordingly.
62ce573 to
e9f1f79
Compare
Summary
getBesuVersion()andgetBesuCommitHash()toBesuConfigurationBesuConfigurationImpldelegates toBesuVersionUtilsMotivation
While
PluginVerifierprovides build-time version compatibility checking via the artifact catalog, plugins have no way to access the node version at runtime through the plugin API. This limits plugins from performing their own compatibility checks, including version info in diagnostics, or adapting behavior to node capabilities.The only indirect workaround is calling
RpcEndpointService.call("web3_clientVersion", ...), but this has significant limitations:call()throws if invoked beforebeforeExternalServices()completesWEB3RPC namespace to be enabledFixes #10226
Changes
getBesuVersion()toBesuConfiguration-- returnsBesuVersionUtils.shortVersion()getBesuCommitHash()toBesuConfiguration-- returnsBesuVersionUtils.commit()checkAPIChangesknown hashBesuConfigurationImplTestwith tests for both methodsTest plan
BesuConfigurationImplTestverifies non-null, non-empty return valuescheckAPIChangeshash updated./gradlew :plugin-api:buildpasses./gradlew :app:testpasses