Skip to content

Commit

Permalink
Add config option to clique to allow not creating empty blocks (#6082)
Browse files Browse the repository at this point in the history
Signed-off-by: Jason Frame <jason.frame@consensys.net>
  • Loading branch information
jframe committed Nov 2, 2023
1 parent 41b9575 commit 6a2d41f
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 14 deletions.
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;
}
}

0 comments on commit 6a2d41f

Please sign in to comment.