diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java index edc506b86ae..191bf6770be 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; import java.math.BigInteger; @@ -64,14 +65,16 @@ public static ProtocolSchedule create( return new ProtocolScheduleBuilder( config, DEFAULT_CHAIN_ID, - builder -> - applyCliqueSpecificModifications( - epochManager, - cliqueConfig.getBlockPeriodSeconds(), - localNodeAddress, - builder, - eip1559, - privacyParameters.getGoQuorumPrivacyParameters().isPresent()), + ProtocolSpecAdapters.create( + 0, + builder -> + applyCliqueSpecificModifications( + epochManager, + cliqueConfig.getBlockPeriodSeconds(), + localNodeAddress, + builder, + eip1559, + privacyParameters.getGoQuorumPrivacyParameters().isPresent())), privacyParameters, isRevertReasonEnabled, config.isQuorum()) diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java index 94516a389db..a5563c9ecdc 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; import java.math.BigInteger; @@ -44,9 +45,14 @@ public static ProtocolSchedule create( return new ProtocolScheduleBuilder( config, DEFAULT_CHAIN_ID, - builder -> - applyBftChanges( - config.getBftConfigOptions(), builder, config.isQuorum(), blockHeaderRuleset), + ProtocolSpecAdapters.create( + 0, + builder -> + applyBftChanges( + config.getBftConfigOptions(), + builder, + config.isQuorum(), + blockHeaderRuleset)), privacyParameters, isRevertReasonEnabled, config.isQuorum()) diff --git a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftProtocolSchedule.java b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftProtocolSchedule.java index 5bcf98a5188..694682473c3 100644 --- a/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftProtocolSchedule.java +++ b/consensus/ibftlegacy/src/main/java/org/hyperledger/besu/consensus/ibftlegacy/IbftProtocolSchedule.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; import java.math.BigInteger; @@ -44,9 +45,11 @@ public static ProtocolSchedule create( return new ProtocolScheduleBuilder( config, DEFAULT_CHAIN_ID, - builder -> - applyIbftChanges( - blockPeriod, builder, config.isQuorum(), ibftConfig.getCeil2Nby3Block()), + ProtocolSpecAdapters.create( + 0, + builder -> + applyIbftChanges( + blockPeriod, builder, config.isQuorum(), ibftConfig.getCeil2Nby3Block())), privacyParameters, isRevertReasonEnabled, config.isQuorum()) diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java index 7ea143588c6..5a3dc51ad4e 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/EthHashBlockCreatorTest.java @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.mainnet.EthHashSolver; import org.hyperledger.besu.ethereum.mainnet.EthHasher; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ValidationTestUtils; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -71,7 +72,7 @@ public void createMainnetBlock1() throws IOException { new ProtocolScheduleBuilder( genesisConfigOptions, BigInteger.valueOf(42), - Function.identity(), + ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, genesisConfigOptions.isQuorum()) @@ -130,7 +131,7 @@ public void createMainnetBlock1_fixedDifficulty1() { new ProtocolScheduleBuilder( genesisConfigOptions, BigInteger.valueOf(42), - Function.identity(), + ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, genesisConfigOptions.isQuorum()) @@ -184,7 +185,7 @@ public void rewardBeneficiary_zeroReward_skipZeroRewardsFalse() { new ProtocolScheduleBuilder( genesisConfigOptions, BigInteger.valueOf(42), - Function.identity(), + ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, genesisConfigOptions.isQuorum()) @@ -254,7 +255,7 @@ public void rewardBeneficiary_zeroReward_skipZeroRewardsTrue() { new ProtocolScheduleBuilder( genesisConfigOptions, BigInteger.valueOf(42), - Function.identity(), + ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, genesisConfigOptions.isQuorum()) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java index 014ea997fa4..2b4ee876c4e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; /** A ProtocolSchedule which behaves similarly to MainNet, but with a much reduced difficulty. */ public class FixedDifficultyProtocolSchedule { @@ -28,7 +29,10 @@ public static ProtocolSchedule create( final boolean isRevertReasonEnabled) { return new ProtocolScheduleBuilder( config, - builder -> builder.difficultyCalculator(FixedDifficultyCalculators.calculator(config)), + ProtocolSpecAdapters.create( + 0, + builder -> + builder.difficultyCalculator(FixedDifficultyCalculators.calculator(config))), privacyParameters, isRevertReasonEnabled, config.isQuorum()) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java index 3fcc0eaf4a4..c0806ff23c4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java @@ -47,7 +47,7 @@ public static ProtocolSchedule fromConfig( return new ProtocolScheduleBuilder( config, DEFAULT_CHAIN_ID, - Function.identity(), + ProtocolSpecAdapters.create(0, Function.identity()), privacyParameters, isRevertReasonEnabled, config.isQuorum()) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java new file mode 100644 index 00000000000..9b86ae41bdb --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java @@ -0,0 +1,163 @@ +/* + * Copyright 2020 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.OptionalLong; + +public class MainnetProtocolSpecFactory { + + private final Optional chainId; + private final OptionalInt contractSizeLimit; + private final OptionalInt evmStackSize; + private final boolean isRevertReasonEnabled; + private final boolean quorumCompatibilityMode; + private final OptionalLong ecip1017EraRounds; + + public MainnetProtocolSpecFactory( + final Optional chainId, + final OptionalInt contractSizeLimit, + final OptionalInt evmStackSize, + final boolean isRevertReasonEnabled, + final boolean quorumCompatibilityMode, + final OptionalLong ecip1017EraRounds) { + this.chainId = chainId; + this.contractSizeLimit = contractSizeLimit; + this.evmStackSize = evmStackSize; + this.isRevertReasonEnabled = isRevertReasonEnabled; + this.quorumCompatibilityMode = quorumCompatibilityMode; + this.ecip1017EraRounds = ecip1017EraRounds; + } + + public ProtocolSpecBuilder frontierDefinition() { + return MainnetProtocolSpecs.frontierDefinition( + contractSizeLimit, evmStackSize, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder homesteadDefinition() { + return MainnetProtocolSpecs.homesteadDefinition( + contractSizeLimit, evmStackSize, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder daoRecoveryInitDefinition() { + return MainnetProtocolSpecs.daoRecoveryInitDefinition( + contractSizeLimit, evmStackSize, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder daoRecoveryTransitionDefinition() { + return MainnetProtocolSpecs.daoRecoveryTransitionDefinition( + contractSizeLimit, evmStackSize, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder tangerineWhistleDefinition() { + return MainnetProtocolSpecs.tangerineWhistleDefinition( + contractSizeLimit, evmStackSize, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder spuriousDragonDefinition() { + return MainnetProtocolSpecs.spuriousDragonDefinition( + chainId, contractSizeLimit, evmStackSize, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder byzantiumDefinition() { + return MainnetProtocolSpecs.byzantiumDefinition( + chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder constantinopleDefinition() { + return MainnetProtocolSpecs.constantinopleDefinition( + chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder petersburgDefinition() { + return MainnetProtocolSpecs.petersburgDefinition( + chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder istanbulDefinition() { + return MainnetProtocolSpecs.istanbulDefinition( + chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder muirGlacierDefinition() { + return MainnetProtocolSpecs.muirGlacierDefinition( + chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder berlinDefinition() { + return MainnetProtocolSpecs.berlinDefinition( + chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, quorumCompatibilityMode); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + // Classic Protocol Specs + public ProtocolSpecBuilder dieHardDefinition() { + return ClassicProtocolSpecs.dieHardDefinition( + chainId, contractSizeLimit, evmStackSize, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder gothamDefinition() { + return ClassicProtocolSpecs.gothamDefinition( + chainId, contractSizeLimit, evmStackSize, ecip1017EraRounds, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder defuseDifficultyBombDefinition() { + return ClassicProtocolSpecs.defuseDifficultyBombDefinition( + chainId, contractSizeLimit, evmStackSize, ecip1017EraRounds, quorumCompatibilityMode); + } + + public ProtocolSpecBuilder atlantisDefinition() { + return ClassicProtocolSpecs.atlantisDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + ecip1017EraRounds, + quorumCompatibilityMode); + } + + public ProtocolSpecBuilder aghartaDefinition() { + return ClassicProtocolSpecs.aghartaDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + ecip1017EraRounds, + quorumCompatibilityMode); + } + + public ProtocolSpecBuilder phoenixDefinition() { + return ClassicProtocolSpecs.phoenixDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + ecip1017EraRounds, + quorumCompatibilityMode); + } + + public ProtocolSpecBuilder thanosDefinition() { + return ClassicProtocolSpecs.thanosDefinition( + chainId, + contractSizeLimit, + evmStackSize, + isRevertReasonEnabled, + ecip1017EraRounds, + quorumCompatibilityMode); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java index fb3398cfebb..ebfe0bbcf0e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java @@ -24,16 +24,47 @@ import java.math.BigInteger; import java.util.Optional; import java.util.OptionalLong; +import java.util.TreeMap; import java.util.function.Function; +import java.util.stream.Collectors; +import com.google.common.collect.Lists; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class ProtocolScheduleBuilder { + private static class BuilderMapEntry { + + private final long block; + private final ProtocolSpecBuilder builder; + private final Function modifier; + + public BuilderMapEntry( + final long block, + final ProtocolSpecBuilder builder, + final Function modifier) { + this.block = block; + this.builder = builder; + this.modifier = modifier; + } + + public long getBlock() { + return block; + } + + public ProtocolSpecBuilder getBuilder() { + return builder; + } + + public Function getModifier() { + return modifier; + } + } + private static final Logger LOG = LogManager.getLogger(); private final GenesisConfigOptions config; - private final Function protocolSpecAdapter; + private final ProtocolSpecAdapters protocolSpecAdapters; private final Optional defaultChainId; private final PrivacyParameters privacyParameters; private final boolean isRevertReasonEnabled; @@ -43,29 +74,39 @@ public class ProtocolScheduleBuilder { public ProtocolScheduleBuilder( final GenesisConfigOptions config, final BigInteger defaultChainId, - final Function protocolSpecAdapter, + final ProtocolSpecAdapters protocolSpecAdapters, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, final boolean quorumCompatibilityMode) { this( config, Optional.of(defaultChainId), - protocolSpecAdapter, + protocolSpecAdapters, privacyParameters, isRevertReasonEnabled, quorumCompatibilityMode); } + private Optional create( + final OptionalLong block, final ProtocolSpecBuilder builder) { + if (block.isEmpty()) { + return Optional.empty(); + } + final long blockVal = block.getAsLong(); + return Optional.of( + new BuilderMapEntry(blockVal, builder, protocolSpecAdapters.getModifierForBlock(blockVal))); + } + public ProtocolScheduleBuilder( final GenesisConfigOptions config, - final Function protocolSpecAdapter, + final ProtocolSpecAdapters protocolSpecAdapters, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, final boolean quorumCompatibilityMode) { this( config, Optional.empty(), - protocolSpecAdapter, + protocolSpecAdapters, privacyParameters, isRevertReasonEnabled, quorumCompatibilityMode); @@ -74,13 +115,13 @@ public ProtocolScheduleBuilder( private ProtocolScheduleBuilder( final GenesisConfigOptions config, final Optional defaultChainId, - final Function protocolSpecAdapter, + final ProtocolSpecAdapters protocolSpecAdapters, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, final boolean quorumCompatibilityMode) { this.config = config; this.defaultChainId = defaultChainId; - this.protocolSpecAdapter = protocolSpecAdapter; + this.protocolSpecAdapters = protocolSpecAdapters; this.privacyParameters = privacyParameters; this.isRevertReasonEnabled = isRevertReasonEnabled; this.quorumCompatibilityMode = quorumCompatibilityMode; @@ -91,19 +132,38 @@ public ProtocolSchedule createProtocolSchedule() { config.getChainId().map(Optional::of).orElse(defaultChainId); final MutableProtocolSchedule protocolSchedule = new MutableProtocolSchedule(chainId); + final MainnetProtocolSpecFactory specFactory = + new MainnetProtocolSpecFactory( + chainId, + config.getContractSizeLimit(), + config.getEvmStackSize(), + isRevertReasonEnabled, + quorumCompatibilityMode, + config.getEcip1017EraRounds()); + validateForkOrdering(); - addProtocolSpec( - protocolSchedule, - OptionalLong.of(0), - MainnetProtocolSpecs.frontierDefinition( - config.getContractSizeLimit(), config.getEvmStackSize(), quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getHomesteadBlockNumber(), - MainnetProtocolSpecs.homesteadDefinition( - config.getContractSizeLimit(), config.getEvmStackSize(), quorumCompatibilityMode)); + final TreeMap builders = buildMilestoneMap(specFactory, chainId); + + // At this stage, all milestones are flagged with correct modifier, but ProtocolSpecs must be + // inserted _AT_ the modifier block entry. + protocolSpecAdapters.stream() + .forEach( + entry -> { + final long modifierBlock = entry.getKey(); + final BuilderMapEntry parent = builders.floorEntry(modifierBlock).getValue(); + builders.put( + modifierBlock, + new BuilderMapEntry(modifierBlock, parent.getBuilder(), entry.getValue())); + }); + // Create the ProtocolSchedule, such that the Dao/fork milestones can be inserted + builders + .values() + .forEach(e -> addProtocolSpec(protocolSchedule, e.getBlock(), e.getBuilder(), e.modifier)); + + // NOTE: It is assumed that Daofork blocks will not be used for private networks + // as too many risks exist around inserting a protocol-spec between daoBlock and daoBlock+10. config .getDaoForkBlock() .ifPresent( @@ -112,108 +172,18 @@ public ProtocolSchedule createProtocolSchedule() { protocolSchedule.getByBlockNumber(daoBlockNumber); addProtocolSpec( protocolSchedule, - OptionalLong.of(daoBlockNumber), - MainnetProtocolSpecs.daoRecoveryInitDefinition( - config.getContractSizeLimit(), - config.getEvmStackSize(), - quorumCompatibilityMode)); + daoBlockNumber, + specFactory.daoRecoveryInitDefinition(), + protocolSpecAdapters.getModifierForBlock(daoBlockNumber)); addProtocolSpec( protocolSchedule, - OptionalLong.of(daoBlockNumber + 1), - MainnetProtocolSpecs.daoRecoveryTransitionDefinition( - config.getContractSizeLimit(), - config.getEvmStackSize(), - quorumCompatibilityMode)); - + daoBlockNumber + 1L, + specFactory.daoRecoveryTransitionDefinition(), + protocolSpecAdapters.getModifierForBlock(daoBlockNumber + 1L)); // Return to the previous protocol spec after the dao fork has completed. protocolSchedule.putMilestone(daoBlockNumber + 10, originalProtocolSpec); }); - addProtocolSpec( - protocolSchedule, - config.getTangerineWhistleBlockNumber(), - MainnetProtocolSpecs.tangerineWhistleDefinition( - config.getContractSizeLimit(), config.getEvmStackSize(), quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getSpuriousDragonBlockNumber(), - MainnetProtocolSpecs.spuriousDragonDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getByzantiumBlockNumber(), - MainnetProtocolSpecs.byzantiumDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getConstantinopleBlockNumber(), - MainnetProtocolSpecs.constantinopleDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getPetersburgBlockNumber(), - MainnetProtocolSpecs.petersburgDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getIstanbulBlockNumber(), - MainnetProtocolSpecs.istanbulDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getMuirGlacierBlockNumber(), - MainnetProtocolSpecs.muirGlacierDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - quorumCompatibilityMode)); - - addProtocolSpec( - protocolSchedule, - config.getBerlinBlockNumber(), - MainnetProtocolSpecs.berlinDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - quorumCompatibilityMode)); - - if (ExperimentalEIPs.eip1559Enabled) { - final Optional transactionPriceCalculator = - Optional.of(TransactionPriceCalculator.eip1559()); - addProtocolSpec( - protocolSchedule, - config.getEIP1559BlockNumber(), - MainnetProtocolSpecs.eip1559Definition( - chainId, - transactionPriceCalculator, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - config, - quorumCompatibilityMode)); - } - // specs for classic network config .getClassicForkBlock() @@ -231,100 +201,91 @@ public ProtocolSchedule createProtocolSchedule() { protocolSchedule.putMilestone(classicBlockNumber + 1, originalProtocolSpec); }); - addProtocolSpec( - protocolSchedule, - config.getEcip1015BlockNumber(), - ClassicProtocolSpecs.tangerineWhistleDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getDieHardBlockNumber(), - ClassicProtocolSpecs.dieHardDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getGothamBlockNumber(), - ClassicProtocolSpecs.gothamDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - config.getEcip1017EraRounds(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getDefuseDifficultyBombBlockNumber(), - ClassicProtocolSpecs.defuseDifficultyBombDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - config.getEcip1017EraRounds(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getAtlantisBlockNumber(), - ClassicProtocolSpecs.atlantisDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - config.getEcip1017EraRounds(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getAghartaBlockNumber(), - ClassicProtocolSpecs.aghartaDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - config.getEcip1017EraRounds(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getPhoenixBlockNumber(), - ClassicProtocolSpecs.phoenixDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - config.getEcip1017EraRounds(), - quorumCompatibilityMode)); - addProtocolSpec( - protocolSchedule, - config.getThanosBlockNumber(), - ClassicProtocolSpecs.thanosDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - config.getEcip1017EraRounds(), - quorumCompatibilityMode)); - LOG.info("Protocol schedule created with milestones: {}", protocolSchedule.listMilestones()); return protocolSchedule; } + private TreeMap buildMilestoneMap( + final MainnetProtocolSpecFactory specFactory, final Optional chainId) { + final TreeMap builders = + Lists.newArrayList( + create(OptionalLong.of(0), specFactory.frontierDefinition()), + create(config.getHomesteadBlockNumber(), specFactory.homesteadDefinition()), + create( + config.getTangerineWhistleBlockNumber(), + specFactory.tangerineWhistleDefinition()), + create( + config.getSpuriousDragonBlockNumber(), specFactory.spuriousDragonDefinition()), + create(config.getByzantiumBlockNumber(), specFactory.byzantiumDefinition()), + create( + config.getConstantinopleBlockNumber(), specFactory.constantinopleDefinition()), + create(config.getPetersburgBlockNumber(), specFactory.petersburgDefinition()), + create(config.getIstanbulBlockNumber(), specFactory.istanbulDefinition()), + create(config.getMuirGlacierBlockNumber(), specFactory.muirGlacierDefinition()), + create(config.getBerlinBlockNumber(), specFactory.berlinDefinition()), + // Classic Milestones + create(config.getEcip1015BlockNumber(), specFactory.tangerineWhistleDefinition()), + create(config.getDieHardBlockNumber(), specFactory.dieHardDefinition()), + create(config.getGothamBlockNumber(), specFactory.gothamDefinition()), + create( + config.getDefuseDifficultyBombBlockNumber(), + specFactory.defuseDifficultyBombDefinition()), + create(config.getAtlantisBlockNumber(), specFactory.atlantisDefinition()), + create(config.getAghartaBlockNumber(), specFactory.aghartaDefinition()), + create(config.getPhoenixBlockNumber(), specFactory.phoenixDefinition()), + create(config.getThanosBlockNumber(), specFactory.thanosDefinition())) + .stream() + .filter(Optional::isPresent) + .map(Optional::get) + .collect( + Collectors.toMap( + BuilderMapEntry::getBlock, + b -> b, + (existing, replacement) -> replacement, + TreeMap::new)); + + if (ExperimentalEIPs.eip1559Enabled) { + final Optional transactionPriceCalculator = + Optional.of(TransactionPriceCalculator.eip1559()); + final long eip1559Block = config.getEIP1559BlockNumber().getAsLong(); + builders.put( + eip1559Block, + new BuilderMapEntry( + eip1559Block, + MainnetProtocolSpecs.eip1559Definition( + chainId, + transactionPriceCalculator, + config.getContractSizeLimit(), + config.getEvmStackSize(), + isRevertReasonEnabled, + config, + quorumCompatibilityMode), + protocolSpecAdapters.getModifierForBlock(eip1559Block))); + } + + return builders; + } + private void addProtocolSpec( final MutableProtocolSchedule protocolSchedule, final OptionalLong blockNumber, final ProtocolSpecBuilder definition) { blockNumber.ifPresent( - number -> - protocolSchedule.putMilestone( - number, - protocolSpecAdapter - .apply(definition) - .badBlocksManager(badBlockManager) - .privacyParameters(privacyParameters) - .privateTransactionValidatorBuilder( - () -> new PrivateTransactionValidator(protocolSchedule.getChainId())) - .build(protocolSchedule))); + bn -> addProtocolSpec(protocolSchedule, bn, definition, Function.identity())); + } + + private void addProtocolSpec( + final MutableProtocolSchedule protocolSchedule, + final long blockNumber, + final ProtocolSpecBuilder definition, + final Function modifier) { + definition + .badBlocksManager(badBlockManager) + .privacyParameters(privacyParameters) + .privateTransactionValidatorBuilder( + () -> new PrivateTransactionValidator(protocolSchedule.getChainId())); + + protocolSchedule.putMilestone(blockNumber, modifier.apply(definition).build(protocolSchedule)); } private long validateForkOrder( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java new file mode 100644 index 00000000000..03bb2a46356 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import java.util.HashMap; +import java.util.Map; +import java.util.NavigableSet; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.stream.Stream; + +public class ProtocolSpecAdapters { + + final Map> modifiers; + + public ProtocolSpecAdapters( + final Map> modifiers) { + this.modifiers = modifiers; + } + + public static ProtocolSpecAdapters create( + final long block, final Function modifier) { + final Map> entries = new HashMap<>(); + entries.put(block, modifier); + return new ProtocolSpecAdapters(entries); + } + + public Function getModifierForBlock( + final long blockNumber) { + final NavigableSet epochs = new TreeSet<>(modifiers.keySet()); + final Long modifier = epochs.floor(blockNumber); + + if (modifier == null) { + return Function.identity(); + } + + return modifiers.get(modifier); + } + + public Stream>> stream() { + return modifiers.entrySet().stream(); + } +} diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java index cea7d932d55..802dfce3706 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -116,7 +117,7 @@ public ExecutionContextTestFixture build() { new ProtocolScheduleBuilder( new StubGenesisConfigOptions().petersburgBlock(0), BigInteger.valueOf(42), - Function.identity(), + ProtocolSpecAdapters.create(0, Function.identity()), new PrivacyParameters(), false, genesisConfigFile.getConfigOptions().isQuorum()) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java new file mode 100644 index 00000000000..35db641f1ff --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2020 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; + +import java.math.BigInteger; +import java.util.OptionalLong; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +@RunWith(MockitoJUnitRunner.class) +public class ProtocolScheduleBuilderTest { + + @Mock GenesisConfigOptions configOptions; + + @Mock private Function modifier; + + @Test + public void modifierInsertedBetweenBlocksIsAppliedToLaterAndCreatesInterimMilestone() { + when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(5L)); + + when(modifier.apply(any())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0)); + + final ProtocolScheduleBuilder builder = + new ProtocolScheduleBuilder( + configOptions, + BigInteger.ONE, + ProtocolSpecAdapters.create(2, modifier), + new PrivacyParameters(), + false, + false); + + final MutableProtocolSchedule schedule = + (MutableProtocolSchedule) builder.createProtocolSchedule(); + + // A default spec exists at 0 (frontier), then the spec as requested in config, then another + // added at the point at which the modifier is applied. + assertThat(schedule.streamMilestoneBlocks().collect(Collectors.toList())) + .containsExactly(0L, 2L, 5L); + assertThat(schedule.getByBlockNumber(0).getName()).isEqualTo("Frontier"); + assertThat(schedule.getByBlockNumber(2).getName()).isEqualTo("Frontier"); + assertThat(schedule.getByBlockNumber(5).getName()).isEqualTo("Homestead"); + + verify(modifier, times(2)).apply(any()); + } + + @Test + public void modifierPastEndOfDefinedMilestonesGetsItsOwnMilestoneCreated() { + when(modifier.apply(any())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0)); + + final ProtocolScheduleBuilder builder = + new ProtocolScheduleBuilder( + configOptions, + BigInteger.ONE, + ProtocolSpecAdapters.create(2, modifier), + new PrivacyParameters(), + false, + false); + + final MutableProtocolSchedule schedule = + (MutableProtocolSchedule) builder.createProtocolSchedule(); + + // A default spec exists at 0 (frontier), then the spec as requested in config, then another + // added at the point at which the modifier is applied. + assertThat(schedule.streamMilestoneBlocks().collect(Collectors.toList())) + .containsExactly(0L, 2L); + assertThat(schedule.getByBlockNumber(0).getName()).isEqualTo("Frontier"); + assertThat(schedule.getByBlockNumber(2).getName()).isEqualTo("Frontier"); + + verify(modifier, times(1)).apply(any()); + } + + @Test + public void modifierOnDefinedMilestoneIsAppliedButDoesNotGetAnExtraMilestoneCreated() { + when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(5L)); + when(modifier.apply(any())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0)); + + final ProtocolScheduleBuilder builder = + new ProtocolScheduleBuilder( + configOptions, + BigInteger.ONE, + ProtocolSpecAdapters.create(5, modifier), + new PrivacyParameters(), + false, + false); + + final MutableProtocolSchedule schedule = + (MutableProtocolSchedule) builder.createProtocolSchedule(); + + // A default spec exists at 0 (frontier), then the spec as requested in config, then another + // added at the point at which the modifier is applied. + assertThat(schedule.streamMilestoneBlocks().collect(Collectors.toList())) + .containsExactly(0L, 5L); + assertThat(schedule.getByBlockNumber(0).getName()).isEqualTo("Frontier"); + assertThat(schedule.getByBlockNumber(5).getName()).isEqualTo("Homestead"); + + verify(modifier, times(1)).apply(any()); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdaptersTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdaptersTest.java new file mode 100644 index 00000000000..7b01135148f --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdaptersTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.function.Function; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ProtocolSpecAdaptersTest { + + @Mock private Function firstModifier; + + @Mock private Function secondModifier; + + @Test + public void specAdapterFindsTheModifierBelowRequestedBlock() { + + final ProtocolSpecAdapters adapters = + new ProtocolSpecAdapters(Map.of(3L, firstModifier, 5L, secondModifier)); + + assertThat(adapters.getModifierForBlock(3)).isEqualTo(firstModifier); + assertThat(adapters.getModifierForBlock(4)).isEqualTo(firstModifier); + assertThat(adapters.getModifierForBlock(5)).isEqualTo(secondModifier); + assertThat(adapters.getModifierForBlock(6)).isEqualTo(secondModifier); + assertThat(adapters.getModifierForBlock(0)).isEqualTo(Function.identity()); + } +} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java index ac5775f6450..71fac1e7210 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import java.math.BigInteger; import java.util.Arrays; @@ -80,7 +81,7 @@ private static ProtocolSchedule createSchedule(final GenesisConfigOptions option return new ProtocolScheduleBuilder( options, CHAIN_ID, - Function.identity(), + ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, options.isQuorum())