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
Prev Previous commit
Next Next commit
Header validation to ensure no empty blocks when createEmptyBlocks is…
… false

Signed-off-by: Jason Frame <jason.frame@consensys.net>
  • Loading branch information
jframe committed Oct 27, 2023
commit 2aca68faf6a2804b757a19e7f1812409c8c58126
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, false, 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 @@ -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 @@ -57,10 +58,15 @@ public class BlockHeaderValidationRulesetFactory {
*/
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());
}

/**
Expand All @@ -75,6 +81,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 +106,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
@@ -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.getNumber());
siladu marked this conversation as resolved.
Show resolved Hide resolved
}
return hasTransactions;
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}