diff --git a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeFactory.java b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeFactory.java index f08eb88510..a78edb2ebc 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeFactory.java +++ b/acceptance-tests/src/test/java/tech/pegasys/pantheon/tests/acceptance/dsl/node/PantheonNodeFactory.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.tests.acceptance.dsl.node; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static tech.pegasys.pantheon.consensus.clique.jsonrpc.CliqueRpcApis.CLIQUE; @@ -154,6 +155,7 @@ public class PantheonNodeFactory { final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault(); config.setEnabled(true); config.setPort(0); + config.setHostsWhitelist(singletonList("*")); return config; } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcConfiguration.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcConfiguration.java index 627fab32a7..fff515bdac 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcConfiguration.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcConfiguration.java @@ -20,7 +20,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Objects; public class JsonRpcConfiguration { - public static final String DEFAULT_JSON_RPC_HOST = "127.0.0.1"; + private static final String DEFAULT_JSON_RPC_HOST = "127.0.0.1"; public static final int DEFAULT_JSON_RPC_PORT = 8545; private boolean enabled; @@ -28,6 +28,7 @@ public class JsonRpcConfiguration { private String host; private Collection corsAllowedDomains = Collections.emptyList(); private Collection rpcApis; + private Collection hostsWhitelist = Collections.singletonList("localhost"); public static JsonRpcConfiguration createDefault() { final JsonRpcConfiguration config = new JsonRpcConfiguration(); @@ -87,6 +88,14 @@ public class JsonRpcConfiguration { rpcApis.add(rpcApi); } + public Collection getHostsWhitelist() { + return Collections.unmodifiableCollection(this.hostsWhitelist); + } + + public void setHostsWhitelist(final Collection hostsWhitelist) { + this.hostsWhitelist = hostsWhitelist; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -94,6 +103,7 @@ public class JsonRpcConfiguration { .add("port", port) .add("host", host) .add("corsAllowedDomains", corsAllowedDomains) + .add("hostsWhitelist", hostsWhitelist) .add("rpcApis", rpcApis) .toString(); } @@ -111,11 +121,12 @@ public class JsonRpcConfiguration { && port == that.port && Objects.equal(host, that.host) && Objects.equal(corsAllowedDomains, that.corsAllowedDomains) + && Objects.equal(hostsWhitelist, that.hostsWhitelist) && Objects.equal(rpcApis, that.rpcApis); } @Override public int hashCode() { - return Objects.hashCode(enabled, port, host, corsAllowedDomains, rpcApis); + return Objects.hashCode(enabled, port, host, corsAllowedDomains, hostsWhitelist, rpcApis); } } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 9ff5c36aac..805fb8a518 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -13,6 +13,7 @@ package tech.pegasys.pantheon.ethereum.jsonrpc; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Streams.stream; import static java.util.stream.Collectors.toList; import static tech.pegasys.pantheon.util.NetworkUtility.urlForSocketAddress; @@ -38,13 +39,17 @@ import java.net.SocketException; import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.CompositeFuture; import io.vertx.core.Future; +import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServer; @@ -113,6 +118,10 @@ public class JsonRpcHttpService { // Handle json rpc requests final Router router = Router.router(vertx); + + // Verify Host header to avoid rebind attack. + router.route().handler(checkWhitelistHostHeader()); + router .route() .handler( @@ -161,6 +170,42 @@ public class JsonRpcHttpService { return resultFuture; } + private Handler checkWhitelistHostHeader() { + return event -> { + Optional hostHeader = getAndValidateHostHeader(event); + if (config.getHostsWhitelist().contains("*") + || (hostHeader.isPresent() && hostIsInWhitelist(hostHeader))) { + event.next(); + } else { + event + .response() + .setStatusCode(403) + .putHeader("Content-Type", "application/json; charset=utf-8") + .end("{\"message\":\"Host not authorized.\"}"); + } + }; + } + + private Optional getAndValidateHostHeader(final RoutingContext event) { + Iterable splitHostHeader = Splitter.on(':').split(event.request().host()); + long hostPieces = stream(splitHostHeader).count(); + if (hostPieces > 1) { + // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] + if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { + return Optional.empty(); + } + } + return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); + } + + private boolean hostIsInWhitelist(final Optional hostHeader) { + return config + .getHostsWhitelist() + .stream() + .anyMatch( + whitelistEntry -> whitelistEntry.toLowerCase().equals(hostHeader.get().toLowerCase())); + } + public CompletableFuture stop() { if (httpServer == null) { return CompletableFuture.completedFuture(null); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java new file mode 100644 index 0000000000..6e44557180 --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceHostWhitelistTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2018 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. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import tech.pegasys.pantheon.config.StubGenesisConfigOptions; +import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator; +import tech.pegasys.pantheon.ethereum.core.Synchronizer; +import tech.pegasys.pantheon.ethereum.core.TransactionPool; +import tech.pegasys.pantheon.ethereum.eth.EthProtocol; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.filter.FilterManager; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.JsonRpcMethod; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.queries.BlockchainQueries; +import tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; +import tech.pegasys.pantheon.ethereum.p2p.api.P2PNetwork; +import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; +import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.vertx.core.Vertx; +import io.vertx.core.json.Json; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class JsonRpcHttpServiceHostWhitelistTest { + + @ClassRule public static final TemporaryFolder folder = new TemporaryFolder(); + + protected static final Vertx vertx = Vertx.vertx(); + + private static Map rpcMethods; + private static JsonRpcHttpService service; + private static OkHttpClient client; + private static String baseUrl; + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final int CHAIN_ID = 123; + private static final Collection JSON_RPC_APIS = + Arrays.asList(RpcApis.ETH, RpcApis.NET, RpcApis.WEB3); + private final JsonRpcConfiguration jsonRpcConfig = createJsonRpcConfig(); + private final List hostsWhitelist = Arrays.asList("ally", "friend"); + + @Before + public void initServerAndClient() throws Exception { + P2PNetwork peerDiscoveryMock = mock(P2PNetwork.class); + BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); + Synchronizer synchronizer = mock(Synchronizer.class); + + final Set supportedCapabilities = new HashSet<>(); + supportedCapabilities.add(EthProtocol.ETH62); + supportedCapabilities.add(EthProtocol.ETH63); + + rpcMethods = + spy( + new JsonRpcMethodsFactory() + .methods( + CLIENT_VERSION, + peerDiscoveryMock, + blockchainQueries, + synchronizer, + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID)), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(EthHashMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + JSON_RPC_APIS)); + service = createJsonRpcHttpService(); + service.start().join(); + + client = new OkHttpClient(); + baseUrl = service.url(); + } + + private JsonRpcHttpService createJsonRpcHttpService() throws Exception { + return new JsonRpcHttpService( + vertx, folder.newFolder().toPath(), jsonRpcConfig, new NoOpMetricsSystem(), rpcMethods); + } + + private static JsonRpcConfiguration createJsonRpcConfig() { + final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault(); + config.setPort(0); + return config; + } + + @After + public void shutdownServer() { + service.stop().join(); + } + + @Test + public void requestWithDefaultHeaderAndDefaultConfigIsAccepted() throws IOException { + assertThat(doRequest("localhost:50012")).isEqualTo(200); + } + + @Test + public void requestWithEmptyHeaderAndDefaultConfigIsRejected() throws IOException { + assertThat(doRequest("")).isEqualTo(403); + } + + @Test + public void requestWithAnyHostnameAndWildcardConfigIsAccepted() throws IOException { + jsonRpcConfig.setHostsWhitelist(Collections.singletonList("*")); + assertThat(doRequest("ally")).isEqualTo(200); + assertThat(doRequest("foe")).isEqualTo(200); + } + + @Test + public void requestWithWhitelistedHostIsAccepted() throws IOException { + jsonRpcConfig.setHostsWhitelist(hostsWhitelist); + assertThat(doRequest("ally")).isEqualTo(200); + assertThat(doRequest("ally:12345")).isEqualTo(200); + assertThat(doRequest("friend")).isEqualTo(200); + } + + @Test + public void requestWithUnknownHostIsRejected() throws IOException { + jsonRpcConfig.setHostsWhitelist(hostsWhitelist); + assertThat(doRequest("foe")).isEqualTo(403); + } + + private int doRequest(final String hostname) throws IOException { + final RequestBody body = + RequestBody.create( + JSON, + "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode("123") + ",\"method\":\"net_version\"}"); + + Request build = + new Request.Builder().post(body).url(baseUrl).addHeader("Host", hostname).build(); + return client.newCall(build).execute().code(); + } + + @Test + public void requestWithMalformedHostIsRejected() throws IOException { + jsonRpcConfig.setHostsWhitelist(hostsWhitelist); + assertThat(doRequest("ally:friend")).isEqualTo(403); + assertThat(doRequest("ally:123456")).isEqualTo(403); + assertThat(doRequest("ally:friend:1234")).isEqualTo(403); + } +} diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java index 2cdbbc0653..fe42260ec5 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceRpcApisTest.java @@ -87,9 +87,8 @@ public class JsonRpcHttpServiceRpcApisTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_version\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); } } @@ -102,9 +101,8 @@ public class JsonRpcHttpServiceRpcApisTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_version\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); } } @@ -117,9 +115,8 @@ public class JsonRpcHttpServiceRpcApisTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_version\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -137,9 +134,8 @@ public class JsonRpcHttpServiceRpcApisTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_version\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); } } @@ -188,4 +184,8 @@ public class JsonRpcHttpServiceRpcApisTest { baseUrl = jsonRpcHttpService.url(); return jsonRpcHttpService; } + + private Request buildRequest(final RequestBody body) { + return new Request.Builder().post(body).url(baseUrl).build(); + } } diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java index 3150587602..b80f73d296 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceTest.java @@ -131,13 +131,13 @@ public class JsonRpcHttpServiceTest { baseUrl = service.url(); } - protected static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfiguration config) + private static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfiguration config) throws Exception { return new JsonRpcHttpService( vertx, folder.newFolder().toPath(), config, new NoOpMetricsSystem(), rpcMethods); } - protected static JsonRpcHttpService createJsonRpcHttpService() throws Exception { + private static JsonRpcHttpService createJsonRpcHttpService() throws Exception { return new JsonRpcHttpService( vertx, folder.newFolder().toPath(), @@ -146,9 +146,10 @@ public class JsonRpcHttpServiceTest { rpcMethods); } - protected static JsonRpcConfiguration createJsonRpcConfig() { + private static JsonRpcConfiguration createJsonRpcConfig() { final JsonRpcConfiguration config = JsonRpcConfiguration.createDefault(); config.setPort(0); + config.setHostsWhitelist(Collections.singletonList("*")); return config; } @@ -170,18 +171,14 @@ public class JsonRpcHttpServiceTest { @Test public void http404() throws Exception { - final Request request = new Request.Builder().get().url(baseUrl + "/foo").build(); - - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildGetRequest("/foo")).execute()) { assertThat(resp.code()).isEqualTo(404); } } @Test public void handleEmptyRequest() throws Exception { - final Request request = new Request.Builder().get().url(baseUrl).build(); - - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildGetRequest("")).execute()) { assertThat(resp.code()).isEqualTo(201); } } @@ -229,9 +226,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.header("Content-Type")).isEqualTo("application/json"); } } @@ -245,9 +241,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -265,9 +260,8 @@ public class JsonRpcHttpServiceTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_version\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -285,9 +279,8 @@ public class JsonRpcHttpServiceTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_accounts\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -307,9 +300,8 @@ public class JsonRpcHttpServiceTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_peerCount\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -336,9 +328,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockHash\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String jsonStr = resp.body().string(); @@ -365,9 +356,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockHash\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String jsonStr = resp.body().string(); @@ -395,9 +385,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockNumber\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -424,9 +413,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockNumber\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -452,9 +440,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockNumber\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -481,9 +468,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockNumber\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -506,9 +492,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockNumber\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -530,9 +515,8 @@ public class JsonRpcHttpServiceTest { + "," + params + ",\"method\":\"eth_getUncleCountByBlockNumber\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -551,9 +535,8 @@ public class JsonRpcHttpServiceTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_peerCount\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -583,9 +566,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBalance\", \"params\": [\"" + address + "\",\"latest\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -616,9 +598,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBalance\", \"params\": [\"" + address + "\",\"latest\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -648,9 +629,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBalance\", \"params\": [\"" + address + "\",\"earliest\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -683,9 +663,8 @@ public class JsonRpcHttpServiceTest { + "\",\"0x" + Long.toString(blockNumber, 16) + "\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -711,12 +690,11 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByHash\", \"params\": [\"" + blockHashString + "\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); // Setup mocks when(blockchainQueries.blockByHash(eq(blockHash))).thenReturn(Optional.empty()); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -747,9 +725,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByHash\", \"params\": [\"" + blockHash + "\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -780,9 +757,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByHash\", \"params\": [\"" + blockHash + "\",false]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -803,13 +779,12 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByHash\", \"params\": [true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); // Setup mocks when(blockchainQueries.blockByHash(ArgumentMatchers.isA(Hash.class))) .thenReturn(Optional.empty()); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -832,9 +807,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByHash\", \"params\": [\"" + blockHashString + "\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -856,9 +830,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByHash\", \"params\": [\"" + blockHashString + "\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -880,9 +853,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByHash\", \"params\": [\"" + blockHashString + "\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -905,9 +877,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByHash\", \"params\": [\"" + blockHashString + "\",{}]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -926,9 +897,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByHash\", \"params\": []}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -947,9 +917,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByHash\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -978,9 +947,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"0x" + Long.toString(number, 16) + "\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1011,9 +979,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"0x" + Long.toString(number, 16) + "\",false]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1035,9 +1002,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"bla\",false]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final String respBody = resp.body().string(); @@ -1065,9 +1031,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"earliest\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1088,7 +1053,6 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"0x0\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); // Setup mocks to return a block final BlockDataGenerator gen = new BlockDataGenerator(); @@ -1097,7 +1061,7 @@ public class JsonRpcHttpServiceTest { blockWithMetadata(block); when(blockchainQueries.blockByNumber(eq(0L))).thenReturn(Optional.of(blockWithMetadata)); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1118,7 +1082,6 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"latest\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); // Setup mocks to return a block final BlockDataGenerator gen = new BlockDataGenerator(); @@ -1128,7 +1091,7 @@ public class JsonRpcHttpServiceTest { when(blockchainQueries.headBlockNumber()).thenReturn(0L); when(blockchainQueries.blockByNumber(eq(0L))).thenReturn(Optional.of(blockWithMetadata)); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1149,9 +1112,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_getBlockByNumber\", \"params\": [\"pending\",true]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1172,9 +1134,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\", \"params\": [1,2,3]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -1191,9 +1152,8 @@ public class JsonRpcHttpServiceTest { final RequestBody body = RequestBody.create( JSON, "{\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); final JsonObject json = new JsonObject(resp.body().string()); testHelper.assertValidJsonRpcResult(json, id); @@ -1207,9 +1167,8 @@ public class JsonRpcHttpServiceTest { // No id field is present - marking this as a notification final RequestBody body = RequestBody.create(JSON, "{\"jsonrpc\":\"2.0\",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { // Notifications return an empty response assertThat(resp.code()).isEqualTo(200); final String resBody = resp.body().string(); @@ -1223,9 +1182,8 @@ public class JsonRpcHttpServiceTest { final RequestBody body = RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":null,\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -1245,9 +1203,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { // An empty string is still a string, so should be a valid id assertThat(resp.code()).isEqualTo(200); // Check general format of result @@ -1268,9 +1225,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -1293,9 +1249,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -1320,9 +1275,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -1342,9 +1296,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -1360,9 +1313,8 @@ public class JsonRpcHttpServiceTest { final RequestBody body = RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":{},\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.INVALID_REQUEST; @@ -1376,9 +1328,8 @@ public class JsonRpcHttpServiceTest { final RequestBody body = RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":[],\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.INVALID_REQUEST; @@ -1392,9 +1343,8 @@ public class JsonRpcHttpServiceTest { final Integer id = 2; final RequestBody body = RequestBody.create(JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + "}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.INVALID_REQUEST; @@ -1406,9 +1356,8 @@ public class JsonRpcHttpServiceTest { @Test public void invalidJson() throws Exception { final RequestBody body = RequestBody.create(JSON, "{bla"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.PARSE_ERROR; @@ -1420,9 +1369,8 @@ public class JsonRpcHttpServiceTest { @Test public void wrongJsonType() throws Exception { final RequestBody body = RequestBody.create(JSON, "\"a string\""); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.PARSE_ERROR; @@ -1440,9 +1388,8 @@ public class JsonRpcHttpServiceTest { "{\"jsonrpc\":\"1.0\",\"id\":" + Json.encode(id) + ",\"method\":\"web3_clientVersion\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); final JsonObject json = new JsonObject(resp.body().string()); testHelper.assertValidJsonRpcResult(json, id); @@ -1457,9 +1404,8 @@ public class JsonRpcHttpServiceTest { final RequestBody body = RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"bla\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.METHOD_NOT_FOUND; @@ -1479,9 +1425,8 @@ public class JsonRpcHttpServiceTest { final RequestBody body = RequestBody.create(JSON, "{\"jsonrpc\":\"2.0\",\"id\":\"666\",\"method\":\"foo\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(500); } } @@ -1500,9 +1445,8 @@ public class JsonRpcHttpServiceTest { "[{\"jsonrpc\":\"2.0\",\"id\":\"000\",\"method\":\"web3_clientVersion\"}," + "{\"jsonrpc\":\"2.0\",\"id\":\"111\",\"method\":\"foo\"}," + "{\"jsonrpc\":\"2.0\",\"id\":\"222\",\"method\":\"net_version\"}]"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(500); } } @@ -1524,9 +1468,8 @@ public class JsonRpcHttpServiceTest { + "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(netVersionRequestId) + ",\"method\":\"net_version\"}]"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonArray json = new JsonArray(resp.body().string()); @@ -1573,9 +1516,8 @@ public class JsonRpcHttpServiceTest { + ",\"method\":\"net_version\"}"; final String batchRequest = "[" + String.join(", ", reqs) + "]"; final RequestBody body = RequestBody.create(JSON, batchRequest); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String jsonStr = resp.body().string(); @@ -1621,9 +1563,8 @@ public class JsonRpcHttpServiceTest { + "]"; final RequestBody body = RequestBody.create(JSON, req); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.PARSE_ERROR; @@ -1646,9 +1587,8 @@ public class JsonRpcHttpServiceTest { + "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(netVersionRequestId) + ",\"method\":\"net_version\"}]"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonArray json = new JsonArray(resp.body().string()); @@ -1680,9 +1620,8 @@ public class JsonRpcHttpServiceTest { @Test public void emptyBatchRequest() throws Exception { final RequestBody body = RequestBody.create(JSON, "[]"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); final JsonObject json = new JsonObject(resp.body().string()); final JsonRpcError expectedError = JsonRpcError.INVALID_REQUEST; @@ -1812,10 +1751,9 @@ public class JsonRpcHttpServiceTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_syncing\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); when(synchronizer.getSyncStatus()).thenReturn(Optional.empty()); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Verify general result format. final JsonObject json = new JsonObject(resp.body().string()); @@ -1834,9 +1772,8 @@ public class JsonRpcHttpServiceTest { RequestBody.create( JSON, "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_syncing\"}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { final String respBody = resp.body().string(); final JsonObject json = new JsonObject(respBody); final JsonObject result = json.getJsonObject("result"); @@ -1902,9 +1839,8 @@ public class JsonRpcHttpServiceTest { + "\",\"" + UInt256.ZERO + "\",\"latest\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1938,9 +1874,8 @@ public class JsonRpcHttpServiceTest { + "\",\"" + UInt256.ONE + "\",\"latest\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -1973,9 +1908,8 @@ public class JsonRpcHttpServiceTest { + "\",\"" + UInt256.ONE + "\",\"earliest\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -2010,9 +1944,8 @@ public class JsonRpcHttpServiceTest { + "\",\"" + 0L + "\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(200); // Check general format of result final String respBody = resp.body().string(); @@ -2043,9 +1976,8 @@ public class JsonRpcHttpServiceTest { + "\",\"" + "blah" + "\",\"latest\"]}"); - final Request request = new Request.Builder().post(body).url(baseUrl).build(); - try (final Response resp = client.newCall(request).execute()) { + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { assertThat(resp.code()).isEqualTo(400); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); @@ -2054,4 +1986,12 @@ public class JsonRpcHttpServiceTest { json, id, expectedError.getCode(), expectedError.getMessage()); } } + + private Request buildPostRequest(final RequestBody body) { + return new Request.Builder().post(body).url(baseUrl).build(); + } + + private Request buildGetRequest(final String path) { + return new Request.Builder().get().url(baseUrl + path).build(); + } } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java index 978a027b21..b6918c9a64 100644 --- a/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/PantheonCommand.java @@ -19,6 +19,7 @@ import static tech.pegasys.pantheon.cli.DefaultCommandValues.getDefaultPantheonD import tech.pegasys.pantheon.Runner; import tech.pegasys.pantheon.RunnerBuilder; import tech.pegasys.pantheon.cli.custom.CorsAllowedOriginsProperty; +import tech.pegasys.pantheon.cli.custom.JsonRPCWhitelistHostsProperty; import tech.pegasys.pantheon.consensus.clique.jsonrpc.CliqueRpcApis; import tech.pegasys.pantheon.consensus.ibft.jsonrpc.IbftRpcApis; import tech.pegasys.pantheon.controller.KeyPairUtil; @@ -311,6 +312,16 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { ) private final Collection wsApis = null; + @Option( + names = {"--host-whitelist"}, + paramLabel = "", + description = + "Comma separated list of hostnames to whitelist for RPC access. default: ${DEFAULT-VALUE}", + defaultValue = "localhost", + converter = JsonRPCWhitelistHostsProperty.JsonRPCWhitelistHostsConverter.class + ) + private final JsonRPCWhitelistHostsProperty hostsWhitelist = new JsonRPCWhitelistHostsProperty(); + @Option( names = {"--dev-mode"}, description = @@ -483,6 +494,7 @@ public class PantheonCommand implements DefaultCommandValues, Runnable { jsonRpcConfiguration.setPort(rpcHostAndPort.getPort()); jsonRpcConfiguration.setCorsAllowedDomains(rpcCorsAllowedOrigins.getDomains()); jsonRpcConfiguration.setRpcApis(rpcApis); + jsonRpcConfiguration.setHostsWhitelist(hostsWhitelist.hostnamesWhitelist()); return jsonRpcConfiguration; } diff --git a/pantheon/src/main/java/tech/pegasys/pantheon/cli/custom/JsonRPCWhitelistHostsProperty.java b/pantheon/src/main/java/tech/pegasys/pantheon/cli/custom/JsonRPCWhitelistHostsProperty.java new file mode 100644 index 0000000000..bab8e33ce6 --- /dev/null +++ b/pantheon/src/main/java/tech/pegasys/pantheon/cli/custom/JsonRPCWhitelistHostsProperty.java @@ -0,0 +1,70 @@ +/* + * Copyright 2018 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. + */ +package tech.pegasys.pantheon.cli.custom; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import picocli.CommandLine; + +public class JsonRPCWhitelistHostsProperty { + private List hostnamesWhitelist = Collections.emptyList(); + + private JsonRPCWhitelistHostsProperty(final List hostnamesWhitelist) { + this.hostnamesWhitelist = hostnamesWhitelist; + } + + public JsonRPCWhitelistHostsProperty() {} + + public List hostnamesWhitelist() { + return hostnamesWhitelist; + } + + public static class JsonRPCWhitelistHostsConverter + implements CommandLine.ITypeConverter { + + @Override + public JsonRPCWhitelistHostsProperty convert(final String value) { + final List hostNames; + if (value != null && !value.isEmpty()) { + hostNames = new ArrayList<>(Arrays.asList(value.split("\\s*,\\s*"))); + } else { + throw new IllegalArgumentException("Property can't be null/empty string"); + } + + if (hostNames.contains("all")) { + if (hostNames.size() > 1) { + throw new IllegalArgumentException("Value 'all' can't be used with other hostnames"); + } else { + return new JsonRPCWhitelistHostsProperty(Collections.singletonList("*")); + } + } + + if (hostNames.contains("*")) { + if (hostNames.size() > 1) { + throw new IllegalArgumentException("Value '*' can't be used with other hostnames"); + } else { + return new JsonRPCWhitelistHostsProperty(Collections.singletonList("*")); + } + } + + if (hostNames.size() > 0) { + return new JsonRPCWhitelistHostsProperty(hostNames); + } else { + return new JsonRPCWhitelistHostsProperty(); + } + } + } +} diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java index 9e9bf7d542..75aeb29a66 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/RunnerTest.java @@ -252,6 +252,7 @@ public final class RunnerTest { final JsonRpcConfiguration configuration = JsonRpcConfiguration.createDefault(); configuration.setPort(0); configuration.setEnabled(true); + configuration.setHostsWhitelist(Collections.singletonList("*")); return configuration; } @@ -263,8 +264,7 @@ public final class RunnerTest { } private PermissioningConfiguration permissioningConfiguration() { - final PermissioningConfiguration configuration = PermissioningConfiguration.createDefault(); - return configuration; + return PermissioningConfiguration.createDefault(); } private static void setupState( diff --git a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java index aa0bd27e86..1ec444ce4d 100644 --- a/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java +++ b/pantheon/src/test/java/tech/pegasys/pantheon/cli/PantheonCommandTest.java @@ -707,6 +707,62 @@ public class PantheonCommandTest extends CommandTestAbstract { .contains("Domain values result in invalid regex pattern"); } + @Test + public void jsonRpcHostWhitelistAcceptsSingleArgument() { + parseCommand("--host-whitelist", "a"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsWhitelist().size()).isEqualTo(1); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsWhitelist()).contains("a"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsWhitelist()) + .doesNotContain("localhost"); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void jsonRpcHostWhitelistAcceptsMultipleArguments() { + parseCommand("--host-whitelist", "a,b"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsWhitelist().size()).isEqualTo(2); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsWhitelist()).contains("a", "b"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsWhitelist()) + .doesNotContain("*", "localhost"); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()).isEmpty(); + } + + @Test + public void jsonRpcHostWhitelistStarWithAnotherHostnameMustFail() { + final String[] origins = {"friend", "*"}; + parseCommand("--host-whitelist", String.join(",", origins)); + + verifyZeroInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()) + .contains("Value '*' can't be used with other hostnames"); + } + + @Test + public void jsonRpcHostWhitelistAllWithAnotherHostnameMustFail() { + final String[] origins = {"friend", "all"}; + parseCommand("--host-whitelist", String.join(",", origins)); + + verifyZeroInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString()).isEmpty(); + assertThat(commandErrorOutput.toString()) + .contains("Value 'all' can't be used with other hostnames"); + } + @Test public void wsRpcEnabledPropertyDefaultIsFalse() { parseCommand(); diff --git a/quickstart/src/test/java/tech/pegasys/pantheon/tests/quickstart/DockerQuickstartTest.java b/quickstart/src/test/java/tech/pegasys/pantheon/tests/quickstart/DockerQuickstartTest.java index 5ea600f3d2..af6cd96bbb 100644 --- a/quickstart/src/test/java/tech/pegasys/pantheon/tests/quickstart/DockerQuickstartTest.java +++ b/quickstart/src/test/java/tech/pegasys/pantheon/tests/quickstart/DockerQuickstartTest.java @@ -57,6 +57,7 @@ public class DockerQuickstartTest { private static final String DEFAULT_RPC_HOST = Optional.ofNullable(System.getenv("DOCKER_PORT_2375_TCP_ADDR")).orElse("localhost"); private static final String DEFAULT_HTTP_RPC_HOST = "http://" + DEFAULT_RPC_HOST; + private static final String DEFAULT_HOST_HEADER = "localhost"; private final Map services = new EnumMap<>(ServicesIdentifier.class); private final Map endpoints = new EnumMap<>(EndpointsIdentifier.class); @@ -104,19 +105,17 @@ public class DockerQuickstartTest { assertThat(services).isNotNull().isNotEmpty(); assertThat(endpoints).isNotNull().isNotEmpty(); - web3HttpClient = - Web3j.build( - new HttpService( - DEFAULT_HTTP_RPC_HOST - + ":" - + services - .get(ServicesIdentifier.RPCNODE) - .exposedPorts - .get(DEFAULT_HTTP_RPC_PORT) - .externalPort), - 2000, - Async.defaultExecutorService()); - + HttpService httpService = + new HttpService( + DEFAULT_HTTP_RPC_HOST + + ":" + + services + .get(ServicesIdentifier.RPCNODE) + .exposedPorts + .get(DEFAULT_HTTP_RPC_PORT) + .externalPort); + httpService.addHeader("Host", DEFAULT_HOST_HEADER); + web3HttpClient = Web3j.build(httpService, 2000, Async.defaultExecutorService()); assertThat(web3HttpClient).isNotNull(); }