[BESU-1919] Add proper support for eth_hashrate (#1063)

- Implemented submitHashrate endpoint.
- Updated eth_hashrateEndpoint so that it returns the cumulative hashrate of all sealers if available. Otherwise it returns the local hashrate
- Added hashrate submission with Stratum1EthProxyProtocol and Stratum1Protocol

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>
pull/1089/head
Karim T 5 years ago committed by GitHub
parent 0702075265
commit 165be86aa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java
  2. 1
      besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
  3. 22
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  4. 6
      besu/src/main/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommand.java
  5. 7
      besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java
  6. 3
      besu/src/test/resources/everything_config.toml
  7. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java
  8. 47
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitHashRate.java
  9. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java
  10. 45
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/EthHashMiningCoordinator.java
  11. 11
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java
  12. 16
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashMiningCoordinatorTest.java
  13. 81
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/HashRateMiningCoordinatorTest.java
  14. 41
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java
  15. 2
      ethereum/stratum/build.gradle
  16. 13
      ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/Stratum1EthProxyProtocol.java
  17. 10
      ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/Stratum1Protocol.java
  18. 24
      ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/StratumProtocol.java
  19. 12
      ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/StratumServer.java
  20. 60
      ethereum/stratum/src/test/java/org/hyperledger/besu/ethereum/stratum/StratumConnectionTest.java

@ -103,6 +103,10 @@ public class ProcessBesuNodeRunner implements BesuNodeRunner {
params.add("--min-gas-price");
params.add(
Integer.toString(node.getMiningParameters().getMinTransactionGasPrice().intValue()));
params.add("--Xminer-remote-sealers-limit");
params.add(Integer.toString(node.getMiningParameters().getRemoteSealersLimit()));
params.add("--Xminer-remote-sealers-hashrate-ttl");
params.add(Long.toString(node.getMiningParameters().getRemoteSealersTimeToLive()));
}
if (node.getMiningParameters().isStratumMiningEnabled()) {
params.add("--miner-stratum-enabled");

@ -417,6 +417,7 @@ public class RunnerBuilder {
Optional.of(
new StratumServer(
vertx,
miningCoordinator,
miningParameters.getStratumPort(),
miningParameters.getStratumNetworkInterface(),
miningParameters.getStratumExtranonce()));

@ -26,6 +26,8 @@ import static org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration.DEF
import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT;
import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_JSON_RPC_APIS;
import static org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration.DEFAULT_WEBSOCKET_PORT;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_LIMIT;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_TTL;
import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES;
import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PORT;
import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PUSH_PORT;
@ -724,6 +726,18 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
description = "Stratum port binding (default: ${DEFAULT-VALUE})")
private final Integer stratumPort = 8008;
@Option(
names = {"--Xminer-remote-sealers-limit"},
description =
"Limits the number of remote sealers that can submit their hashrates (default: ${DEFAULT-VALUE})")
private final Integer remoteSealersLimit = DEFAULT_REMOTE_SEALERS_LIMIT;
@Option(
names = {"--Xminer-remote-sealers-hashrate-ttl"},
description =
"Specifies the lifetime of each entry in the cache. An entry will be automatically deleted if no update has been received before the deadline (default: ${DEFAULT-VALUE} minutes)")
private final Long remoteSealersTimeToLive = DEFAULT_REMOTE_SEALERS_TTL;
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
@Option(
hidden = true,
@ -1320,7 +1334,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
"--min-gas-price",
"--min-block-occupancy-ratio",
"--miner-extra-data",
"--miner-stratum-enabled"));
"--miner-stratum-enabled",
"--Xminer-remote-sealers-limit",
"--Xminer-remote-sealers-hashrate-ttl"));
CommandLineUtils.checkOptionDependencies(
logger,
@ -1415,7 +1431,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
stratumPort,
stratumExtranonce,
Optional.empty(),
minBlockOccupancyRatio))
minBlockOccupancyRatio,
remoteSealersLimit,
remoteSealersTimeToLive))
.transactionPoolConfiguration(buildTransactionPoolConfiguration())
.nodeKey(buildNodeKey())
.metricsSystem(metricsSystem.get())

@ -16,6 +16,8 @@ package org.hyperledger.besu.cli.subcommands.blocks;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hyperledger.besu.cli.subcommands.blocks.BlocksSubCommand.COMMAND_NAME;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_LIMIT;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_TTL;
import org.hyperledger.besu.chainexport.RlpBlockExporter;
import org.hyperledger.besu.chainimport.JsonBlockImporter;
@ -244,7 +246,9 @@ public class BlocksSubCommand implements Runnable {
8008,
"080c",
Optional.of(new IncrementingNonceGenerator(0)),
0.0);
0.0,
DEFAULT_REMOTE_SEALERS_LIMIT,
DEFAULT_REMOTE_SEALERS_TTL);
}
private void importJsonBlocks(final BesuController controller, final Path path)

@ -52,7 +52,12 @@ public class MainnetBesuControllerBuilder extends BesuControllerBuilder {
gasLimitCalculator);
final EthHashMiningCoordinator miningCoordinator =
new EthHashMiningCoordinator(protocolContext.getBlockchain(), executor, syncState);
new EthHashMiningCoordinator(
protocolContext.getBlockchain(),
executor,
syncState,
miningParameters.getRemoteSealersLimit(),
miningParameters.getRemoteSealersTimeToLive());
miningCoordinator.addMinedBlockObserver(ethProtocolManager);
miningCoordinator.setStratumMiningEnabled(miningParameters.isStratumMiningEnabled());
if (miningParameters.isMiningEnabled()) {

@ -104,7 +104,8 @@ min-gas-price=1
min-block-occupancy-ratio=0.7
miner-stratum-host="0.0.0.0"
miner-stratum-port=8008
Xminer-remote-sealers-limit=1000
Xminer-remote-sealers-hashrate-ttl=10
# Pruning
pruning-enabled=true
pruning-blocks-retained=1024

@ -85,6 +85,7 @@ public enum RpcMethod {
ETH_GET_UNCLE_COUNT_BY_BLOCK_NUMBER("eth_getUncleCountByBlockNumber"),
ETH_GET_WORK("eth_getWork"),
ETH_HASHRATE("eth_hashrate"),
ETH_SUBMIT_HASHRATE("eth_submitHashrate"),
ETH_MINING("eth_mining"),
ETH_NEW_BLOCK_FILTER("eth_newBlockFilter"),
ETH_NEW_FILTER("eth_newFilter"),

@ -0,0 +1,47 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.apache.tuweni.bytes.Bytes;
public class EthSubmitHashRate implements JsonRpcMethod {
private final MiningCoordinator miningCoordinator;
public EthSubmitHashRate(final MiningCoordinator miningCoordinator) {
this.miningCoordinator = miningCoordinator;
}
@Override
public String getName() {
return RpcMethod.ETH_SUBMIT_HASHRATE.getMethodName();
}
@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
final String hashRate = requestContext.getRequiredParameter(0, String.class);
final String id = requestContext.getRequiredParameter(1, String.class);
return new JsonRpcSuccessResponse(
requestContext.getRequest().getId(),
miningCoordinator.submitHashRate(
id, Bytes.fromHexString(hashRate).toBigInteger().longValue()));
}
}

@ -53,6 +53,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthNewPendingT
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthProtocolVersion;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthSendRawTransaction;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthSendTransaction;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthSubmitHashRate;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthSubmitWork;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthSyncing;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthUninstallFilter;
@ -154,6 +155,7 @@ public class EthJsonRpcMethods extends ApiGroupJsonRpcMethods {
new EthGetWork(miningCoordinator),
new EthSubmitWork(miningCoordinator),
new EthHashrate(miningCoordinator),
new EthSubmitHashRate(miningCoordinator),
new EthChainId(protocolSchedule.getChainId()));
}
}

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.blockcreation;
import static org.apache.logging.log4j.LogManager.getLogger;
import org.hyperledger.besu.ethereum.chain.BlockAddedObserver;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.Address;
@ -23,6 +25,11 @@ import org.hyperledger.besu.ethereum.mainnet.EthHashSolution;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolverInputs;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.logging.log4j.Logger;
/**
* Responsible for determining when a block mining operation should be started/stopped, then
@ -31,13 +38,27 @@ import java.util.Optional;
public class EthHashMiningCoordinator extends AbstractMiningCoordinator<EthHashBlockMiner>
implements BlockAddedObserver {
private static final Logger LOG = getLogger();
private final EthHashMinerExecutor executor;
private final Cache<String, Long> sealerHashRate;
private volatile Optional<Long> cachedHashesPerSecond = Optional.empty();
public EthHashMiningCoordinator(
final Blockchain blockchain, final EthHashMinerExecutor executor, final SyncState syncState) {
final Blockchain blockchain,
final EthHashMinerExecutor executor,
final SyncState syncState,
final int remoteSealersLimit,
final long remoteSealersTimeToLive) {
super(blockchain, executor, syncState);
this.executor = executor;
this.sealerHashRate =
CacheBuilder.newBuilder()
.maximumSize(remoteSealersLimit)
.expireAfterWrite(remoteSealersTimeToLive, TimeUnit.MINUTES)
.build();
}
@Override
@ -51,6 +72,18 @@ public class EthHashMiningCoordinator extends AbstractMiningCoordinator<EthHashB
@Override
public Optional<Long> hashesPerSecond() {
if (sealerHashRate.size() <= 0) {
return localHashesPerSecond();
} else {
return remoteHashesPerSecond();
}
}
private Optional<Long> remoteHashesPerSecond() {
return Optional.of(sealerHashRate.asMap().values().stream().mapToLong(Long::longValue).sum());
}
private Optional<Long> localHashesPerSecond() {
final Optional<Long> currentHashesPerSecond =
currentRunningMiner.flatMap(EthHashBlockMiner::getHashesPerSecond);
@ -62,6 +95,16 @@ public class EthHashMiningCoordinator extends AbstractMiningCoordinator<EthHashB
}
}
@Override
public boolean submitHashRate(final String id, final Long hashrate) {
if (hashrate == 0) {
return false;
}
LOG.info("Hashrate submitted id {} hashrate {}", id, hashrate);
sealerHashRate.put(id, hashrate);
return true;
}
@Override
public Optional<EthHashSolverInputs> getWorkDefinition() {
return currentRunningMiner.flatMap(EthHashBlockMiner::getWorkDefinition);

@ -77,6 +77,17 @@ public interface MiningCoordinator {
"Current consensus mechanism prevents submission of work solutions.");
}
/**
* Allows to submit the hashrate of a sealer with a specific id
*
* @param id of the sealer
* @param hashrate of the sealer
* @return true if the hashrate has been added otherwise false
*/
default boolean submitHashRate(final String id, final Long hashrate) {
return false;
}
/**
* Creates a block if possible, otherwise return an empty result
*

@ -15,6 +15,8 @@
package org.hyperledger.besu.ethereum.blockcreation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_LIMIT;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_TTL;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -45,7 +47,12 @@ public class EthHashMiningCoordinatorTest {
@Test
public void miningCoordinatorIsCreatedDisabledWithNoReportableMiningStatistics() {
final EthHashMiningCoordinator miningCoordinator =
new EthHashMiningCoordinator(executionContext.getBlockchain(), executor, syncState);
new EthHashMiningCoordinator(
executionContext.getBlockchain(),
executor,
syncState,
DEFAULT_REMOTE_SEALERS_LIMIT,
DEFAULT_REMOTE_SEALERS_TTL);
final EthHashSolution solution = new EthHashSolution(1L, Hash.EMPTY, new byte[Bytes32.SIZE]);
assertThat(miningCoordinator.isMining()).isFalse();
@ -66,7 +73,12 @@ public class EthHashMiningCoordinatorTest {
when(executor.startAsyncMining(any(), any(), any())).thenReturn(Optional.of(miner));
final EthHashMiningCoordinator miningCoordinator =
new EthHashMiningCoordinator(executionContext.getBlockchain(), executor, syncState);
new EthHashMiningCoordinator(
executionContext.getBlockchain(),
executor,
syncState,
DEFAULT_REMOTE_SEALERS_LIMIT,
DEFAULT_REMOTE_SEALERS_TTL);
// Must enable prior returning data
miningCoordinator.enable();

@ -0,0 +1,81 @@
/*
* Copyright ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.blockcreation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class HashRateMiningCoordinatorTest {
private final Blockchain blockchain = mock(Blockchain.class);
private final SyncState syncState = mock(SyncState.class);
private final EthHashMinerExecutor minerExecutor = mock(EthHashMinerExecutor.class);
private final String id;
private final Long hashRate;
private final Long wantTotalHashrate;
private final int startSealersSize;
private final boolean wantAdded;
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[][] {
{"1", 1L, 2L, 1, true},
{"1", 1L, 1L, 0, true},
{"1", 0L, 0L, 0, false},
{"1", 1L, 501L, 500, true},
});
}
public HashRateMiningCoordinatorTest(
final String id,
final long hashRate,
final long wantTotalHashrate,
final int startSealersSize,
final boolean wantAdded) {
this.id = id;
this.hashRate = hashRate;
this.startSealersSize = startSealersSize;
this.wantAdded = wantAdded;
this.wantTotalHashrate = wantTotalHashrate;
}
@Test
public void test() {
final EthHashMiningCoordinator miningCoordinator =
new EthHashMiningCoordinator(blockchain, minerExecutor, syncState, 1000, 10);
for (int i = 0; i < startSealersSize; i++) {
miningCoordinator.submitHashRate(UUID.randomUUID().toString(), 1L);
}
assertThat(miningCoordinator.submitHashRate(id, hashRate)).isEqualTo(wantAdded);
if (wantTotalHashrate == 0L) {
assertThat(miningCoordinator.hashesPerSecond()).isEmpty();
} else {
assertThat(miningCoordinator.hashesPerSecond()).contains(wantTotalHashrate);
}
}
}

@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.ethereum.core;
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
@ -21,6 +22,10 @@ import org.apache.tuweni.bytes.Bytes;
public class MiningParameters {
public static final int DEFAULT_REMOTE_SEALERS_LIMIT = 1000;
public static final long DEFAULT_REMOTE_SEALERS_TTL = Duration.ofMinutes(10).toMinutes();
private final Optional<Address> coinbase;
private final Wei minTransactionGasPrice;
private final Bytes extraData;
@ -31,6 +36,8 @@ public class MiningParameters {
private final String stratumExtranonce;
private final Optional<Iterable<Long>> maybeNonceGenerator;
private final Double minBlockOccupancyRatio;
private final int remoteSealersLimit;
private final long remoteSealersTimeToLive;
public MiningParameters(
final Address coinbase,
@ -47,7 +54,9 @@ public class MiningParameters {
8008,
"080c",
Optional.empty(),
0.8);
0.8,
DEFAULT_REMOTE_SEALERS_LIMIT,
DEFAULT_REMOTE_SEALERS_TTL);
}
public MiningParameters(
@ -60,7 +69,9 @@ public class MiningParameters {
final int stratumPort,
final String stratumExtranonce,
final Optional<Iterable<Long>> maybeNonceGenerator,
final Double minBlockOccupancyRatio) {
final Double minBlockOccupancyRatio,
final int remoteSealersLimit,
final long remoteSealersTimeToLive) {
this.coinbase = Optional.ofNullable(coinbase);
this.minTransactionGasPrice = minTransactionGasPrice;
this.extraData = extraData;
@ -71,6 +82,8 @@ public class MiningParameters {
this.stratumExtranonce = stratumExtranonce;
this.maybeNonceGenerator = maybeNonceGenerator;
this.minBlockOccupancyRatio = minBlockOccupancyRatio;
this.remoteSealersLimit = remoteSealersLimit;
this.remoteSealersTimeToLive = remoteSealersTimeToLive;
}
public Optional<Address> getCoinbase() {
@ -113,6 +126,14 @@ public class MiningParameters {
return minBlockOccupancyRatio;
}
public int getRemoteSealersLimit() {
return remoteSealersLimit;
}
public long getRemoteSealersTimeToLive() {
return remoteSealersTimeToLive;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@ -126,7 +147,9 @@ public class MiningParameters {
&& Objects.equals(stratumMiningEnabled, that.stratumMiningEnabled)
&& Objects.equals(stratumNetworkInterface, that.stratumNetworkInterface)
&& Objects.equals(stratumExtranonce, that.stratumExtranonce)
&& Objects.equals(minBlockOccupancyRatio, that.minBlockOccupancyRatio);
&& Objects.equals(minBlockOccupancyRatio, that.minBlockOccupancyRatio)
&& Objects.equals(remoteSealersTimeToLive, that.remoteSealersTimeToLive)
&& Objects.equals(remoteSealersLimit, that.remoteSealersLimit);
}
@Override
@ -140,7 +163,9 @@ public class MiningParameters {
stratumNetworkInterface,
stratumPort,
stratumExtranonce,
minBlockOccupancyRatio);
minBlockOccupancyRatio,
remoteSealersLimit,
remoteSealersTimeToLive);
}
@Override
@ -164,6 +189,14 @@ public class MiningParameters {
+ ", stratumExtranonce='"
+ stratumExtranonce
+ '\''
+ ", maybeNonceGenerator="
+ maybeNonceGenerator
+ ", minBlockOccupancyRatio="
+ minBlockOccupancyRatio
+ ", remoteSealersLimit="
+ remoteSealersLimit
+ ", remoteSealersTimeToLive="
+ remoteSealersTimeToLive
+ '}';
}
}

@ -33,6 +33,7 @@ dependencies {
implementation project(':ethereum:api')
implementation project(':ethereum:core')
implementation project(':ethereum:blockcreation')
implementation 'com.google.guava:guava'
implementation 'io.vertx:vertx-core'
@ -44,4 +45,5 @@ dependencies {
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.mockito:mockito-core'
}

@ -16,9 +16,11 @@ package org.hyperledger.besu.ethereum.stratum;
import static org.apache.logging.log4j.LogManager.getLogger;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.mainnet.DirectAcyclicGraphSeed;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolution;
@ -44,9 +46,14 @@ public class Stratum1EthProxyProtocol implements StratumProtocol {
private static final Logger LOG = getLogger();
private static final JsonMapper mapper = new JsonMapper();
private final MiningCoordinator miningCoordinator;
private EthHashSolverInputs currentInput;
private Function<EthHashSolution, Boolean> submitCallback;
public Stratum1EthProxyProtocol(final MiningCoordinator miningCoordinator) {
this.miningCoordinator = miningCoordinator;
}
@Override
public boolean canHandle(final String initialMessage, final StratumConnection conn) {
JsonRpcRequestContext req;
@ -95,10 +102,12 @@ public class Stratum1EthProxyProtocol implements StratumProtocol {
public void handle(final StratumConnection conn, final String message) {
try {
final JsonRpcRequest req = new JsonObject(message).mapTo(JsonRpcRequest.class);
if ("eth_getWork".equals(req.getMethod())) {
if (RpcMethod.ETH_GET_WORK.getMethodName().equals(req.getMethod())) {
sendNewWork(conn, req.getId());
} else if ("eth_submitWork".equals(req.getMethod())) {
} else if (RpcMethod.ETH_SUBMIT_WORK.getMethodName().equals(req.getMethod())) {
handleMiningSubmit(conn, req);
} else if (RpcMethod.ETH_SUBMIT_HASHRATE.getMethodName().equals(req.getMethod())) {
handleHashrateSubmit(mapper, miningCoordinator, conn, req);
}
} catch (IllegalArgumentException | IOException e) {
LOG.debug(e.getMessage(), e);

@ -16,8 +16,10 @@ package org.hyperledger.besu.ethereum.stratum;
import static org.apache.logging.log4j.LogManager.getLogger;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.ethereum.mainnet.DirectAcyclicGraphSeed;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolution;
@ -54,6 +56,7 @@ public class Stratum1Protocol implements StratumProtocol {
return Bytes.wrap(subscriptionBytes).toShortHexString();
}
private final MiningCoordinator miningCoordinator;
private final String extranonce;
private EthHashSolverInputs currentInput;
private Function<EthHashSolution, Boolean> submitCallback;
@ -61,9 +64,10 @@ public class Stratum1Protocol implements StratumProtocol {
private final Supplier<String> subscriptionIdCreator;
private final List<StratumConnection> activeConnections = new ArrayList<>();
public Stratum1Protocol(final String extranonce) {
public Stratum1Protocol(final String extranonce, final MiningCoordinator miningCoordinator) {
this(
extranonce,
miningCoordinator,
() -> {
Bytes timeValue = Bytes.minimalBytes(Instant.now().toEpochMilli());
return timeValue.slice(timeValue.size() - 4, 4).toShortHexString();
@ -73,9 +77,11 @@ public class Stratum1Protocol implements StratumProtocol {
Stratum1Protocol(
final String extranonce,
final MiningCoordinator miningCoordinator,
final Supplier<String> jobIdSupplier,
final Supplier<String> subscriptionIdCreator) {
this.extranonce = extranonce;
this.miningCoordinator = miningCoordinator;
this.jobIdSupplier = jobIdSupplier;
this.subscriptionIdCreator = subscriptionIdCreator;
}
@ -152,6 +158,8 @@ public class Stratum1Protocol implements StratumProtocol {
handleMiningAuthorize(conn, req);
} else if ("mining.submit".equals(req.getMethod())) {
handleMiningSubmit(conn, req);
} else if (RpcMethod.ETH_SUBMIT_HASHRATE.getMethodName().equals(req.getMethod())) {
handleHashrateSubmit(mapper, miningCoordinator, conn, req);
}
} catch (IllegalArgumentException | IOException e) {
LOG.debug(e.getMessage(), e);

@ -14,11 +14,18 @@
*/
package org.hyperledger.besu.ethereum.stratum;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolution;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolverInputs;
import java.io.IOException;
import java.util.function.Function;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.apache.tuweni.bytes.Bytes;
/**
* Stratum protocol handler.
*
@ -59,4 +66,21 @@ public interface StratumProtocol {
void setCurrentWorkTask(EthHashSolverInputs input);
void setSubmitCallback(Function<EthHashSolution, Boolean> submitSolutionCallback);
default void handleHashrateSubmit(
final JsonMapper mapper,
final MiningCoordinator miningCoordinator,
final StratumConnection conn,
final JsonRpcRequest message)
throws IOException {
final String hashRate = message.getRequiredParameter(0, String.class);
final String id = message.getRequiredParameter(1, String.class);
String response =
mapper.writeValueAsString(
new JsonRpcSuccessResponse(
message.getId(),
miningCoordinator.submitHashRate(
id, Bytes.fromHexString(hashRate).toBigInteger().longValue())));
conn.send(response + "\n");
}
}

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.stratum;
import static org.apache.logging.log4j.LogManager.getLogger;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.chain.EthHashObserver;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolution;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolverInputs;
@ -47,12 +48,19 @@ public class StratumServer implements EthHashObserver {
private NetServer server;
public StratumServer(
final Vertx vertx, final int port, final String networkInterface, final String extraNonce) {
final Vertx vertx,
final MiningCoordinator miningCoordinator,
final int port,
final String networkInterface,
final String extraNonce) {
this.vertx = vertx;
this.port = port;
this.networkInterface = networkInterface;
protocols =
new StratumProtocol[] {new Stratum1Protocol(extraNonce), new Stratum1EthProxyProtocol()};
new StratumProtocol[] {
new Stratum1Protocol(extraNonce, miningCoordinator),
new Stratum1EthProxyProtocol(miningCoordinator)
};
}
public CompletableFuture<?> start() {

@ -16,6 +16,7 @@ package org.hyperledger.besu.ethereum.stratum;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.mainnet.EthHashSolverInputs;
import java.util.concurrent.atomic.AtomicBoolean;
@ -24,10 +25,20 @@ import java.util.concurrent.atomic.AtomicReference;
import io.vertx.core.buffer.Buffer;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
public class StratumConnectionTest {
@Mock MiningCoordinator miningCoordinator;
@Before
public void setup() {
miningCoordinator = Mockito.mock(MiningCoordinator.class);
}
@Test
public void testNoSuitableProtocol() {
AtomicBoolean called = new AtomicBoolean(false);
@ -42,7 +53,9 @@ public class StratumConnectionTest {
AtomicBoolean called = new AtomicBoolean(false);
StratumConnection conn =
new StratumConnection(
new StratumProtocol[] {new Stratum1Protocol("")}, () -> called.set(true), bytes -> {});
new StratumProtocol[] {new Stratum1Protocol("", miningCoordinator)},
() -> called.set(true),
bytes -> {});
conn.handleBuffer(Buffer.buffer("{}\n"));
assertThat(called.get()).isTrue();
}
@ -56,7 +69,9 @@ public class StratumConnectionTest {
StratumConnection conn =
new StratumConnection(
new StratumProtocol[] {new Stratum1Protocol("", () -> "abcd", () -> "abcd")},
new StratumProtocol[] {
new Stratum1Protocol("", miningCoordinator, () -> "abcd", () -> "abcd")
},
() -> called.set(true),
message::set);
conn.handleBuffer(
@ -82,7 +97,8 @@ public class StratumConnectionTest {
AtomicReference<String> message = new AtomicReference<>();
Stratum1Protocol protocol = new Stratum1Protocol("", () -> "abcd", () -> "abcd");
Stratum1Protocol protocol =
new Stratum1Protocol("", miningCoordinator, () -> "abcd", () -> "abcd");
StratumConnection conn =
new StratumConnection(
@ -115,4 +131,42 @@ public class StratumConnectionTest {
.isEqualTo(
"{\"jsonrpc\":\"2.0\",\"method\":\"mining.notify\",\"params\":[\"abcd\",\"0xdeadbeef\",\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"0x0000000000000000000000000000000000000000000000000000000000000003\",true],\"id\":null}\n");
}
@Test
public void testStratum1SubmitHashrate() {
AtomicBoolean called = new AtomicBoolean(false);
AtomicReference<String> message = new AtomicReference<>();
Stratum1Protocol protocol =
new Stratum1Protocol("", miningCoordinator, () -> "abcd", () -> "abcd");
Mockito.when(miningCoordinator.submitHashRate("0x02", 3L)).thenReturn(true);
StratumConnection conn =
new StratumConnection(
new StratumProtocol[] {protocol}, () -> called.set(true), message::set);
conn.handleBuffer(
Buffer.buffer(
"{"
+ " \"id\": 23,"
+ " \"method\": \"mining.subscribe\", "
+ " \"params\": [ "
+ " \"MinerName/1.0.0\", \"EthereumStratum/1.0.0\" "
+ " ]"
+ "}\n"));
conn.handleBuffer(
Buffer.buffer(
"{"
+ " \"id\": 23,"
+ " \"method\": \"eth_submitHashrate\", "
+ " \"params\": [ "
+ " \"0x03\",\"0x02\" "
+ " ]"
+ "}\n"));
assertThat(called.get()).isFalse();
assertThat(message.get()).isEqualTo("{\"jsonrpc\":\"2.0\",\"id\":23,\"result\":true}\n");
}
}

Loading…
Cancel
Save