diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/WaitUtils.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/WaitUtils.java index ea76875aeaa..632e6733384 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/WaitUtils.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/WaitUtils.java @@ -21,7 +21,7 @@ public class WaitUtils { public static void waitFor(final ThrowingRunnable condition) { - waitFor(30, condition); + waitFor(40, condition); } public static void waitFor(final int timeout, final ThrowingRunnable condition) { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/QuorumIBFTMigrationTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/QuorumIBFTMigrationTest.java new file mode 100644 index 00000000000..5ec0dd6374d --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/QuorumIBFTMigrationTest.java @@ -0,0 +1,127 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * 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.tests.acceptance; + +import static org.apache.logging.log4j.util.LoaderUtil.getClassLoader; + +import org.hyperledger.besu.tests.acceptance.bft.BftAcceptanceTestParameterization; +import org.hyperledger.besu.tests.acceptance.bft.ParameterizedBftTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class QuorumIBFTMigrationTest extends ParameterizedBftTestBase { + + private static final Logger LOG = LoggerFactory.getLogger(QuorumIBFTMigrationTest.class); + + public static void copyKeyFilesToNodeDataDirs(final BesuNode... nodes) throws IOException { + for (BesuNode node : nodes) { + copyKeyFile(node, "key"); + copyKeyFile(node, "key.pub"); + } + } + + private static void copyKeyFile(final BesuNode node, final String keyFileName) + throws IOException { + String resourceFileName = node.getName() + keyFileName; + try (InputStream keyFileStream = getClassLoader().getResourceAsStream(resourceFileName)) { + if (keyFileStream == null) { + throw new IOException("Resource not found: " + resourceFileName); + } + Path targetPath = node.homeDirectory().resolve(keyFileName); + Files.createDirectories(targetPath.getParent()); + Files.copy(keyFileStream, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + } + + public static void runBesuCommand(final Path dataPath) throws IOException, InterruptedException { + ProcessBuilder processBuilder = + new ProcessBuilder( + "../../build/install/besu/bin/besu", + "--genesis-file", + "src/test/resources/qbft/qbft.json", + "--data-path", + dataPath.toString(), + "--data-storage-format", + "FOREST", + "blocks", + "import", + "src/test/resources/ibft.blocks"); + + processBuilder.directory(new File(System.getProperty("user.dir"))); + processBuilder.inheritIO(); // This will redirect the output to the console + + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + if (exitCode == 0) { + System.out.println("Import command executed successfully."); + } else { + throw new RuntimeException("Import command execution failed with exit code: " + exitCode); + } + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldImportIBFTBlocksAndTransitionToQBFT( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + + if ("ibft2".equals(testName)) { + LOG.info("Skipping test: " + testName); + return; + } + + setUp(testName, nodeFactory); + + // Create a mix of Bonsai and Forest DB nodes + final BesuNode minerNode1 = nodeFactory.createForestNodeFixedPort(besu, "miner1"); + final BesuNode minerNode2 = nodeFactory.createForestNodeFixedPort(besu, "miner2"); + final BesuNode minerNode3 = nodeFactory.createForestNodeFixedPort(besu, "miner3"); + final BesuNode minerNode4 = nodeFactory.createForestNodeFixedPort(besu, "miner4"); + final BesuNode minerNode5 = nodeFactory.createForestNodeFixedPort(besu, "miner5"); + + // Copy key files to the node datadirs + // Use the key files saved in resources directory + copyKeyFilesToNodeDataDirs(minerNode1, minerNode2, minerNode3, minerNode4, minerNode5); + + // start one node and import blocks from import file + // Use import file, genesis saved in resources directory + + runBesuCommand(minerNode1.homeDirectory()); + + // After the import is done, start the rest of the nodes using the same genesis and respective + // node keys + + cluster.start(minerNode1, minerNode2, minerNode3, minerNode4, minerNode5); + + // Check that the chain is progressing as expected + cluster.verify(blockchain.reachesHeight(minerNode2, 1, 120)); + } + + @Override + public void tearDownAcceptanceTestBase() { + cluster.stop(); + super.tearDownAcceptanceTestBase(); + } +} diff --git a/acceptance-tests/tests/src/test/resources/ibft.blocks b/acceptance-tests/tests/src/test/resources/ibft.blocks new file mode 100755 index 00000000000..3e04a5441d0 Binary files /dev/null and b/acceptance-tests/tests/src/test/resources/ibft.blocks differ diff --git a/acceptance-tests/tests/src/test/resources/miner1key b/acceptance-tests/tests/src/test/resources/miner1key new file mode 100644 index 00000000000..cd51abdc871 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner1key @@ -0,0 +1 @@ +0a46b91fe0c770a4355d1fec9ccd72d39264f46a74ed67a69a12ed4c265aa768 \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner1key.pub b/acceptance-tests/tests/src/test/resources/miner1key.pub new file mode 100644 index 00000000000..bd7912f9ca8 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner1key.pub @@ -0,0 +1 @@ +8093fb3200c783555ed487b8b5210ef3369b062a1f3ce5762d83d7a62205693d1e4f253e840ca48ec98d8f20c5b41bbbd43f34f87a1f68324ab51afe73732b96 \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner2key b/acceptance-tests/tests/src/test/resources/miner2key new file mode 100644 index 00000000000..225990f0449 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner2key @@ -0,0 +1 @@ +17c2aacfdf1f6defde20e6ae7132c6d3991e758af3799a307a75b38135678a48 \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner2key.pub b/acceptance-tests/tests/src/test/resources/miner2key.pub new file mode 100644 index 00000000000..5a93124a188 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner2key.pub @@ -0,0 +1 @@ +d81f65976ccc44c5e7e6ca859c5ede06b0f484f72c52d35dd8f7bd7581a8b7020d9ef45878696b4593daf5575b48dda5259f78f192a3445d5cc97c032660642f \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner3key b/acceptance-tests/tests/src/test/resources/miner3key new file mode 100644 index 00000000000..5abdb3440df --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner3key @@ -0,0 +1 @@ +917fb1b03034e5d7156b89bc2a3bc2aae1d146d2640d75e095f07110b0871bc1 \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner3key.pub b/acceptance-tests/tests/src/test/resources/miner3key.pub new file mode 100644 index 00000000000..905ea256a93 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner3key.pub @@ -0,0 +1 @@ +67785a2ac328648d94245abb25bdcfab853d06e68c70026d90f2fd5c8338b65c19235398eac205e4bbdb3fc1de9669ad6309e43ab203c8e7430664cea6451f56 \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner4key b/acceptance-tests/tests/src/test/resources/miner4key new file mode 100644 index 00000000000..0c185203a10 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner4key @@ -0,0 +1 @@ +3b3cbae8c034c4ba2d5f4df44faa013888216a6eabb7fffa0b224003ea770ba7 \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner4key.pub b/acceptance-tests/tests/src/test/resources/miner4key.pub new file mode 100644 index 00000000000..0821e84cb7a --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner4key.pub @@ -0,0 +1 @@ +ec403552908986b5d9e4def3be9ddcb26d5b03def3b44ef2d5d728bb9a3028603405e7379c3e185cb2bc3e784548fcdd0e7616162029c3f407155b2fb25ba0ca \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner5key b/acceptance-tests/tests/src/test/resources/miner5key new file mode 100644 index 00000000000..ad59591a13f --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner5key @@ -0,0 +1 @@ +4ddfb30d4fcd6f5a9f959961f734e4c1469af223bc16b217eef0d3796e64973d \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/miner5key.pub b/acceptance-tests/tests/src/test/resources/miner5key.pub new file mode 100644 index 00000000000..f259f681d76 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/miner5key.pub @@ -0,0 +1 @@ +28c02b375d62b0adef8213b76882dfec264d5dcbb0a8ba73f167bc718a6d02f1833af80ab9f51a5f007e5f3b8320e8fc5ab287d4bd59ad497f8949bf4abb9e80 \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/qbft/qbft.json b/acceptance-tests/tests/src/test/resources/qbft/qbft.json index b614538b60f..04ed47ed42c 100644 --- a/acceptance-tests/tests/src/test/resources/qbft/qbft.json +++ b/acceptance-tests/tests/src/test/resources/qbft/qbft.json @@ -1,39 +1,65 @@ { - "config": { - "chainId": 4, - "berlinBlock": 0, - "qbft": { - "blockperiodseconds": 1, - "epochlength": 30000, - "requesttimeoutseconds": 5, - "blockreward": "5000000000000000000" - } - }, "nonce": "0x0", "timestamp": "0x58ee40ba", - "extraData": "%extraData%", - "gasLimit": "0x47b760", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f86df86994a18182ee8ca476f2f0fb8170a1d4620edb39c5e194065541903bf3bb8c088a18046b441f5d286288c994d1e106d68cac92668b100f6f43791ddcb2c7588094d156777a1e1539fe654fc82266f41fd5d4aa548494efbbd8900222d7b2f75d081c3e7446a1f4fe10ce80c0", + "gasLimit": "700000000", + "gasUsed": "0x0", + "number": "0x0", "difficulty": "0x1", - "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", "coinbase": "0x0000000000000000000000000000000000000000", + "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "config": { + "chainId": 1337, + "homesteadBlock": 10, + "eip150Block": 20, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 25, + "eip158Block": 30, + "byzantiumBlock": 50, + "constantinopleBlock": 60, + "petersburgBlock": 70, + "istanbulBlock": 80, + "ibft": { + "epochlength": 100, + "blockperiodseconds": 5, + "requesttimeoutseconds": 10, + "policy": 0, + "ceil2Nby3Block": 0, + "validatorcontractaddress": "0x0000000000000000000000000000000000000000" + }, + "qbft": { + "epochLength": 30000, + "blockPeriodSeconds" : 1, + "requestTimeoutSeconds": 10, + "startBlock": 101 + }, + "txnSizeLimit": 64, + "maxCodeSize": 0, + "maxCodeSizeConfig": [ + { + "block": 0, + "size": 64 + } + ], + "isMPS": false + + }, "alloc": { - "fe3b557e8fb62b89f4916b721be55ceb828dbd73": { - "privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", - "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", - "balance": "0xad78ebc5ac6200000" + "0xde8e2ae09f2ee2c6c282c054b2384f8b5f9debee": { + "balance": "1000000000000000000000000000" + }, + "0x23bcbca17fc4978909ab44ac82559c7d379aa006": { + "balance": "1000000000000000000000000000" }, - "627306090abaB3A6e1400e9345bC60c78a8BEf57": { - "privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", - "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", - "balance": "90000000000000000000000" + "0x870276532cca9f33e66273cfa494cf41e04b5a66": { + "balance": "1000000000000000000000000000" }, - "f17f52151EbEF6C7334FAD080c5704D77216b732": { - "privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", - "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", - "balance": "90000000000000000000000" + "0x7d7fc9fdfa49e2db22fc6ebab593dcf3aeffbde8": { + "balance": "1000000000000000000000000000" + }, + "0x4df76ad0678513846699056e0070c5f587580eb5": { + "balance": "1000000000000000000000000000" } - }, - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } } diff --git a/besu/build.gradle b/besu/build.gradle index d1230b6974b..fc45c43155b 100644 --- a/besu/build.gradle +++ b/besu/build.gradle @@ -23,7 +23,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), - 'Commit-Hash': getGitCommitDetails(40).hash + 'Commit-Hash': 'UNKNOWN' ) } } @@ -37,6 +37,7 @@ dependencies { implementation project(':consensus:clique') implementation project(':consensus:common') implementation project(':consensus:ibft') + implementation project(':consensus:ibftlegacy') implementation project(':consensus:merge') implementation project(':consensus:qbft') implementation project(':consensus:qbft-core') diff --git a/besu/src/main/java/org/hyperledger/besu/Runner.java b/besu/src/main/java/org/hyperledger/besu/Runner.java index 609dab117ff..477b1a9db9c 100644 --- a/besu/src/main/java/org/hyperledger/besu/Runner.java +++ b/besu/src/main/java/org/hyperledger/besu/Runner.java @@ -176,6 +176,7 @@ public void startEthereumMainLoop() { if (networkRunner.getNetwork().isP2pEnabled()) { besuController.getSynchronizer().start(); } + besuController.getMiningCoordinator().subscribe(); besuController.getMiningCoordinator().start(); transactionPoolEvictionService.start(); diff --git a/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java b/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java index d006f42010e..c40f0eeb1b2 100644 --- a/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java +++ b/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java @@ -267,14 +267,19 @@ private void logProgress(final long blockNum) { private BlockHeader lookupPreviousHeader( final MutableBlockchain blockchain, final BlockHeader header) { - return blockchain - .getBlockHeader(header.getParentHash()) - .orElseThrow( - () -> - new IllegalStateException( - String.format( - "Block %s does not connect to the existing chain. Current chain head %s", - header.getNumber(), blockchain.getChainHeadBlockNumber()))); + try { + return blockchain + .getBlockHeader(header.getParentHash()) + .orElseThrow( + () -> + new IllegalStateException( + String.format( + "Block %s does not connect to the existing chain. Current chain head %s", + header.getNumber(), blockchain.getChainHeadBlockNumber()))); + } catch (IllegalStateException e) { + e.printStackTrace(); + throw e; + } } @Override diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java index 700b414edce..9f6f6bb54ea 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java @@ -394,8 +394,7 @@ private BesuControllerBuilder createConsensusScheduleBesuControllerBuilder( if (configOptions.isIbft2()) { originalControllerBuilder = new IbftBesuControllerBuilder(); } else if (configOptions.isIbftLegacy()) { - throw new IllegalStateException( - "IBFT1 (legacy) is no longer supported. Consider using IBFT2 or QBFT."); + originalControllerBuilder = new IbftLegacyBesuControllerBuilder(); } else { throw new IllegalStateException( "Invalid genesis migration config. Migration is supported from IBFT (legacy) or IBFT2 to QBFT)"); diff --git a/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java index 0b7299cedb5..ee8b7e3951a 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java @@ -199,7 +199,7 @@ protected ConsensusContext createConsensusContext( protected PluginServiceFactory createAdditionalPluginServices( final Blockchain blockchain, final ProtocolContext protocolContext) { return besuControllerBuilderSchedule - .get(0L) + .get(besuControllerBuilderSchedule.keySet().stream().skip(1).findFirst().orElseThrow()) .createAdditionalPluginServices(blockchain, protocolContext); } @@ -209,7 +209,7 @@ protected JsonRpcMethods createAdditionalJsonRpcMethodFactory( final ProtocolSchedule protocolSchedule, final MiningConfiguration miningConfiguration) { return besuControllerBuilderSchedule - .get(0L) + .get(besuControllerBuilderSchedule.keySet().stream().skip(1).findFirst().orElseThrow()) .createAdditionalJsonRpcMethodFactory( protocolContext, protocolSchedule, miningConfiguration); } @@ -219,7 +219,7 @@ protected SubProtocolConfiguration createSubProtocolConfiguration( final EthProtocolManager ethProtocolManager, final Optional maybeSnapProtocolManager) { return besuControllerBuilderSchedule - .get(0L) + .get(besuControllerBuilderSchedule.keySet().stream().skip(1).findFirst().orElseThrow()) .createSubProtocolConfiguration(ethProtocolManager, maybeSnapProtocolManager); } @@ -242,7 +242,7 @@ protected EthProtocolManager createEthProtocolManager( final Optional mergePeerFilter, final ForkIdManager forkIdManager) { return besuControllerBuilderSchedule - .get(0L) + .get(besuControllerBuilderSchedule.keySet().stream().skip(1).findFirst().orElseThrow()) .createEthProtocolManager( protocolContext, synchronizerConfiguration, diff --git a/besu/src/main/java/org/hyperledger/besu/controller/IbftLegacyBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/IbftLegacyBesuControllerBuilder.java new file mode 100644 index 00000000000..581494012ec --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/controller/IbftLegacyBesuControllerBuilder.java @@ -0,0 +1,99 @@ +/* + * 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.controller; + +import org.hyperledger.besu.config.IbftLegacyConfigOptions; +import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; +import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; +import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider; +import org.hyperledger.besu.consensus.ibft.IbftLegacyContext; +import org.hyperledger.besu.consensus.ibftlegacy.IbftExtraDataCodec; +import org.hyperledger.besu.consensus.ibftlegacy.IbftLegacyBlockInterface; +import org.hyperledger.besu.consensus.ibftlegacy.IbftProtocolSchedule; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; +import org.hyperledger.besu.ethereum.blockcreation.NoopMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; +import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; +import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** The Ibft legacy besu controller builder. */ +public class IbftLegacyBesuControllerBuilder extends BesuControllerBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(IbftLegacyBesuControllerBuilder.class); + private final BftBlockInterface blockInterface; + + /** Default constructor */ + public IbftLegacyBesuControllerBuilder() { + LOG.warn( + "IBFT1 is deprecated. This class should be used only while migrating to another consensus mechanism."); + this.blockInterface = new IbftLegacyBlockInterface(new IbftExtraDataCodec()); + } + + @Override + protected MiningCoordinator createMiningCoordinator( + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final TransactionPool transactionPool, + final MiningConfiguration miningConfiguration, + final SyncState syncState, + final EthProtocolManager ethProtocolManager) { + return new NoopMiningCoordinator(miningConfiguration); + } + + @Override + protected ProtocolSchedule createProtocolSchedule() { + return IbftProtocolSchedule.create( + genesisConfigOptions, privacyParameters, isRevertReasonEnabled, evmConfiguration); + } + + @Override + protected IbftLegacyContext createConsensusContext( + final Blockchain blockchain, + final WorldStateArchive worldStateArchive, + final ProtocolSchedule protocolSchedule) { + final IbftLegacyConfigOptions ibftConfig = genesisConfigOptions.getIbftLegacyConfigOptions(); + final EpochManager epochManager = new EpochManager(ibftConfig.getEpochLength()); + final ValidatorProvider validatorProvider = + BlockValidatorProvider.nonForkingValidatorProvider( + blockchain, epochManager, blockInterface); + + return new IbftLegacyContext(validatorProvider, epochManager, blockInterface); + } + + @Override + protected PluginServiceFactory createAdditionalPluginServices( + final Blockchain blockchain, final ProtocolContext protocolContext) { + return new NoopPluginServiceFactory(); + } + + @Override + protected void validateContext(final ProtocolContext context) { + final BlockHeader genesisBlockHeader = context.getBlockchain().getGenesisBlock().getHeader(); + + if (blockInterface.validatorsInBlock(genesisBlockHeader).isEmpty()) { + LOG.warn("Genesis block contains no signers - chain will not progress."); + } + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java index 8a0324560d6..5e944f81466 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -91,7 +91,6 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.util.Subscribers; import java.time.Duration; @@ -147,7 +146,9 @@ protected JsonRpcMethods createAdditionalJsonRpcMethodFactory( private ValidatorProvider createReadOnlyValidatorProvider(final Blockchain blockchain) { checkNotNull( transactionValidatorProvider, "transactionValidatorProvider should have been initialised"); - final EpochManager epochManager = new EpochManager(qbftConfig.getEpochLength()); + final long startBlock = + qbftConfig.getStartBlock().isPresent() ? qbftConfig.getStartBlock().getAsLong() : 0; + final EpochManager epochManager = new EpochManager(qbftConfig.getEpochLength(), startBlock); // Must create our own voteTallyCache as using this would pollute the main voteTallyCache final BlockValidatorProvider readOnlyBlockValidatorProvider = BlockValidatorProvider.nonForkingValidatorProvider( @@ -207,8 +208,16 @@ protected MiningCoordinator createMiningCoordinator( qbftExtraDataCodec, ethProtocolManager.ethContext().getScheduler()); - final ValidatorProvider validatorProvider = - protocolContext.getConsensusContext(BftContext.class).getValidatorProvider(); + final ValidatorProvider validatorProvider; + if (qbftConfig.getStartBlock().isPresent()) { + validatorProvider = + protocolContext + .getConsensusContext(BftContext.class, qbftConfig.getStartBlock().getAsLong()) + .getValidatorProvider(); + } else { + validatorProvider = + protocolContext.getConsensusContext(BftContext.class).getValidatorProvider(); + } final QbftBlockInterface qbftBlockInterface = new QbftBlockInterfaceAdaptor(bftBlockInterface); final QbftContext qbftContext = new QbftContext(validatorProvider, qbftBlockInterface); @@ -306,7 +315,8 @@ protected MiningCoordinator createMiningCoordinator( bftProcessor, blockCreatorFactory, blockchain, - bftEventQueue); + bftEventQueue, + syncState); // Update the next block period in seconds according to the transition schedule protocolContext @@ -325,35 +335,6 @@ protected MiningCoordinator createMiningCoordinator( .getEmptyBlockPeriodSeconds()); }); - syncState.subscribeSyncStatus( - syncStatus -> { - if (syncState.syncTarget().isPresent()) { - // We're syncing so stop doing other stuff - LOG.info("Stopping QBFT mining coordinator while we are syncing"); - miningCoordinator.stop(); - } else { - LOG.info("Starting QBFT mining coordinator following sync"); - miningCoordinator.enable(); - miningCoordinator.start(); - } - }); - - syncState.subscribeCompletionReached( - new BesuEvents.InitialSyncCompletionListener() { - @Override - public void onInitialSyncCompleted() { - LOG.info("Starting QBFT mining coordinator following initial sync"); - miningCoordinator.enable(); - miningCoordinator.start(); - } - - @Override - public void onInitialSyncRestart() { - // Nothing to do. The mining coordinator won't be started until - // sync has completed. - } - }); - return miningCoordinator; } @@ -418,7 +399,9 @@ protected BftContext createConsensusContext( final Blockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule) { - final EpochManager epochManager = new EpochManager(qbftConfig.getEpochLength()); + final long startBlock = + qbftConfig.getStartBlock().isPresent() ? qbftConfig.getStartBlock().getAsLong() : 0; + final EpochManager epochManager = new EpochManager(qbftConfig.getEpochLength(), startBlock); final BftValidatorOverrides validatorOverrides = convertBftForks(genesisConfigOptions.getTransitions().getQbftForks()); diff --git a/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java index 2f707edc6f4..dbc8c0703d0 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.chainimport; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; @@ -21,6 +22,7 @@ import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.MergeConfiguration; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; @@ -41,9 +43,12 @@ import java.io.IOException; import java.math.BigInteger; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.concurrent.CompletionException; +import com.google.common.io.Resources; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -156,4 +161,47 @@ public void blockImportCanSkipPow() throws IOException { assertThat(result.count).isEqualTo(1); assertThat(result.td).isEqualTo(UInt256.valueOf(34351349760L)); } + + @Test + public void ibftImport() throws IOException { + final Path source = dataDir.resolve("ibft.blocks"); + final String config = + Resources.toString(this.getClass().getResource("/ibft-genesis-2.json"), UTF_8); + + try { + Files.write( + source, + Resources.toByteArray(this.getClass().getResource("/ibft.blocks")), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } catch (final IOException ex) { + throw new IllegalStateException(ex); + } + + final BesuController controller = + new BesuController.Builder() + .fromGenesisFile(GenesisConfigFile.fromConfig(config), SyncMode.FULL) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryKeyValueStorageProvider()) + .networkId(BigInteger.valueOf(1337)) + .miningParameters(MiningConfiguration.newDefault()) + .nodeKey(NodeKeyUtils.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .privacyParameters(PrivacyParameters.DEFAULT) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) + .apiConfiguration(ImmutableApiConfiguration.builder().build()) + .build(); + final RlpBlockImporter.ImportResult result = + rlpBlockImporter.importBlockchain(source, controller, true); + + // Don't count the Genesis block + assertThat(result.count).isEqualTo(100); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java index 1f6927ca995..f6d252b28ac 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java @@ -98,6 +98,27 @@ public void createConsensusScheduleBesuControllerBuilderWhenMigratingFromIbft2To .isInstanceOf(QbftBesuControllerBuilder.class); } + @Test + public void createConsensusScheduleBesuControllerBuilderWhenMigratingFromIbftLegacyToQbft() { + final long qbftStartBlock = 10L; + mockGenesisConfigForMigration("ibftLegacy", OptionalLong.of(qbftStartBlock)); + + final BesuControllerBuilder besuControllerBuilder = + new BesuController.Builder().fromGenesisFile(genesisConfigFile, SyncMode.FULL); + + assertThat(besuControllerBuilder).isInstanceOf(ConsensusScheduleBesuControllerBuilder.class); + + final Map besuControllerBuilderSchedule = + ((ConsensusScheduleBesuControllerBuilder) besuControllerBuilder) + .getBesuControllerBuilderSchedule(); + + assertThat(besuControllerBuilderSchedule).containsKeys(0L, qbftStartBlock); + assertThat(besuControllerBuilderSchedule.get(0L)) + .isInstanceOf(IbftLegacyBesuControllerBuilder.class); + assertThat(besuControllerBuilderSchedule.get(qbftStartBlock)) + .isInstanceOf(QbftBesuControllerBuilder.class); + } + private void mockGenesisConfigForMigration( final String consensus, final OptionalLong startBlock) { when(genesisConfigOptions.isConsensusMigration()).thenReturn(true); @@ -108,6 +129,11 @@ private void mockGenesisConfigForMigration( when(genesisConfigOptions.isIbft2()).thenReturn(true); break; } + case "ibftlegacy": + { + when(genesisConfigOptions.isIbftLegacy()).thenReturn(true); + break; + } default: fail("Invalid consensus algorithm"); } diff --git a/besu/src/test/resources/ibft-genesis-2.json b/besu/src/test/resources/ibft-genesis-2.json new file mode 100644 index 00000000000..fa98f9a6bd4 --- /dev/null +++ b/besu/src/test/resources/ibft-genesis-2.json @@ -0,0 +1,65 @@ +{ + "nonce": "0x0", + "timestamp": "0x58ee40ba", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000f86df86994a18182ee8ca476f2f0fb8170a1d4620edb39c5e194065541903bf3bb8c088a18046b441f5d286288c994d1e106d68cac92668b100f6f43791ddcb2c7588094d156777a1e1539fe654fc82266f41fd5d4aa548494efbbd8900222d7b2f75d081c3e7446a1f4fe10ce80c0", + "gasLimit": "700000000", + "gasUsed": "0x0", + "number": "0x0", + "difficulty": "0x1", + "coinbase": "0x0000000000000000000000000000000000000000", + "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "config": { + "chainId": 1337, + "homesteadBlock": 0, + "eip150Block": 20, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 30, + "eip158Block": 40, + "byzantiumBlock": 50, + "constantinopleBlock": 60, + "petersburgBlock": 70, + "istanbulBlock": 80, + "ibft": { + "epochlength": 1000, + "blockperiodseconds": 5, + "requesttimeoutseconds": 10, + "policy": 0, + "ceil2Nby3Block": 0, + "validatorcontractaddress": "0x0000000000000000000000000000000000000000" + }, + "qbft": { + "epochLength": 30000, + "blockPeriodSeconds" : 1, + "requestTimeoutSeconds": 10, + "startBlock": 101 + }, + "txnSizeLimit": 64, + "maxCodeSize": 0, + "maxCodeSizeConfig": [ + { + "block": 0, + "size": 64 + } + ], + "isMPS": false + + }, + "alloc": { + "0xde8e2ae09f2ee2c6c282c054b2384f8b5f9debee": { + "balance": "1000000000000000000000000000" + }, + "0x23bcbca17fc4978909ab44ac82559c7d379aa006": { + "balance": "1000000000000000000000000000" + }, + "0x870276532cca9f33e66273cfa494cf41e04b5a66": { + "balance": "1000000000000000000000000000" + }, + "0x7d7fc9fdfa49e2db22fc6ebab593dcf3aeffbde8": { + "balance": "1000000000000000000000000000" + }, + "0x4df76ad0678513846699056e0070c5f587580eb5": { + "balance": "1000000000000000000000000000" + } + } +} diff --git a/besu/src/test/resources/ibft.blocks b/besu/src/test/resources/ibft.blocks old mode 100644 new mode 100755 index a0f05ccb81a..3e04a5441d0 Binary files a/besu/src/test/resources/ibft.blocks and b/besu/src/test/resources/ibft.blocks differ diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java index 877f17de416..df27720cd0c 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java @@ -78,7 +78,7 @@ public interface GenesisConfigOptions { * @return the boolean */ default boolean isConsensusMigration() { - return isIbft2() && isQbft(); + return (isIbft2() || isIbftLegacy()) && isQbft(); } /** @@ -88,6 +88,13 @@ default boolean isConsensusMigration() { */ String getConsensusEngine(); + /** + * Gets ibft legacy config options. + * + * @return the ibft legacy config options + */ + IbftLegacyConfigOptions getIbftLegacyConfigOptions(); + /** * Gets checkpoint options. * diff --git a/config/src/main/java/org/hyperledger/besu/config/IbftLegacyConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/IbftLegacyConfigOptions.java new file mode 100644 index 00000000000..8208ba7182c --- /dev/null +++ b/config/src/main/java/org/hyperledger/besu/config/IbftLegacyConfigOptions.java @@ -0,0 +1,73 @@ +/* + * 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.config; + +import java.util.Map; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; + +/** The Ibft legacy config options. */ +public class IbftLegacyConfigOptions { + + /** The constant DEFAULT. */ + public static final IbftLegacyConfigOptions DEFAULT = + new IbftLegacyConfigOptions(JsonUtil.createEmptyObjectNode()); + + private static final long DEFAULT_EPOCH_LENGTH = 30_000; + private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 1; + + private final ObjectNode ibftConfigRoot; + + /** + * Instantiates a new Ibft legacy config options. + * + * @param ibftConfigRoot the ibft config root + */ + IbftLegacyConfigOptions(final ObjectNode ibftConfigRoot) { + this.ibftConfigRoot = ibftConfigRoot; + } + + /* + */ + /** + * Gets epoch length. + * + * @return the epoch length + */ + public long getEpochLength() { + return JsonUtil.getLong(ibftConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH); + } + + /** + * Gets block period seconds. + * + * @return the block period seconds + */ + public int getBlockPeriodSeconds() { + return JsonUtil.getPositiveInt( + ibftConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS); + } + + /** + * As map. + * + * @return the map + */ + Map asMap() { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + return builder.build(); + } +} diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java index 9239d4977e8..80213dce1e1 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java @@ -118,6 +118,8 @@ public String getConsensusEngine() { return ETHASH_CONFIG_KEY; } else if (isIbft2()) { return IBFT2_CONFIG_KEY; + } else if (isIbftLegacy()) { + return IBFT_LEGACY_CONFIG_KEY; } else if (isQbft()) { return QBFT_CONFIG_KEY; } else if (isClique()) { @@ -157,6 +159,13 @@ public boolean isPoa() { return isQbft() || isClique() || isIbft2() || isIbftLegacy(); } + @Override + public IbftLegacyConfigOptions getIbftLegacyConfigOptions() { + return JsonUtil.getObjectNode(configRoot, IBFT_LEGACY_CONFIG_KEY) + .map(IbftLegacyConfigOptions::new) + .orElse(IbftLegacyConfigOptions.DEFAULT); + } + @Override public BftConfigOptions getBftConfigOptions() { final String fieldKey = isIbft2() ? IBFT2_CONFIG_KEY : QBFT_CONFIG_KEY; @@ -529,6 +538,9 @@ public Map asMap() { if (isEthHash()) { builder.put("ethash", getEthashConfigOptions().asMap()); } + if (isIbftLegacy()) { + builder.put("ibft", getIbftLegacyConfigOptions().asMap()); + } if (isIbft2()) { builder.put("ibft2", getBftConfigOptions().asMap()); } diff --git a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java index 2f6e6897a94..9e289dcac7a 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -116,6 +116,11 @@ public boolean isClique() { return false; } + @Override + public IbftLegacyConfigOptions getIbftLegacyConfigOptions() { + return IbftLegacyConfigOptions.DEFAULT; + } + @Override public boolean isIbft2() { return false; @@ -416,6 +421,9 @@ public Map asMap() { if (isEthHash()) { builder.put("ethash", getEthashConfigOptions().asMap()); } + if (isIbftLegacy()) { + builder.put("ibft", getIbftLegacyConfigOptions().asMap()); + } if (isIbft2()) { builder.put("ibft2", getBftConfigOptions().asMap()); } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/EpochManager.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/EpochManager.java index 47bbb3e446f..43a4d63472f 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/EpochManager.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/EpochManager.java @@ -18,6 +18,7 @@ public class EpochManager { private final long epochLengthInBlocks; + private final long startBlock; /** * Instantiates a new Epoch manager. @@ -25,7 +26,18 @@ public class EpochManager { * @param epochLengthInBlocks the epoch length in blocks */ public EpochManager(final long epochLengthInBlocks) { + this(epochLengthInBlocks, 0); + } + + /** + * Instantiates a new Epoch manager. + * + * @param epochLengthInBlocks the epoch length in blocks + * @param startBlock the block number where the epoch counting starts + */ + public EpochManager(final long epochLengthInBlocks, final long startBlock) { this.epochLengthInBlocks = epochLengthInBlocks; + this.startBlock = startBlock; } /** @@ -35,7 +47,10 @@ public EpochManager(final long epochLengthInBlocks) { * @return the boolean */ public boolean isEpochBlock(final long blockNumber) { - return (blockNumber % epochLengthInBlocks) == 0; + if (blockNumber < startBlock) { + return false; + } + return ((blockNumber - startBlock) % epochLengthInBlocks) == 0; } /** @@ -45,6 +60,9 @@ public boolean isEpochBlock(final long blockNumber) { * @return the last epoch block */ public long getLastEpochBlock(final long blockNumber) { - return blockNumber - (blockNumber % epochLengthInBlocks); + if (blockNumber < startBlock) { + throw new IllegalArgumentException("Block number is before start block."); + } + return startBlock + ((blockNumber - startBlock) / epochLengthInBlocks) * epochLengthInBlocks; } } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java index 27cce3157b2..0fb18185c85 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java @@ -45,6 +45,12 @@ public MigratingProtocolContext( @Override public C getConsensusContext(final Class klass) { final long chainHeadBlockNumber = getBlockchain().getChainHeadBlockNumber(); - return consensusContextSchedule.getFork(chainHeadBlockNumber).getValue().as(klass); + return consensusContextSchedule.getFork(chainHeadBlockNumber + 1).getValue().as(klass); + } + + @Override + public C getConsensusContext( + final Class klass, final long blockNumber) { + return consensusContextSchedule.getFork(blockNumber).getValue().as(klass); } } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java index a24c33a93d9..51db35f3d3f 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraData.java @@ -29,6 +29,7 @@ public class BftExtraData implements ParsedExtraData { private final Bytes vanityData; private final Collection seals; + private final SECPSignature proposerSeal; private final Collection
validators; private final Optional vote; private final int round; @@ -56,6 +57,31 @@ public BftExtraData( this.validators = validators; this.vote = vote; this.round = round; + this.proposerSeal = null; + } + + /** + * Instantiates a new Bft extra data. + * + * @param vanityData the vanity data + * @param seals the seals + * @param validators the validators + * @param proposerSeal the proposer seal + */ + public BftExtraData( + final Bytes vanityData, + final Collection seals, + final SECPSignature proposerSeal, + final Collection
validators) { + checkNotNull(vanityData); + checkNotNull(seals); + checkNotNull(validators); + this.vanityData = vanityData; + this.seals = seals; + this.validators = validators; + this.proposerSeal = proposerSeal; + this.vote = Optional.empty(); + this.round = 0; } /** @@ -76,6 +102,15 @@ public Collection getSeals() { return seals; } + /** + * Gets proposer seal. + * + * @return the proposer seal + */ + public SECPSignature getProposerSeal() { + return proposerSeal; + } + /** * Gets validators. * diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java index 795a064b6bc..7028eb11ec0 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java @@ -28,6 +28,8 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.plugin.services.BesuEvents; import java.util.List; import java.util.Optional; @@ -65,6 +67,8 @@ private enum State { private long blockAddedObserverId; private final AtomicReference state = new AtomicReference<>(State.PAUSED); + private SyncState syncState; + /** * Instantiates a new Bft mining coordinator. * @@ -91,6 +95,35 @@ public BftMiningCoordinator( this.blockchain = blockchain; } + /** + * Instantiates a new Bft mining coordinator. + * + * @param bftExecutors the bft executors + * @param eventHandler the event handler + * @param bftProcessor the bft processor + * @param blockCreatorFactory the block creator factory + * @param blockchain the blockchain + * @param eventQueue the event queue + * @param syncState the sync state + */ + public BftMiningCoordinator( + final BftExecutors bftExecutors, + final BftEventHandler eventHandler, + final BftProcessor bftProcessor, + final BftBlockCreatorFactory blockCreatorFactory, + final Blockchain blockchain, + final BftEventQueue eventQueue, + final SyncState syncState) { + this.bftExecutors = bftExecutors; + this.eventHandler = eventHandler; + this.bftProcessor = bftProcessor; + this.blockCreatorFactory = blockCreatorFactory; + this.eventQueue = eventQueue; + + this.blockchain = blockchain; + this.syncState = syncState; + } + @Override public void start() { if (state.compareAndSet(State.IDLE, State.RUNNING) @@ -119,6 +152,38 @@ public void stop() { } } + @Override + public void subscribe() { + syncState.subscribeSyncStatus( + syncStatus -> { + if (syncState.syncTarget().isPresent()) { + // We're syncing so stop doing other stuff + LOG.info("Stopping QBFT mining coordinator while we are syncing"); + stop(); + } else { + LOG.info("Starting QBFT mining coordinator following sync"); + enable(); + start(); + } + }); + + syncState.subscribeCompletionReached( + new BesuEvents.InitialSyncCompletionListener() { + @Override + public void onInitialSyncCompleted() { + LOG.info("Starting QBFT mining coordinator following initial sync"); + enable(); + start(); + } + + @Override + public void onInitialSyncRestart() { + // Nothing to do. The mining coordinator won't be started until + // sync has completed. + } + }); + } + @Override public void awaitStop() throws InterruptedException { bftExecutors.awaitStop(); diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCache.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCache.java index 2201df13891..1138d3f12e7 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCache.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCache.java @@ -85,6 +85,7 @@ private VoteTally populateCacheUptoAndIncluding(final BlockHeader start) { VoteTally voteTally = null; while (true) { // Will run into an epoch block (and thus a VoteTally) to break loop. + intermediateBlocks.push(header); voteTally = getValidatorsAfter(header); if (voteTally != null) { @@ -103,7 +104,7 @@ private VoteTally populateCacheUptoAndIncluding(final BlockHeader start) { } protected VoteTally getValidatorsAfter(final BlockHeader header) { - if (epochManager.isEpochBlock(header.getNumber())) { + if (header.getNumber() == 0 || epochManager.isEpochBlock(header.getNumber() + 1)) { return new VoteTally(blockInterface.validatorsInBlock(header)); } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdater.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdater.java index bae5540aad0..dbeacd69d68 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdater.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdater.java @@ -71,7 +71,7 @@ VoteTally buildVoteTallyFromBlockchain(final Blockchain blockchain) { * @param voteTally the vote tally to update */ void updateForBlock(final BlockHeader header, final VoteTally voteTally) { - if (epochManager.isEpochBlock(header.getNumber())) { + if (epochManager.isEpochBlock(header.getNumber() + 1)) { voteTally.discardOutstandingVotes(); return; } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java index 514e5b8ab37..05a26f2b54f 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java @@ -15,7 +15,7 @@ package org.hyperledger.besu.consensus.common.validator.blockbased; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java index 21039c84d85..07ce0ac25fa 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java @@ -62,7 +62,7 @@ public void voteTallyUpdatedWithAddVote() { .thenReturn(Optional.of(new ValidatorVote(VoteType.ADD, proposerAddress, subject))); final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); - headerBuilder.number(EPOCH_LENGTH - 1); + headerBuilder.number(EPOCH_LENGTH); final BlockHeader header = headerBuilder.buildHeader(); updater.updateForBlock(header, voteTally); @@ -75,7 +75,7 @@ public void voteTallyNotUpdatedWhenBlockHasNoVoteSubject() { when(serialiser.extractVoteFromHeader(any())).thenReturn(Optional.empty()); final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); - headerBuilder.number(EPOCH_LENGTH - 1); + headerBuilder.number(EPOCH_LENGTH); final BlockHeader header = headerBuilder.buildHeader(); updater.updateForBlock(header, voteTally); @@ -88,7 +88,7 @@ public void outstandingVotesDiscardedWhenEpochReached() { when(serialiser.extractVoteFromHeader(any())).thenReturn(Optional.empty()); final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); - headerBuilder.number(EPOCH_LENGTH); + headerBuilder.number(EPOCH_LENGTH - 1); final BlockHeader header = headerBuilder.buildHeader(); updater.updateForBlock(header, voteTally); diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftLegacyContext.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftLegacyContext.java new file mode 100644 index 00000000000..551575e3c31 --- /dev/null +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftLegacyContext.java @@ -0,0 +1,76 @@ +/* + * 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.ibft; + +import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; +import org.hyperledger.besu.consensus.common.bft.BftContext; +import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; +import org.hyperledger.besu.ethereum.ConsensusContext; + +/** Holds the BFT specific mutable state. */ +public class IbftLegacyContext extends BftContext { + + private final ValidatorProvider validatorProvider; + private final EpochManager epochManager; + private final BftBlockInterface blockInterface; + + /** + * Instantiates a new Ibft legacy context. + * + * @param validatorProvider the validator provider + * @param epochManager the epoch manager + * @param blockInterface the block interface + */ + public IbftLegacyContext( + final ValidatorProvider validatorProvider, + final EpochManager epochManager, + final BftBlockInterface blockInterface) { + super(validatorProvider, epochManager, blockInterface); + this.validatorProvider = validatorProvider; + this.epochManager = epochManager; + this.blockInterface = blockInterface; + } + + /** + * Gets validator provider. + * + * @return the validator provider + */ + @Override + public ValidatorProvider getValidatorProvider() { + return validatorProvider; + } + + /** + * Gets epoch manager. + * + * @return the epoch manager + */ + @Override + public EpochManager getEpochManager() { + return epochManager; + } + + @Override + public BftBlockInterface getBlockInterface() { + return blockInterface; + } + + @Override + public C as(final Class klass) { + return klass.cast(this); + } +} diff --git a/consensus/ibftlegacy/build.gradle b/consensus/ibftlegacy/build.gradle new file mode 100644 index 00000000000..533ef4f12f9 --- /dev/null +++ b/consensus/ibftlegacy/build.gradle @@ -0,0 +1,58 @@ +apply plugin: 'java-library' + +jar { + archiveBaseName = 'besu-ibftlegacy' + manifest { + attributes( + 'Specification-Title': archiveBaseName, + 'Specification-Version': project.version, + 'Implementation-Title': archiveBaseName, + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': 'UNKNOWN' + ) + } +} + +dependencies { + implementation project(':config') + implementation project(':consensus:common') + implementation project(':consensus:ibft') + implementation project(':crypto:algorithms') + implementation project(':datatypes') + implementation project(':ethereum:api') + implementation project(':ethereum:blockcreation') + implementation project(':ethereum:core') + implementation project(':ethereum:eth') + implementation project(':ethereum:p2p') + implementation project(':ethereum:rlp') + implementation project(':evm') + implementation project(':metrics:core') + implementation project(':services:kvstore') + + implementation 'com.google.guava:guava' + implementation 'io.vertx:vertx-core' + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'io.tmio:tuweni-bytes' + implementation 'io.tmio:tuweni-units' + + testImplementation project(path: ':consensus:common', configuration: 'testSupportArtifacts') + testImplementation project(path: ':consensus:ibft', configuration: 'testSupportArtifacts') + testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') + testImplementation project(path: ':ethereum:eth', configuration: 'testSupportArtifacts') + testImplementation project(':metrics:core') + testImplementation project(':testutil') + + integrationTestImplementation 'org.assertj:assertj-core' + integrationTestImplementation 'org.junit.jupiter:junit-jupiter-api' + integrationTestImplementation 'org.mockito:mockito-core' + integrationTestImplementation 'org.mockito:mockito-junit-jupiter' + + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.awaitility:awaitility' + testImplementation 'org.mockito:mockito-core' + testImplementation 'org.mockito:mockito-junit-jupiter' + + + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftBlockHashing.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftBlockHashing.java new file mode 100644 index 00000000000..5a725de3ba1 --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftBlockHashing.java @@ -0,0 +1,180 @@ +/* + * 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.ibftlegacy; + +import org.hyperledger.besu.consensus.common.bft.BftExtraData; +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.tuweni.bytes.Bytes; + +/** The Ibft block hashing. */ +public class IbftBlockHashing { + + /** Default constructor */ + public IbftBlockHashing() {} + + private static final Bytes COMMIT_MSG_CODE = Bytes.wrap(new byte[] {2}); + private static final IbftExtraDataCodec ibftExtraDataCodec = new IbftExtraDataCodec(); + + /** + * Constructs a hash of the block header, suitable for use when creating the proposer seal. The + * extra data is modified to have a null proposer seal and empty list of committed seals. + * + * @param header The header for which a proposer seal is to be calculated + * @param ibftExtraData The extra data block which is to be inserted to the header once seal is + * calculated + * @return the hash of the header suitable for signing as the proposer seal + */ + public static Hash calculateDataHashForProposerSeal( + final BlockHeader header, final BftExtraData ibftExtraData) { + final Bytes headerRlp = + serializeHeader(header, () -> encodeExtraDataWithoutCommittedSeals(ibftExtraData, null)); + + // Proposer hash is the hash of the hash + return Hash.hash(Hash.hash(headerRlp)); + } + + /** + * Constructs a hash of the block header suitable for signing as a committed seal. The extra data + * in the hash uses an empty list for the committed seals. + * + * @param header The header for which a proposer seal is to be calculated (without extra data) + * @param ibftExtraData The extra data block which is to be inserted to the header once seal is + * calculated + * @return the hash of the header including the validator and proposer seal in the extra data + */ + public static Hash calculateDataHashForCommittedSeal( + final BlockHeader header, final BftExtraData ibftExtraData) { + // The data signed by a committer is an array of [Hash, COMMIT_MSG_CODE] + final Hash dataHash = Hash.hash(serializeHeaderWithoutCommittedSeals(header, ibftExtraData)); + final Bytes seal = Bytes.wrap(dataHash, COMMIT_MSG_CODE); + return Hash.hash(seal); + } + + /** + * Constructs a hash of the block header, but omits the committerSeals (as this changes on each of + * the potentially circulated blocks at the current chain height). + * + * @param header The header for which a block hash is to be calculated + * @return the hash of the header including the validator and proposer seal in the extra data + */ + public static Hash calculateHashOfIbftBlockOnchain(final BlockHeader header) { + final BftExtraData ibftExtraData = ibftExtraDataCodec.decode(header); + Hash hash = Hash.hash(serializeHeaderWithoutCommittedSeals(header, ibftExtraData)); + return hash; + } + + private static Bytes serializeHeaderWithoutCommittedSeals( + final BlockHeader header, final BftExtraData ibftExtraData) { + return serializeHeader( + header, + () -> encodeExtraDataWithoutCommittedSeals(ibftExtraData, ibftExtraData.getProposerSeal())); + } + + /** + * Recovers the proposer's {@link Address} from the proposer seal. + * + * @param header the block header that was signed by the proposer seal + * @param ibftExtraData the parsed IBftExtraData from the header + * @return the proposer address + */ + public static Address recoverProposerAddress( + final BlockHeader header, final BftExtraData ibftExtraData) { + final Hash proposerHash = calculateDataHashForProposerSeal(header, ibftExtraData); + Address addr = Util.signatureToAddress(ibftExtraData.getProposerSeal(), proposerHash); + return addr; + } + + /** + * Recovers the {@link Address} for each validator that contributed a committed seal to the block. + * + * @param header the block header that was signed by the committed seals + * @param ibftExtraData the parsed IBftExtraData from the header + * @return the addresses of validators that provided a committed seal + */ + public static List
recoverCommitterAddresses( + final BlockHeader header, final BftExtraData ibftExtraData) { + final Hash committerHash = + IbftBlockHashing.calculateDataHashForCommittedSeal(header, ibftExtraData); + + return ibftExtraData.getSeals().stream() + .map(p -> Util.signatureToAddress(p, committerHash)) + .collect(Collectors.toList()); + } + + private static Bytes encodeExtraDataWithoutCommittedSeals( + final BftExtraData ibftExtraData, final SECPSignature proposerSeal) { + final BytesValueRLPOutput extraDataEncoding = new BytesValueRLPOutput(); + extraDataEncoding.startList(); + extraDataEncoding.writeList( + ibftExtraData.getValidators(), (validator, rlp) -> rlp.writeBytes(validator)); + + if (proposerSeal != null) { + extraDataEncoding.writeBytes(proposerSeal.encodedBytes()); + } else { + extraDataEncoding.writeNull(); + } + + // Represents an empty committer list (i.e this is not included in the hashing of the block) + extraDataEncoding.startList(); + extraDataEncoding.endList(); + + extraDataEncoding.endList(); + + Bytes vanityBytes = ibftExtraData.getVanityData(); + + return Bytes.wrap(vanityBytes, extraDataEncoding.encoded()); + } + + private static Bytes serializeHeader( + final BlockHeader header, final Supplier extraDataSerializer) { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.startList(); + + out.writeBytes(header.getParentHash()); + out.writeBytes(header.getOmmersHash()); + out.writeBytes(header.getCoinbase()); + out.writeBytes(header.getStateRoot()); + out.writeBytes(header.getTransactionsRoot()); + out.writeBytes(header.getReceiptsRoot()); + out.writeBytes(header.getLogsBloom()); + // out.writeBytes(header.getDifficulty()); + out.writeBytes(Bytes.fromHexString("0x01")); + out.writeLongScalar(header.getNumber()); + out.writeLongScalar(header.getGasLimit()); + out.writeLongScalar(header.getGasUsed()); + out.writeLongScalar(header.getTimestamp()); + // Cannot decode an IbftExtraData on block 0 due to missing/illegal signatures + if (header.getNumber() == 0) { + out.writeBytes(header.getExtraData()); + } else { + out.writeBytes(extraDataSerializer.get()); + } + out.writeBytes(header.getMixHash()); + out.writeLong(header.getNonce()); + out.endList(); + Bytes encoded = out.encoded(); + return encoded; + } +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftBlockHeaderValidationRulesetFactory.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftBlockHeaderValidationRulesetFactory.java new file mode 100644 index 00000000000..2427e864404 --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftBlockHeaderValidationRulesetFactory.java @@ -0,0 +1,104 @@ +/* + * 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.ibftlegacy; + +import static org.hyperledger.besu.ethereum.mainnet.AbstractGasLimitSpecification.DEFAULT_MAX_GAS_LIMIT; +import static org.hyperledger.besu.ethereum.mainnet.AbstractGasLimitSpecification.DEFAULT_MIN_GAS_LIMIT; + +import org.hyperledger.besu.consensus.ibftlegacy.headervalidationrules.IbftExtraDataValidationRule; +import org.hyperledger.besu.consensus.ibftlegacy.headervalidationrules.VoteValidationRule; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; +import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.AncestryValidationRule; +import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.ConstantFieldValidationRule; +import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.GasLimitRangeAndDeltaValidationRule; +import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.GasUsageValidationRule; +import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampBoundedByFutureParameter; +import org.hyperledger.besu.ethereum.mainnet.headervalidationrules.TimestampMoreRecentThanParent; + +import org.apache.tuweni.units.bigints.UInt256; + +/** The Ibft block header validation ruleset factory. */ +public class IbftBlockHeaderValidationRulesetFactory { + + /** Default constructor */ + public IbftBlockHeaderValidationRulesetFactory() {} + + /** + * Produces a BlockHeaderValidator configured for assessing ibft block headers which are to form + * part of the BlockChain (i.e. not proposed blocks, which do not contain commit seals) + * + * @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks. + * @param ceil2nBy3Block the block after which 2/3n commit seals must exist, rather than 2F+1 + * @return BlockHeaderValidator configured for assessing ibft block headers + */ + public static BlockHeaderValidator.Builder ibftBlockHeaderValidator( + final long secondsBetweenBlocks, final long ceil2nBy3Block) { + return createValidator(secondsBetweenBlocks, true, ceil2nBy3Block); + } + + /** + * Creates a builder for the IBFT block header validator. + * + * @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks. + * @return a builder for the IBFT block header validator + */ + public static BlockHeaderValidator.Builder ibftBlockHeaderValidator( + final long secondsBetweenBlocks) { + return createValidator(secondsBetweenBlocks); + } + + /** + * Produces a BlockHeaderValidator configured for assessing IBFT proposed blocks (i.e. blocks + * which need to be vetted by the validators, and do not contain commit seals). + * + * @param secondsBetweenBlocks the minimum number of seconds which must elapse between blocks. + * @return BlockHeaderValidator configured for assessing ibft block headers + */ + public static BlockHeaderValidator.Builder ibftProposedBlockValidator( + final long secondsBetweenBlocks) { + return createValidator(secondsBetweenBlocks, false, 0); + } + + private static BlockHeaderValidator.Builder createValidator( + final long secondsBetweenBlocks, + final boolean validateCommitSeals, + final long ceil2nBy3Block) { + BlockHeaderValidator.Builder builder = createValidator(secondsBetweenBlocks); + builder.addRule(new IbftExtraDataValidationRule(validateCommitSeals, ceil2nBy3Block)); + return builder; + } + + private static BlockHeaderValidator.Builder createValidator(final long secondsBetweenBlocks) { + return new BlockHeaderValidator.Builder() + .addRule(new AncestryValidationRule()) + .addRule(new GasUsageValidationRule()) + .addRule( + new GasLimitRangeAndDeltaValidationRule(DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT)) + .addRule(new TimestampBoundedByFutureParameter(1)) + .addRule(new TimestampMoreRecentThanParent(secondsBetweenBlocks)) + .addRule( + new ConstantFieldValidationRule<>( + "MixHash", BlockHeader::getMixHash, IbftHelpers.EXPECTED_MIX_HASH)) + .addRule( + new ConstantFieldValidationRule<>( + "OmmersHash", BlockHeader::getOmmersHash, Hash.EMPTY_LIST_HASH)) + .addRule( + new ConstantFieldValidationRule<>( + "Difficulty", BlockHeader::getDifficulty, UInt256.ONE)) + .addRule(new VoteValidationRule()); + } +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftExtraDataCodec.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftExtraDataCodec.java new file mode 100644 index 00000000000..dcb9935cffe --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftExtraDataCodec.java @@ -0,0 +1,91 @@ +/* + * 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.ibftlegacy; + +import static com.google.common.base.Preconditions.checkArgument; + +import org.hyperledger.besu.consensus.common.bft.BftExtraData; +import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import java.util.Collection; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; + +/** + * Represents encoder/decoder of the serialized data structure stored in the extraData field of the + * BlockHeader used when operating under an IBFT consensus mechanism. + */ +public class IbftExtraDataCodec extends BftExtraDataCodec { + // private static final Logger LOG = LoggerFactory.getLogger(IbftExtraDataCodec.class); + + /** The constant EXTRA_VANITY_LENGTH. */ + public static final int EXTRA_VANITY_LENGTH = 32; + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + /** Default constructor */ + public IbftExtraDataCodec() {} + + /** + * Decode raw input and return ibft extra data. + * + * @param input the input + * @return the ibft extra data + */ + @Override + public BftExtraData decodeRaw(final Bytes input) { + checkArgument( + input.size() > EXTRA_VANITY_LENGTH, + "Invalid Bytes supplied - too short to produce a valid IBFT Extra Data object."); + + final Bytes vanityData = input.slice(0, EXTRA_VANITY_LENGTH); + + final Bytes rlpData = input.slice(EXTRA_VANITY_LENGTH); + final RLPInput rlpInput = new BytesValueRLPInput(rlpData, false); + + rlpInput.enterList(); // This accounts for the "root node" which contains IBFT data items. + final Collection
validators = rlpInput.readList(Address::readFrom); + final SECPSignature proposerSeal = parseProposerSeal(rlpInput); + final Collection seals = + rlpInput.readList(rlp -> SIGNATURE_ALGORITHM.get().decodeSignature(rlp.readBytes())); + rlpInput.leaveList(); + + return new BftExtraData(vanityData, seals, proposerSeal, validators); + } + + private static SECPSignature parseProposerSeal(final RLPInput rlpInput) { + final Bytes data = rlpInput.readBytes(); + return data.isZero() ? null : SIGNATURE_ALGORITHM.get().decodeSignature(data); + } + + /** + * Encode extra data to bytes. + * + * @return the bytes + */ + @Override + public Bytes encode(final BftExtraData bftExtraData, final EncodingType encodingType) { + throw new UnsupportedOperationException("The encode method is not supported."); + } +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftHelpers.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftHelpers.java new file mode 100644 index 00000000000..978915c830c --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftHelpers.java @@ -0,0 +1,38 @@ +/* + * 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.ibftlegacy; + +import org.hyperledger.besu.datatypes.Hash; + +/** The Ibft helpers utility class. */ +public class IbftHelpers { + /** Default constructor */ + public IbftHelpers() {} + + /** The constant EXPECTED_MIX_HASH. */ + public static final Hash EXPECTED_MIX_HASH = + Hash.fromHexString("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365"); + + /** + * Calculate required validator quorum. + * + * @param validatorCount the validator count + * @return the int + */ + public static int calculateRequiredValidatorQuorum(final int validatorCount) { + final int F = (validatorCount - 1) / 3; + return (2 * F) + 1; + } +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftLegacyBlockInterface.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftLegacyBlockInterface.java new file mode 100644 index 00000000000..6fab1fa0f1a --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftLegacyBlockInterface.java @@ -0,0 +1,104 @@ +/* + * 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.ibftlegacy; + +import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; +import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; +import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; +import org.hyperledger.besu.consensus.common.bft.BftExtraData; +import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.validator.ValidatorVote; +import org.hyperledger.besu.consensus.common.validator.VoteType; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.BlockHeader; + +import java.util.Collection; +import java.util.Optional; + +import com.google.common.collect.ImmutableBiMap; +import org.apache.tuweni.bytes.Bytes; + +/** The Ibft legacy block interface. */ +public class IbftLegacyBlockInterface extends BftBlockInterface { + + /** The constant NO_VOTE_SUBJECT. */ + public static final Address NO_VOTE_SUBJECT = Address.wrap(Bytes.wrap(new byte[Address.SIZE])); + + /** The constant ADD_NONCE. */ + public static final long ADD_NONCE = 0xFFFFFFFFFFFFFFFFL; + + /** The constant DROP_NONCE. */ + public static final long DROP_NONCE = 0x0L; + + private static final ImmutableBiMap voteToValue = + ImmutableBiMap.of( + VoteType.ADD, ADD_NONCE, + VoteType.DROP, DROP_NONCE); + + private static final IbftExtraDataCodec ibftExtraDataCodec = new IbftExtraDataCodec(); + + /** + * Constructor for IbftLegacyBlockInterface. + * + * @param bftExtraDataCodec the codec for BFT extra data + */ + public IbftLegacyBlockInterface(final BftExtraDataCodec bftExtraDataCodec) { + super(bftExtraDataCodec); + } + + @Override + public Address getProposerOfBlock(final BlockHeader header) { + final BftExtraData ibftExtraData = ibftExtraDataCodec.decode(header); + return IbftBlockHashing.recoverProposerAddress(header, ibftExtraData); + } + + @Override + public Address getProposerOfBlock(final org.hyperledger.besu.plugin.data.BlockHeader header) { + return getProposerOfBlock( + BlockHeader.convertPluginBlockHeader( + header, + new BftBlockHeaderFunctions( + h -> new BftBlockHashing(ibftExtraDataCodec).calculateDataHashForCommittedSeal(h), + ibftExtraDataCodec))); + } + + @Override + public Optional extractVoteFromHeader(final BlockHeader header) { + final Address candidate = header.getCoinbase(); + if (!candidate.equals(NO_VOTE_SUBJECT)) { + final Address proposer = getProposerOfBlock(header); + final VoteType votePolarity = voteToValue.inverse().get(header.getNonce()); + final Address recipient = header.getCoinbase(); + + return Optional.of(new ValidatorVote(votePolarity, proposer, recipient)); + } + return Optional.empty(); + } + + @Override + public Collection
validatorsInBlock(final BlockHeader header) { + return ibftExtraDataCodec.decode(header).getValidators(); + } + + /** + * Is valid vote value. + * + * @param value the value + * @return the boolean + */ + public static boolean isValidVoteValue(final long value) { + return voteToValue.values().contains(value); + } +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftProtocolSchedule.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftProtocolSchedule.java new file mode 100644 index 00000000000..a75d1ae65d4 --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftProtocolSchedule.java @@ -0,0 +1,108 @@ +/* + * 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.ibftlegacy; + +import static org.hyperledger.besu.consensus.ibftlegacy.IbftBlockHeaderValidationRulesetFactory.ibftBlockHeaderValidator; + +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.config.IbftLegacyConfigOptions; +import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockBodyValidator; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockImporter; +import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.math.BigInteger; +import java.util.Optional; + +/** Defines the protocol behaviours for a blockchain using IBFT. */ +public class IbftProtocolSchedule { + + // Default constructor + /** Default constructor */ + public IbftProtocolSchedule() {} + + private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE; + private static final IbftExtraDataCodec ibftExtraDataCodec = new IbftExtraDataCodec(); + + /** + * Create protocol schedule. + * + * @param config the config + * @param privacyParameters the privacy parameters + * @param isRevertReasonEnabled the is revert reason enabled + * @param evmConfiguration the evm configuration + * @return the protocol schedule + */ + public static ProtocolSchedule create( + final GenesisConfigOptions config, + final PrivacyParameters privacyParameters, + final boolean isRevertReasonEnabled, + final EvmConfiguration evmConfiguration) { + final IbftLegacyConfigOptions ibftConfig = config.getIbftLegacyConfigOptions(); + final long blockPeriod = ibftConfig.getBlockPeriodSeconds(); + + return new ProtocolScheduleBuilder( + config, + Optional.of(DEFAULT_CHAIN_ID), + ProtocolSpecAdapters.create(0, builder -> applyIbftChanges(blockPeriod, builder)), + privacyParameters, + isRevertReasonEnabled, + evmConfiguration, + null, + new BadBlockManager(), + false, + null) + .createProtocolSchedule(); + } + + /** + * Create protocol schedule. + * + * @param config the config + * @param isRevertReasonEnabled the is revert reason enabled + * @param evmConfiguration the evm configuration + * @return the protocol schedule + */ + public static ProtocolSchedule create( + final GenesisConfigOptions config, + final boolean isRevertReasonEnabled, + final EvmConfiguration evmConfiguration) { + return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled, evmConfiguration); + } + + private static ProtocolSpecBuilder applyIbftChanges( + final long secondsBetweenBlocks, final ProtocolSpecBuilder builder) { + return builder + .blockHeaderValidatorBuilder(feeMarket -> ibftBlockHeaderValidator(secondsBetweenBlocks)) + .ommerHeaderValidatorBuilder(feeMarket -> ibftBlockHeaderValidator(secondsBetweenBlocks)) + .blockBodyValidatorBuilder(MainnetBlockBodyValidator::new) + .blockValidatorBuilder(MainnetProtocolSpecs.blockValidatorBuilder()) + .blockImporterBuilder(MainnetBlockImporter::new) + .difficultyCalculator((time, parent) -> BigInteger.ONE) + .blockReward(Wei.ZERO) + .skipZeroBlockRewards(true) + .blockHeaderFunctions( + new BftBlockHeaderFunctions( + IbftBlockHashing::calculateHashOfIbftBlockOnchain, ibftExtraDataCodec)); + } +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/headervalidationrules/IbftExtraDataValidationRule.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/headervalidationrules/IbftExtraDataValidationRule.java new file mode 100644 index 00000000000..b6744c03d0a --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/headervalidationrules/IbftExtraDataValidationRule.java @@ -0,0 +1,149 @@ +/* + * 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.ibftlegacy.headervalidationrules; + +import org.hyperledger.besu.consensus.common.bft.BftExtraData; +import org.hyperledger.besu.consensus.common.bft.BftHelpers; +import org.hyperledger.besu.consensus.ibft.IbftLegacyContext; +import org.hyperledger.besu.consensus.ibftlegacy.IbftBlockHashing; +import org.hyperledger.besu.consensus.ibftlegacy.IbftExtraDataCodec; +import org.hyperledger.besu.consensus.ibftlegacy.IbftHelpers; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.mainnet.AttachedBlockHeaderValidationRule; +import org.hyperledger.besu.ethereum.rlp.RLPException; + +import java.util.Collection; +import java.util.List; +import java.util.NavigableSet; +import java.util.TreeSet; + +import com.google.common.collect.Iterables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Ensures the byte content of the extraData field can be deserialised into an appropriate + * structure, and that the structure created contains data matching expectations from preceding + * blocks. + */ +public class IbftExtraDataValidationRule implements AttachedBlockHeaderValidationRule { + + private static final Logger LOG = LoggerFactory.getLogger(IbftExtraDataValidationRule.class); + private static final IbftExtraDataCodec ibftExtraDataCodec = new IbftExtraDataCodec(); + + private final boolean validateCommitSeals; + private final long ceil2nBy3Block; + + /** + * Instantiates a new Ibft extra data validation rule. + * + * @param validateCommitSeals the validate commit seals + * @param ceil2nBy3Block the ceil 2 n by 3 block + */ + public IbftExtraDataValidationRule(final boolean validateCommitSeals, final long ceil2nBy3Block) { + this.validateCommitSeals = validateCommitSeals; + this.ceil2nBy3Block = ceil2nBy3Block; + } + + @Override + public boolean validate( + final BlockHeader header, final BlockHeader parent, final ProtocolContext context) { + try { + final Collection
storedValidators = + context + .getConsensusContext(IbftLegacyContext.class) + .getValidatorProvider() + .getValidatorsAfterBlock(parent); + final BftExtraData ibftExtraData = ibftExtraDataCodec.decode(header); + + final Address proposer = IbftBlockHashing.recoverProposerAddress(header, ibftExtraData); + + if (!storedValidators.contains(proposer)) { + LOG.info("Invalid block header: Proposer sealing block is not a member of the validators."); + return false; + } + + if (validateCommitSeals) { + final List
committers = + IbftBlockHashing.recoverCommitterAddresses(header, ibftExtraData); + + final int minimumSealsRequired = + header.getNumber() < ceil2nBy3Block + ? IbftHelpers.calculateRequiredValidatorQuorum(storedValidators.size()) + : BftHelpers.calculateRequiredValidatorQuorum(storedValidators.size()); + + if (!validateCommitters(committers, storedValidators, minimumSealsRequired)) { + return false; + } + } + + final NavigableSet
sortedReportedValidators = + new TreeSet<>(ibftExtraData.getValidators()); + + if (!Iterables.elementsEqual(ibftExtraData.getValidators(), sortedReportedValidators)) { + LOG.info( + "Invalid block header: Validators are not sorted in ascending order. Expected {} but got {}.", + sortedReportedValidators, + ibftExtraData.getValidators()); + return false; + } + + if (!Iterables.elementsEqual(ibftExtraData.getValidators(), storedValidators)) { + LOG.info( + "Invalid block header: Incorrect validators. Expected {} but got {}.", + storedValidators, + ibftExtraData.getValidators()); + return false; + } + + } catch (final RLPException ex) { + LOG.info( + "Invalid block header: ExtraData field was unable to be deserialised into an IBFT Struct.", + ex); + return false; + } catch (final IllegalArgumentException ex) { + LOG.info("Invalid block header: Failed to verify extra data", ex); + return false; + } catch (final RuntimeException ex) { + LOG.info("Invalid block header: Failed to find validators at parent"); + return false; + } + + return true; + } + + private boolean validateCommitters( + final Collection
committers, + final Collection
storedValidators, + final int minimumSealsRequired) { + if (committers.size() < minimumSealsRequired) { + LOG.info( + "Invalid block header: Insufficient committers to seal block. (Required {}, received {})", + minimumSealsRequired, + committers.size()); + return false; + } + + if (!storedValidators.containsAll(committers)) { + LOG.info( + "Invalid block header: Not all committers are in the locally maintained validator list."); + return false; + } + + return true; + } +} diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/headervalidationrules/VoteValidationRule.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/headervalidationrules/VoteValidationRule.java new file mode 100644 index 00000000000..95687592948 --- /dev/null +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/headervalidationrules/VoteValidationRule.java @@ -0,0 +1,48 @@ +/* + * 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.ibftlegacy.headervalidationrules; + +import org.hyperledger.besu.consensus.ibftlegacy.IbftLegacyBlockInterface; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.mainnet.DetachedBlockHeaderValidationRule; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** The Vote validation rule. */ +public class VoteValidationRule implements DetachedBlockHeaderValidationRule { + + private static final Logger LOG = LoggerFactory.getLogger(VoteValidationRule.class); + + /** Default constructor */ + public VoteValidationRule() {} + + /** + * Responsible for ensuring the nonce is either auth or drop. + * + * @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 nonce in the header is a valid validator vote value. + */ + @Override + public boolean validate(final BlockHeader header, final BlockHeader parent) { + final long nonce = header.getNonce(); + if (!IbftLegacyBlockInterface.isValidVoteValue(nonce)) { + LOG.info("Invalid block header: Nonce value ({}) is neither auth or drop.", nonce); + return false; + } + return true; + } +} diff --git a/consensus/qbft/build.gradle b/consensus/qbft/build.gradle index 5481d9776e8..d5cc79c6df9 100644 --- a/consensus/qbft/build.gradle +++ b/consensus/qbft/build.gradle @@ -31,7 +31,13 @@ jar { dependencies { implementation project(':config') implementation project(':consensus:common') +<<<<<<< HEAD implementation project(':consensus:qbft-core') + implementation project(':consensus:ibftlegacy') +======= + implementation project(':consensus:ibftlegacy') + implementation project(':consensus:qbft-core') +>>>>>>> 65f1a71cff9d2d2682e92e82d3711230842b1767 implementation project(':crypto:services') implementation project(':datatypes') implementation project(':ethereum:api') diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java index d1a91083b63..45cdc98924d 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java @@ -32,6 +32,8 @@ public interface MiningCoordinator { void stop(); + default void subscribe() {} + void awaitStop() throws InterruptedException; /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java index 33897c3e1ec..5bd90259685 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java @@ -89,6 +89,19 @@ public C getConsensusContext(final Class klass) return consensusContext.as(klass); } + /** + * Gets the consensus context of the protocol context. + * + * @param the type of the consensus context + * @param klass the klass + * @param blockNumber the block number + * @return the consensus context of the protocol context + */ + public C getConsensusContext( + final Class klass, final long blockNumber) { + return consensusContext.as(klass); + } + /** * Gets the safe consensus context of the protocol context. * diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java index 7af024bf18b..1bd89f48615 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java @@ -130,7 +130,7 @@ static GenesisFileModule createGenesisModule() { private static GenesisFileModule createGenesisModule(final String genesisConfig) { final JsonObject genesis = new JsonObject(genesisConfig); final JsonObject config = genesis.getJsonObject("config"); - if (config.containsKey("clique") || config.containsKey("qbft")) { + if (config.containsKey("ibft") || config.containsKey("clique") || config.containsKey("qbft")) { throw new RuntimeException("Only Ethash and Merge configs accepted as genesis files"); } return new MainnetGenesisFileModule(genesisConfig); diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 42b3d5d5662..8c87ae2fc8a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3836,6 +3836,14 @@ + + + + + + + + @@ -3868,6 +3876,14 @@ + + + + + + + + @@ -5417,6 +5433,11 @@ + + + + + @@ -5472,6 +5493,11 @@ + + + + + @@ -5496,6 +5522,11 @@ + + + + + @@ -5888,6 +5919,14 @@ + + + + + + + + diff --git a/platform/build.gradle b/platform/build.gradle index 609189993f4..aabd5d8720a 100644 --- a/platform/build.gradle +++ b/platform/build.gradle @@ -46,6 +46,7 @@ dependencies { api project(':consensus:clique') api project(':consensus:common') api project(':consensus:ibft') + api project(':consensus:ibftlegacy') api project(':consensus:merge') api project(':consensus:qbft') api project(':crypto:algorithms') diff --git a/settings.gradle b/settings.gradle index 2473b7c0502..1eba157ed7e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,6 +34,7 @@ include 'config' include 'consensus:clique' include 'consensus:common' include 'consensus:ibft' +include 'consensus:ibftlegacy' include 'consensus:merge' include 'consensus:qbft' include 'consensus:qbft-core'