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