Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If a PoS block creation repetition takes less than a configurable dur… #5048

Merged
merged 7 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ tests are updated to use EC private keys instead of RSA keys.
- Besu requires minimum Java 17 and up to build and run [#3320](https://github.com/hyperledger/besu/issues/3320)
- Add worldstate auto-heal mechanism [#5059](https://github.com/hyperledger/besu/pull/5059)
- Support for EIP-4895 - Withdrawals for Shanghai fork
- If a PoS block creation repetition takes less than a configurable duration, then waits before next repetition [#5048](https://github.com/hyperledger/besu/pull/5048)

### Bug Fixes
- Mitigation fix for stale bonsai code storage leading to log rolling issues on contract recreates [#4906](https://github.com/hyperledger/besu/pull/4906)
Expand Down
9 changes: 9 additions & 0 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,13 @@ private void validateMiningParams() {
throw new ParameterException(
this.commandLine, "--Xpos-block-creation-max-time must be positive and ≤ 12000");
}

if (unstableMiningOptions.getPosBlockCreationRepetitionMinDuration() <= 0
|| unstableMiningOptions.getPosBlockCreationRepetitionMinDuration() > 2000) {
throw new ParameterException(
this.commandLine,
"--Xpos-block-creation-repetition-min-duration must be positive and ≤ 2000");
}
}

/**
Expand Down Expand Up @@ -2271,6 +2278,8 @@ public BesuControllerBuilder getControllerBuilder() {
.powJobTimeToLive(unstableMiningOptions.getPowJobTimeToLive())
.maxOmmerDepth(unstableMiningOptions.getMaxOmmersDepth())
.posBlockCreationMaxTime(unstableMiningOptions.getPosBlockCreationMaxTime())
.posBlockCreationRepetitionMinDuration(
unstableMiningOptions.getPosBlockCreationRepetitionMinDuration())
.build())
.transactionPoolConfiguration(buildTransactionPoolConfiguration())
.nodeKey(new NodeKey(securityModule()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_MAX_OMMERS_DEPTH;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_POS_BLOCK_CREATION_MAX_TIME;
import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION;
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;
Expand Down Expand Up @@ -67,6 +68,15 @@ public class MiningOptions {
"Specifies the maximum time, in milliseconds, a PoS block creation jobs is allowed to run. Must be positive and ≤ 12000 (default: ${DEFAULT-VALUE} milliseconds)")
private final Long posBlockCreationMaxTime = DEFAULT_POS_BLOCK_CREATION_MAX_TIME;

@CommandLine.Option(
hidden = true,
names = {"--Xpos-block-creation-repetition-min-duration"},
description =
"If a PoS block creation repetition takes less than this duration, in milliseconds,"
+ " then it waits before next repetition. Must be positive and ≤ 2000 (default: ${DEFAULT-VALUE} milliseconds)")
private final Long posBlockCreationRepetitionMinDuration =
DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION;

/**
* Create mining options.
*
Expand Down Expand Up @@ -129,4 +139,13 @@ public int getMaxOmmersDepth() {
public Long getPosBlockCreationMaxTime() {
return posBlockCreationMaxTime;
}

/**
* Gets pos block creation repetition min duration.
*
* @return the pos block creation repetition min duration.
*/
public Long getPosBlockCreationRepetitionMinDuration() {
return posBlockCreationRepetitionMinDuration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,20 @@ private void tryToBuildBetterBlock(
private Void retryBlockCreationUntilUseful(
final PayloadIdentifier payloadIdentifier, final Supplier<BlockCreationResult> blockCreator) {

long lastStartAt;

while (!isBlockCreationCancelled(payloadIdentifier)) {
try {
recoverableBlockCreation(payloadIdentifier, blockCreator, System.currentTimeMillis());
} catch (final CancellationException ce) {
lastStartAt = System.currentTimeMillis();
recoverableBlockCreation(payloadIdentifier, blockCreator, lastStartAt);
final long lastDuration = System.currentTimeMillis() - lastStartAt;
final long waitBeforeRepetition =
miningParameters.getPosBlockCreationRepetitionMinDuration() - lastDuration;
if (waitBeforeRepetition > 0) {
LOG.debug("Waiting {}ms before repeating block creation", waitBeforeRepetition);
Thread.sleep(waitBeforeRepetition);
}
} catch (final CancellationException | InterruptedException ce) {
debugLambda(
LOG,
"Block creation for payload id {} has been cancelled, reason {}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import org.hyperledger.besu.testutil.TestClock;

import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -114,14 +115,20 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper {
private static final KeyPair KEYS1 =
new KeyPair(PRIVATE_KEY1, SIGNATURE_ALGORITHM.get().createPublicKey(PRIVATE_KEY1));
private static final Optional<List<Withdrawal>> EMPTY_WITHDRAWALS = Optional.empty();

private static final long REPETITION_MIN_DURATION = 100;
@Mock MergeContext mergeContext;
@Mock BackwardSyncContext backwardSyncContext;

@Mock ProposalBuilderExecutor proposalBuilderExecutor;
private final Address coinbase = genesisAllocations(getPosGenesisConfigFile()).findFirst().get();

@Spy
MiningParameters miningParameters = new MiningParameters.Builder().coinbase(coinbase).build();
MiningParameters miningParameters =
new MiningParameters.Builder()
.coinbase(coinbase)
.posBlockCreationRepetitionMinDuration(REPETITION_MIN_DURATION)
.build();

private MergeCoordinator coordinator;
private ProtocolContext protocolContext;
Expand Down Expand Up @@ -353,6 +360,51 @@ public void shouldContinueBuildingBlocksUntilFinalizeIsCalled()
}
}

@Test
public void blockCreationRepetitionShouldTakeNotLessThanRepetitionMinDuration()
throws InterruptedException, ExecutionException {
final AtomicLong retries = new AtomicLong(0);
final AtomicLong lastPutAt = new AtomicLong();
final List<Long> repetitionDurations = new ArrayList<>();

doAnswer(
invocation -> {
final long r = retries.getAndIncrement();
if (r == 0) {
// ignore first one, that is the empty block
} else if (r < 5) {
if (lastPutAt.get() > 0) {
// each repetition should take >= REPETITION_MIN_DURATION
repetitionDurations.add(System.currentTimeMillis() - lastPutAt.get());
}
lastPutAt.set(System.currentTimeMillis());
} else {
// finalize after 5 repetitions
coordinator.finalizeProposalById(
invocation.getArgument(0, PayloadIdentifier.class));
}
return null;
})
.when(mergeContext)
.putPayloadById(any(), any());

var payloadId =
coordinator.preparePayload(
genesisState.getBlock().getHeader(),
System.currentTimeMillis() / 1000,
Bytes32.ZERO,
suggestedFeeRecipient,
Optional.empty());

blockCreationTask.get();

verify(mergeContext, times(retries.intValue())).putPayloadById(eq(payloadId), any());

// check with a tolerance
assertThat(repetitionDurations)
.allSatisfy(d -> assertThat(d).isGreaterThanOrEqualTo(REPETITION_MIN_DURATION - 10));
}

@Test
public void shouldRetryBlockCreationOnRecoverableError()
throws InterruptedException, ExecutionException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public class MiningParameters {

public static final long DEFAULT_POS_BLOCK_CREATION_MAX_TIME = Duration.ofSeconds(12).toMillis();

public static final long DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION =
Duration.ofMillis(500).toMillis();

private final Optional<Address> coinbase;
private final Optional<AtomicLong> targetGasLimit;
private final Wei minTransactionGasPrice;
Expand All @@ -52,6 +55,7 @@ public class MiningParameters {
private final long powJobTimeToLive;
private final int maxOmmerDepth;
private final long posBlockCreationMaxTime;
private final long posBlockCreationRepetitionMinDuration;

private MiningParameters(
final Address coinbase,
Expand All @@ -69,7 +73,8 @@ private MiningParameters(
final long remoteSealersTimeToLive,
final long powJobTimeToLive,
final int maxOmmerDepth,
final long posBlockCreationMaxTime) {
final long posBlockCreationMaxTime,
final long posBlockCreationRepetitionMinDuration) {
this.coinbase = Optional.ofNullable(coinbase);
this.targetGasLimit = Optional.ofNullable(targetGasLimit).map(AtomicLong::new);
this.minTransactionGasPrice = minTransactionGasPrice;
Expand All @@ -86,6 +91,7 @@ private MiningParameters(
this.powJobTimeToLive = powJobTimeToLive;
this.maxOmmerDepth = maxOmmerDepth;
this.posBlockCreationMaxTime = posBlockCreationMaxTime;
this.posBlockCreationRepetitionMinDuration = posBlockCreationRepetitionMinDuration;
}

public Optional<Address> getCoinbase() {
Expand Down Expand Up @@ -152,6 +158,10 @@ public long getPosBlockCreationMaxTime() {
return posBlockCreationMaxTime;
}

public long getPosBlockCreationRepetitionMinDuration() {
return posBlockCreationRepetitionMinDuration;
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
Expand All @@ -170,7 +180,8 @@ public boolean equals(final Object o) {
&& remoteSealersTimeToLive == that.remoteSealersTimeToLive
&& remoteSealersLimit == that.remoteSealersLimit
&& powJobTimeToLive == that.powJobTimeToLive
&& posBlockCreationMaxTime == that.posBlockCreationMaxTime;
&& posBlockCreationMaxTime == that.posBlockCreationMaxTime
&& posBlockCreationRepetitionMinDuration == that.posBlockCreationRepetitionMinDuration;
}

@Override
Expand All @@ -189,7 +200,8 @@ public int hashCode() {
remoteSealersLimit,
remoteSealersTimeToLive,
powJobTimeToLive,
posBlockCreationMaxTime);
posBlockCreationMaxTime,
posBlockCreationRepetitionMinDuration);
}

@Override
Expand Down Expand Up @@ -227,6 +239,8 @@ public String toString() {
+ powJobTimeToLive
+ ", posBlockCreationMaxTime="
+ posBlockCreationMaxTime
+ ", posBlockCreationRepetitionMinDuration="
+ posBlockCreationRepetitionMinDuration
+ '}';
}

Expand All @@ -249,6 +263,9 @@ public static class Builder {
private int maxOmmerDepth = DEFAULT_MAX_OMMERS_DEPTH;
private long posBlockCreationMaxTime = DEFAULT_POS_BLOCK_CREATION_MAX_TIME;

private long posBlockCreationRepetitionMinDuration =
DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION;

public Builder() {
// zero arg
}
Expand All @@ -273,6 +290,8 @@ public Builder(final MiningParameters existing) {
this.powJobTimeToLive = existing.getPowJobTimeToLive();
this.maxOmmerDepth = existing.getMaxOmmerDepth();
this.posBlockCreationMaxTime = existing.getPosBlockCreationMaxTime();
this.posBlockCreationRepetitionMinDuration =
existing.getPosBlockCreationRepetitionMinDuration();
}

public Builder coinbase(final Address address) {
Expand Down Expand Up @@ -355,6 +374,12 @@ public Builder posBlockCreationMaxTime(final long posBlockCreationMaxTime) {
return this;
}

public Builder posBlockCreationRepetitionMinDuration(
final long posBlockCreationRepetitionMinDuration) {
this.posBlockCreationRepetitionMinDuration = posBlockCreationRepetitionMinDuration;
return this;
}

public MiningParameters build() {
return new MiningParameters(
coinbase,
Expand All @@ -372,7 +397,8 @@ public MiningParameters build() {
remoteSealersTimeToLive,
powJobTimeToLive,
maxOmmerDepth,
posBlockCreationMaxTime);
posBlockCreationMaxTime,
posBlockCreationRepetitionMinDuration);
}
}
}