Add components to handle the transition from PoW to PoS (#3462)

Code originally written by garyschulte and jflo on the merge branch.
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
pull/3464/head
Fabio Di Fabio 3 years ago committed by GitHub
parent 6dcca0c52c
commit 4d73f3d262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      besu/src/main/java/org/hyperledger/besu/controller/BesuController.java
  2. 310
      besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java
  3. 113
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionContext.java
  4. 73
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java
  5. 161
      consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java
  6. 192
      consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java
  7. 27
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java

@ -19,6 +19,7 @@ import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.PowAlgorithm;
import org.hyperledger.besu.config.QbftConfigOptions;
import org.hyperledger.besu.config.experimental.MergeConfigOptions;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
@ -210,7 +211,15 @@ public class BesuController implements java.io.Closeable {
} else {
throw new IllegalArgumentException("Unknown consensus mechanism defined");
}
return builder.genesisConfigFile(genesisConfig);
// use merge config if experimental merge flag is enabled:
if (MergeConfigOptions.isMergeEnabled()) {
// TODO this should be changed to vanilla MergeBesuControllerBuilder and the Transition*
// series of classes removed after we successfully transition to PoS
// https://github.com/hyperledger/besu/issues/2897
return new TransitionBesuControllerBuilder(builder, new MergeBesuControllerBuilder())
.genesisConfigFile(genesisConfig);
} else return builder.genesisConfigFile(genesisConfig);
}
private BesuControllerBuilder createConsensusScheduleBesuControllerBuilder(

@ -0,0 +1,310 @@
/*
* 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.controller;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.consensus.merge.PostMergeContext;
import org.hyperledger.besu.consensus.merge.TransitionContext;
import org.hyperledger.besu.consensus.merge.TransitionProtocolSchedule;
import org.hyperledger.besu.consensus.merge.blockcreation.TransitionCoordinator;
import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.ConsensusContext;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration;
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider;
import java.math.BigInteger;
import java.nio.file.Path;
import java.time.Clock;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
public class TransitionBesuControllerBuilder extends BesuControllerBuilder {
private final BesuControllerBuilder preMergeBesuControllerBuilder;
private final MergeBesuControllerBuilder mergeBesuControllerBuilder;
public TransitionBesuControllerBuilder(
final BesuControllerBuilder preMergeBesuControllerBuilder,
final MergeBesuControllerBuilder mergeBesuControllerBuilder) {
this.preMergeBesuControllerBuilder = preMergeBesuControllerBuilder;
this.mergeBesuControllerBuilder = mergeBesuControllerBuilder;
}
@Override
protected void prepForBuild() {
preMergeBesuControllerBuilder.prepForBuild();
mergeBesuControllerBuilder.prepForBuild();
}
@Override
protected MiningCoordinator createMiningCoordinator(
final ProtocolSchedule protocolSchedule,
final ProtocolContext protocolContext,
final TransactionPool transactionPool,
final MiningParameters miningParameters,
final SyncState syncState,
final EthProtocolManager ethProtocolManager) {
// cast to transition schedule for explicit access to pre and post objects:
final TransitionProtocolSchedule tps = (TransitionProtocolSchedule) protocolSchedule;
final TransitionCoordinator composedCoordinator =
new TransitionCoordinator(
preMergeBesuControllerBuilder.createMiningCoordinator(
tps.getPreMergeSchedule(),
protocolContext,
transactionPool,
new MiningParameters.Builder(miningParameters).enabled(false).build(),
syncState,
ethProtocolManager),
mergeBesuControllerBuilder.createMiningCoordinator(
tps.getPostMergeSchedule(),
protocolContext,
transactionPool,
miningParameters,
syncState,
ethProtocolManager));
initTransitionWatcher(protocolContext, composedCoordinator);
return composedCoordinator;
}
@Override
protected ProtocolSchedule createProtocolSchedule() {
return new TransitionProtocolSchedule(
preMergeBesuControllerBuilder.createProtocolSchedule(),
mergeBesuControllerBuilder.createProtocolSchedule());
}
@Override
protected ConsensusContext createConsensusContext(
final Blockchain blockchain,
final WorldStateArchive worldStateArchive,
final ProtocolSchedule protocolSchedule) {
return new TransitionContext(
preMergeBesuControllerBuilder.createConsensusContext(
blockchain, worldStateArchive, protocolSchedule),
mergeBesuControllerBuilder.createConsensusContext(
blockchain, worldStateArchive, protocolSchedule));
}
@Override
protected PluginServiceFactory createAdditionalPluginServices(
final Blockchain blockchain, final ProtocolContext protocolContext) {
return new NoopPluginServiceFactory();
}
private void initTransitionWatcher(
final ProtocolContext protocolContext, final TransitionCoordinator composedCoordinator) {
PostMergeContext postMergeContext = protocolContext.getConsensusContext(PostMergeContext.class);
postMergeContext.observeNewIsPostMergeState(
newIsPostMergeState -> {
if (newIsPostMergeState) {
// if we transitioned to post-merge, stop and disable any mining
composedCoordinator.getPreMergeObject().disable();
composedCoordinator.getPreMergeObject().stop();
} else if (composedCoordinator.isMiningBeforeMerge()) {
// if we transitioned back to pre-merge and were mining, restart mining
composedCoordinator.getPreMergeObject().enable();
composedCoordinator.getPreMergeObject().start();
}
});
// initialize our merge context merge status before we would start either
Blockchain blockchain = protocolContext.getBlockchain();
blockchain
.getTotalDifficultyByHash(blockchain.getChainHeadHash())
.ifPresent(postMergeContext::setIsPostMerge);
}
@Override
public BesuControllerBuilder storageProvider(final StorageProvider storageProvider) {
super.storageProvider(storageProvider);
return propagateConfig(z -> z.storageProvider(storageProvider));
}
@Override
public BesuController build() {
BesuController controller = super.build();
PostMergeContext.get().setSyncState(controller.getSyncState());
return controller;
}
@Override
public BesuControllerBuilder evmConfiguration(final EvmConfiguration evmConfiguration) {
super.evmConfiguration(evmConfiguration);
return propagateConfig(z -> z.evmConfiguration(evmConfiguration));
}
@Override
public BesuControllerBuilder genesisConfigFile(final GenesisConfigFile genesisConfig) {
super.genesisConfigFile(genesisConfig);
return propagateConfig(z -> z.genesisConfigFile(genesisConfig));
}
@Override
public BesuControllerBuilder synchronizerConfiguration(
final SynchronizerConfiguration synchronizerConfig) {
super.synchronizerConfiguration(synchronizerConfig);
return propagateConfig(z -> z.synchronizerConfiguration(synchronizerConfig));
}
@Override
public BesuControllerBuilder ethProtocolConfiguration(
final EthProtocolConfiguration ethProtocolConfiguration) {
super.ethProtocolConfiguration(ethProtocolConfiguration);
return propagateConfig(z -> z.ethProtocolConfiguration(ethProtocolConfiguration));
}
@Override
public BesuControllerBuilder networkId(final BigInteger networkId) {
super.networkId(networkId);
return propagateConfig(z -> z.networkId(networkId));
}
@Override
public BesuControllerBuilder miningParameters(final MiningParameters miningParameters) {
super.miningParameters(miningParameters);
return propagateConfig(z -> z.miningParameters(miningParameters));
}
@Override
public BesuControllerBuilder messagePermissioningProviders(
final List<NodeMessagePermissioningProvider> messagePermissioningProviders) {
super.messagePermissioningProviders(messagePermissioningProviders);
return propagateConfig(z -> z.messagePermissioningProviders(messagePermissioningProviders));
}
@Override
public BesuControllerBuilder nodeKey(final NodeKey nodeKey) {
super.nodeKey(nodeKey);
return propagateConfig(z -> z.nodeKey(nodeKey));
}
@Override
public BesuControllerBuilder metricsSystem(final ObservableMetricsSystem metricsSystem) {
super.metricsSystem(metricsSystem);
return propagateConfig(z -> z.metricsSystem(metricsSystem));
}
@Override
public BesuControllerBuilder privacyParameters(final PrivacyParameters privacyParameters) {
super.privacyParameters(privacyParameters);
return propagateConfig(z -> z.privacyParameters(privacyParameters));
}
@Override
public BesuControllerBuilder pkiBlockCreationConfiguration(
final Optional<PkiBlockCreationConfiguration> pkiBlockCreationConfiguration) {
super.pkiBlockCreationConfiguration(pkiBlockCreationConfiguration);
return propagateConfig(z -> z.pkiBlockCreationConfiguration(pkiBlockCreationConfiguration));
}
@Override
public BesuControllerBuilder dataDirectory(final Path dataDirectory) {
super.dataDirectory(dataDirectory);
return propagateConfig(z -> z.dataDirectory(dataDirectory));
}
@Override
public BesuControllerBuilder clock(final Clock clock) {
super.clock(clock);
return propagateConfig(z -> z.clock(clock));
}
@Override
public BesuControllerBuilder transactionPoolConfiguration(
final TransactionPoolConfiguration transactionPoolConfiguration) {
super.transactionPoolConfiguration(transactionPoolConfiguration);
return propagateConfig(z -> z.transactionPoolConfiguration(transactionPoolConfiguration));
}
@Override
public BesuControllerBuilder isRevertReasonEnabled(final boolean isRevertReasonEnabled) {
super.isRevertReasonEnabled(isRevertReasonEnabled);
return propagateConfig(z -> z.isRevertReasonEnabled(isRevertReasonEnabled));
}
@Override
public BesuControllerBuilder isPruningEnabled(final boolean isPruningEnabled) {
super.isPruningEnabled(isPruningEnabled);
return propagateConfig(z -> z.isPruningEnabled(isPruningEnabled));
}
@Override
public BesuControllerBuilder pruningConfiguration(final PrunerConfiguration prunerConfiguration) {
super.pruningConfiguration(prunerConfiguration);
return propagateConfig(z -> z.pruningConfiguration(prunerConfiguration));
}
@Override
public BesuControllerBuilder genesisConfigOverrides(
final Map<String, String> genesisConfigOverrides) {
super.genesisConfigOverrides(genesisConfigOverrides);
return propagateConfig(z -> z.genesisConfigOverrides(genesisConfigOverrides));
}
@Override
public BesuControllerBuilder gasLimitCalculator(final GasLimitCalculator gasLimitCalculator) {
super.gasLimitCalculator(gasLimitCalculator);
return propagateConfig(z -> z.gasLimitCalculator(gasLimitCalculator));
}
@Override
public BesuControllerBuilder requiredBlocks(final Map<Long, Hash> requiredBlocks) {
super.requiredBlocks(requiredBlocks);
return propagateConfig(z -> z.requiredBlocks(requiredBlocks));
}
@Override
public BesuControllerBuilder reorgLoggingThreshold(final long reorgLoggingThreshold) {
super.reorgLoggingThreshold(reorgLoggingThreshold);
return propagateConfig(z -> z.reorgLoggingThreshold(reorgLoggingThreshold));
}
@Override
public BesuControllerBuilder dataStorageConfiguration(
final DataStorageConfiguration dataStorageConfiguration) {
super.dataStorageConfiguration(dataStorageConfiguration);
return propagateConfig(z -> z.dataStorageConfiguration(dataStorageConfiguration));
}
private BesuControllerBuilder propagateConfig(final Consumer<BesuControllerBuilder> toPropogate) {
toPropogate.accept(preMergeBesuControllerBuilder);
toPropogate.accept(mergeBesuControllerBuilder);
return this;
}
}

@ -0,0 +1,113 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.merge;
import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier;
import org.hyperledger.besu.ethereum.ConsensusContext;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.eth.sync.state.SyncState;
import java.util.Optional;
public class TransitionContext implements MergeContext {
final ConsensusContext preMergeContext;
final MergeContext postMergeContext;
public TransitionContext(
final ConsensusContext preMergeContext, final MergeContext postMergeContext) {
this.preMergeContext = preMergeContext;
this.postMergeContext = postMergeContext;
}
@Override
public <C extends ConsensusContext> C as(final Class<C> klass) {
if (klass.isInstance(postMergeContext)) {
return klass.cast(postMergeContext);
}
return klass.cast(preMergeContext);
}
@Override
public MergeContext setSyncState(final SyncState syncState) {
return postMergeContext.setSyncState(syncState);
}
@Override
public MergeContext setTerminalTotalDifficulty(final Difficulty newTerminalTotalDifficulty) {
return postMergeContext.setTerminalTotalDifficulty(newTerminalTotalDifficulty);
}
@Override
public void setIsPostMerge(final Difficulty totalDifficulty) {
postMergeContext.setIsPostMerge(totalDifficulty);
}
@Override
public boolean isPostMerge() {
return postMergeContext.isPostMerge();
}
@Override
public boolean isSyncing() {
return postMergeContext.isSyncing();
}
@Override
public void observeNewIsPostMergeState(final NewMergeStateCallback newMergeStateCallback) {
postMergeContext.observeNewIsPostMergeState(newMergeStateCallback);
}
@Override
public Difficulty getTerminalTotalDifficulty() {
return postMergeContext.getTerminalTotalDifficulty();
}
@Override
public void setFinalized(final BlockHeader blockHeader) {
postMergeContext.setFinalized(blockHeader);
}
@Override
public Optional<BlockHeader> getFinalized() {
return postMergeContext.getFinalized();
}
@Override
public Optional<BlockHeader> getTerminalPoWBlock() {
return this.postMergeContext.getTerminalPoWBlock();
}
@Override
public void setTerminalPoWBlock(final Optional<BlockHeader> hashAndNumber) {
this.postMergeContext.setTerminalPoWBlock(hashAndNumber);
}
@Override
public boolean validateCandidateHead(final BlockHeader candidateHeader) {
return postMergeContext.validateCandidateHead(candidateHeader);
}
@Override
public void putPayloadById(final PayloadIdentifier payloadId, final Block block) {
postMergeContext.putPayloadById(payloadId, block);
}
@Override
public Optional<Block> retrieveBlockById(final PayloadIdentifier payloadId) {
return postMergeContext.retrieveBlockById(payloadId);
}
}

@ -0,0 +1,73 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.merge;
import org.hyperledger.besu.ethereum.core.TransactionFilter;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import java.math.BigInteger;
import java.util.Optional;
import java.util.stream.Stream;
public class TransitionProtocolSchedule extends TransitionUtils<ProtocolSchedule>
implements ProtocolSchedule {
public TransitionProtocolSchedule(
final ProtocolSchedule preMergeProtocolSchedule,
final ProtocolSchedule postMergeProtocolSchedule) {
super(preMergeProtocolSchedule, postMergeProtocolSchedule);
}
public ProtocolSchedule getPreMergeSchedule() {
return getPreMergeObject();
}
public ProtocolSchedule getPostMergeSchedule() {
return getPostMergeObject();
}
@Override
public ProtocolSpec getByBlockNumber(final long number) {
return dispatchFunctionAccordingToMergeState(
protocolSchedule -> protocolSchedule.getByBlockNumber(number));
}
@Override
public Stream<Long> streamMilestoneBlocks() {
return dispatchFunctionAccordingToMergeState(ProtocolSchedule::streamMilestoneBlocks);
}
@Override
public Optional<BigInteger> getChainId() {
return dispatchFunctionAccordingToMergeState(ProtocolSchedule::getChainId);
}
@Override
public void setTransactionFilter(final TransactionFilter transactionFilter) {
dispatchConsumerAccordingToMergeState(
protocolSchedule -> protocolSchedule.setTransactionFilter(transactionFilter));
}
@Override
public void setPublicWorldStateArchiveForPrivacyBlockProcessor(
final WorldStateArchive publicWorldStateArchive) {
dispatchConsumerAccordingToMergeState(
protocolSchedule ->
protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor(
publicWorldStateArchive));
}
}

@ -0,0 +1,161 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.merge.blockcreation;
import org.hyperledger.besu.consensus.merge.TransitionUtils;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockValidator.Result;
import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
public class TransitionCoordinator extends TransitionUtils<MiningCoordinator>
implements MergeMiningCoordinator {
private final MiningCoordinator miningCoordinator;
private final MergeMiningCoordinator mergeCoordinator;
public TransitionCoordinator(
final MiningCoordinator miningCoordinator, final MiningCoordinator mergeCoordinator) {
super(miningCoordinator, mergeCoordinator);
this.miningCoordinator = miningCoordinator;
this.mergeCoordinator = (MergeMiningCoordinator) mergeCoordinator;
}
@Override
public void start() {
if (isMiningBeforeMerge()) {
miningCoordinator.start();
}
}
@Override
public void stop() {
miningCoordinator.stop();
}
@Override
public void awaitStop() throws InterruptedException {
miningCoordinator.awaitStop();
}
@Override
public boolean enable() {
return dispatchFunctionAccordingToMergeState(MiningCoordinator::enable);
}
@Override
public boolean disable() {
return dispatchFunctionAccordingToMergeState(MiningCoordinator::disable);
}
@Override
public boolean isMining() {
return dispatchFunctionAccordingToMergeState(MiningCoordinator::isMining);
}
@Override
public Wei getMinTransactionGasPrice() {
return dispatchFunctionAccordingToMergeState(MiningCoordinator::getMinTransactionGasPrice);
}
@Override
public void setExtraData(final Bytes extraData) {
miningCoordinator.setExtraData(extraData);
mergeCoordinator.setExtraData(extraData);
}
@Override
public Optional<Address> getCoinbase() {
return dispatchFunctionAccordingToMergeState(MiningCoordinator::getCoinbase);
}
@Override
public Optional<Block> createBlock(
final BlockHeader parentHeader,
final List<Transaction> transactions,
final List<BlockHeader> ommers) {
return dispatchFunctionAccordingToMergeState(
(MiningCoordinator coordinator) ->
miningCoordinator.createBlock(parentHeader, transactions, ommers));
}
@Override
public Optional<Block> createBlock(final BlockHeader parentHeader, final long timestamp) {
return dispatchFunctionAccordingToMergeState(
(MiningCoordinator coordinator) -> coordinator.createBlock(parentHeader, timestamp));
}
@Override
public void changeTargetGasLimit(final Long targetGasLimit) {
miningCoordinator.changeTargetGasLimit(targetGasLimit);
mergeCoordinator.changeTargetGasLimit(targetGasLimit);
}
@Override
public PayloadIdentifier preparePayload(
final BlockHeader parentHeader,
final Long timestamp,
final Bytes32 random,
final Address feeRecipient) {
return mergeCoordinator.preparePayload(parentHeader, timestamp, random, feeRecipient);
}
@Override
public Result executeBlock(final Block block) {
return mergeCoordinator.executeBlock(block);
}
@Override
public ForkchoiceResult updateForkChoice(
final Hash headBlockHash, final Hash finalizedBlockHash) {
return mergeCoordinator.updateForkChoice(headBlockHash, finalizedBlockHash);
}
@Override
public Optional<Hash> getLatestValidAncestor(final Hash blockHash) {
return mergeCoordinator.getLatestValidAncestor(blockHash);
}
@Override
public Optional<Hash> getLatestValidAncestor(final BlockHeader blockHeader) {
return mergeCoordinator.getLatestValidAncestor(blockHeader);
}
@Override
public boolean latestValidAncestorDescendsFromTerminal(final BlockHeader blockHeader) {
// this is nonsensical pre-merge, but should be fine to delegate
return mergeCoordinator.latestValidAncestorDescendsFromTerminal(blockHeader);
}
@Override
public boolean isBackwardSyncing() {
return mergeCoordinator.isBackwardSyncing();
}
@Override
public boolean isMiningBeforeMerge() {
return mergeCoordinator.isMiningBeforeMerge();
}
}

@ -0,0 +1,192 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.consensus.merge.blockcreation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain;
import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive;
import static org.mockito.Mockito.mock;
import org.hyperledger.besu.config.experimental.MergeConfigOptions;
import org.hyperledger.besu.consensus.merge.MergeContext;
import org.hyperledger.besu.consensus.merge.PostMergeContext;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.chain.GenesisState;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture;
import org.hyperledger.besu.ethereum.core.Difficulty;
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardsSyncContext;
import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter;
import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator;
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket;
import org.hyperledger.besu.ethereum.mainnet.feemarket.LondonFeeMarket;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
import org.hyperledger.besu.util.Log4j2ConfiguratorUtil;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class MergeReorgTest implements MergeGenesisConfigHelper {
@Mock AbstractPendingTransactionsSorter mockSorter;
private MergeCoordinator coordinator;
private final MergeContext mergeContext = PostMergeContext.get();
private final ProtocolSchedule mockProtocolSchedule = getMergeProtocolSchedule();
private final GenesisState genesisState =
GenesisState.fromConfig(getPowGenesisConfigFile(), mockProtocolSchedule);
private final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive();
private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock());
private final ProtocolContext protocolContext =
new ProtocolContext(blockchain, worldStateArchive, mergeContext);
private final Address coinbase = genesisAllocations(getPowGenesisConfigFile()).findFirst().get();
private final BlockHeaderTestFixture headerGenerator = new BlockHeaderTestFixture();
private final BaseFeeMarket feeMarket =
new LondonFeeMarket(0, genesisState.getBlock().getHeader().getBaseFee());
@Before
public void setUp() {
var mutable = worldStateArchive.getMutable();
genesisState.writeStateTo(mutable);
mutable.persist(null);
mergeContext.setTerminalTotalDifficulty(Difficulty.of(1001));
MergeConfigOptions.setMergeEnabled(true);
this.coordinator =
new MergeCoordinator(
protocolContext,
mockProtocolSchedule,
mockSorter,
new MiningParameters.Builder().coinbase(coinbase).build(),
mock(BackwardsSyncContext.class));
mergeContext.setIsPostMerge(genesisState.getBlock().getHeader().getDifficulty());
blockchain.observeBlockAdded(
blockAddedEvent ->
blockchain
.getTotalDifficultyByHash(blockAddedEvent.getBlock().getHeader().getHash())
.ifPresent(mergeContext::setIsPostMerge));
}
/* Validation scenario as described over Discord:
as long as a post-merge PoS block has not been finalized,
then you can and should be able to re-org to a different pre-TTD block
say there is viable TTD block A and B, then we can have a PoS chain build on A for a while
and then see another PoS chain build on B that has a higher fork choice weight and causes a re-org
once any post-merge PoS chain is finalied though, you'd never re-org any PoW blocks in the tree ever again */
@Test
public void reorgsAcrossTDDToDifferentTargetsWhenNotFinal() {
// Add N blocks to chain from genesis, where total diff is < TTD
Log4j2ConfiguratorUtil.setLevelDebug(BlockHeaderValidator.class.getName());
List<Block> endOfWork = subChain(genesisState.getBlock().getHeader(), 10, Difficulty.of(100L));
endOfWork.stream().forEach(coordinator::executeBlock);
assertThat(blockchain.getChainHead().getHeight()).isEqualTo(10L);
BlockHeader tddPenultimate = this.blockchain.getChainHeadHeader();
// Add TTD block A to chain as child of N.
Block ttdA = new Block(terminalPowBlock(tddPenultimate, Difficulty.ONE), BlockBody.empty());
boolean worked = coordinator.executeBlock(ttdA).blockProcessingOutputs.isPresent();
assertThat(worked).isTrue();
assertThat(blockchain.getChainHead().getHeight()).isEqualTo(11L);
assertThat(blockchain.getTotalDifficultyByHash(ttdA.getHash())).isPresent();
Difficulty tdd = blockchain.getTotalDifficultyByHash(ttdA.getHash()).get();
assertThat(tdd.getAsBigInteger())
.isGreaterThan(
getPosGenesisConfigFile()
.getConfigOptions()
.getTerminalTotalDifficulty()
.get()
.toBigInteger());
assertThat(mergeContext.isPostMerge()).isTrue();
List<Block> builtOnTTDA = subChain(ttdA.getHeader(), 5, Difficulty.of(0L));
builtOnTTDA.stream().forEach(coordinator::executeBlock);
assertThat(blockchain.getChainHead().getHeight()).isEqualTo(16);
assertThat(blockchain.getChainHead().getHash())
.isEqualTo(builtOnTTDA.get(builtOnTTDA.size() - 1).getHash());
Block ttdB = new Block(terminalPowBlock(tddPenultimate, Difficulty.of(2L)), BlockBody.empty());
worked = coordinator.executeBlock(ttdB).blockProcessingOutputs.isPresent();
assertThat(worked).isTrue();
List<Block> builtOnTTDB = subChain(ttdB.getHeader(), 10, Difficulty.of(0L));
builtOnTTDB.stream().forEach(coordinator::executeBlock);
assertThat(blockchain.getChainHead().getHeight()).isEqualTo(21);
assertThat(blockchain.getChainHead().getHash())
.isEqualTo(builtOnTTDB.get(builtOnTTDB.size() - 1).getHash());
// don't finalize
// Create a new chain back to A which has
}
private List<Block> subChain(
final BlockHeader parentHeader, final long length, final Difficulty each) {
BlockHeader newParent = parentHeader;
List<Block> retval = new ArrayList<>();
for (int i = 1; i <= length; i++) {
headerGenerator
.parentHash(newParent.getHash())
.number(newParent.getNumber() + 1)
.baseFeePerGas(
feeMarket.computeBaseFee(
genesisState.getBlock().getHeader().getNumber() + 1,
newParent.getBaseFee().orElse(Wei.of(0x3b9aca00)),
0,
15000000))
.gasLimit(newParent.getGasLimit())
.stateRoot(newParent.getStateRoot());
if (each.greaterOrEqualThan(Difficulty.ZERO)) {
headerGenerator.difficulty(each);
}
BlockHeader h = headerGenerator.buildHeader();
retval.add(new Block(h, BlockBody.empty()));
newParent = h;
}
return retval;
}
private BlockHeader terminalPowBlock(final BlockHeader parent, final Difficulty diff) {
BlockHeader terminal =
headerGenerator
.difficulty(diff)
.parentHash(parent.getHash())
.number(parent.getNumber() + 1)
.baseFeePerGas(
feeMarket.computeBaseFee(
genesisState.getBlock().getHeader().getNumber() + 1,
parent.getBaseFee().orElse(Wei.of(0x3b9aca00)),
0,
15000000l))
.gasLimit(parent.getGasLimit())
.stateRoot(parent.getStateRoot())
.buildHeader();
return terminal;
}
}

@ -1,5 +1,5 @@
/*
* Copyright contributors to Hyperledger Besu
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@ -235,6 +235,31 @@ public class MiningParameters {
private long powJobTimeToLive = DEFAULT_POW_JOB_TTL;
private int maxOmmerDepth = DEFAULT_MAX_OMMERS_DEPTH;
public Builder() {
// zero arg
}
public Builder(final MiningParameters existing) {
existing.getCoinbase().ifPresent(cb -> this.coinbase = cb);
existing
.getTargetGasLimit()
.map(AtomicLong::longValue)
.ifPresent(gasLimit -> this.targetGasLimit = gasLimit);
this.minTransactionGasPrice = existing.getMinTransactionGasPrice();
this.extraData = existing.getExtraData();
this.enabled = existing.isMiningEnabled();
this.stratumMiningEnabled = existing.isStratumMiningEnabled();
this.stratumNetworkInterface = existing.getStratumNetworkInterface();
this.stratumPort = existing.getStratumPort();
this.stratumExtranonce = existing.getStratumExtranonce();
existing.getNonceGenerator().ifPresent(ng -> this.maybeNonceGenerator = ng);
this.minBlockOccupancyRatio = existing.getMinBlockOccupancyRatio();
this.remoteSealersLimit = existing.getRemoteSealersLimit();
this.remoteSealersTimeToLive = existing.getRemoteSealersTimeToLive();
this.powJobTimeToLive = existing.getPowJobTimeToLive();
this.maxOmmerDepth = existing.getMaxOmmerDepth();
}
public Builder coinbase(final Address address) {
this.coinbase = address;
return this;

Loading…
Cancel
Save