mirror of https://github.com/hyperledger/besu
Add config option to clique to allow not creating empty blocks (#6082)
Signed-off-by: Jason Frame <jason.frame@consensys.net>pull/6135/head
parent
41b95758b4
commit
6a2d41fccc
@ -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; |
||||
} |
||||
} |
@ -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(); |
||||
} |
||||
} |
Loading…
Reference in new issue