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

Add option to clique to skip creating empty blocks #6082

Merged
merged 13 commits into from
Nov 2, 2023
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- TraceService: return results for transactions in block [#6086](https://github.com/hyperledger/besu/pull/6086)
- New option `--min-priority-fee` that sets the minimum priority fee a transaction must meet to be selected for a block. [#6080](https://github.com/hyperledger/besu/pull/6080) [#6083](https://github.com/hyperledger/besu/pull/6083)
- Implement new `miner_setMinPriorityFee` and `miner_getMinPriorityFee` RPC methods [#6080](https://github.com/hyperledger/besu/pull/6080)
- Clique config option `createemptyblocks` to not create empty blocks [#6082](https://github.com/hyperledger/besu/pull/6082)

### Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class CliqueBesuControllerBuilder extends BesuControllerBuilder {
private Address localAddress;
private EpochManager epochManager;
private long secondsBetweenBlocks;
private boolean createEmptyBlocks = true;
private final BlockInterface blockInterface = new CliqueBlockInterface();

@Override
Expand All @@ -61,6 +62,7 @@ protected void prepForBuild() {
final CliqueConfigOptions cliqueConfig = configOptionsSupplier.get().getCliqueConfigOptions();
final long blocksPerEpoch = cliqueConfig.getEpochLength();
secondsBetweenBlocks = cliqueConfig.getBlockPeriodSeconds();
createEmptyBlocks = cliqueConfig.getCreateEmptyBlocks();

epochManager = new EpochManager(blocksPerEpoch);
}
Expand Down Expand Up @@ -91,7 +93,8 @@ protected MiningCoordinator createMiningCoordinator(
protocolContext.getConsensusContext(CliqueContext.class).getValidatorProvider(),
localAddress,
secondsBetweenBlocks),
epochManager);
epochManager,
createEmptyBlocks);
final CliqueMiningCoordinator miningCoordinator =
new CliqueMiningCoordinator(
protocolContext.getBlockchain(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public void assertPostMergeScheduleForPostMergeExactlyAtTerminalDifficultyIfNotF
public void assertCliqueDetachedHeaderValidationPreMerge() {
BlockHeaderValidator cliqueValidator =
BlockHeaderValidationRulesetFactory.cliqueBlockHeaderValidator(
5L, new EpochManager(5L), Optional.of(FeeMarket.london(1L)), true)
5L, true, new EpochManager(5L), Optional.of(FeeMarket.london(1L)), true)
.build();
assertDetachedRulesForPostMergeBlocks(cliqueValidator);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class CliqueConfigOptions {

private static final long DEFAULT_EPOCH_LENGTH = 30_000;
private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 15;
private static final boolean DEFAULT_CREATE_EMPTY_BLOCKS = true;

private final ObjectNode cliqueConfigRoot;

Expand Down Expand Up @@ -59,13 +60,27 @@ public int getBlockPeriodSeconds() {
cliqueConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS);
}

/**
* Whether the creation of empty blocks is allowed.
*
* @return the create empty block status
*/
public boolean getCreateEmptyBlocks() {
return JsonUtil.getBoolean(cliqueConfigRoot, "createemptyblocks", DEFAULT_CREATE_EMPTY_BLOCKS);
}

/**
* As map.
*
* @return the map
*/
Map<String, Object> asMap() {
return ImmutableMap.of(
"epochLength", getEpochLength(), "blockPeriodSeconds", getBlockPeriodSeconds());
"epochLength",
getEpochLength(),
"blockPeriodSeconds",
getBlockPeriodSeconds(),
"createemptyblocks",
getCreateEmptyBlocks());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hyperledger.besu.config.MergeConfigOptions;
import org.hyperledger.besu.consensus.clique.headervalidationrules.CliqueDifficultyValidationRule;
import org.hyperledger.besu.consensus.clique.headervalidationrules.CliqueExtraDataValidationRule;
import org.hyperledger.besu.consensus.clique.headervalidationrules.CliqueNoEmptyBlockValidationRule;
import org.hyperledger.besu.consensus.clique.headervalidationrules.CoinbaseHeaderValidationRule;
import org.hyperledger.besu.consensus.clique.headervalidationrules.SignerRateLimitValidationRule;
import org.hyperledger.besu.consensus.clique.headervalidationrules.VoteValidationRule;
Expand Down Expand Up @@ -51,22 +52,29 @@ public class BlockHeaderValidationRulesetFactory {
* <p>Specifically the set of rules provided by this function are to be used for a Clique chain.
*
* @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks.
* @param createEmptyBlocks whether clique should allow the creation of empty blocks.
* @param epochManager an object which determines if a given block is an epoch block.
* @param baseFeeMarket an {@link Optional} wrapping {@link BaseFeeMarket} class if appropriate.
* @return the header validator.
*/
public static BlockHeaderValidator.Builder cliqueBlockHeaderValidator(
final long secondsBetweenBlocks,
final boolean createEmptyBlocks,
final EpochManager epochManager,
final Optional<BaseFeeMarket> baseFeeMarket) {
return cliqueBlockHeaderValidator(
secondsBetweenBlocks, epochManager, baseFeeMarket, MergeConfigOptions.isMergeEnabled());
secondsBetweenBlocks,
createEmptyBlocks,
epochManager,
baseFeeMarket,
MergeConfigOptions.isMergeEnabled());
}

/**
* Clique block header validator. Visible for testing.
*
* @param secondsBetweenBlocks the seconds between blocks
* @param createEmptyBlocks whether clique should allow the creation of empty blocks.
* @param epochManager the epoch manager
* @param baseFeeMarket the base fee market
* @param isMergeEnabled the is merge enabled
Expand All @@ -75,6 +83,7 @@ public static BlockHeaderValidator.Builder cliqueBlockHeaderValidator(
@VisibleForTesting
public static BlockHeaderValidator.Builder cliqueBlockHeaderValidator(
final long secondsBetweenBlocks,
final boolean createEmptyBlocks,
final EpochManager epochManager,
final Optional<BaseFeeMarket> baseFeeMarket,
final boolean isMergeEnabled) {
Expand All @@ -99,6 +108,10 @@ public static BlockHeaderValidator.Builder cliqueBlockHeaderValidator(
builder.addRule(new BaseFeeMarketBlockHeaderGasPriceValidationRule(baseFeeMarket.get()));
}

if (!createEmptyBlocks) {
builder.addRule(new CliqueNoEmptyBlockValidationRule());
}

var mixHashRule =
new ConstantFieldValidationRule<>("MixHash", BlockHeader::getMixHash, Hash.ZERO);
var voteValidationRule = new VoteValidationRule();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public static ProtocolSchedule create(
applyCliqueSpecificModifications(
epochManager,
cliqueConfig.getBlockPeriodSeconds(),
cliqueConfig.getCreateEmptyBlocks(),
localNodeAddress,
builder)),
privacyParameters,
Expand Down Expand Up @@ -107,16 +108,19 @@ public static ProtocolSchedule create(
private static ProtocolSpecBuilder applyCliqueSpecificModifications(
final EpochManager epochManager,
final long secondsBetweenBlocks,
final boolean createEmptyBlocks,
final Address localNodeAddress,
final ProtocolSpecBuilder specBuilder) {

return specBuilder
.blockHeaderValidatorBuilder(
baseFeeMarket ->
getBlockHeaderValidator(epochManager, secondsBetweenBlocks, baseFeeMarket))
getBlockHeaderValidator(
epochManager, secondsBetweenBlocks, createEmptyBlocks, baseFeeMarket))
.ommerHeaderValidatorBuilder(
baseFeeMarket ->
getBlockHeaderValidator(epochManager, secondsBetweenBlocks, baseFeeMarket))
getBlockHeaderValidator(
epochManager, secondsBetweenBlocks, createEmptyBlocks, baseFeeMarket))
.blockBodyValidatorBuilder(MainnetBlockBodyValidator::new)
.blockValidatorBuilder(MainnetProtocolSpecs.blockValidatorBuilder())
.blockImporterBuilder(MainnetBlockImporter::new)
Expand All @@ -128,11 +132,14 @@ private static ProtocolSpecBuilder applyCliqueSpecificModifications(
}

private static BlockHeaderValidator.Builder getBlockHeaderValidator(
final EpochManager epochManager, final long secondsBetweenBlocks, final FeeMarket feeMarket) {
final EpochManager epochManager,
final long secondsBetweenBlocks,
final boolean createEmptyBlocks,
final FeeMarket feeMarket) {
Optional<BaseFeeMarket> baseFeeMarket =
Optional.of(feeMarket).filter(FeeMarket::implementsBaseFee).map(BaseFeeMarket.class::cast);

return BlockHeaderValidationRulesetFactory.cliqueBlockHeaderValidator(
secondsBetweenBlocks, epochManager, baseFeeMarket);
secondsBetweenBlocks, createEmptyBlocks, epochManager, baseFeeMarket);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,23 @@
import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockScheduler;
import org.hyperledger.besu.ethereum.blockcreation.BlockMiner;
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.util.Subscribers;

import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** The Clique block miner. */
public class CliqueBlockMiner extends BlockMiner<CliqueBlockCreator> {
private static final Logger LOG = LoggerFactory.getLogger(CliqueBlockMiner.class);
private static final int WAIT_IN_MS_BETWEEN_EMPTY_BUILD_ATTEMPTS = 1_000;

private final Address localAddress;
private final boolean createEmptyBlocks;

/**
* Instantiates a new Clique block miner.
Expand All @@ -41,6 +48,7 @@ public class CliqueBlockMiner extends BlockMiner<CliqueBlockCreator> {
* @param scheduler the scheduler
* @param parentHeader the parent header
* @param localAddress the local address
* @param createEmptyBlocks whether clique should allow the creation of empty blocks.
*/
public CliqueBlockMiner(
final Function<BlockHeader, CliqueBlockCreator> blockCreator,
Expand All @@ -49,9 +57,11 @@ public CliqueBlockMiner(
final Subscribers<MinedBlockObserver> observers,
final AbstractBlockScheduler scheduler,
final BlockHeader parentHeader,
final Address localAddress) {
final Address localAddress,
final boolean createEmptyBlocks) {
super(blockCreator, protocolSchedule, protocolContext, observers, scheduler, parentHeader);
this.localAddress = localAddress;
this.createEmptyBlocks = createEmptyBlocks;
}

@Override
Expand All @@ -63,4 +73,18 @@ protected boolean mineBlock() throws InterruptedException {

return true; // terminate mining.
}

@Override
protected boolean shouldImportBlock(final Block block) throws InterruptedException {
if (createEmptyBlocks) {
return true;
}

final boolean isEmpty = block.getBody().getTransactions().isEmpty();
if (isEmpty) {
LOG.debug("Skipping creating empty block {}", block.toLogString());
Thread.sleep(WAIT_IN_MS_BETWEEN_EMPTY_BUILD_ATTEMPTS);
}
return !isEmpty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class CliqueMinerExecutor extends AbstractMinerExecutor<CliqueBlockMiner>
private final Address localAddress;
private final NodeKey nodeKey;
private final EpochManager epochManager;
private final boolean createEmptyBlocks;

/**
* Instantiates a new Clique miner executor.
Expand All @@ -58,6 +59,7 @@ public class CliqueMinerExecutor extends AbstractMinerExecutor<CliqueBlockMiner>
* @param miningParams the mining params
* @param blockScheduler the block scheduler
* @param epochManager the epoch manager
* @param createEmptyBlocks whether clique should allow the creation of empty blocks.
*/
public CliqueMinerExecutor(
final ProtocolContext protocolContext,
Expand All @@ -66,11 +68,13 @@ public CliqueMinerExecutor(
final NodeKey nodeKey,
final MiningParameters miningParams,
final AbstractBlockScheduler blockScheduler,
final EpochManager epochManager) {
final EpochManager epochManager,
final boolean createEmptyBlocks) {
super(protocolContext, protocolSchedule, transactionPool, miningParams, blockScheduler);
this.nodeKey = nodeKey;
this.localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey());
this.epochManager = epochManager;
this.createEmptyBlocks = createEmptyBlocks;
miningParams.setCoinbase(localAddress);
}

Expand Down Expand Up @@ -98,7 +102,8 @@ public CliqueBlockMiner createMiner(
observers,
blockScheduler,
parentHeader,
localAddress);
localAddress,
createEmptyBlocks);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -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.consensus.clique.headervalidationrules;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.DetachedBlockHeaderValidationRule;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** The No empty block validation rule. */
public class CliqueNoEmptyBlockValidationRule implements DetachedBlockHeaderValidationRule {

private static final Logger LOG = LoggerFactory.getLogger(CliqueNoEmptyBlockValidationRule.class);

/**
* Responsible for ensuring there are no empty transactions. This is used when createEmptyBlocks
* is false, to ensure that no empty blocks are created.
*
* @param header the block header to validate
* @param parent the block header corresponding to the parent of the header being validated.
* @return true if the transactionsRoot in the header is not the empty trie hash.
*/
@Override
public boolean validate(final BlockHeader header, final BlockHeader parent) {
final boolean hasTransactions = !header.getTransactionsRoot().equals(Hash.EMPTY_TRIE_HASH);
if (!hasTransactions) {
LOG.info(
"Invalid block header: {} has no transactions but create empty blocks is not enabled",
header.toLogString());
}
return hasTransactions;
}
}
Loading
Loading