Skip to content

Commit 8eb98de

Browse files
antonydenyershemnon
andcommitted
Initial PoC for RPC end points via the plugin mechanism.
re-implementation of PegaSysEng/pantheon#1909 Signed-off-by: Antony Denyer <git@antonydenyer.co.uk> Co-authored-by: Danno Ferrin <danno.ferrin@gmail.com>
1 parent a2fd214 commit 8eb98de

File tree

14 files changed

+400
-8
lines changed

14 files changed

+400
-8
lines changed

acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.hyperledger.besu.services.BesuPluginContextImpl;
4949
import org.hyperledger.besu.services.PermissioningServiceImpl;
5050
import org.hyperledger.besu.services.PicoCLIOptionsImpl;
51+
import org.hyperledger.besu.services.RpcEndpointServiceImpl;
5152
import org.hyperledger.besu.services.SecurityModuleServiceImpl;
5253
import org.hyperledger.besu.services.StorageServiceImpl;
5354

@@ -209,6 +210,7 @@ public void startNode(final BesuNode node) {
209210
.autoLogBloomCaching(false)
210211
.storageProvider(storageProvider)
211212
.forkIdSupplier(() -> besuController.getProtocolManager().getForkIdAsBytesList())
213+
.rpcEndpointService(new RpcEndpointServiceImpl())
212214
.build();
213215

214216
runner.start();

acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ public BesuNode createPluginsNode(
255255
return create(
256256
new BesuNodeConfigurationBuilder()
257257
.name(name)
258+
.jsonRpcConfiguration(node.createJsonRpcWithIbft2AdminEnabledConfig())
259+
.webSocketConfiguration(node.createWebSocketEnabledConfig())
258260
.plugins(plugins)
259261
.extraCLIOptions(extraCLIOptions)
260262
.build());
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright ConsenSys AG.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.plugins;
16+
17+
import static com.google.common.base.Preconditions.checkArgument;
18+
19+
import org.hyperledger.besu.plugin.BesuContext;
20+
import org.hyperledger.besu.plugin.BesuPlugin;
21+
import org.hyperledger.besu.plugin.services.RpcEndpointService;
22+
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest;
23+
24+
import java.util.concurrent.atomic.AtomicReference;
25+
26+
import com.google.auto.service.AutoService;
27+
28+
@AutoService(BesuPlugin.class)
29+
public class TestRpcEndpointServicePlugin implements BesuPlugin {
30+
31+
static class Bean {
32+
final String value;
33+
34+
Bean(final String value) {
35+
this.value = value;
36+
}
37+
38+
public String getValue() {
39+
return value;
40+
}
41+
}
42+
43+
private final AtomicReference<String> storage = new AtomicReference<>("InitialValue");
44+
45+
private String replaceValue(final PluginRpcRequest request) {
46+
checkArgument(request.getParams().length == 1, "Only one parameter accepted");
47+
return storage.getAndSet(request.getParams()[0].toString());
48+
}
49+
50+
private String[] replaceValueArray(final PluginRpcRequest request) {
51+
return new String[] {replaceValue(request)};
52+
}
53+
54+
private Bean replaceValueBean(final PluginRpcRequest request) {
55+
return new Bean(replaceValue(request));
56+
}
57+
58+
@Override
59+
public void register(final BesuContext context) {
60+
context
61+
.getService(RpcEndpointService.class)
62+
.ifPresent(
63+
rpcEndpointService -> {
64+
rpcEndpointService.registerRPCEndpoint(
65+
"unitTests", "replaceValue", this::replaceValue);
66+
rpcEndpointService.registerRPCEndpoint(
67+
"unitTests", "replaceValueArray", this::replaceValueArray);
68+
rpcEndpointService.registerRPCEndpoint(
69+
"unitTests", "replaceValueBean", this::replaceValueBean);
70+
});
71+
}
72+
73+
@Override
74+
public void start() {}
75+
76+
@Override
77+
public void stop() {}
78+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright ConsenSys AG.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.tests.acceptance.plugins;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
import org.hyperledger.besu.config.JsonUtil;
20+
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
21+
import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode;
22+
23+
import java.io.IOException;
24+
import java.util.Collections;
25+
26+
import com.fasterxml.jackson.databind.node.ObjectNode;
27+
import okhttp3.MediaType;
28+
import okhttp3.OkHttpClient;
29+
import okhttp3.Request;
30+
import okhttp3.RequestBody;
31+
import org.junit.Before;
32+
import org.junit.Test;
33+
34+
public class RpcEndpointServicePluginTest extends AcceptanceTestBase {
35+
36+
private BesuNode node;
37+
38+
private OkHttpClient client;
39+
protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
40+
41+
@Before
42+
public void setUp() throws Exception {
43+
node =
44+
besu.createPluginsNode(
45+
"node1", Collections.singletonList("testPlugins"), Collections.emptyList());
46+
cluster.start(node);
47+
client = new OkHttpClient();
48+
}
49+
50+
@Test
51+
public void rpcWorking() throws IOException {
52+
final String firstCall = "FirstCall";
53+
final String secondCall = "SecondCall";
54+
final String thirdCall = "ThirdCall";
55+
56+
ObjectNode resultJson = callTestMethod("unitTests_replaceValue", firstCall);
57+
assertThat(resultJson.get("result").asText()).isEqualTo("InitialValue");
58+
59+
resultJson = callTestMethod("unitTests_replaceValueArray", secondCall);
60+
assertThat(resultJson.get("result").get(0).asText()).isEqualTo(firstCall);
61+
62+
resultJson = callTestMethod("unitTests_replaceValueBean", thirdCall);
63+
assertThat(resultJson.get("result").get("value").asText()).isEqualTo(secondCall);
64+
}
65+
66+
@Test
67+
public void throwsError() throws IOException {
68+
ObjectNode resultJson = callTestMethod("unitTests_replaceValue", null);
69+
assertThat(resultJson.get("error").get("message").asText()).isEqualTo("Internal error");
70+
}
71+
72+
private ObjectNode callTestMethod(final String method, final String value) throws IOException {
73+
final String resultString =
74+
client
75+
.newCall(
76+
new Request.Builder()
77+
.post(
78+
RequestBody.create(
79+
"{\"jsonrpc\":\"2.0\",\"method\":\""
80+
+ method
81+
+ "\",\"params\":["
82+
+ "\""
83+
+ value
84+
+ "\""
85+
+ "],\"id\":33}",
86+
JSON))
87+
.url(
88+
"http://"
89+
+ node.getHostName()
90+
+ ":"
91+
+ node.getJsonRpcSocketPort().get()
92+
+ "/")
93+
.build())
94+
.execute()
95+
.body()
96+
.string();
97+
return JsonUtil.objectNodeFromString(resultString);
98+
}
99+
}

besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
import org.hyperledger.besu.plugin.data.EnodeURL;
110110
import org.hyperledger.besu.services.BesuPluginContextImpl;
111111
import org.hyperledger.besu.services.PermissioningServiceImpl;
112+
import org.hyperledger.besu.services.RpcEndpointServiceImpl;
112113
import org.hyperledger.besu.util.NetworkUtility;
113114

114115
import java.io.IOException;
@@ -175,6 +176,7 @@ public class RunnerBuilder {
175176
private boolean randomPeerPriority;
176177
private StorageProvider storageProvider;
177178
private Supplier<List<Bytes>> forkIdSupplier;
179+
private RpcEndpointServiceImpl rpcEndpointServiceImpl;
178180

179181
public RunnerBuilder vertx(final Vertx vertx) {
180182
this.vertx = vertx;
@@ -368,6 +370,11 @@ public RunnerBuilder forkIdSupplier(final Supplier<List<Bytes>> forkIdSupplier)
368370
return this;
369371
}
370372

373+
public RunnerBuilder rpcEndpointService(final RpcEndpointServiceImpl rpcEndpointService) {
374+
this.rpcEndpointServiceImpl = rpcEndpointService;
375+
return this;
376+
}
377+
371378
public Runner build() {
372379

373380
Preconditions.checkNotNull(besuController);
@@ -567,7 +574,8 @@ public Runner build() {
567574
metricsConfiguration,
568575
natService,
569576
besuPluginContext.getNamedPlugins(),
570-
dataDir);
577+
dataDir,
578+
rpcEndpointServiceImpl);
571579
jsonRpcHttpService =
572580
Optional.of(
573581
new JsonRpcHttpService(
@@ -634,7 +642,8 @@ public Runner build() {
634642
metricsConfiguration,
635643
natService,
636644
besuPluginContext.getNamedPlugins(),
637-
dataDir);
645+
dataDir,
646+
rpcEndpointServiceImpl);
638647

639648
final SubscriptionManager subscriptionManager =
640649
createSubscriptionManager(vertx, transactionPool, blockchainQueries);
@@ -826,7 +835,8 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
826835
final MetricsConfiguration metricsConfiguration,
827836
final NatService natService,
828837
final Map<String, BesuPlugin> namedPlugins,
829-
final Path dataDir) {
838+
final Path dataDir,
839+
final RpcEndpointServiceImpl rpcEndpointServiceImpl) {
830840
final Map<String, JsonRpcMethod> methods =
831841
new JsonRpcMethodsFactory()
832842
.methods(
@@ -854,6 +864,7 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
854864
dataDir,
855865
besuController.getProtocolManager().ethContext().getEthPeers());
856866
methods.putAll(besuController.getAdditionalJsonRpcMethods(jsonRpcApis));
867+
methods.putAll(rpcEndpointServiceImpl.getPluginMethods());
857868
return methods;
858869
}
859870

besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
import org.hyperledger.besu.plugin.services.PermissioningService;
151151
import org.hyperledger.besu.plugin.services.PicoCLIOptions;
152152
import org.hyperledger.besu.plugin.services.PrivacyPluginService;
153+
import org.hyperledger.besu.plugin.services.RpcEndpointService;
153154
import org.hyperledger.besu.plugin.services.SecurityModuleService;
154155
import org.hyperledger.besu.plugin.services.StorageService;
155156
import org.hyperledger.besu.plugin.services.exception.StorageException;
@@ -163,6 +164,7 @@
163164
import org.hyperledger.besu.services.PermissioningServiceImpl;
164165
import org.hyperledger.besu.services.PicoCLIOptionsImpl;
165166
import org.hyperledger.besu.services.PrivacyPluginServiceImpl;
167+
import org.hyperledger.besu.services.RpcEndpointServiceImpl;
166168
import org.hyperledger.besu.services.SecurityModuleServiceImpl;
167169
import org.hyperledger.besu.services.StorageServiceImpl;
168170
import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin;
@@ -276,6 +278,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
276278
private final SecurityModuleServiceImpl securityModuleService;
277279
private final PermissioningServiceImpl permissioningService;
278280
private final PrivacyPluginServiceImpl privacyPluginPluginService;
281+
private final RpcEndpointServiceImpl rpcEndpointServiceImpl;
279282

280283
private final Map<String, String> environment;
281284
private final MetricCategoryRegistryImpl metricCategoryRegistry =
@@ -1143,7 +1146,8 @@ public BesuCommand(
11431146
new SecurityModuleServiceImpl(),
11441147
new PermissioningServiceImpl(),
11451148
new PrivacyPluginServiceImpl(),
1146-
new PkiBlockCreationConfigurationProvider());
1149+
new PkiBlockCreationConfigurationProvider(),
1150+
new RpcEndpointServiceImpl());
11471151
}
11481152

11491153
@VisibleForTesting
@@ -1160,7 +1164,8 @@ protected BesuCommand(
11601164
final SecurityModuleServiceImpl securityModuleService,
11611165
final PermissioningServiceImpl permissioningService,
11621166
final PrivacyPluginServiceImpl privacyPluginPluginService,
1163-
final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider) {
1167+
final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider,
1168+
final RpcEndpointServiceImpl rpcEndpointServiceImpl) {
11641169
this.logger = logger;
11651170
this.rlpBlockImporter = rlpBlockImporter;
11661171
this.rlpBlockExporterFactory = rlpBlockExporterFactory;
@@ -1176,6 +1181,7 @@ protected BesuCommand(
11761181
pluginCommonConfiguration = new BesuCommandConfigurationService();
11771182
besuPluginContext.addService(BesuConfiguration.class, pluginCommonConfiguration);
11781183
this.pkiBlockCreationConfigProvider = pkiBlockCreationConfigProvider;
1184+
this.rpcEndpointServiceImpl = rpcEndpointServiceImpl;
11791185
}
11801186

11811187
public void parse(
@@ -1308,6 +1314,7 @@ private void preparePlugins() {
13081314
besuPluginContext.addService(MetricCategoryRegistry.class, metricCategoryRegistry);
13091315
besuPluginContext.addService(PermissioningService.class, permissioningService);
13101316
besuPluginContext.addService(PrivacyPluginService.class, privacyPluginPluginService);
1317+
besuPluginContext.addService(RpcEndpointService.class, rpcEndpointServiceImpl);
13111318

13121319
// register built-in plugins
13131320
new RocksDBPlugin().register(besuPluginContext);
@@ -2375,6 +2382,7 @@ private void synchronize(
23752382
.ethstatsContact(ethstatsOptions.getEthstatsContact())
23762383
.storageProvider(keyValueStorageProvider(keyValueStorageName))
23772384
.forkIdSupplier(() -> besuController.getProtocolManager().getForkIdAsBytesList())
2385+
.rpcEndpointService(rpcEndpointServiceImpl)
23782386
.build();
23792387

23802388
addShutdownHook(runner);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright ConsenSys AG.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.services;
16+
17+
import static com.google.common.base.Preconditions.checkArgument;
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
20+
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
21+
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginJsonRpcMethod;
22+
import org.hyperledger.besu.plugin.services.RpcEndpointService;
23+
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest;
24+
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.function.Function;
28+
import java.util.stream.Collectors;
29+
30+
public class RpcEndpointServiceImpl implements RpcEndpointService {
31+
private final Map<String, Function<PluginRpcRequest, ?>> rpcMethods = new HashMap<>();
32+
33+
@Override
34+
public <T> void registerRPCEndpoint(
35+
final String namespace,
36+
final String functionName,
37+
final Function<PluginRpcRequest, T> function) {
38+
checkArgument(namespace.matches("\\p{Alnum}+"), "Namespace must be only alpha numeric");
39+
checkArgument(functionName.matches("\\p{Alnum}+"), "Function Name must be only alpha numeric");
40+
checkNotNull(function);
41+
42+
rpcMethods.put(namespace + "_" + functionName, function);
43+
}
44+
45+
public Map<String, ? extends JsonRpcMethod> getPluginMethods() {
46+
return rpcMethods.entrySet().stream()
47+
.map(entry -> new PluginJsonRpcMethod(entry.getKey(), entry.getValue()))
48+
.collect(Collectors.toMap(PluginJsonRpcMethod::getName, e -> e));
49+
}
50+
}

0 commit comments

Comments
 (0)