diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java index 6b86d7ce8e..4202165e2f 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcMethodsFactory.java @@ -49,6 +49,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetUncleByBloc import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetUncleCountByBlockHash; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetUncleCountByBlockNumber; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthGetWork; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthHashrate; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthMining; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthNewBlockFilter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods.EthNewFilter; @@ -182,6 +183,7 @@ public class JsonRpcMethodsFactory { new EthProtocolVersion(supportedCapabilities), new EthGasPrice(miningCoordinator), new EthGetWork(miningCoordinator), + new EthHashrate(miningCoordinator), new EthChainId(protocolSchedule.getChainId())); } if (rpcApis.contains(RpcApis.DEBUG)) { diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthHashrate.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthHashrate.java new file mode 100644 index 0000000000..23d0350ab3 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthHashrate.java @@ -0,0 +1,50 @@ +/* + * 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.internal.methods; + +import tech.pegasys.pantheon.ethereum.blockcreation.MiningCoordinator; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.results.Quantity; + +import java.util.Optional; + +public class EthHashrate implements JsonRpcMethod { + + private final MiningCoordinator miningCoordinator; + + public EthHashrate(final MiningCoordinator miningCoordinator) { + this.miningCoordinator = miningCoordinator; + } + + @Override + public String getName() { + return "eth_hashrate"; + } + + @Override + public JsonRpcResponse response(final JsonRpcRequest req) { + try { + final Optional hashesPerSecond = miningCoordinator.hashesPerSecond(); + if (hashesPerSecond.isPresent()) { + return new JsonRpcSuccessResponse(req.getId(), Quantity.create(hashesPerSecond.get())); + } + return new JsonRpcErrorResponse(req.getId(), JsonRpcError.NO_HASHES_PER_SECOND); + } catch (final UnsupportedOperationException ex) { + return new JsonRpcErrorResponse(req.getId(), JsonRpcError.INVALID_REQUEST); + } + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java index c03a35ba78..174aec2dea 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/response/JsonRpcError.java @@ -42,6 +42,7 @@ public enum JsonRpcError { // Miner failures COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase."), + NO_HASHES_PER_SECOND(-32011, "No hashes being generated by the current node."), // Wallet errors COINBASE_NOT_SPECIFIED(-32000, "Coinbase must be explicitly specified"); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthHashrateTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthHashrateTest.java new file mode 100644 index 0000000000..a3b42be9cd --- /dev/null +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthHashrateTest.java @@ -0,0 +1,86 @@ +/* + * 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.internal.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import tech.pegasys.pantheon.ethereum.blockcreation.EthHashMiningCoordinator; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcError; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcErrorResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcResponse; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.response.JsonRpcSuccessResponse; + +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class EthHashrateTest { + + @Mock private EthHashMiningCoordinator miningCoordinator; + private EthHashrate method; + private final String JSON_RPC_VERSION = "2.0"; + private final String ETH_METHOD = "eth_hashrate"; + + @Before + public void setUp() { + method = new EthHashrate(miningCoordinator); + } + + @Test + public void returnsCorrectMethodName() { + assertThat(method.getName()).isEqualTo(ETH_METHOD); + } + + @Test + public void shouldReturnValueFromMiningCoordinator() { + final JsonRpcRequest request = requestWithParams(); + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(request.getId(), "0xc"); + when(miningCoordinator.hashesPerSecond()).thenReturn(Optional.of(12L)); + + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + } + + @Test + public void shouldReturnErrorWhenMiningCoordinatorDoesNotHaveHashes() { + final JsonRpcRequest request = requestWithParams(); + final JsonRpcResponse expectedResponse = + new JsonRpcErrorResponse(request.getId(), JsonRpcError.NO_HASHES_PER_SECOND); + when(miningCoordinator.hashesPerSecond()).thenReturn(Optional.empty()); + + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + } + + @Test + public void shouldReturnErrorWhenMiningCoordinatorDoesNotSupportHashing() { + final JsonRpcRequest request = requestWithParams(); + final JsonRpcResponse expectedResponse = + new JsonRpcErrorResponse(request.getId(), JsonRpcError.INVALID_REQUEST); + when(miningCoordinator.hashesPerSecond()).thenThrow(UnsupportedOperationException.class); + + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse); + } + + private JsonRpcRequest requestWithParams() { + return new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, new Object[] {}); + } +}