mirror of
https://github.com/vacp2p/status-linea-besu.git
synced 2026-01-09 15:28:09 -05:00
Add config option to clique to allow not creating empty blocks (#6082)
Signed-off-by: Jason Frame <jason.frame@consensys.net>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -61,6 +62,7 @@ public class CliqueBesuControllerBuilder extends BesuControllerBuilder {
|
||||
final CliqueConfigOptions cliqueConfig = configOptionsSupplier.get().getCliqueConfigOptions();
|
||||
final long blocksPerEpoch = cliqueConfig.getEpochLength();
|
||||
secondsBetweenBlocks = cliqueConfig.getBlockPeriodSeconds();
|
||||
createEmptyBlocks = cliqueConfig.getCreateEmptyBlocks();
|
||||
|
||||
epochManager = new EpochManager(blocksPerEpoch);
|
||||
}
|
||||
@@ -91,7 +93,8 @@ public class CliqueBesuControllerBuilder extends BesuControllerBuilder {
|
||||
protocolContext.getConsensusContext(CliqueContext.class).getValidatorProvider(),
|
||||
localAddress,
|
||||
secondsBetweenBlocks),
|
||||
epochManager);
|
||||
epochManager,
|
||||
createEmptyBlocks);
|
||||
final CliqueMiningCoordinator miningCoordinator =
|
||||
new CliqueMiningCoordinator(
|
||||
protocolContext.getBlockchain(),
|
||||
|
||||
@@ -199,7 +199,7 @@ public class TransitionControllerBuilderTest {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -59,6 +60,15 @@ public class CliqueConfigOptions {
|
||||
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.
|
||||
*
|
||||
@@ -66,6 +76,11 @@ public class CliqueConfigOptions {
|
||||
*/
|
||||
Map<String, Object> asMap() {
|
||||
return ImmutableMap.of(
|
||||
"epochLength", getEpochLength(), "blockPeriodSeconds", getBlockPeriodSeconds());
|
||||
"epochLength",
|
||||
getEpochLength(),
|
||||
"blockPeriodSeconds",
|
||||
getBlockPeriodSeconds(),
|
||||
"createemptyblocks",
|
||||
getCreateEmptyBlocks());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.hyperledger.besu.ethereum.mainnet.AbstractGasLimitSpecificatio
|
||||
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;
|
||||
@@ -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
|
||||
@@ -75,6 +83,7 @@ public class BlockHeaderValidationRulesetFactory {
|
||||
@VisibleForTesting
|
||||
public static BlockHeaderValidator.Builder cliqueBlockHeaderValidator(
|
||||
final long secondsBetweenBlocks,
|
||||
final boolean createEmptyBlocks,
|
||||
final EpochManager epochManager,
|
||||
final Optional<BaseFeeMarket> baseFeeMarket,
|
||||
final boolean isMergeEnabled) {
|
||||
@@ -99,6 +108,10 @@ public class BlockHeaderValidationRulesetFactory {
|
||||
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();
|
||||
|
||||
@@ -78,6 +78,7 @@ public class CliqueProtocolSchedule {
|
||||
applyCliqueSpecificModifications(
|
||||
epochManager,
|
||||
cliqueConfig.getBlockPeriodSeconds(),
|
||||
cliqueConfig.getCreateEmptyBlocks(),
|
||||
localNodeAddress,
|
||||
builder)),
|
||||
privacyParameters,
|
||||
@@ -107,16 +108,19 @@ public class CliqueProtocolSchedule {
|
||||
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)
|
||||
@@ -128,11 +132,14 @@ public class CliqueProtocolSchedule {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,23 @@ import org.hyperledger.besu.ethereum.ProtocolContext;
|
||||
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.
|
||||
@@ -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,
|
||||
@@ -49,9 +57,11 @@ public class CliqueBlockMiner extends BlockMiner<CliqueBlockCreator> {
|
||||
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
|
||||
@@ -63,4 +73,18 @@ public class CliqueBlockMiner extends BlockMiner<CliqueBlockCreator> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
@@ -66,11 +68,13 @@ public class CliqueMinerExecutor extends AbstractMinerExecutor<CliqueBlockMiner>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -98,7 +102,8 @@ public class CliqueMinerExecutor extends AbstractMinerExecutor<CliqueBlockMiner>
|
||||
observers,
|
||||
blockScheduler,
|
||||
parentHeader,
|
||||
localAddress);
|
||||
localAddress,
|
||||
createEmptyBlocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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,0 +1,180 @@
|
||||
/*
|
||||
* Copyright Hyperledger Besu Contributors.
|
||||
*
|
||||
* 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.blockcreation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.hyperledger.besu.consensus.clique.CliqueContext;
|
||||
import org.hyperledger.besu.consensus.common.validator.ValidatorProvider;
|
||||
import org.hyperledger.besu.crypto.KeyPair;
|
||||
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.ethereum.ProtocolContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.DefaultBlockScheduler;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
|
||||
import org.hyperledger.besu.ethereum.core.Block;
|
||||
import org.hyperledger.besu.ethereum.core.BlockBody;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeader;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
|
||||
import org.hyperledger.besu.ethereum.core.BlockImporter;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
|
||||
import org.hyperledger.besu.ethereum.mainnet.BlockImportResult;
|
||||
import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule;
|
||||
import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
|
||||
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
|
||||
import org.hyperledger.besu.util.Subscribers;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CliqueBlockMinerTest {
|
||||
|
||||
@Test
|
||||
void doesNotMineBlockIfNoTransactionsWhenEmptyBlocksNotAllowed() throws InterruptedException {
|
||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
|
||||
|
||||
final Block blockToCreate =
|
||||
new Block(
|
||||
headerBuilder.buildHeader(), new BlockBody(Lists.newArrayList(), Lists.newArrayList()));
|
||||
|
||||
final ValidatorProvider validatorProvider = mock(ValidatorProvider.class);
|
||||
when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(List.of(Address.ZERO));
|
||||
|
||||
final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, null);
|
||||
final ProtocolContext protocolContext =
|
||||
new ProtocolContext(null, null, cliqueContext, Optional.empty());
|
||||
|
||||
final CliqueBlockCreator blockCreator = mock(CliqueBlockCreator.class);
|
||||
final Function<BlockHeader, CliqueBlockCreator> blockCreatorSupplier =
|
||||
(parentHeader) -> blockCreator;
|
||||
when(blockCreator.createBlock(anyLong()))
|
||||
.thenReturn(
|
||||
new BlockCreator.BlockCreationResult(blockToCreate, new TransactionSelectionResults()));
|
||||
|
||||
final BlockImporter blockImporter = mock(BlockImporter.class);
|
||||
final ProtocolSpec protocolSpec = mock(ProtocolSpec.class);
|
||||
|
||||
final ProtocolSchedule protocolSchedule = singleSpecSchedule(protocolSpec);
|
||||
|
||||
when(protocolSpec.getBlockImporter()).thenReturn(blockImporter);
|
||||
when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true));
|
||||
|
||||
final MinedBlockObserver observer = mock(MinedBlockObserver.class);
|
||||
final DefaultBlockScheduler scheduler = mock(DefaultBlockScheduler.class);
|
||||
when(scheduler.waitUntilNextBlockCanBeMined(any())).thenReturn(5L);
|
||||
final CliqueBlockMiner miner =
|
||||
new CliqueBlockMiner(
|
||||
blockCreatorSupplier,
|
||||
protocolSchedule,
|
||||
protocolContext,
|
||||
subscribersContaining(observer),
|
||||
scheduler,
|
||||
headerBuilder.buildHeader(),
|
||||
Address.ZERO,
|
||||
false); // parent header is arbitrary for the test.
|
||||
|
||||
final boolean result = miner.mineBlock();
|
||||
assertThat(result).isFalse();
|
||||
verify(blockImporter, never())
|
||||
.importBlock(protocolContext, blockToCreate, HeaderValidationMode.FULL);
|
||||
verify(observer, never()).blockMined(blockToCreate);
|
||||
}
|
||||
|
||||
@Test
|
||||
void minesBlockIfHasTransactionsWhenEmptyBlocksNotAllowed() throws InterruptedException {
|
||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
|
||||
|
||||
final TransactionTestFixture transactionTestFixture = new TransactionTestFixture();
|
||||
final KeyPair keyPair = SignatureAlgorithmFactory.getInstance().generateKeyPair();
|
||||
final Transaction transaction = transactionTestFixture.createTransaction(keyPair);
|
||||
|
||||
final Block blockToCreate =
|
||||
new Block(
|
||||
headerBuilder.buildHeader(), new BlockBody(List.of(transaction), Lists.newArrayList()));
|
||||
|
||||
final ValidatorProvider validatorProvider = mock(ValidatorProvider.class);
|
||||
when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(List.of(Address.ZERO));
|
||||
|
||||
final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, null);
|
||||
final ProtocolContext protocolContext =
|
||||
new ProtocolContext(null, null, cliqueContext, Optional.empty());
|
||||
|
||||
final CliqueBlockCreator blockCreator = mock(CliqueBlockCreator.class);
|
||||
final Function<BlockHeader, CliqueBlockCreator> blockCreatorSupplier =
|
||||
(parentHeader) -> blockCreator;
|
||||
when(blockCreator.createBlock(anyLong()))
|
||||
.thenReturn(
|
||||
new BlockCreator.BlockCreationResult(blockToCreate, new TransactionSelectionResults()));
|
||||
|
||||
final BlockImporter blockImporter = mock(BlockImporter.class);
|
||||
final ProtocolSpec protocolSpec = mock(ProtocolSpec.class);
|
||||
|
||||
final ProtocolSchedule protocolSchedule = singleSpecSchedule(protocolSpec);
|
||||
|
||||
when(protocolSpec.getBlockImporter()).thenReturn(blockImporter);
|
||||
when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true));
|
||||
|
||||
final MinedBlockObserver observer = mock(MinedBlockObserver.class);
|
||||
final DefaultBlockScheduler scheduler = mock(DefaultBlockScheduler.class);
|
||||
when(scheduler.waitUntilNextBlockCanBeMined(any())).thenReturn(5L);
|
||||
final CliqueBlockMiner miner =
|
||||
new CliqueBlockMiner(
|
||||
blockCreatorSupplier,
|
||||
protocolSchedule,
|
||||
protocolContext,
|
||||
subscribersContaining(observer),
|
||||
scheduler,
|
||||
headerBuilder.buildHeader(),
|
||||
Address.ZERO,
|
||||
false); // parent header is arbitrary for the test.
|
||||
|
||||
final boolean result = miner.mineBlock();
|
||||
assertThat(result).isTrue();
|
||||
verify(blockImporter).importBlock(protocolContext, blockToCreate, HeaderValidationMode.FULL);
|
||||
verify(observer).blockMined(blockToCreate);
|
||||
}
|
||||
|
||||
private static Subscribers<MinedBlockObserver> subscribersContaining(
|
||||
final MinedBlockObserver... observers) {
|
||||
final Subscribers<MinedBlockObserver> result = Subscribers.create();
|
||||
for (final MinedBlockObserver obs : observers) {
|
||||
result.subscribe(obs);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ProtocolSchedule singleSpecSchedule(final ProtocolSpec protocolSpec) {
|
||||
final DefaultProtocolSchedule protocolSchedule =
|
||||
new DefaultProtocolSchedule(Optional.of(BigInteger.valueOf(1234)));
|
||||
protocolSchedule.putBlockNumberMilestone(0, protocolSpec);
|
||||
return protocolSchedule;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,8 @@ public class CliqueMinerExecutorTest {
|
||||
proposerNodeKey,
|
||||
miningParameters,
|
||||
mock(CliqueBlockScheduler.class),
|
||||
new EpochManager(EPOCH_LENGTH));
|
||||
new EpochManager(EPOCH_LENGTH),
|
||||
true);
|
||||
|
||||
// NOTE: Passing in the *parent* block, so must be 1 less than EPOCH
|
||||
final BlockHeader header = blockHeaderBuilder.number(EPOCH_LENGTH - 1).buildHeader();
|
||||
@@ -147,7 +148,8 @@ public class CliqueMinerExecutorTest {
|
||||
proposerNodeKey,
|
||||
miningParameters,
|
||||
mock(CliqueBlockScheduler.class),
|
||||
new EpochManager(EPOCH_LENGTH));
|
||||
new EpochManager(EPOCH_LENGTH),
|
||||
true);
|
||||
|
||||
// Parent block was epoch, so the next block should contain no validators.
|
||||
final BlockHeader header = blockHeaderBuilder.number(EPOCH_LENGTH).buildHeader();
|
||||
@@ -181,7 +183,8 @@ public class CliqueMinerExecutorTest {
|
||||
proposerNodeKey,
|
||||
miningParameters,
|
||||
mock(CliqueBlockScheduler.class),
|
||||
new EpochManager(EPOCH_LENGTH));
|
||||
new EpochManager(EPOCH_LENGTH),
|
||||
true);
|
||||
|
||||
executor.setExtraData(modifiedVanityData);
|
||||
final Bytes extraDataBytes = executor.calculateExtraData(blockHeaderBuilder.buildHeader());
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright Hyperledger Besu Contributors.
|
||||
*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.hyperledger.besu.crypto.KeyPair;
|
||||
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
|
||||
import org.hyperledger.besu.datatypes.Hash;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeader;
|
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.core.TransactionTestFixture;
|
||||
import org.hyperledger.besu.ethereum.mainnet.BodyValidation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CliqueNoEmptyBlockValidationRuleTest {
|
||||
|
||||
@Test
|
||||
void headerWithNoTransactionsIsInvalid() {
|
||||
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
|
||||
|
||||
final CliqueNoEmptyBlockValidationRule noEmptyBlockRule =
|
||||
new CliqueNoEmptyBlockValidationRule();
|
||||
assertThat(noEmptyBlockRule.validate(blockHeader, null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void headerWithTransactionsIsValid() {
|
||||
final TransactionTestFixture transactionTestFixture = new TransactionTestFixture();
|
||||
final KeyPair keyPair = SignatureAlgorithmFactory.getInstance().generateKeyPair();
|
||||
final Transaction transaction = transactionTestFixture.createTransaction(keyPair);
|
||||
final Hash transactionRoot = BodyValidation.transactionsRoot(List.of(transaction));
|
||||
final BlockHeader blockHeader =
|
||||
new BlockHeaderTestFixture().transactionsRoot(transactionRoot).buildHeader();
|
||||
|
||||
final CliqueNoEmptyBlockValidationRule noEmptyBlockRule =
|
||||
new CliqueNoEmptyBlockValidationRule();
|
||||
assertThat(noEmptyBlockRule.validate(blockHeader, null)).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,10 @@ public class BlockMiner<M extends AbstractBlockCreator> implements Runnable {
|
||||
return blockCreator.createBlock(Optional.empty(), Optional.empty(), timestamp);
|
||||
}
|
||||
|
||||
protected boolean shouldImportBlock(final Block block) throws InterruptedException {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean mineBlock() throws InterruptedException {
|
||||
// Ensure the block is allowed to be mined - i.e. the timestamp on the new block is sufficiently
|
||||
// ahead of the parent, and still within allowable clock tolerance.
|
||||
@@ -140,6 +144,10 @@ public class BlockMiner<M extends AbstractBlockCreator> implements Runnable {
|
||||
"Block created, importing to local chain, block includes {} transactions",
|
||||
block.getBody().getTransactions().size());
|
||||
|
||||
if (!shouldImportBlock(block)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final BlockImporter importer =
|
||||
protocolSchedule.getByBlockHeader(block.getHeader()).getBlockImporter();
|
||||
final BlockImportResult blockImportResult =
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
package org.hyperledger.besu.ethereum.blockcreation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -39,6 +40,7 @@ import org.hyperledger.besu.util.Subscribers;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -132,6 +134,54 @@ public class BlockMinerTest {
|
||||
verify(observer, times(1)).blockMined(blockToCreate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blockValidationFailureBeforeImportDoesNotImportBlock() throws InterruptedException {
|
||||
final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture();
|
||||
|
||||
final Block blockToCreate =
|
||||
new Block(
|
||||
headerBuilder.buildHeader(), new BlockBody(Lists.newArrayList(), Lists.newArrayList()));
|
||||
|
||||
final ProtocolContext protocolContext = new ProtocolContext(null, null, null, Optional.empty());
|
||||
|
||||
final PoWBlockCreator blockCreator = mock(PoWBlockCreator.class);
|
||||
final Function<BlockHeader, PoWBlockCreator> blockCreatorSupplier =
|
||||
(parentHeader) -> blockCreator;
|
||||
when(blockCreator.createBlock(anyLong()))
|
||||
.thenReturn(new BlockCreationResult(blockToCreate, new TransactionSelectionResults()));
|
||||
|
||||
final BlockImporter blockImporter = mock(BlockImporter.class);
|
||||
final ProtocolSpec protocolSpec = mock(ProtocolSpec.class);
|
||||
final ProtocolSchedule protocolSchedule = singleSpecSchedule(protocolSpec);
|
||||
|
||||
when(protocolSpec.getBlockImporter()).thenReturn(blockImporter);
|
||||
when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true));
|
||||
|
||||
final MinedBlockObserver observer = mock(MinedBlockObserver.class);
|
||||
final DefaultBlockScheduler scheduler = mock(DefaultBlockScheduler.class);
|
||||
when(scheduler.waitUntilNextBlockCanBeMined(any())).thenReturn(5L);
|
||||
final AtomicInteger importValidationCount = new AtomicInteger();
|
||||
final BlockMiner<PoWBlockCreator> miner =
|
||||
new BlockMiner<>(
|
||||
blockCreatorSupplier,
|
||||
protocolSchedule,
|
||||
protocolContext,
|
||||
subscribersContaining(observer),
|
||||
scheduler,
|
||||
headerBuilder.buildHeader()) {
|
||||
@Override
|
||||
protected boolean shouldImportBlock(final Block block) {
|
||||
return importValidationCount.getAndIncrement() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
miner.run();
|
||||
assertThat(importValidationCount.get()).isEqualTo(2);
|
||||
verify(blockImporter, times(1))
|
||||
.importBlock(protocolContext, blockToCreate, HeaderValidationMode.FULL);
|
||||
verify(observer, times(1)).blockMined(blockToCreate);
|
||||
}
|
||||
|
||||
private static Subscribers<MinedBlockObserver> subscribersContaining(
|
||||
final MinedBlockObserver... observers) {
|
||||
final Subscribers<MinedBlockObserver> result = Subscribers.create();
|
||||
|
||||
Reference in New Issue
Block a user