Skip to content

Commit

Permalink
Allow Besu to host RPC endpoints via a plugin. (hyperledger#2754)
Browse files Browse the repository at this point in the history
This is a re-implementation of the initial POC done in PegaSysEng/pantheon#1909 by Danno Ferrin <[email protected]>

* Only enable plugin rpc api when enabled on --rpc-http-api or --rpc-ws-apis
* Only allow new rpc endpoints to be defined

Signed-off-by: Antony Denyer <[email protected]>
  • Loading branch information
antonydenyer authored and eum602 committed Nov 3, 2023
1 parent 5514ed5 commit 38ac15a
Show file tree
Hide file tree
Showing 64 changed files with 635 additions and 422 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
### Bug Fixes

### Early Access Features

- Enable plugins to expose custom JSON-RPC / WebSocket methods [#1317](https://github.com/hyperledger/besu/issues/1317)

## 21.10.0-RC1
### Additions and Improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
import static java.nio.charset.StandardCharsets.UTF_8;

import org.hyperledger.besu.cli.options.unstable.NetworkingOptions;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration;
import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
Expand Down Expand Up @@ -447,8 +445,8 @@ private void createStaticNodes(final BesuNode node) {
StaticNodesUtils.createStaticNodesFile(node.homeDirectory(), node.getStaticNodes());
}

private String apiList(final Collection<RpcApi> rpcApis) {
return rpcApis.stream().map(RpcApis::getValue).collect(Collectors.joining(","));
private String apiList(final Collection<String> rpcApis) {
return String.join(",", rpcApis);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.services.PermissioningServiceImpl;
import org.hyperledger.besu.services.PicoCLIOptionsImpl;
import org.hyperledger.besu.services.RpcEndpointServiceImpl;
import org.hyperledger.besu.services.SecurityModuleServiceImpl;
import org.hyperledger.besu.services.StorageServiceImpl;

Expand Down Expand Up @@ -211,6 +212,7 @@ public void startNode(final BesuNode node) {
.autoLogBloomCaching(false)
.storageProvider(storageProvider)
.forkIdSupplier(() -> besuController.getProtocolManager().getForkIdAsBytesList())
.rpcEndpointService(new RpcEndpointServiceImpl())
.build();

runner.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,13 @@ public BesuNodeConfigurationBuilder miningEnabled() {
public BesuNodeConfigurationBuilder miningEnabled(final boolean enabled) {
this.miningParameters =
new MiningParameters.Builder().enabled(enabled).coinbase(AddressHelpers.ofValue(1)).build();
this.jsonRpcConfiguration.addRpcApi(RpcApis.MINER);
this.jsonRpcConfiguration.addRpcApi(RpcApis.MINER.name());
return this;
}

public BesuNodeConfigurationBuilder miningConfiguration(final MiningParameters miningParameters) {
this.miningParameters = miningParameters;
this.jsonRpcConfiguration.addRpcApi(RpcApis.MINER);
this.jsonRpcConfiguration.addRpcApi(RpcApis.MINER.name());
return this;
}

Expand Down Expand Up @@ -143,18 +143,18 @@ public BesuNodeConfigurationBuilder metricsEnabled() {
}

public BesuNodeConfigurationBuilder enablePrivateTransactions() {
this.jsonRpcConfiguration.addRpcApi(RpcApis.EEA);
this.jsonRpcConfiguration.addRpcApi(RpcApis.PRIV);
this.jsonRpcConfiguration.addRpcApi(RpcApis.EEA.name());
this.jsonRpcConfiguration.addRpcApi(RpcApis.PRIV.name());
return this;
}

public BesuNodeConfigurationBuilder jsonRpcTxPool() {
this.jsonRpcConfiguration.addRpcApi(RpcApis.TX_POOL);
this.jsonRpcConfiguration.addRpcApi(RpcApis.TXPOOL.name());
return this;
}

public BesuNodeConfigurationBuilder jsonRpcAdmin() {
this.jsonRpcConfiguration.addRpcApi(RpcApis.ADMIN);
this.jsonRpcConfiguration.addRpcApi(RpcApis.ADMIN.name());
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.enclave.EnclaveFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration;
import org.hyperledger.besu.ethereum.core.AddressHelpers;
import org.hyperledger.besu.ethereum.core.InMemoryPrivacyStorageProvider;
Expand Down Expand Up @@ -255,12 +254,14 @@ public BesuNode createPluginsNode(
return create(
new BesuNodeConfigurationBuilder()
.name(name)
.jsonRpcConfiguration(node.createJsonRpcWithIbft2AdminEnabledConfig())
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.plugins(plugins)
.extraCLIOptions(extraCLIOptions)
.build());
}

public BesuNode createArchiveNodeWithRpcApis(final String name, final RpcApi... enabledRpcApis)
public BesuNode createArchiveNodeWithRpcApis(final String name, final String... enabledRpcApis)
throws IOException {
final JsonRpcConfiguration jsonRpcConfig = node.createJsonRpcEnabledConfig();
jsonRpcConfig.setRpcApis(asList(enabledRpcApis));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@

import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.hyperledger.besu.consensus.clique.jsonrpc.CliqueRpcApis.CLIQUE;
import static org.hyperledger.besu.consensus.ibft.jsonrpc.IbftRpcApis.IBFT;
import static org.hyperledger.besu.consensus.qbft.jsonrpc.QbftRpcApis.QBFT;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ADMIN;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.CLIQUE;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.IBFT;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.MINER;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.QBFT;

import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration;
import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider;
Expand All @@ -46,23 +45,23 @@ public Optional<String> createGenesisConfigForValidators(
}

public JsonRpcConfiguration createJsonRpcWithCliqueEnabledConfig() {
return createJsonRpcWithRpcApiEnabledConfig(CLIQUE);
return createJsonRpcWithRpcApiEnabledConfig(CLIQUE.name());
}

public JsonRpcConfiguration createJsonRpcWithIbft2EnabledConfig(final boolean minerEnabled) {
return minerEnabled
? createJsonRpcWithRpcApiEnabledConfig(IBFT, MINER)
: createJsonRpcWithRpcApiEnabledConfig(IBFT);
? createJsonRpcWithRpcApiEnabledConfig(IBFT.name(), MINER.name())
: createJsonRpcWithRpcApiEnabledConfig(IBFT.name());
}

public JsonRpcConfiguration createJsonRpcWithIbft2AdminEnabledConfig() {
return createJsonRpcWithRpcApiEnabledConfig(IBFT, ADMIN);
return createJsonRpcWithRpcApiEnabledConfig(IBFT.name(), ADMIN.name());
}

public JsonRpcConfiguration createJsonRpcWithQbftEnabledConfig(final boolean minerEnabled) {
return minerEnabled
? createJsonRpcWithRpcApiEnabledConfig(QBFT, MINER)
: createJsonRpcWithRpcApiEnabledConfig(QBFT);
? createJsonRpcWithRpcApiEnabledConfig(QBFT.name(), MINER.name())
: createJsonRpcWithRpcApiEnabledConfig(QBFT.name());
}

public JsonRpcConfiguration createJsonRpcEnabledConfig() {
Expand All @@ -81,12 +80,12 @@ public WebSocketConfiguration createWebSocketEnabledConfig() {
}

public JsonRpcConfiguration jsonRpcConfigWithAdmin() {
return createJsonRpcWithRpcApiEnabledConfig(ADMIN);
return createJsonRpcWithRpcApiEnabledConfig(ADMIN.name());
}

public JsonRpcConfiguration createJsonRpcWithRpcApiEnabledConfig(final RpcApi... rpcApi) {
public JsonRpcConfiguration createJsonRpcWithRpcApiEnabledConfig(final String... rpcApi) {
final JsonRpcConfiguration jsonRpcConfig = createJsonRpcEnabledConfig();
final List<RpcApi> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
final List<String> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
rpcApis.addAll(Arrays.asList(rpcApi));
jsonRpcConfig.setRpcApis(rpcApis);
return jsonRpcConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.permissioning.AllowlistPersistor;
Expand Down Expand Up @@ -297,9 +296,9 @@ private JsonRpcConfiguration jsonRpcConfigWithPermApiEnabled() {
jsonRpcConfig.setPort(0);
jsonRpcConfig.setHostsAllowlist(singletonList("*"));
jsonRpcConfig.setCorsAllowedDomains(singletonList("*"));
final List<RpcApi> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
rpcApis.add(RpcApis.PERM);
rpcApis.add(RpcApis.ADMIN);
final List<String> rpcApis = new ArrayList<>(jsonRpcConfig.getRpcApis());
rpcApis.add(RpcApis.PERM.name());
rpcApis.add(RpcApis.ADMIN.name());
jsonRpcConfig.setRpcApis(rpcApis);
return jsonRpcConfig;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ public void register(final BesuContext context) {

context
.getService(PicoCLIOptions.class)
.ifPresent(
picoCLIOptions -> picoCLIOptions.addPicoCLIOptions("test", TestPicoCLIPlugin.this));
.ifPresent(picoCLIOptions -> picoCLIOptions.addPicoCLIOptions("test", this));

callbackDir = new File(System.getProperty("besu.plugins.dir", "plugins"));
writeSignal("registered");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.plugins;

import static com.google.common.base.Preconditions.checkArgument;

import org.hyperledger.besu.plugin.BesuContext;
import org.hyperledger.besu.plugin.BesuPlugin;
import org.hyperledger.besu.plugin.services.RpcEndpointService;
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest;

import java.util.concurrent.atomic.AtomicReference;

import com.google.auto.service.AutoService;

@AutoService(BesuPlugin.class)
public class TestRpcEndpointServicePlugin implements BesuPlugin {

private final AtomicReference<String> stringStorage = new AtomicReference<>("InitialValue");
private final AtomicReference<Object[]> arrayStorage = new AtomicReference<>();

private String setValue(final PluginRpcRequest request) {
checkArgument(request.getParams().length == 1, "Only one parameter accepted");
return stringStorage.updateAndGet(x -> request.getParams()[0].toString());
}

private String getValue(final PluginRpcRequest request) {
return stringStorage.get();
}

private Object[] replaceValueList(final PluginRpcRequest request) {
return arrayStorage.updateAndGet(x -> request.getParams());
}

private String throwException(final PluginRpcRequest request) {
throw new RuntimeException("Kaboom");
}

@Override
public void register(final BesuContext context) {
context
.getService(RpcEndpointService.class)
.ifPresent(
rpcEndpointService -> {
rpcEndpointService.registerRPCEndpoint("tests", "getValue", this::getValue);
rpcEndpointService.registerRPCEndpoint("tests", "setValue", this::setValue);
rpcEndpointService.registerRPCEndpoint(
"tests", "replaceValueList", this::replaceValueList);
rpcEndpointService.registerRPCEndpoint(
"tests", "throwException", this::throwException);
rpcEndpointService.registerRPCEndpoint("notEnabled", "getValue", this::getValue);
});
}

@Override
public void start() {}

@Override
public void stop() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class RpcApisTogglesAcceptanceTest extends AcceptanceTestBase {
public void before() throws Exception {
rpcEnabledNode = besu.createArchiveNode("rpc-enabled");
rpcDisabledNode = besu.createArchiveNodeWithRpcDisabled("rpc-disabled");
ethApiDisabledNode = besu.createArchiveNodeWithRpcApis("eth-api-disabled", RpcApis.NET);
ethApiDisabledNode = besu.createArchiveNodeWithRpcApis("eth-api-disabled", RpcApis.NET.name());
cluster.start(rpcEnabledNode, rpcDisabledNode, ethApiDisabledNode);
}

Expand Down
Loading

0 comments on commit 38ac15a

Please sign in to comment.