Skip to content
This repository was archived by the owner on Sep 26, 2019. It is now read-only.

Commit af6b45c

Browse files
committed
Initial PoC for RPC end points via the plugin mechanism.
1 parent 61eeab1 commit af6b45c

File tree

14 files changed

+478
-13
lines changed

14 files changed

+478
-13
lines changed

acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public Optional<Integer> getJsonRpcWebSocketPort() {
247247
}
248248

249249
public Optional<Integer> getJsonRpcSocketPort() {
250-
if (isWebSocketsRpcEnabled()) {
250+
if (isJsonRpcEnabled()) {
251251
return Optional.of(Integer.valueOf(portsProperties.getProperty("json-rpc")));
252252
} else {
253253
return Optional.empty();

acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/ThreadPantheonNodeRunner.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem;
3131
import tech.pegasys.pantheon.plugin.services.PantheonEvents;
3232
import tech.pegasys.pantheon.plugin.services.PicoCLIOptions;
33+
import tech.pegasys.pantheon.plugin.services.RpcEndpointService;
3334
import tech.pegasys.pantheon.services.PantheonEventsImpl;
3435
import tech.pegasys.pantheon.services.PantheonPluginContextImpl;
3536
import tech.pegasys.pantheon.services.PicoCLIOptionsImpl;
37+
import tech.pegasys.pantheon.services.RPCEndpointServiceImpl;
3638
import tech.pegasys.pantheon.services.kvstore.RocksDbConfiguration;
3739

3840
import java.io.File;
@@ -72,6 +74,9 @@ private PantheonPluginContextImpl buildPluginContext(final PantheonNode node) {
7274
pluginsDirFile.deleteOnExit();
7375
}
7476
System.setProperty("pantheon.plugins.dir", pluginsPath.toString());
77+
final RPCEndpointServiceImpl rpcEndpointService = new RPCEndpointServiceImpl();
78+
node.jsonRpcConfiguration().setPluginEndpoints(rpcEndpointService.getRpcMethods());
79+
pantheonPluginContext.addService(RpcEndpointService.class, rpcEndpointService);
7580
pantheonPluginContext.registerPlugins(pluginsPath);
7681
return pantheonPluginContext;
7782
}

acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/configuration/PantheonNodeFactory.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,23 @@ public PantheonNode createArchiveNodeWithRpcDisabled(final String name) throws I
165165
}
166166

167167
public PantheonNode createPluginsNode(
168-
final String name, final List<String> plugins, final List<String> extraCLIOptions)
168+
final String name,
169+
final List<String> plugins,
170+
final List<String> extraCLIOptions,
171+
final RpcApi... enabledRpcApis)
169172
throws IOException {
170-
return create(
173+
final PantheonNodeConfigurationBuilder pantheonNodeConfigurationBuilder =
171174
new PantheonNodeConfigurationBuilder()
172175
.name(name)
173176
.plugins(plugins)
174-
.extraCLIOptions(extraCLIOptions)
175-
.build());
177+
.extraCLIOptions(extraCLIOptions);
178+
if (enabledRpcApis.length > 0) {
179+
final JsonRpcConfiguration jsonRpcConfig = node.createJsonRpcEnabledConfig();
180+
jsonRpcConfig.setRpcApis(asList(enabledRpcApis));
181+
pantheonNodeConfigurationBuilder.jsonRpcConfiguration(jsonRpcConfig);
182+
}
183+
184+
return create(pantheonNodeConfigurationBuilder.build());
176185
}
177186

178187
public PantheonNode createArchiveNodeWithRpcApis(
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2019 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+
package tech.pegasys.pantheon.tests.acceptance.plugins;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
17+
import tech.pegasys.pantheon.config.JsonUtil;
18+
import tech.pegasys.pantheon.ethereum.jsonrpc.RpcApis;
19+
import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase;
20+
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode;
21+
22+
import java.io.IOException;
23+
import java.util.Collections;
24+
25+
import com.fasterxml.jackson.databind.node.ObjectNode;
26+
import okhttp3.MediaType;
27+
import okhttp3.OkHttpClient;
28+
import okhttp3.Request;
29+
import okhttp3.RequestBody;
30+
import org.junit.Before;
31+
import org.junit.Test;
32+
33+
public class RpcEndpointPluginTest extends AcceptanceTestBase {
34+
35+
private PantheonNode node;
36+
private OkHttpClient client;
37+
protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
38+
39+
@Before
40+
public void setUp() throws Exception {
41+
node =
42+
pantheon.createPluginsNode(
43+
"node1",
44+
Collections.singletonList("testPlugins"),
45+
Collections.emptyList(),
46+
RpcApis.NET);
47+
cluster.start(node);
48+
client = new OkHttpClient();
49+
}
50+
51+
@Test
52+
public void rpcWorking() throws IOException {
53+
final String firstCall = "FirstCall";
54+
final String secondCall = "SecondCall";
55+
final String thirdCall = "ThirdCall";
56+
final String fourthCall = "FourthCall";
57+
58+
ObjectNode resultJson = callTestMethod("unitTests_replaceValue", firstCall);
59+
assertThat(resultJson.get("result").asText()).isEqualTo("InitialValue");
60+
61+
resultJson = callTestMethod("unitTests_replaceValueArray", secondCall);
62+
assertThat(resultJson.get("result").get(0).asText()).isEqualTo(firstCall);
63+
64+
resultJson = callTestMethod("unitTests_replaceValueBean", thirdCall);
65+
assertThat(resultJson.get("result").get("value").asText()).isEqualTo(secondCall);
66+
67+
resultJson = callTestMethod("unitTests_replaceValueLength", fourthCall);
68+
assertThat(resultJson.get("result").asInt()).isEqualTo(thirdCall.length());
69+
}
70+
71+
@Test
72+
public void throwsError() throws IOException {
73+
ObjectNode resultJson = callTestMethod("unitTests_replaceValue", null);
74+
assertThat(resultJson.get("result").asText()).isEqualTo("InitialValue");
75+
76+
resultJson = callTestMethod("unitTests_replaceValueLength", "InitialValue");
77+
assertThat(resultJson.get("error").get("message").asText()).isEqualTo("Internal error");
78+
}
79+
80+
private ObjectNode callTestMethod(final String method, final String value) throws IOException {
81+
final String resultString =
82+
client
83+
.newCall(
84+
new Request.Builder()
85+
.post(
86+
RequestBody.create(
87+
JSON,
88+
"{\"jsonrpc\":\"2.0\",\"method\":\""
89+
+ method
90+
+ "\",\"params\":["
91+
+ (value == null ? value : "\"" + value + "\"")
92+
+ "],\"id\":33}"))
93+
.url(
94+
"http://"
95+
+ node.getHostName()
96+
+ ":"
97+
+ node.getJsonRpcSocketPort().get()
98+
+ "/")
99+
.build())
100+
.execute()
101+
.body()
102+
.string();
103+
System.out.println(resultString);
104+
return JsonUtil.objectNodeFromString(resultString);
105+
}
106+
}

ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcConfiguration.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import java.util.Collection;
1818
import java.util.Collections;
1919
import java.util.List;
20+
import java.util.Map;
2021
import java.util.Objects;
22+
import java.util.function.Function;
2123

2224
import com.google.common.base.MoreObjects;
2325

@@ -33,6 +35,7 @@ public class JsonRpcConfiguration {
3335
private List<String> hostsWhitelist = Arrays.asList("localhost", "127.0.0.1");
3436
private boolean authenticationEnabled = false;
3537
private String authenticationCredentialsFile;
38+
private Map<String, Function<List<String>, ?>> pluginEndpoints = Collections.emptyMap();
3639

3740
public static JsonRpcConfiguration createDefault() {
3841
final JsonRpcConfiguration config = new JsonRpcConfiguration();
@@ -100,6 +103,14 @@ public void setHostsWhitelist(final List<String> hostsWhitelist) {
100103
this.hostsWhitelist = hostsWhitelist;
101104
}
102105

106+
public Map<String, Function<List<String>, ?>> getPluginEndpoints() {
107+
return pluginEndpoints;
108+
}
109+
110+
public void setPluginEndpoints(final Map<String, Function<List<String>, ?>> pluginEndpoints) {
111+
this.pluginEndpoints = pluginEndpoints;
112+
}
113+
103114
@Override
104115
public String toString() {
105116
return MoreObjects.toStringHelper(this)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2019 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+
package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods;
14+
15+
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
16+
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError;
17+
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse;
18+
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse;
19+
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse;
20+
21+
import java.util.Arrays;
22+
import java.util.List;
23+
import java.util.function.Function;
24+
import java.util.stream.Collectors;
25+
26+
public class PluginJsonRpcMethod implements JsonRpcMethod {
27+
28+
private final String name;
29+
private final Function<List<String>, ?> function;
30+
31+
public PluginJsonRpcMethod(final String name, final Function<List<String>, ?> function) {
32+
this.name = name;
33+
this.function = function;
34+
}
35+
36+
@Override
37+
public String getName() {
38+
return name;
39+
}
40+
41+
@Override
42+
public JsonRpcResponse response(final JsonRpcRequest request) {
43+
try {
44+
return new JsonRpcSuccessResponse(
45+
request.getId(),
46+
function.apply(
47+
Arrays.stream(request.getParams())
48+
.map(o -> o == null ? null : o.toString())
49+
.collect(Collectors.toList())));
50+
} catch (final RuntimeException re) {
51+
return new JsonRpcErrorResponse(request.getId(), JsonRpcError.INTERNAL_ERROR);
52+
}
53+
}
54+
}

pantheon/src/main/java/tech/pegasys/pantheon/RunnerBuilder.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager;
3838
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterRepository;
3939
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod;
40+
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.PluginJsonRpcMethod;
4041
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries;
4142
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketConfiguration;
4243
import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.WebSocketRequestHandler;
@@ -92,6 +93,7 @@
9293
import java.util.Map;
9394
import java.util.Optional;
9495
import java.util.Set;
96+
import java.util.function.Function;
9597
import java.util.stream.Collectors;
9698

9799
import com.google.common.annotations.VisibleForTesting;
@@ -105,7 +107,7 @@ public class RunnerBuilder {
105107
private PantheonController<?> pantheonController;
106108

107109
private NetworkingConfiguration networkingConfiguration = NetworkingConfiguration.create();
108-
private Collection<BytesValue> bannedNodeIds = new ArrayList<>();
110+
private final Collection<BytesValue> bannedNodeIds = new ArrayList<>();
109111
private boolean p2pEnabled = true;
110112
private boolean discovery;
111113
private String p2pAdvertisedHost;
@@ -304,8 +306,8 @@ public Runner build() {
304306

305307
final Optional<UpnpNatManager> natManager = buildNatManager(natMethod);
306308

307-
NetworkBuilder inactiveNetwork = (caps) -> new NoopP2PNetwork();
308-
NetworkBuilder activeNetwork =
309+
final NetworkBuilder inactiveNetwork = (caps) -> new NoopP2PNetwork();
310+
final NetworkBuilder activeNetwork =
309311
(caps) ->
310312
DefaultP2PNetwork.builder()
311313
.vertx(vertx)
@@ -375,7 +377,8 @@ public Runner build() {
375377
privacyParameters,
376378
jsonRpcConfiguration,
377379
webSocketConfiguration,
378-
metricsConfiguration);
380+
metricsConfiguration,
381+
jsonRpcConfiguration.getPluginEndpoints());
379382
jsonRpcHttpService =
380383
Optional.of(
381384
new JsonRpcHttpService(
@@ -433,7 +436,8 @@ public Runner build() {
433436
privacyParameters,
434437
jsonRpcConfiguration,
435438
webSocketConfiguration,
436-
metricsConfiguration);
439+
metricsConfiguration,
440+
Collections.emptyMap());
437441

438442
final SubscriptionManager subscriptionManager =
439443
createSubscriptionManager(vertx, transactionPool);
@@ -510,7 +514,7 @@ private Optional<AccountPermissioningController> buildAccountPermissioningContro
510514
final TransactionSimulator transactionSimulator) {
511515

512516
if (permissioningConfiguration.isPresent()) {
513-
Optional<AccountPermissioningController> accountPermissioningController =
517+
final Optional<AccountPermissioningController> accountPermissioningController =
514518
AccountPermissioningControllerFactory.create(
515519
permissioningConfiguration.get(), transactionSimulator, metricsSystem);
516520

@@ -563,7 +567,8 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
563567
final PrivacyParameters privacyParameters,
564568
final JsonRpcConfiguration jsonRpcConfiguration,
565569
final WebSocketConfiguration webSocketConfiguration,
566-
final MetricsConfiguration metricsConfiguration) {
570+
final MetricsConfiguration metricsConfiguration,
571+
final Map<String, Function<List<String>, ?>> pluginMethods) {
567572
final Map<String, JsonRpcMethod> methods =
568573
new JsonRpcMethodsFactory()
569574
.methods(
@@ -588,6 +593,8 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
588593
webSocketConfiguration,
589594
metricsConfiguration);
590595
methods.putAll(pantheonController.getAdditionalJsonRpcMethods(jsonRpcApis));
596+
pluginMethods.forEach(
597+
(name, function) -> methods.put(name, new PluginJsonRpcMethod(name, function)));
591598
return methods;
592599
}
593600

pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,12 @@
9292
import tech.pegasys.pantheon.plugin.services.MetricsSystem;
9393
import tech.pegasys.pantheon.plugin.services.PantheonEvents;
9494
import tech.pegasys.pantheon.plugin.services.PicoCLIOptions;
95+
import tech.pegasys.pantheon.plugin.services.RpcEndpointService;
9596
import tech.pegasys.pantheon.plugin.services.metrics.MetricCategory;
9697
import tech.pegasys.pantheon.services.PantheonEventsImpl;
9798
import tech.pegasys.pantheon.services.PantheonPluginContextImpl;
9899
import tech.pegasys.pantheon.services.PicoCLIOptionsImpl;
100+
import tech.pegasys.pantheon.services.RPCEndpointServiceImpl;
99101
import tech.pegasys.pantheon.services.kvstore.RocksDbConfiguration;
100102
import tech.pegasys.pantheon.util.PermissioningConfigurationValidator;
101103
import tech.pegasys.pantheon.util.bytes.BytesValue;
@@ -171,6 +173,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable {
171173
private final RunnerBuilder runnerBuilder;
172174
private final PantheonController.Builder controllerBuilderFactory;
173175
private final PantheonPluginContextImpl pantheonPluginContext;
176+
private final RPCEndpointServiceImpl rpcEndpointService;
174177
private final Map<String, String> environment;
175178

176179
protected KeyLoader getKeyLoader() {
@@ -679,6 +682,7 @@ public PantheonCommand(
679682
this.controllerBuilderFactory = controllerBuilderFactory;
680683
this.pantheonPluginContext = pantheonPluginContext;
681684
this.environment = environment;
685+
this.rpcEndpointService = new RPCEndpointServiceImpl();
682686
}
683687

684688
private StandaloneCommand standaloneCommands;
@@ -774,6 +778,7 @@ private PantheonCommand handleUnstableOptions() {
774778
private PantheonCommand preparePlugins() {
775779
pantheonPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine));
776780
pantheonPluginContext.addService(MetricsSystem.class, getMetricsSystem());
781+
pantheonPluginContext.addService(RpcEndpointService.class, rpcEndpointService);
777782
pantheonPluginContext.registerPlugins(pluginsDir());
778783
return this;
779784
}
@@ -1002,6 +1007,7 @@ private JsonRpcConfiguration jsonRpcConfiguration() {
10021007
jsonRpcConfiguration.setHostsWhitelist(hostsWhitelist);
10031008
jsonRpcConfiguration.setAuthenticationEnabled(isRpcHttpAuthenticationEnabled);
10041009
jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile());
1010+
jsonRpcConfiguration.setPluginEndpoints(rpcEndpointService.getRpcMethods());
10051011
return jsonRpcConfiguration;
10061012
}
10071013

0 commit comments

Comments
 (0)