diff --git a/ethereumj-core/src/main/java/org/ethereum/config/Constants.java b/ethereumj-core/src/main/java/org/ethereum/config/Constants.java
index 0379771a22..bc477d8510 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/Constants.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/Constants.java
@@ -17,6 +17,8 @@
*/
package org.ethereum.config;
+import org.ethereum.util.blockchain.EtherUtil;
+
import java.math.BigInteger;
/**
@@ -37,7 +39,7 @@ public class Constants {
private static final int BEST_NUMBER_DIFF_LIMIT = 100;
- private static final BigInteger BLOCK_REWARD = new BigInteger("1500000000000000000");
+ private static final BigInteger BLOCK_REWARD = EtherUtil.convert(1500, EtherUtil.Unit.FINNEY); // 1.5 ETH
private static final BigInteger SECP256K1N = new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16);
diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java
index 5d103c1695..2de35dd954 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java
@@ -20,9 +20,8 @@
import org.ethereum.config.BlockchainConfig;
import org.ethereum.config.Constants;
import org.ethereum.config.ConstantsAdapter;
-import org.ethereum.core.Block;
import org.ethereum.core.BlockHeader;
-import org.ethereum.core.Repository;
+import org.ethereum.util.blockchain.EtherUtil;
import java.math.BigInteger;
@@ -51,7 +50,7 @@ public class ByzantiumConfig extends Eip160HFConfig {
public ByzantiumConfig(BlockchainConfig parent) {
super(parent);
constants = new ConstantsAdapter(super.getConstants()) {
- private final BigInteger BLOCK_REWARD = new BigInteger("3000000000000000000");
+ private final BigInteger BLOCK_REWARD = EtherUtil.convert(3, EtherUtil.Unit.ETHER);
@Override
public BigInteger getBLOCK_REWARD() {
diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java
index 84068ecbef..9198ce3cfa 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ConstantinopleConfig.java
@@ -18,10 +18,17 @@
package org.ethereum.config.blockchain;
import org.ethereum.config.BlockchainConfig;
+import org.ethereum.config.Constants;
+import org.ethereum.config.ConstantsAdapter;
+import org.ethereum.core.BlockHeader;
+import org.ethereum.util.blockchain.EtherUtil;
+
+import java.math.BigInteger;
/**
* EIPs included in the Constantinople Hard Fork:
*
+ * - 1234 - Constantinople Difficulty Bomb Delay and Block Reward Adjustment (2 ETH)
* - 145 - Bitwise shifting instructions in EVM
* - 1014 - Skinny CREATE2
* - 1052 - EXTCODEHASH opcode
@@ -30,8 +37,29 @@
*/
public class ConstantinopleConfig extends ByzantiumConfig {
+ private final Constants constants;
+
public ConstantinopleConfig(BlockchainConfig parent) {
super(parent);
+ constants = new ConstantsAdapter(super.getConstants()) {
+ private final BigInteger BLOCK_REWARD = EtherUtil.convert(2, EtherUtil.Unit.ETHER);
+
+ @Override
+ public BigInteger getBLOCK_REWARD() {
+ return BLOCK_REWARD;
+ }
+ };
+ }
+
+ @Override
+ public Constants getConstants() {
+ return constants;
+ }
+
+ @Override
+ protected int getExplosion(BlockHeader curBlock, BlockHeader parent) {
+ int periodCount = (int) (Math.max(0, curBlock.getNumber() - 5_000_000) / getConstants().getEXP_DIFFICULTY_PERIOD());
+ return periodCount - 2;
}
@Override
diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/FrontierConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/FrontierConfig.java
index 4e22ddcbe7..0cd05d8eb9 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/FrontierConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/FrontierConfig.java
@@ -19,6 +19,7 @@
import org.ethereum.config.Constants;
import org.ethereum.core.Transaction;
+import org.ethereum.util.blockchain.EtherUtil;
import java.math.BigInteger;
@@ -28,7 +29,7 @@
public class FrontierConfig extends OlympicConfig {
public static class FrontierConstants extends Constants {
- private static final BigInteger BLOCK_REWARD = new BigInteger("5000000000000000000");
+ private static final BigInteger BLOCK_REWARD = EtherUtil.convert(5, EtherUtil.Unit.ETHER);
@Override
public int getDURATION_LIMIT() {
diff --git a/ethereumj-core/src/main/java/org/ethereum/config/net/JsonNetConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/net/JsonNetConfig.java
index 5cf0b7b116..2f70fb4890 100644
--- a/ethereumj-core/src/main/java/org/ethereum/config/net/JsonNetConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/config/net/JsonNetConfig.java
@@ -104,6 +104,20 @@ public Integer getChainId() {
}
candidates.add(lastCandidate);
}
+ if (config.constantinopleBlock != null) {
+ if (config.chainId != null) {
+ final int chainId = config.chainId;
+ lastCandidate = Pair.of(config.constantinopleBlock, new ConstantinopleConfig(lastCandidate.getRight()) {
+ @Override
+ public Integer getChainId() {
+ return chainId;
+ }
+ });
+ } else {
+ lastCandidate = Pair.of(config.constantinopleBlock, new ConstantinopleConfig(lastCandidate.getRight()));
+ }
+ candidates.add(lastCandidate);
+ }
}
{
diff --git a/ethereumj-core/src/main/java/org/ethereum/core/genesis/GenesisConfig.java b/ethereumj-core/src/main/java/org/ethereum/core/genesis/GenesisConfig.java
index 4c4421d58b..9b5f24e755 100644
--- a/ethereumj-core/src/main/java/org/ethereum/core/genesis/GenesisConfig.java
+++ b/ethereumj-core/src/main/java/org/ethereum/core/genesis/GenesisConfig.java
@@ -30,6 +30,7 @@ public class GenesisConfig {
public boolean daoForkSupport;
public Integer eip158Block;
public Integer byzantiumBlock;
+ public Integer constantinopleBlock;
public Integer chainId;
// EthereumJ private options
@@ -43,6 +44,7 @@ public static class HashValidator {
public boolean isCustomConfig() {
return homesteadBlock != null || daoForkBlock != null || eip150Block != null ||
- eip155Block != null || eip158Block != null || byzantiumBlock != null;
+ eip155Block != null || eip158Block != null || byzantiumBlock != null ||
+ constantinopleBlock != null;
}
}
diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ByzantiumConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ByzantiumConfigTest.java
index 76eadbe520..fcf931b363 100644
--- a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ByzantiumConfigTest.java
+++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ByzantiumConfigTest.java
@@ -23,7 +23,6 @@
import org.ethereum.config.Constants;
import org.ethereum.config.ConstantsAdapter;
import org.ethereum.core.BlockHeader;
-import org.junit.Ignore;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;
@@ -86,7 +85,6 @@ public void testDifficultyAdjustedForParentBlockHavingUncles() throws Exception
}
@Test
- @Ignore
public void testEtherscanIoBlock4490790() throws Exception {
ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig());
@@ -104,8 +102,6 @@ public void testEtherscanIoBlock4490790() throws Exception {
.build();
BigInteger minimumDifficulty = byzantiumConfig.calcDifficulty(current, parent);
- assertEquals(BlockHeaderBuilder.parse("1,378,600,421,631,340"), minimumDifficulty);
-
BigInteger actualDifficultyOnEtherscan = BlockHeaderBuilder.parse("1,377,927,933,620,791");
assertTrue(actualDifficultyOnEtherscan.compareTo(minimumDifficulty) > -1);
}
diff --git a/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ConstantinopleConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ConstantinopleConfigTest.java
new file mode 100644
index 0000000000..5a3977cd50
--- /dev/null
+++ b/ethereumj-core/src/test/java/org/ethereum/config/blockchain/ConstantinopleConfigTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) [2017] [ ]
+ * This file is part of the ethereumJ library.
+ *
+ * The ethereumJ library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The ethereumJ library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the ethereumJ library. If not, see .
+ *
+ *
+ */
+
+package org.ethereum.config.blockchain;
+
+import org.ethereum.config.Constants;
+import org.ethereum.config.ConstantsAdapter;
+import org.ethereum.core.BlockHeader;
+import org.ethereum.util.blockchain.EtherUtil;
+import org.junit.Test;
+
+import java.math.BigInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@SuppressWarnings("SameParameterValue")
+public class ConstantinopleConfigTest {
+
+ private static final byte[] FAKE_HASH = {11, 12};
+ private static final ConstantinopleConfig constantinopleConfig = new ConstantinopleConfig(new TestBlockchainConfig());
+
+ @Test
+ public void testRelatedEip() throws Exception {
+ // Byzantium
+ assertTrue(constantinopleConfig.eip198());
+ assertTrue(constantinopleConfig.eip206());
+ assertTrue(constantinopleConfig.eip211());
+ assertTrue(constantinopleConfig.eip212());
+ assertTrue(constantinopleConfig.eip213());
+ assertTrue(constantinopleConfig.eip214());
+ assertTrue(constantinopleConfig.eip658());
+
+ // Constantinople
+ assertTrue(constantinopleConfig.eip145());
+ assertTrue(constantinopleConfig.eip1014());
+ assertTrue(constantinopleConfig.eip1052());
+ assertTrue(constantinopleConfig.eip1283());
+
+ ByzantiumConfig byzantiumConfig = new ByzantiumConfig(new TestBlockchainConfig());
+
+ // Constantinople eips in Byzantium
+ assertFalse(byzantiumConfig.eip145());
+ assertFalse(byzantiumConfig.eip1014());
+ assertFalse(byzantiumConfig.eip1052());
+ assertFalse(byzantiumConfig.eip1283());
+ }
+
+
+ @Test
+ public void testDifficultyWithoutExplosion() throws Exception {
+ BlockHeader parent = new BlockHeaderBuilder(FAKE_HASH, 0L, 1_000_000).build();
+ BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, -1).build();
+
+ BigInteger difficulty = constantinopleConfig.calcDifficulty(current, parent);
+ assertEquals(BigInteger.valueOf(1_000_976), difficulty);
+ }
+
+ @Test
+ public void testDifficultyAdjustedForParentBlockHavingUncles() throws Exception {
+ BlockHeader parent = new BlockHeaderBuilder(FAKE_HASH, 0L, 0)
+ .withTimestamp(0L)
+ .withUncles(new byte[]{1, 2})
+ .build();
+ BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, 0)
+ .withTimestamp(9L)
+ .build();
+ assertEquals(1, constantinopleConfig.getCalcDifficultyMultiplier(current, parent).intValue());
+ }
+
+ @Test
+ public void testDifficultyWithExplosionShouldBeImpactedByBlockTimestamp() throws Exception {
+
+ BlockHeader parent = new BlockHeaderBuilder(new byte[]{11, 12}, 2_500_000, 8_388_608)
+ .withTimestamp(0)
+ .build();
+ BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 2_500_001, 8_388_608)
+ .withTimestamp(10 * 60) // 10 minutes later, longer time: lowers difficulty
+ .build();
+
+ BigInteger difficulty = constantinopleConfig.calcDifficulty(current, parent);
+ assertEquals(BigInteger.valueOf(8126464), difficulty);
+
+
+ parent = new BlockHeaderBuilder(new byte[]{11, 12}, 2_500_000, 8_388_608)
+ .withTimestamp(0)
+ .build();
+ current = new BlockHeaderBuilder(parent.getHash(), 2_500_001, 8_388_608)
+ .withTimestamp(5) // 5 seconds later, shorter time: higher difficulty
+ .build();
+
+ difficulty = constantinopleConfig.calcDifficulty(current, parent);
+ assertEquals(BigInteger.valueOf(8396800), difficulty);
+ }
+
+ @Test
+ public void testDifficultyAboveBlock5MShouldTriggerExplosion() throws Exception {
+
+ int parentDifficulty = 268_435_456;
+ BlockHeader parent = new BlockHeaderBuilder(FAKE_HASH, 6_000_000, parentDifficulty).build();
+ BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 6_000_001, -1).build();
+ int actualDifficulty = constantinopleConfig.calcDifficulty(current, parent).intValue();
+ int differenceWithoutExplosion = actualDifficulty - parentDifficulty;
+ assertEquals(262_400, differenceWithoutExplosion);
+
+ parent = new BlockHeaderBuilder(FAKE_HASH, 7_000_000, parentDifficulty).build();
+ current = new BlockHeaderBuilder(parent.getHash(), 7_000_001, -1).build();
+ actualDifficulty = constantinopleConfig.calcDifficulty(current, parent).intValue();
+ differenceWithoutExplosion = actualDifficulty - parentDifficulty;
+ assertEquals(524_288, differenceWithoutExplosion);
+
+ parent = new BlockHeaderBuilder(FAKE_HASH, 8_000_000, parentDifficulty).build();
+ current = new BlockHeaderBuilder(parent.getHash(), 8_000_001, -1).build();
+ actualDifficulty = constantinopleConfig.calcDifficulty(current, parent).intValue();
+ differenceWithoutExplosion = actualDifficulty - parentDifficulty;
+ assertEquals(268_697_600, differenceWithoutExplosion); // Explosion!
+ }
+
+ @Test
+ @SuppressWarnings("PointlessArithmeticExpression")
+ public void testCalcDifficultyMultiplier() throws Exception {
+ // Note; timestamps are in seconds
+ assertCalcDifficultyMultiplier(0L, 1L, 2);
+ assertCalcDifficultyMultiplier(0L, 5, 2); // 5 seconds
+ assertCalcDifficultyMultiplier(0L, 1 * 10, 1); // 10 seconds
+ assertCalcDifficultyMultiplier(0L, 2 * 10, -0); // 20 seconds
+ assertCalcDifficultyMultiplier(0L, 10 * 10, -9); // 100 seconds
+ assertCalcDifficultyMultiplier(0L, 60 * 10, -64); // 10 mins
+ assertCalcDifficultyMultiplier(0L, 60 * 12, -78); // 12 mins
+ }
+
+ private void assertCalcDifficultyMultiplier(long parentBlockTimestamp, long curBlockTimestamp, int expectedMultiplier) {
+ BlockHeader parent = new BlockHeaderBuilder(FAKE_HASH, 0L, 0)
+ .withTimestamp(parentBlockTimestamp)
+ .build();
+ BlockHeader current = new BlockHeaderBuilder(parent.getHash(), 1L, 0)
+ .withTimestamp(curBlockTimestamp)
+ .build();
+ assertEquals(expectedMultiplier, constantinopleConfig.getCalcDifficultyMultiplier(current, parent).intValue());
+ }
+
+
+ @Test
+ public void testExplosionChanges() throws Exception {
+
+ BlockHeader beforePauseBlock = new BlockHeaderBuilder(FAKE_HASH, 4_000_000, 0).build();
+ assertEquals(-2, constantinopleConfig.getExplosion(beforePauseBlock, null));
+
+ BlockHeader endOfIceAge = new BlockHeaderBuilder(FAKE_HASH, 5_000_000, 0).build();
+ assertEquals(-2, constantinopleConfig.getExplosion(endOfIceAge, null));
+
+ BlockHeader startExplodingBlock = new BlockHeaderBuilder(FAKE_HASH, 5_200_000, 0).build();
+ assertEquals(0, constantinopleConfig.getExplosion(startExplodingBlock, null));
+
+ startExplodingBlock = new BlockHeaderBuilder(FAKE_HASH, 6_000_000, 0).build();
+ assertEquals(8, constantinopleConfig.getExplosion(startExplodingBlock, null));
+
+ startExplodingBlock = new BlockHeaderBuilder(FAKE_HASH, 8_000_000, 0).build();
+ assertEquals(28, constantinopleConfig.getExplosion(startExplodingBlock, null));
+ }
+
+ @Test
+ public void testBlockReward() throws Exception {
+ ConstantinopleConfig constantinopleConfig2 = new ConstantinopleConfig(new TestBlockchainConfig() {
+ @Override
+ public Constants getConstants() {
+ return new ConstantsAdapter(super.getConstants()) {
+ @Override
+ public BigInteger getBLOCK_REWARD() {
+ // Make sure ConstantinopleConfig is not using parent's block reward
+ return BigInteger.TEN;
+ }
+ };
+ }
+ });
+ assertEquals(EtherUtil.convert(2, EtherUtil.Unit.ETHER), constantinopleConfig2.getConstants().getBLOCK_REWARD());
+ }
+}
\ No newline at end of file
diff --git a/ethereumj-core/src/test/java/org/ethereum/config/net/JsonNetConfigTest.java b/ethereumj-core/src/test/java/org/ethereum/config/net/JsonNetConfigTest.java
index 6e4ac87e8e..6b9555ea7f 100644
--- a/ethereumj-core/src/test/java/org/ethereum/config/net/JsonNetConfigTest.java
+++ b/ethereumj-core/src/test/java/org/ethereum/config/net/JsonNetConfigTest.java
@@ -24,9 +24,11 @@
import org.ethereum.config.BlockchainNetConfig;
import org.ethereum.config.blockchain.*;
import org.ethereum.core.genesis.GenesisConfig;
+import org.ethereum.util.blockchain.EtherUtil;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class JsonNetConfigTest {
@@ -130,6 +132,38 @@ public void testByzantiumBlock() {
assertEquals("chainId should be copied from genesis config", new Integer(99), eip160.getChainId());
}
+ @Test
+ public void testConstantinopleBlock() {
+ final int byzStart = 50;
+ final int cnstStart = 60;
+
+ GenesisConfig genesisConfig = new GenesisConfig();
+ genesisConfig.constantinopleBlock = cnstStart;
+
+ JsonNetConfig config = new JsonNetConfig(genesisConfig);
+ assertBlockchainConfigExistsAt(config, cnstStart, ConstantinopleConfig.class);
+
+ BlockchainConfig blockchainConfig = config.getConfigForBlock(cnstStart);
+ assertEquals("Default chainId must be '1'", new Integer(1), blockchainConfig.getChainId());
+ assertEquals("Reward should be 2 ETH", EtherUtil.convert(2, EtherUtil.Unit.ETHER), blockchainConfig.getConstants().getBLOCK_REWARD());
+ assertTrue("EIP-1014 skinny CREATE2 should be activated among others", blockchainConfig.eip1014());
+
+ genesisConfig.chainId = 99;
+
+ config = new JsonNetConfig(genesisConfig);
+ blockchainConfig = config.getConfigForBlock(cnstStart);
+ assertEquals("chainId should be copied from genesis config", new Integer(99), blockchainConfig.getChainId());
+
+ assertEquals("Default Frontier reward is 5 ETH", EtherUtil.convert(5, EtherUtil.Unit.ETHER),
+ config.getConfigForBlock(byzStart).getConstants().getBLOCK_REWARD());
+ genesisConfig.byzantiumBlock = byzStart;
+ config = new JsonNetConfig(genesisConfig); // Respawn so we have Byzantium on byzStart instead of Frontier
+ assertEquals("Reward should be 3 ETH in Byzantium", EtherUtil.convert(3, EtherUtil.Unit.ETHER),
+ config.getConfigForBlock(byzStart).getConstants().getBLOCK_REWARD());
+ assertEquals("Reward should be changed to 2 ETH in Constantinople", EtherUtil.convert(2, EtherUtil.Unit.ETHER),
+ config.getConfigForBlock(cnstStart).getConstants().getBLOCK_REWARD());
+ }
+
private void assertBlockchainConfigExistsAt(BlockchainNetConfig netConfig, long blockNumber, Class configType) {
BlockchainConfig block = netConfig.getConfigForBlock(blockNumber);
if (!configType.isAssignableFrom(block.getClass())) {