Add support for mining ommers, up to 8 blocks behind the head job (#2576)

* Add support for mining ommers, up to 8 blocks behind the head job

Signed-off-by: Antoine Toulme <antoine@lunar-ocean.com>

* Add a CLI option to set PoW jobs TTL

Signed-off-by: Antoine Toulme <antoine@lunar-ocean.com>
pull/2584/head
Antoine Toulme 3 years ago committed by GitHub
parent d318ce2aa8
commit ea623a3c2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  2. 12
      besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MiningOptions.java
  3. 4
      besu/src/main/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommand.java
  4. 3
      besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java
  5. 8
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java
  6. 12
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java
  7. 6
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java
  8. 1
      ethereum/core/build.gradle
  9. 22
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java
  10. 51
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PoWSolver.java
  11. 363
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PoWSolverTest.java
  12. 6
      ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java
  13. 1
      gradle/versions.gradle

@ -1722,7 +1722,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
Optional.empty(),
minBlockOccupancyRatio,
unstableMiningOptions.getRemoteSealersLimit(),
unstableMiningOptions.getRemoteSealersTimeToLive()))
unstableMiningOptions.getRemoteSealersTimeToLive(),
unstableMiningOptions.getPowJobTimeToLive()))
.transactionPoolConfiguration(buildTransactionPoolConfiguration())
.nodeKey(buildNodeKey())
.metricsSystem(metricsSystem.get())

@ -14,6 +14,7 @@
*/
package org.hyperledger.besu.cli.options.unstable;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_POW_JOB_TTL;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_LIMIT;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_TTL;
@ -35,6 +36,13 @@ public class MiningOptions {
"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;
@CommandLine.Option(
hidden = true,
names = {"--Xminer-pow-job-ttl"},
description =
"Specifies the time PoW jobs are kept in cache and will accept a solution from miners (default: ${DEFAULT-VALUE} milliseconds)")
private final Long powJobTimeToLive = DEFAULT_POW_JOB_TTL;
@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
@CommandLine.Option(
hidden = true,
@ -57,4 +65,8 @@ public class MiningOptions {
public String getStratumExtranonce() {
return stratumExtranonce;
}
public Long getPowJobTimeToLive() {
return powJobTimeToLive;
}
}

@ -16,6 +16,7 @@ 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_POW_JOB_TTL;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_LIMIT;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_REMOTE_SEALERS_TTL;
@ -269,7 +270,8 @@ public class BlocksSubCommand implements Runnable {
Optional.of(new IncrementingNonceGenerator(0)),
0.0,
DEFAULT_REMOTE_SEALERS_LIMIT,
DEFAULT_REMOTE_SEALERS_TTL);
DEFAULT_REMOTE_SEALERS_TTL,
DEFAULT_POW_JOB_TTL);
}
private void importJsonBlocks(final BesuController controller, final Path path)

@ -54,7 +54,8 @@ public class MainnetBesuControllerBuilder extends BesuControllerBuilder {
MainnetBlockHeaderValidator.TIMESTAMP_TOLERANCE_S,
clock),
gasLimitCalculator,
epochCalculator);
epochCalculator,
miningParameters.getPowJobTimeToLive());
final PoWMiningCoordinator miningCoordinator =
new PoWMiningCoordinator(

@ -35,6 +35,7 @@ public class PoWMinerExecutor extends AbstractMinerExecutor<PoWBlockMiner> {
protected boolean stratumMiningEnabled;
protected final Iterable<Long> nonceGenerator;
protected final EpochCalculator epochCalculator;
protected final long powJobTimeToLive;
public PoWMinerExecutor(
final ProtocolContext protocolContext,
@ -43,7 +44,8 @@ public class PoWMinerExecutor extends AbstractMinerExecutor<PoWBlockMiner> {
final MiningParameters miningParams,
final AbstractBlockScheduler blockScheduler,
final GasLimitCalculator gasLimitCalculator,
final EpochCalculator epochCalculator) {
final EpochCalculator epochCalculator,
final long powJobTimeToLive) {
super(
protocolContext,
protocolSchedule,
@ -54,6 +56,7 @@ public class PoWMinerExecutor extends AbstractMinerExecutor<PoWBlockMiner> {
this.coinbase = miningParams.getCoinbase();
this.nonceGenerator = miningParams.getNonceGenerator().orElse(new RandomNonceGenerator());
this.epochCalculator = epochCalculator;
this.powJobTimeToLive = powJobTimeToLive;
}
@Override
@ -78,7 +81,8 @@ public class PoWMinerExecutor extends AbstractMinerExecutor<PoWBlockMiner> {
protocolSchedule.getByBlockNumber(parentHeader.getNumber() + 1).getPoWHasher().get(),
stratumMiningEnabled,
ethHashObservers,
epochCalculator);
epochCalculator,
powJobTimeToLive);
final Function<BlockHeader, PoWBlockCreator> blockCreator =
(header) ->
new PoWBlockCreator(

@ -85,7 +85,8 @@ public class PoWBlockCreatorTest {
PoWHasher.ETHASH_LIGHT,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
final PendingTransactions pendingTransactions =
new PendingTransactions(
@ -144,7 +145,8 @@ public class PoWBlockCreatorTest {
PoWHasher.ETHASH_LIGHT,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
final PendingTransactions pendingTransactions =
new PendingTransactions(
@ -198,7 +200,8 @@ public class PoWBlockCreatorTest {
PoWHasher.ETHASH_LIGHT,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
final PendingTransactions pendingTransactions =
new PendingTransactions(
@ -268,7 +271,8 @@ public class PoWBlockCreatorTest {
PoWHasher.ETHASH_LIGHT,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
final PendingTransactions pendingTransactions =
new PendingTransactions(

@ -59,7 +59,8 @@ public class PoWMinerExecutorTest {
miningParameters,
new DefaultBlockScheduler(1, 10, TestClock.fixed()),
GasLimitCalculator.constant(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
assertThatExceptionOfType(CoinbaseNotSetException.class)
.isThrownBy(() -> executor.startAsyncMining(Subscribers.create(), Subscribers.none(), null))
@ -88,7 +89,8 @@ public class PoWMinerExecutorTest {
miningParameters,
new DefaultBlockScheduler(1, 10, TestClock.fixed()),
GasLimitCalculator.constant(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> executor.setCoinbase(null))

@ -46,6 +46,7 @@ dependencies {
implementation 'net.java.dev.jna:jna'
implementation 'org.apache.logging.log4j:log4j-api'
implementation 'org.apache.tuweni:tuweni-bytes'
implementation 'org.apache.tuweni:tuweni-concurrent'
implementation 'org.apache.tuweni:tuweni-units'
implementation 'org.hyperledger.besu:bls12-381'
implementation 'org.immutables:value-annotations'

@ -26,6 +26,8 @@ public class MiningParameters {
public static final long DEFAULT_REMOTE_SEALERS_TTL = Duration.ofMinutes(10).toMinutes();
public static final long DEFAULT_POW_JOB_TTL = Duration.ofMinutes(5).toMillis();
private final Optional<Address> coinbase;
private final Wei minTransactionGasPrice;
private final Bytes extraData;
@ -38,6 +40,7 @@ public class MiningParameters {
private final Double minBlockOccupancyRatio;
private final int remoteSealersLimit;
private final long remoteSealersTimeToLive;
private final long powJobTimeToLive;
public MiningParameters(
final Address coinbase,
@ -56,7 +59,8 @@ public class MiningParameters {
Optional.empty(),
0.8,
DEFAULT_REMOTE_SEALERS_LIMIT,
DEFAULT_REMOTE_SEALERS_TTL);
DEFAULT_REMOTE_SEALERS_TTL,
DEFAULT_POW_JOB_TTL);
}
public MiningParameters(
@ -71,7 +75,8 @@ public class MiningParameters {
final Optional<Iterable<Long>> maybeNonceGenerator,
final Double minBlockOccupancyRatio,
final int remoteSealersLimit,
final long remoteSealersTimeToLive) {
final long remoteSealersTimeToLive,
final long powJobTimeToLive) {
this.coinbase = Optional.ofNullable(coinbase);
this.minTransactionGasPrice = minTransactionGasPrice;
this.extraData = extraData;
@ -84,6 +89,7 @@ public class MiningParameters {
this.minBlockOccupancyRatio = minBlockOccupancyRatio;
this.remoteSealersLimit = remoteSealersLimit;
this.remoteSealersTimeToLive = remoteSealersTimeToLive;
this.powJobTimeToLive = powJobTimeToLive;
}
public Optional<Address> getCoinbase() {
@ -134,6 +140,10 @@ public class MiningParameters {
return remoteSealersTimeToLive;
}
public long getPowJobTimeToLive() {
return powJobTimeToLive;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@ -149,7 +159,8 @@ public class MiningParameters {
&& Objects.equals(stratumExtranonce, that.stratumExtranonce)
&& Objects.equals(minBlockOccupancyRatio, that.minBlockOccupancyRatio)
&& Objects.equals(remoteSealersTimeToLive, that.remoteSealersTimeToLive)
&& Objects.equals(remoteSealersLimit, that.remoteSealersLimit);
&& Objects.equals(remoteSealersLimit, that.remoteSealersLimit)
&& Objects.equals(powJobTimeToLive, that.powJobTimeToLive);
}
@Override
@ -165,7 +176,8 @@ public class MiningParameters {
stratumExtranonce,
minBlockOccupancyRatio,
remoteSealersLimit,
remoteSealersTimeToLive);
remoteSealersTimeToLive,
powJobTimeToLive);
}
@Override
@ -197,6 +209,8 @@ public class MiningParameters {
+ remoteSealersLimit
+ ", remoteSealersTimeToLive="
+ remoteSealersTimeToLive
+ ", powJobTimeToLive="
+ powJobTimeToLive
+ '}';
}
}

@ -26,11 +26,15 @@ import java.util.concurrent.TimeUnit;
import com.google.common.base.Stopwatch;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.concurrent.ExpiringMap;
import org.apache.tuweni.units.bigints.UInt256;
public class PoWSolver {
private static final int MAX_OMMER_DEPTH = 8;
private static final Logger LOG = getLogger();
private final long powJobTimeToLive;
public static class PoWSolverJob {
@ -80,30 +84,35 @@ public class PoWSolver {
private final Subscribers<PoWObserver> ethHashObservers;
private final EpochCalculator epochCalculator;
private volatile Optional<PoWSolverJob> currentJob = Optional.empty();
private final ExpiringMap<Bytes, PoWSolverJob> currentJobs = new ExpiringMap<>();
public PoWSolver(
final Iterable<Long> nonceGenerator,
final PoWHasher poWHasher,
final Boolean stratumMiningEnabled,
final Subscribers<PoWObserver> ethHashObservers,
final EpochCalculator epochCalculator) {
final EpochCalculator epochCalculator,
final long powJobTimeToLive) {
this.nonceGenerator = nonceGenerator;
this.poWHasher = poWHasher;
this.stratumMiningEnabled = stratumMiningEnabled;
this.ethHashObservers = ethHashObservers;
ethHashObservers.forEach(observer -> observer.setSubmitWorkCallback(this::submitSolution));
this.epochCalculator = epochCalculator;
this.powJobTimeToLive = powJobTimeToLive;
}
public PoWSolution solveFor(final PoWSolverJob job)
throws InterruptedException, ExecutionException {
currentJob = Optional.of(job);
currentJobs.put(
job.getInputs().getPrePowHash(), job, System.currentTimeMillis() + powJobTimeToLive);
if (stratumMiningEnabled) {
ethHashObservers.forEach(observer -> observer.newJob(job.inputs));
} else {
findValidNonce();
}
return currentJob.get().getSolution();
return job.getSolution();
}
private void findValidNonce() {
@ -149,22 +158,50 @@ public class PoWSolver {
public boolean submitSolution(final PoWSolution solution) {
final Optional<PoWSolverJob> jobSnapshot = currentJob;
PoWSolverJob jobToTestWith = null;
if (jobSnapshot.isEmpty()) {
LOG.debug("No current job, rejecting miner work");
return false;
}
final PoWSolverJob job = jobSnapshot.get();
final PoWSolverInputs inputs = job.getInputs();
if (!inputs.getPrePowHash().equals(solution.getPowHash())) {
LOG.debug("Miner's solution does not match current job");
PoWSolverJob headJob = jobSnapshot.get();
if (headJob.getInputs().getPrePowHash().equals(solution.getPowHash())) {
LOG.debug("Head job matches the solution pow hash {}", solution.getPowHash());
jobToTestWith = headJob;
}
if (jobToTestWith == null) {
PoWSolverJob ommerCandidate = currentJobs.get(solution.getPowHash());
if (ommerCandidate != null) {
long distanceToHead =
headJob.getInputs().getBlockNumber() - ommerCandidate.getInputs().getBlockNumber();
LOG.debug(
"Found ommer candidate {} with block number {}, distance to head {}",
solution.getPowHash(),
ommerCandidate.getInputs().getBlockNumber(),
distanceToHead);
if (distanceToHead <= MAX_OMMER_DEPTH) {
jobToTestWith = ommerCandidate;
} else {
LOG.debug("Discarded ommer solution as too far from head {}", distanceToHead);
}
}
}
if (jobToTestWith == null) {
LOG.debug("No matching job found for hash {}, rejecting solution", solution.getPowHash());
return false;
}
if (jobToTestWith.isDone()) {
LOG.debug("Matching job found for hash {}, but already solved", solution.getPowHash());
return false;
}
final PoWSolverInputs inputs = jobToTestWith.getInputs();
final Optional<PoWSolution> calculatedSolution = testNonce(inputs, solution.getNonce());
if (calculatedSolution.isPresent()) {
LOG.debug("Accepting a solution from a miner");
currentJob.get().solvedWith(calculatedSolution.get());
currentJobs.remove(solution.getPowHash());
jobToTestWith.solvedWith(calculatedSolution.get());
return true;
}
LOG.debug("Rejecting a solution from a miner");

@ -26,8 +26,10 @@ import org.hyperledger.besu.util.Subscribers;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
@ -47,7 +49,8 @@ public class PoWSolverTest {
PoWHasher.ETHASH_LIGHT,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
assertThat(solver.hashesPerSecond()).isEqualTo(Optional.empty());
assertThat(solver.getWorkDefinition()).isEqualTo(Optional.empty());
@ -80,7 +83,8 @@ public class PoWSolverTest {
hasher,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
final Stopwatch operationTimer = Stopwatch.createStarted();
final PoWSolverInputs inputs = new PoWSolverInputs(UInt256.ONE, Bytes.EMPTY, 5);
@ -145,7 +149,8 @@ public class PoWSolverTest {
PoWHasher.ETHASH_LIGHT,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
PoWSolution soln = solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(firstInputs));
assertThat(soln.getMixHash()).isEqualTo(expectedFirstOutput.getMixHash());
@ -153,4 +158,356 @@ public class PoWSolverTest {
soln = solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(secondInputs));
assertThat(soln.getMixHash()).isEqualTo(expectedSecondOutput.getMixHash());
}
@Test
public void canAcceptSolutionsSerially()
throws InterruptedException, ExecutionException, TimeoutException {
final PoWSolverInputs firstInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
15, -114, -104, 87, -95, -36, -17, 120, 52, 1, 124, 61, -6, -66, 78, -27, -57,
118, -18, -64, -103, -91, -74, -121, 42, 91, -14, -98, 101, 86, -43, -51
}),
1);
final PoWSolution expectedFirstOutput =
new PoWSolution(
-6506032554016940193L,
Hash.fromHexString(
"0xc5e3c33c86d64d0641dd3c86e8ce4628fe0aac0ef7b4c087c5fcaa45d5046d90"),
null,
firstInputs.getPrePowHash());
final PoWSolverInputs secondInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
-62, 121, -81, -31, 55, -38, -68, 102, -32, 95, -94, -83, -3, -48, -122, -68, 14,
-125, -83, 84, -55, -23, -123, -57, -34, 25, -89, 23, 64, -9, -114, -3,
}),
2);
final PoWSolution expectedSecondOutput =
new PoWSolution(
8855952212886464488L,
Hash.fromHexString(
"0x2adb0f375dd2d528689cb9e00473c3c9692737109d547130feafbefb2c6c5244"),
null,
secondInputs.getPrePowHash());
// Nonces need to have a 0L inserted, as it is a "wasted" nonce in the solver.
final PoWSolver solver =
new PoWSolver(
Lists.newArrayList(expectedFirstOutput.getNonce(), 0L, expectedSecondOutput.getNonce()),
PoWHasher.ETHASH_LIGHT,
true,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator(),
1000);
CompletableFuture<PoWSolution> soln1 = new CompletableFuture<>();
CompletableFuture<PoWSolution> soln2 = new CompletableFuture<>();
Thread powThread =
new Thread(
() -> {
try {
soln1.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(firstInputs)));
soln2.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(secondInputs)));
} catch (Exception e) {
soln1.completeExceptionally(e);
soln2.completeExceptionally(e);
}
});
powThread.start();
Thread.sleep(200);
assertThat(solver.submitSolution(expectedFirstOutput)).isTrue();
Thread.sleep(200);
assertThat(solver.submitSolution(expectedSecondOutput)).isTrue();
PoWSolution result1 = soln1.get(1, TimeUnit.SECONDS);
PoWSolution result2 = soln2.get(1, TimeUnit.SECONDS);
assertThat(result1.getMixHash()).isEqualTo(expectedFirstOutput.getMixHash());
assertThat(result2.getMixHash()).isEqualTo(expectedSecondOutput.getMixHash());
}
@Test
public void canAcceptSolutionsForMultipleJobs()
throws InterruptedException, ExecutionException, TimeoutException {
final PoWSolverInputs firstInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
15, -114, -104, 87, -95, -36, -17, 120, 52, 1, 124, 61, -6, -66, 78, -27, -57,
118, -18, -64, -103, -91, -74, -121, 42, 91, -14, -98, 101, 86, -43, -51
}),
1);
final PoWSolution expectedFirstOutput =
new PoWSolution(
-6506032554016940193L,
Hash.fromHexString(
"0xc5e3c33c86d64d0641dd3c86e8ce4628fe0aac0ef7b4c087c5fcaa45d5046d90"),
null,
firstInputs.getPrePowHash());
final PoWSolverInputs secondInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
-62, 121, -81, -31, 55, -38, -68, 102, -32, 95, -94, -83, -3, -48, -122, -68, 14,
-125, -83, 84, -55, -23, -123, -57, -34, 25, -89, 23, 64, -9, -114, -3,
}),
2);
final PoWSolution expectedSecondOutput =
new PoWSolution(
8855952212886464488L,
Hash.fromHexString(
"0x2adb0f375dd2d528689cb9e00473c3c9692737109d547130feafbefb2c6c5244"),
null,
secondInputs.getPrePowHash());
// Nonces need to have a 0L inserted, as it is a "wasted" nonce in the solver.
final PoWSolver solver =
new PoWSolver(
Lists.newArrayList(expectedFirstOutput.getNonce(), 0L, expectedSecondOutput.getNonce()),
PoWHasher.ETHASH_LIGHT,
true,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator(),
1000);
CompletableFuture<PoWSolution> soln1 = new CompletableFuture<>();
CompletableFuture<PoWSolution> soln2 = new CompletableFuture<>();
Thread powThread1 =
new Thread(
() -> {
try {
soln1.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(firstInputs)));
} catch (Exception e) {
soln1.completeExceptionally(e);
soln2.completeExceptionally(e);
}
});
powThread1.start();
Thread powThread2 =
new Thread(
() -> {
try {
soln2.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(secondInputs)));
} catch (Exception e) {
soln1.completeExceptionally(e);
soln2.completeExceptionally(e);
}
});
powThread2.start();
Thread.sleep(200);
assertThat(solver.submitSolution(expectedFirstOutput)).isTrue();
Thread.sleep(200);
assertThat(solver.submitSolution(expectedSecondOutput)).isTrue();
PoWSolution result1 = soln1.get(1, TimeUnit.SECONDS);
PoWSolution result2 = soln2.get(1, TimeUnit.SECONDS);
assertThat(result1.getMixHash()).isEqualTo(expectedFirstOutput.getMixHash());
assertThat(result2.getMixHash()).isEqualTo(expectedSecondOutput.getMixHash());
}
@Test
public void canAcceptAtMostOneSolution()
throws InterruptedException, ExecutionException, TimeoutException {
final PoWSolverInputs firstInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
15, -114, -104, 87, -95, -36, -17, 120, 52, 1, 124, 61, -6, -66, 78, -27, -57,
118, -18, -64, -103, -91, -74, -121, 42, 91, -14, -98, 101, 86, -43, -51
}),
1);
final PoWSolution expectedFirstOutput =
new PoWSolution(
-6506032554016940193L,
Hash.fromHexString(
"0xc5e3c33c86d64d0641dd3c86e8ce4628fe0aac0ef7b4c087c5fcaa45d5046d90"),
null,
firstInputs.getPrePowHash());
final PoWSolverInputs secondInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
-62, 121, -81, -31, 55, -38, -68, 102, -32, 95, -94, -83, -3, -48, -122, -68, 14,
-125, -83, 84, -55, -23, -123, -57, -34, 25, -89, 23, 64, -9, -114, -3,
}),
2);
final PoWSolution expectedSecondOutput =
new PoWSolution(
8855952212886464488L,
Hash.fromHexString(
"0x2adb0f375dd2d528689cb9e00473c3c9692737109d547130feafbefb2c6c5244"),
null,
secondInputs.getPrePowHash());
// Nonces need to have a 0L inserted, as it is a "wasted" nonce in the solver.
final PoWSolver solver =
new PoWSolver(
Lists.newArrayList(expectedFirstOutput.getNonce(), 0L, expectedSecondOutput.getNonce()),
PoWHasher.ETHASH_LIGHT,
true,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator(),
1000);
CompletableFuture<PoWSolution> soln1 = new CompletableFuture<>();
CompletableFuture<PoWSolution> soln2 = new CompletableFuture<>();
Thread powThread1 =
new Thread(
() -> {
try {
soln1.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(firstInputs)));
} catch (Exception e) {
soln1.completeExceptionally(e);
soln2.completeExceptionally(e);
}
});
powThread1.start();
Thread powThread2 =
new Thread(
() -> {
try {
soln2.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(secondInputs)));
} catch (Exception e) {
soln1.completeExceptionally(e);
soln2.completeExceptionally(e);
}
});
powThread2.start();
Thread.sleep(200);
assertThat(solver.submitSolution(expectedFirstOutput)).isTrue();
Thread.sleep(200);
assertThat(solver.submitSolution(expectedSecondOutput)).isTrue();
assertThat(solver.submitSolution(expectedSecondOutput)).isFalse();
assertThat(solver.submitSolution(expectedFirstOutput)).isFalse();
PoWSolution result1 = soln1.get(1, TimeUnit.SECONDS);
PoWSolution result2 = soln2.get(1, TimeUnit.SECONDS);
assertThat(result1.getMixHash()).isEqualTo(expectedFirstOutput.getMixHash());
assertThat(result2.getMixHash()).isEqualTo(expectedSecondOutput.getMixHash());
}
@Test
public void rejectsSolutionsForOldBlocks()
throws InterruptedException, ExecutionException, TimeoutException {
final PoWSolverInputs firstInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
15, -114, -104, 87, -95, -36, -17, 120, 52, 1, 124, 61, -6, -66, 78, -27, -57,
118, -18, -64, -103, -91, -74, -121, 42, 91, -14, -98, 101, 86, -43, -51
}),
1);
final PoWSolution expectedFirstOutput =
new PoWSolution(
-6506032554016940193L,
Hash.fromHexString(
"0xc5e3c33c86d64d0641dd3c86e8ce4628fe0aac0ef7b4c087c5fcaa45d5046d90"),
null,
firstInputs.getPrePowHash());
final PoWSolverInputs secondInputs =
new PoWSolverInputs(
UInt256.fromHexString(
"0x0083126e978d4fdf3b645a1cac083126e978d4fdf3b645a1cac083126e978d4f"),
Bytes.wrap(
new byte[] {
-62, 121, -81, -31, 55, -38, -68, 102, -32, 95, -94, -83, -3, -48, -122, -68, 14,
-125, -83, 84, -55, -23, -123, -57, -34, 25, -89, 23, 64, -9, -114, -3,
}),
10);
final PoWSolution expectedSecondOutput =
new PoWSolution(
8855952212886464488L,
Hash.fromHexString(
"0x2adb0f375dd2d528689cb9e00473c3c9692737109d547130feafbefb2c6c5244"),
null,
secondInputs.getPrePowHash());
// Nonces need to have a 0L inserted, as it is a "wasted" nonce in the solver.
final PoWSolver solver =
new PoWSolver(
Lists.newArrayList(expectedFirstOutput.getNonce(), 0L, expectedSecondOutput.getNonce()),
PoWHasher.ETHASH_LIGHT,
true,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator(),
1000);
CompletableFuture<PoWSolution> soln1 = new CompletableFuture<>();
CompletableFuture<PoWSolution> soln2 = new CompletableFuture<>();
Thread powThread1 =
new Thread(
() -> {
try {
soln1.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(firstInputs)));
} catch (Exception e) {
soln1.completeExceptionally(e);
}
});
powThread1.start();
Thread.sleep(200);
Thread powThread2 =
new Thread(
() -> {
try {
soln2.complete(
solver.solveFor(PoWSolver.PoWSolverJob.createFromInputs(secondInputs)));
} catch (Exception e) {
soln2.completeExceptionally(e);
}
});
powThread2.start();
Thread.sleep(200);
// we solve the head job, and still keep it as our reference as the block with the highest
// number.
assertThat(solver.submitSolution(expectedSecondOutput)).isTrue();
Thread.sleep(200);
assertThat(solver.submitSolution(expectedFirstOutput)).isFalse();
PoWSolution result2 = soln2.get(1, TimeUnit.SECONDS);
assertThat(result2.getMixHash()).isEqualTo(expectedSecondOutput.getMixHash());
powThread1.interrupt();
}
}

@ -168,13 +168,15 @@ public class RetestethContext {
NO_WORK_HASHER,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator())
new EpochCalculator.DefaultEpochCalculator(),
1000)
: new PoWSolver(
nonceGenerator,
PoWHasher.ETHASH_LIGHT,
false,
Subscribers.none(),
new EpochCalculator.DefaultEpochCalculator());
new EpochCalculator.DefaultEpochCalculator(),
1000);
blockReplay =
new BlockReplay(

@ -102,6 +102,7 @@ dependencyManagement {
dependency 'org.apache.tuweni:tuweni-bytes:2.0.0'
dependency 'org.apache.tuweni:tuweni-config:2.0.0'
dependency 'org.apache.tuweni:tuweni-concurrent:2.0.0'
dependency 'org.apache.tuweni:tuweni-crypto:2.0.0'
dependency 'org.apache.tuweni:tuweni-devp2p:2.0.0'
dependency 'org.apache.tuweni:tuweni-dns-discovery:2.0.0'

Loading…
Cancel
Save