diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java
index 9c0f629948f..00c61fbe62a 100644
--- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java
+++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java
@@ -64,6 +64,31 @@ public interface GenesisConfigOptions {
*/
OptionalLong getClassicForkBlock();
+ /**
+ * Block number for ECIP-1015 fork on Classic network ECIP-1015: Long-term gas cost changes for
+ * IO-heavy operations to mitigate transaction spam attacks In reference to EIP-150 (ETH Tangerine
+ * Whistle) Note, this fork happens after Homestead (Mainnet definition) and before DieHard fork
+ *
+ * @see https://ecips.ethereumclassic.org/ECIPs/ecip-1015
+ * @return block number to activate ECIP-1015 code
+ */
+ OptionalLong getEcip1015BlockNumber();
+
+ /**
+ * Block number for DieHard fork on Classic network The DieHard fork includes changes to meet
+ * specification for ECIP-1010 and EIP-160 Note, this fork happens after ECIP-1015 (classic
+ * tangerine whistle) and before Gotham fork ECIP-1010: Delay Difficulty Bomb Explosion
+ *
+ * @see https://ecips.ethereumclassic.org/ECIPs/ecip-1010
+ * EIP-160: EXP cost increase
+ * @see https://eips.ethereum.org/EIPS/eip-160
+ * @return block number to activate Classic DieHard fork
+ */
+ OptionalLong getDieHardBlockNumber();
+
Optional getChainId();
OptionalInt getContractSizeLimit();
diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
index ee2456b80d7..dd8c2098114 100644
--- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
+++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java
@@ -200,6 +200,16 @@ public OptionalLong getClassicForkBlock() {
return getOptionalLong("classicforkblock");
}
+ @Override
+ public OptionalLong getEcip1015BlockNumber() {
+ return getOptionalLong("ecip1015block");
+ }
+
+ @Override
+ public OptionalLong getDieHardBlockNumber() {
+ return getOptionalLong("diehardblock");
+ }
+
@Override
public Optional getChainId() {
return getOptionalBigInteger("chainid");
diff --git a/config/src/main/resources/classic.json b/config/src/main/resources/classic.json
index 79d17c002ff..a36dfaeaa70 100644
--- a/config/src/main/resources/classic.json
+++ b/config/src/main/resources/classic.json
@@ -3,6 +3,8 @@
"chainId": 61,
"homesteadBlock": 1150000,
"classicForkBlock": 1920000,
+ "ecip1015Block": 2500000,
+ "diehardBlock": 3000000,
"ethash": {
}
diff --git a/config/src/test-support/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/test-support/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java
index 528d0fa597f..052cb53f300 100644
--- a/config/src/test-support/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java
+++ b/config/src/test-support/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java
@@ -33,6 +33,8 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
private OptionalLong constantinopleFixBlockNumber = OptionalLong.empty();
private OptionalLong istanbulBlockNumber = OptionalLong.empty();
private OptionalLong classicForkBlock = OptionalLong.empty();
+ private OptionalLong ecip1015BlockNumber = OptionalLong.empty();
+ private OptionalLong diehardBlockNumber = OptionalLong.empty();
private Optional chainId = Optional.empty();
private OptionalInt contractSizeLimit = OptionalInt.empty();
private OptionalInt stackSizeLimit = OptionalInt.empty();
@@ -127,6 +129,16 @@ public OptionalLong getClassicForkBlock() {
return classicForkBlock;
}
+ @Override
+ public OptionalLong getEcip1015BlockNumber() {
+ return ecip1015BlockNumber;
+ }
+
+ @Override
+ public OptionalLong getDieHardBlockNumber() {
+ return diehardBlockNumber;
+ }
+
@Override
public OptionalInt getContractSizeLimit() {
return contractSizeLimit;
diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicDifficultyCalculators.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicDifficultyCalculators.java
new file mode 100644
index 00000000000..f0ce73d83f2
--- /dev/null
+++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicDifficultyCalculators.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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 org.hyperledger.besu.util.uint.UInt256;
+
+import java.math.BigInteger;
+
+import com.google.common.primitives.Ints;
+
+public abstract class ClassicDifficultyCalculators {
+ private static final BigInteger MINIMUM_DIFFICULTY = BigInteger.valueOf(131_072L);
+ private static final BigInteger DIFFICULTY_BOUND_DIVISOR = BigInteger.valueOf(2_048L);
+ private static final BigInteger BIGINT_2 = BigInteger.valueOf(2L);
+ private static final long EXPONENTIAL_DIFF_PERIOD = 100_000L;
+ private static final long PAUSE_BLOCK = 3_000_000L;
+ private static final long FIXED_DIFF = PAUSE_BLOCK / EXPONENTIAL_DIFF_PERIOD;
+
+ public static DifficultyCalculator DIFFICULTY_BOMB_PAUSED =
+ (time, parent, protocolContext) -> {
+ final BigInteger parentDifficulty = difficulty(parent.getDifficulty());
+ final BigInteger difficulty =
+ ensureMinimumDifficulty(
+ BigInteger.valueOf(Math.max(1 - (time - parent.getTimestamp()) / 10, -99L))
+ .multiply(parentDifficulty.divide(DIFFICULTY_BOUND_DIVISOR))
+ .add(parentDifficulty));
+ return adjustForDifficultyPause(FIXED_DIFF, difficulty);
+ };
+
+ private static BigInteger adjustForDifficultyPause(
+ final long periodCount, final BigInteger difficulty) {
+ return difficulty.add(BIGINT_2.pow(Ints.checkedCast(periodCount - 2)));
+ }
+
+ private static BigInteger ensureMinimumDifficulty(final BigInteger difficulty) {
+ return difficulty.compareTo(MINIMUM_DIFFICULTY) < 0 ? MINIMUM_DIFFICULTY : difficulty;
+ }
+
+ private static BigInteger difficulty(final UInt256 value) {
+ return new BigInteger(1, value.getBytes().extractArray());
+ }
+}
diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java
new file mode 100644
index 00000000000..a87827c1231
--- /dev/null
+++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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;
+
+public class ClassicProtocolSpecs {
+
+ public static ProtocolSpecBuilder classicRecoveryInitDefinition(
+ final OptionalInt contractSizeLimit, final OptionalInt configStackSizeLimit) {
+ return MainnetProtocolSpecs.homesteadDefinition(contractSizeLimit, configStackSizeLimit)
+ .blockHeaderValidatorBuilder(MainnetBlockHeaderValidator::createClassicValidator)
+ .name("ClassicRecoveryInit");
+ }
+
+ public static ProtocolSpecBuilder tangerineWhistleDefinition(
+ final Optional chainId,
+ final OptionalInt contractSizeLimit,
+ final OptionalInt configStackSizeLimit) {
+ return MainnetProtocolSpecs.homesteadDefinition(contractSizeLimit, configStackSizeLimit)
+ .gasCalculator(TangerineWhistleGasCalculator::new)
+ .transactionValidatorBuilder(
+ gasCalculator -> new MainnetTransactionValidator(gasCalculator, true, chainId))
+ .name("ClassicTangerineWhistle");
+ }
+
+ public static ProtocolSpecBuilder dieHardDefinition(
+ final Optional chainId,
+ final OptionalInt configContractSizeLimit,
+ final OptionalInt configStackSizeLimit) {
+ return tangerineWhistleDefinition(chainId, OptionalInt.empty(), configStackSizeLimit)
+ .gasCalculator(DieHardGasCalculator::new)
+ .difficultyCalculator(ClassicDifficultyCalculators.DIFFICULTY_BOMB_PAUSED)
+ .name("DieHard");
+ }
+}
diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DieHardGasCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DieHardGasCalculator.java
new file mode 100644
index 00000000000..76e5a977d5f
--- /dev/null
+++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DieHardGasCalculator.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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 org.hyperledger.besu.ethereum.core.Gas;
+
+public class DieHardGasCalculator extends TangerineWhistleGasCalculator {
+ private static final Gas EXP_OPERATION_BYTE_GAS_COST = Gas.of(50L);
+
+ @Override
+ protected Gas expOperationByteGasCost() {
+ return EXP_OPERATION_BYTE_GAS_COST;
+ }
+}
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 145f337ea22..c27bedb5659 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
@@ -152,6 +152,32 @@ public ProtocolSchedule createProtocolSchedule() {
config.getEvmStackSize(),
isRevertReasonEnabled));
+ // specs for classic network
+ config
+ .getClassicForkBlock()
+ .ifPresent(
+ classicBlockNumber -> {
+ final ProtocolSpec originalProtocolSpce =
+ protocolSchedule.getByBlockNumber(classicBlockNumber);
+ addProtocolSpec(
+ protocolSchedule,
+ OptionalLong.of(classicBlockNumber),
+ ClassicProtocolSpecs.classicRecoveryInitDefinition(
+ config.getContractSizeLimit(), config.getEvmStackSize()));
+ protocolSchedule.putMilestone(classicBlockNumber + 10, originalProtocolSpce);
+ });
+
+ addProtocolSpec(
+ protocolSchedule,
+ config.getEcip1015BlockNumber(),
+ ClassicProtocolSpecs.tangerineWhistleDefinition(
+ chainId, config.getContractSizeLimit(), config.getEvmStackSize()));
+ addProtocolSpec(
+ protocolSchedule,
+ config.getDieHardBlockNumber(),
+ ClassicProtocolSpecs.dieHardDefinition(
+ chainId, config.getContractSizeLimit(), config.getEvmStackSize()));
+
LOG.info("Protocol schedule created with milestones: {}", protocolSchedule.listMilestones());
return protocolSchedule;
}