From 1c78458ddb3c745457a8c472497ae2138bb09d50 Mon Sep 17 00:00:00 2001 From: Trent Mohay Date: Thu, 4 Mar 2021 14:39:57 +1100 Subject: [PATCH] Allow for switching ProtocolSchedule rules outside of Milestones (#1962) This is the first step in supporting switchable consensus mechanisms. Specifically this allows additional protocol specs to be inserted to the protocol schedule at milestones other than that explicitly specified in the genesis config. Signed-off-by: Trent Mohay --- .../clique/CliqueProtocolSchedule.java | 19 +- .../common/bft/BftProtocolSchedule.java | 12 +- .../ibftlegacy/IbftProtocolSchedule.java | 9 +- .../EthHashBlockCreatorTest.java | 9 +- .../FixedDifficultyProtocolSchedule.java | 6 +- .../mainnet/MainnetProtocolSchedule.java | 2 +- .../mainnet/MainnetProtocolSpecFactory.java | 163 ++++++++ .../mainnet/ProtocolScheduleBuilder.java | 366 ++++++++---------- .../mainnet/ProtocolSpecAdapters.java | 55 +++ .../core/ExecutionContextTestFixture.java | 3 +- .../mainnet/ProtocolScheduleBuilderTest.java | 128 ++++++ .../mainnet/ProtocolSpecAdaptersTest.java | 46 +++ .../ReferenceTestProtocolSchedules.java | 3 +- 13 files changed, 592 insertions(+), 229 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdapters.java create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecAdaptersTest.java 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 06462a97a5f..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,109 +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)); - addProtocolSpec( - protocolSchedule, - config.getLacchainPostQuantumBlockNumber(), - LacchainProtocolSpecs.postQuantumDefinition( - chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), - isRevertReasonEnabled, - 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())