Skip to content

Commit

Permalink
Initial PoC for RPC end points via the plugin mechanism.
Browse files Browse the repository at this point in the history
re-implementation of PegaSysEng/pantheon#1909

Signed-off-by: Antony Denyer <git@antonydenyer.co.uk>
Co-authored-by: Danno Ferrin <danno.ferrin@gmail.com>
  • Loading branch information
antonydenyer and shemnon committed Sep 13, 2021
1 parent a2fd214 commit 8eb98de
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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 @@ -209,6 +210,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 @@ -255,6 +255,8 @@ public BesuNode createPluginsNode(
return create(
new BesuNodeConfigurationBuilder()
.name(name)
.jsonRpcConfiguration(node.createJsonRpcWithIbft2AdminEnabledConfig())
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.plugins(plugins)
.extraCLIOptions(extraCLIOptions)
.build());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright ConsenSys AG.
*
* 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 {

static class Bean {
final String value;

Bean(final String value) {
this.value = value;
}

public String getValue() {
return value;
}
}

private final AtomicReference<String> storage = new AtomicReference<>("InitialValue");

private String replaceValue(final PluginRpcRequest request) {
checkArgument(request.getParams().length == 1, "Only one parameter accepted");
return storage.getAndSet(request.getParams()[0].toString());
}

private String[] replaceValueArray(final PluginRpcRequest request) {
return new String[] {replaceValue(request)};
}

private Bean replaceValueBean(final PluginRpcRequest request) {
return new Bean(replaceValue(request));
}

@Override
public void register(final BesuContext context) {
context
.getService(RpcEndpointService.class)
.ifPresent(
rpcEndpointService -> {
rpcEndpointService.registerRPCEndpoint(
"unitTests", "replaceValue", this::replaceValue);
rpcEndpointService.registerRPCEndpoint(
"unitTests", "replaceValueArray", this::replaceValueArray);
rpcEndpointService.registerRPCEndpoint(
"unitTests", "replaceValueBean", this::replaceValueBean);
});
}

@Override
public void start() {}

@Override
public void stop() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright ConsenSys AG.
*
* 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.tests.acceptance.plugins;

import static org.assertj.core.api.Assertions.assertThat;

import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;

import java.io.IOException;
import java.util.Collections;

import com.fasterxml.jackson.databind.node.ObjectNode;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.junit.Before;
import org.junit.Test;

public class RpcEndpointServicePluginTest extends AcceptanceTestBase {

private BesuNode node;

private OkHttpClient client;
protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

@Before
public void setUp() throws Exception {
node =
besu.createPluginsNode(
"node1", Collections.singletonList("testPlugins"), Collections.emptyList());
cluster.start(node);
client = new OkHttpClient();
}

@Test
public void rpcWorking() throws IOException {
final String firstCall = "FirstCall";
final String secondCall = "SecondCall";
final String thirdCall = "ThirdCall";

ObjectNode resultJson = callTestMethod("unitTests_replaceValue", firstCall);
assertThat(resultJson.get("result").asText()).isEqualTo("InitialValue");

resultJson = callTestMethod("unitTests_replaceValueArray", secondCall);
assertThat(resultJson.get("result").get(0).asText()).isEqualTo(firstCall);

resultJson = callTestMethod("unitTests_replaceValueBean", thirdCall);
assertThat(resultJson.get("result").get("value").asText()).isEqualTo(secondCall);
}

@Test
public void throwsError() throws IOException {
ObjectNode resultJson = callTestMethod("unitTests_replaceValue", null);
assertThat(resultJson.get("error").get("message").asText()).isEqualTo("Internal error");
}

private ObjectNode callTestMethod(final String method, final String value) throws IOException {
final String resultString =
client
.newCall(
new Request.Builder()
.post(
RequestBody.create(
"{\"jsonrpc\":\"2.0\",\"method\":\""
+ method
+ "\",\"params\":["
+ "\""
+ value
+ "\""
+ "],\"id\":33}",
JSON))
.url(
"http://"
+ node.getHostName()
+ ":"
+ node.getJsonRpcSocketPort().get()
+ "/")
.build())
.execute()
.body()
.string();
return JsonUtil.objectNodeFromString(resultString);
}
}
17 changes: 14 additions & 3 deletions besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.services.PermissioningServiceImpl;
import org.hyperledger.besu.services.RpcEndpointServiceImpl;
import org.hyperledger.besu.util.NetworkUtility;

import java.io.IOException;
Expand Down Expand Up @@ -175,6 +176,7 @@ public class RunnerBuilder {
private boolean randomPeerPriority;
private StorageProvider storageProvider;
private Supplier<List<Bytes>> forkIdSupplier;
private RpcEndpointServiceImpl rpcEndpointServiceImpl;

public RunnerBuilder vertx(final Vertx vertx) {
this.vertx = vertx;
Expand Down Expand Up @@ -368,6 +370,11 @@ public RunnerBuilder forkIdSupplier(final Supplier<List<Bytes>> forkIdSupplier)
return this;
}

public RunnerBuilder rpcEndpointService(final RpcEndpointServiceImpl rpcEndpointService) {
this.rpcEndpointServiceImpl = rpcEndpointService;
return this;
}

public Runner build() {

Preconditions.checkNotNull(besuController);
Expand Down Expand Up @@ -567,7 +574,8 @@ public Runner build() {
metricsConfiguration,
natService,
besuPluginContext.getNamedPlugins(),
dataDir);
dataDir,
rpcEndpointServiceImpl);
jsonRpcHttpService =
Optional.of(
new JsonRpcHttpService(
Expand Down Expand Up @@ -634,7 +642,8 @@ public Runner build() {
metricsConfiguration,
natService,
besuPluginContext.getNamedPlugins(),
dataDir);
dataDir,
rpcEndpointServiceImpl);

final SubscriptionManager subscriptionManager =
createSubscriptionManager(vertx, transactionPool, blockchainQueries);
Expand Down Expand Up @@ -826,7 +835,8 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
final MetricsConfiguration metricsConfiguration,
final NatService natService,
final Map<String, BesuPlugin> namedPlugins,
final Path dataDir) {
final Path dataDir,
final RpcEndpointServiceImpl rpcEndpointServiceImpl) {
final Map<String, JsonRpcMethod> methods =
new JsonRpcMethodsFactory()
.methods(
Expand Down Expand Up @@ -854,6 +864,7 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
dataDir,
besuController.getProtocolManager().ethContext().getEthPeers());
methods.putAll(besuController.getAdditionalJsonRpcMethods(jsonRpcApis));
methods.putAll(rpcEndpointServiceImpl.getPluginMethods());
return methods;
}

Expand Down
12 changes: 10 additions & 2 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
import org.hyperledger.besu.plugin.services.PermissioningService;
import org.hyperledger.besu.plugin.services.PicoCLIOptions;
import org.hyperledger.besu.plugin.services.PrivacyPluginService;
import org.hyperledger.besu.plugin.services.RpcEndpointService;
import org.hyperledger.besu.plugin.services.SecurityModuleService;
import org.hyperledger.besu.plugin.services.StorageService;
import org.hyperledger.besu.plugin.services.exception.StorageException;
Expand All @@ -163,6 +164,7 @@
import org.hyperledger.besu.services.PermissioningServiceImpl;
import org.hyperledger.besu.services.PicoCLIOptionsImpl;
import org.hyperledger.besu.services.PrivacyPluginServiceImpl;
import org.hyperledger.besu.services.RpcEndpointServiceImpl;
import org.hyperledger.besu.services.SecurityModuleServiceImpl;
import org.hyperledger.besu.services.StorageServiceImpl;
import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin;
Expand Down Expand Up @@ -276,6 +278,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
private final SecurityModuleServiceImpl securityModuleService;
private final PermissioningServiceImpl permissioningService;
private final PrivacyPluginServiceImpl privacyPluginPluginService;
private final RpcEndpointServiceImpl rpcEndpointServiceImpl;

private final Map<String, String> environment;
private final MetricCategoryRegistryImpl metricCategoryRegistry =
Expand Down Expand Up @@ -1143,7 +1146,8 @@ public BesuCommand(
new SecurityModuleServiceImpl(),
new PermissioningServiceImpl(),
new PrivacyPluginServiceImpl(),
new PkiBlockCreationConfigurationProvider());
new PkiBlockCreationConfigurationProvider(),
new RpcEndpointServiceImpl());
}

@VisibleForTesting
Expand All @@ -1160,7 +1164,8 @@ protected BesuCommand(
final SecurityModuleServiceImpl securityModuleService,
final PermissioningServiceImpl permissioningService,
final PrivacyPluginServiceImpl privacyPluginPluginService,
final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider) {
final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider,
final RpcEndpointServiceImpl rpcEndpointServiceImpl) {
this.logger = logger;
this.rlpBlockImporter = rlpBlockImporter;
this.rlpBlockExporterFactory = rlpBlockExporterFactory;
Expand All @@ -1176,6 +1181,7 @@ protected BesuCommand(
pluginCommonConfiguration = new BesuCommandConfigurationService();
besuPluginContext.addService(BesuConfiguration.class, pluginCommonConfiguration);
this.pkiBlockCreationConfigProvider = pkiBlockCreationConfigProvider;
this.rpcEndpointServiceImpl = rpcEndpointServiceImpl;
}

public void parse(
Expand Down Expand Up @@ -1308,6 +1314,7 @@ private void preparePlugins() {
besuPluginContext.addService(MetricCategoryRegistry.class, metricCategoryRegistry);
besuPluginContext.addService(PermissioningService.class, permissioningService);
besuPluginContext.addService(PrivacyPluginService.class, privacyPluginPluginService);
besuPluginContext.addService(RpcEndpointService.class, rpcEndpointServiceImpl);

// register built-in plugins
new RocksDBPlugin().register(besuPluginContext);
Expand Down Expand Up @@ -2375,6 +2382,7 @@ private void synchronize(
.ethstatsContact(ethstatsOptions.getEthstatsContact())
.storageProvider(keyValueStorageProvider(keyValueStorageName))
.forkIdSupplier(() -> besuController.getProtocolManager().getForkIdAsBytesList())
.rpcEndpointService(rpcEndpointServiceImpl)
.build();

addShutdownHook(runner);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright ConsenSys AG.
*
* 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.services;

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

import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginJsonRpcMethod;
import org.hyperledger.besu.plugin.services.RpcEndpointService;
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class RpcEndpointServiceImpl implements RpcEndpointService {
private final Map<String, Function<PluginRpcRequest, ?>> rpcMethods = new HashMap<>();

@Override
public <T> void registerRPCEndpoint(
final String namespace,
final String functionName,
final Function<PluginRpcRequest, T> function) {
checkArgument(namespace.matches("\\p{Alnum}+"), "Namespace must be only alpha numeric");
checkArgument(functionName.matches("\\p{Alnum}+"), "Function Name must be only alpha numeric");
checkNotNull(function);

rpcMethods.put(namespace + "_" + functionName, function);
}

public Map<String, ? extends JsonRpcMethod> getPluginMethods() {
return rpcMethods.entrySet().stream()
.map(entry -> new PluginJsonRpcMethod(entry.getKey(), entry.getValue()))
.collect(Collectors.toMap(PluginJsonRpcMethod::getName, e -> e));
}
}
Loading

0 comments on commit 8eb98de

Please sign in to comment.