mirror of https://github.com/hyperledger/besu
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
parent
6dcca0c52c
commit
4d73f3d262
@ -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; |
||||
} |
||||
} |
Loading…
Reference in new issue