NC-2147 Implement ConstantinopleFix hardfork (#601)

* create a new fork definition
* make it constantinople without EIP-1283
* update tests where appropriate
* Add PetersbergGasCalculator
* Add fork ordering test
* Add rinkeby Constantinople block
* fix json config bug
* add Ropsten constantinopleFix block
* run reference tests
* clique test genesis file violates block ordering rules

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Danno Ferrin 6 years ago committed by GitHub
parent 948e8fe443
commit 0e3f199ef5
  1. 8
      acceptance-tests/src/test/resources/clique/clique.json
  2. 2
      config/src/main/java/tech/pegasys/pantheon/config/GenesisConfigOptions.java
  3. 5
      config/src/main/java/tech/pegasys/pantheon/config/JsonGenesisConfigOptions.java
  4. 1
      config/src/main/resources/dev.json
  5. 1
      config/src/main/resources/goerli.json
  6. 2
      config/src/main/resources/mainnet.json
  7. 1
      config/src/main/resources/rinkeby.json
  8. 1
      config/src/main/resources/ropsten.json
  9. 11
      config/src/test-support/java/tech/pegasys/pantheon/config/StubGenesisConfigOptions.java
  10. 8
      config/src/test/java/tech/pegasys/pantheon/config/GenesisConfigOptionsTest.java
  11. 4
      ethereum/core/src/jmh/java/tech/pegasys/pantheon/ethereum/vm/operations/BlockHashOperationBenchmark.java
  12. 56
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ConstantinopleFixGasCalculator.java
  13. 6
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolSpecs.java
  14. 36
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/ProtocolScheduleBuilder.java
  15. 2
      ethereum/core/src/test-support/java/tech/pegasys/pantheon/ethereum/core/ExecutionContextTestFixture.java
  16. 2
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/core/TransactionTestCaseSpec.java
  17. 110
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/ConstantinopleFixSstoreGasTest.java
  18. 26
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetProtocolScheduleTest.java
  19. 5
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/BlockchainReferenceTestTools.java
  20. 4
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/GeneralStateReferenceTestTools.java
  21. 3
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/vm/ReferenceTestProtocolSchedules.java

@ -4,9 +4,11 @@
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"eip155Block": 3,
"eip158Block": 4,
"byzantiumBlock": 5,
"constantinopleBlock": 6,
"constantinopleFixBlock": 7,
"clique": {
"blockperiodseconds": 10,
"epochlength": 30000

@ -43,5 +43,7 @@ public interface GenesisConfigOptions {
OptionalLong getConstantinopleBlockNumber();
OptionalLong getConstantinopleFixBlockNumber();
OptionalInt getChainId();
}

@ -100,6 +100,11 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions {
return getOptionalLong("constantinopleblock");
}
@Override
public OptionalLong getConstantinopleFixBlockNumber() {
return getOptionalLong("constantinoplefixblock");
}
@Override
public OptionalInt getChainId() {
return configRoot.containsKey("chainid")

@ -8,6 +8,7 @@
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"constantinopleFixBlock": 0,
"ethash": {
}
},

@ -9,6 +9,7 @@
"eip160Block":0,
"byzantiumBlock":0,
"constantinopleBlock":0,
"constantinopleFixBlock": 0,
"clique":{
"period":15,
"epoch":30000

@ -9,6 +9,8 @@
"eip155Block": 2675000,
"eip158Block": 2675000,
"byzantiumBlock": 4370000,
"constantinopleBlock": 7280000,
"constantinopleFixBlock": 7280000,
"ethash": {
}

@ -7,6 +7,7 @@
"eip155Block": 3,
"eip158Block": 3,
"byzantiumBlock": 1035301,
"constantinopleBlock": 3660663,
"clique": {
"period": 15,
"epoch": 30000

@ -7,6 +7,7 @@
"eip158Block": 10,
"byzantiumBlock": 1700000,
"constantinopleBlock": 4230000,
"constantinopleFixBlock": 4939394,
"ethash": {
}
},

@ -23,6 +23,7 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
private OptionalLong spuriousDragonBlockNumber = OptionalLong.empty();
private OptionalLong byzantiumBlockNumber = OptionalLong.empty();
private OptionalLong constantinopleBlockNumber = OptionalLong.empty();
private OptionalLong constantinopleFixBlockNumber = OptionalLong.empty();
private OptionalInt chainId = OptionalInt.empty();
@Override
@ -90,6 +91,11 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
return constantinopleBlockNumber;
}
@Override
public OptionalLong getConstantinopleFixBlockNumber() {
return constantinopleFixBlockNumber;
}
@Override
public OptionalInt getChainId() {
return chainId;
@ -125,6 +131,11 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions {
return this;
}
public StubGenesisConfigOptions constantinopleFixBlock(final long blockNumber) {
constantinopleFixBlockNumber = OptionalLong.of(blockNumber);
return this;
}
public StubGenesisConfigOptions chainId(final int chainId) {
this.chainId = OptionalInt.of(chainId);
return this;

@ -101,6 +101,13 @@ public class GenesisConfigOptionsTest {
assertThat(config.getConstantinopleBlockNumber()).hasValue(1000);
}
@Test
public void shouldGetConstantinopleFixBlockNumber() {
final GenesisConfigOptions config =
fromConfigOptions(singletonMap("constantinopleFixBlock", 1000));
assertThat(config.getConstantinopleFixBlockNumber()).hasValue(1000);
}
@Test
public void shouldNotReturnEmptyOptionalWhenBlockNumberNotSpecified() {
final GenesisConfigOptions config = fromConfigOptions(emptyMap());
@ -110,6 +117,7 @@ public class GenesisConfigOptionsTest {
assertThat(config.getSpuriousDragonBlockNumber()).isEmpty();
assertThat(config.getByzantiumBlockNumber()).isEmpty();
assertThat(config.getConstantinopleBlockNumber()).isEmpty();
assertThat(config.getConstantinopleFixBlockNumber()).isEmpty();
}
@Test

@ -12,7 +12,7 @@
*/
package tech.pegasys.pantheon.ethereum.vm.operations;
import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator;
import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleFixGasCalculator;
import tech.pegasys.pantheon.ethereum.vm.BlockHashLookup;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.util.bytes.Bytes32;
@ -42,7 +42,7 @@ public class BlockHashOperationBenchmark {
@Setup
public void prepare() throws Exception {
operationBenchmarkHelper = OperationBenchmarkHelper.create();
operation = new BlockHashOperation(new ConstantinopleGasCalculator());
operation = new BlockHashOperation(new ConstantinopleFixGasCalculator());
frame = operationBenchmarkHelper.createMessageFrame();
}

@ -0,0 +1,56 @@
/*
* Copyright 2018 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.
*/
package tech.pegasys.pantheon.ethereum.mainnet;
import tech.pegasys.pantheon.ethereum.core.Account;
import tech.pegasys.pantheon.ethereum.core.Gas;
import tech.pegasys.pantheon.util.uint.UInt256;
/**
* Gas Calculator for Petersberg Hard Fork. Rollback EIP-1283.
*
* <p>Neither {@link TangerineWhistleGasCalculator} nor {@link SpuriousDragonGasCalculator} overrode
* these two methods so {@link FrontierGasCalculator} is the source.
*/
public class ConstantinopleFixGasCalculator extends ConstantinopleGasCalculator {
/** Same as {#link {@link FrontierGasCalculator#STORAGE_SET_GAS_COST} */
private static final Gas STORAGE_SET_GAS_COST = Gas.of(20_000L);
/** Same as {#link {@link FrontierGasCalculator#STORAGE_RESET_GAS_COST} */
private static final Gas STORAGE_RESET_GAS_COST = Gas.of(5_000L);
/** Same as {#link {@link FrontierGasCalculator#STORAGE_RESET_REFUND_AMOUNT} */
private static final Gas STORAGE_RESET_REFUND_AMOUNT = Gas.of(15_000L);
/**
* Same as {#link {@link FrontierGasCalculator#calculateStorageCost(Account, UInt256, UInt256)}
*/
@Override
public Gas calculateStorageCost(
final Account account, final UInt256 key, final UInt256 newValue) {
return !newValue.isZero() && account.getStorageValue(key).isZero()
? STORAGE_SET_GAS_COST
: STORAGE_RESET_GAS_COST;
}
/**
* Same as {#link {@link FrontierGasCalculator#calculateStorageRefundAmount(Account, UInt256,
* UInt256)}
*/
@Override
public Gas calculateStorageRefundAmount(
final Account account, final UInt256 key, final UInt256 newValue) {
return newValue.isZero() && !account.getStorageValue(key).isZero()
? STORAGE_RESET_REFUND_AMOUNT
: Gas.ZERO;
}
}

@ -193,6 +193,12 @@ public abstract class MainnetProtocolSpecs {
.name("Constantinople");
}
public static ProtocolSpecBuilder<Void> constantinopleFixDefinition(final int chainId) {
return constantinopleDefinition(chainId)
.gasCalculator(ConstantinopleFixGasCalculator::new)
.name("ConstantinopleFix");
}
private static TransactionReceipt frontierTransactionReceiptFactory(
final TransactionProcessor.Result result, final WorldState worldState, final long gasUsed) {
return new TransactionReceipt(worldState.rootHash(), gasUsed, result.getLogs());

@ -40,6 +40,8 @@ public class ProtocolScheduleBuilder<C> {
final int chainId = config.getChainId().orElse(defaultChainId);
final MutableProtocolSchedule<C> protocolSchedule = new MutableProtocolSchedule<>(chainId);
validateForkOrdering();
addProtocolSpec(
protocolSchedule, OptionalLong.of(0), MainnetProtocolSpecs.frontierDefinition());
addProtocolSpec(
@ -84,6 +86,10 @@ public class ProtocolScheduleBuilder<C> {
protocolSchedule,
config.getConstantinopleBlockNumber(),
MainnetProtocolSpecs.constantinopleDefinition(chainId));
addProtocolSpec(
protocolSchedule,
config.getConstantinopleFixBlockNumber(),
MainnetProtocolSpecs.constantinopleFixDefinition(chainId));
return protocolSchedule;
}
@ -101,4 +107,34 @@ public class ProtocolScheduleBuilder<C> {
.privacyParameters(privacyParameters)
.build(protocolSchedule)));
}
private long validateForkOrder(
final String forkName, final OptionalLong thisForkBlock, final long lastForkBlock) {
final long referenceForkBlock = thisForkBlock.orElse(lastForkBlock);
if (lastForkBlock > referenceForkBlock) {
throw new RuntimeException(
String.format(
"Genesis Config Error: '%s' is scheduled for block %d but it must be on or after block %d.",
forkName, thisForkBlock.getAsLong(), lastForkBlock));
}
return referenceForkBlock;
}
private void validateForkOrdering() {
long lastForkBlock = 0;
lastForkBlock = validateForkOrder("Homestead", config.getHomesteadBlockNumber(), lastForkBlock);
lastForkBlock = validateForkOrder("DaoFork", config.getDaoForkBlock(), lastForkBlock);
lastForkBlock =
validateForkOrder(
"TangerineWhistle", config.getTangerineWhistleBlockNumber(), lastForkBlock);
lastForkBlock =
validateForkOrder("SpuriousDragon", config.getSpuriousDragonBlockNumber(), lastForkBlock);
lastForkBlock = validateForkOrder("Byzantium", config.getByzantiumBlockNumber(), lastForkBlock);
lastForkBlock =
validateForkOrder("Constantinople", config.getConstantinopleBlockNumber(), lastForkBlock);
lastForkBlock =
validateForkOrder(
"ConstantinopleFix", config.getConstantinopleFixBlockNumber(), lastForkBlock);
assert (lastForkBlock >= 0);
}
}

@ -113,7 +113,7 @@ public class ExecutionContextTestFixture {
if (protocolSchedule == null) {
protocolSchedule =
new ProtocolScheduleBuilder<>(
new StubGenesisConfigOptions().constantinopleBlock(0),
new StubGenesisConfigOptions().constantinopleFixBlock(0),
42,
Function.identity(),
PrivacyParameters.noPrivacy())

@ -96,7 +96,7 @@ public class TransactionTestCaseSpec {
final Expectation expectation = expectations.get(milestone);
if (expectation == null) {
throw new IllegalStateException("Expectation for milestone %s not found" + milestone);
throw new IllegalStateException("Expectation for milestone " + milestone + " not found");
}
return expectation;

@ -0,0 +1,110 @@
/*
* Copyright 2018 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.
*/
package tech.pegasys.pantheon.ethereum.mainnet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static tech.pegasys.pantheon.util.uint.UInt256.ONE;
import static tech.pegasys.pantheon.util.uint.UInt256.ZERO;
import tech.pegasys.pantheon.ethereum.core.Account;
import tech.pegasys.pantheon.ethereum.core.Gas;
import tech.pegasys.pantheon.util.uint.UInt256;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class ConstantinopleFixSstoreGasTest {
private static final UInt256 TWO = UInt256.of(2);
private final ConstantinopleFixGasCalculator gasCalculator = new ConstantinopleFixGasCalculator();
@Parameters(name = "original: {0}, current: {1}, new: {2}")
public static Object[][] scenarios() {
return new Object[][] {
// Zero no-op
{ZERO, ZERO, ZERO, Gas.of(5_000), Gas.ZERO},
// Zero fresh change
{ZERO, ZERO, ONE, Gas.of(20_000), Gas.ZERO},
// Dirty, reset to zero
{ZERO, ONE, ZERO, Gas.of(5_000), Gas.of(15_000)},
// Dirty, changed but not reset
{ZERO, ONE, TWO, Gas.of(5_000), Gas.ZERO},
// Dirty no-op
{ZERO, ONE, ONE, Gas.of(5_000), Gas.ZERO},
// Dirty, zero no-op
{ONE, ZERO, ZERO, Gas.of(5_000), Gas.ZERO},
// Dirty, reset to non-zero
{ONE, ZERO, ONE, Gas.of(20_000), Gas.ZERO},
// Fresh change to zero
{ONE, ONE, ZERO, Gas.of(5_000), Gas.of(15_000)},
// Fresh change with all non-zero
{ONE, ONE, TWO, Gas.of(5_000), Gas.ZERO},
// Dirty, clear originally set value
{ONE, TWO, ZERO, Gas.of(5_000), Gas.of(15_000)},
// Non-zero no-op
{ONE, ONE, ONE, Gas.of(5_000), Gas.ZERO},
};
}
@Parameter public UInt256 originalValue;
@Parameter(value = 1)
public UInt256 currentValue;
@Parameter(value = 2)
public UInt256 newValue;
@Parameter(value = 3)
public Gas expectedGasCost;
@Parameter(value = 4)
public Gas expectedGasRefund;
private final Account account = mock(Account.class);
@Before
public void setUp() {
when(account.getOriginalStorageValue(UInt256.ZERO)).thenReturn(originalValue);
when(account.getStorageValue(UInt256.ZERO)).thenReturn(currentValue);
}
@Test
public void shouldChargeCorrectGas() {
assertThat(gasCalculator.calculateStorageCost(account, UInt256.ZERO, newValue))
.isEqualTo(expectedGasCost);
}
@Test
public void shouldRefundCorrectGas() {
assertThat(gasCalculator.calculateStorageRefundAmount(account, UInt256.ZERO, newValue))
.isEqualTo(expectedGasRefund);
}
}

@ -40,7 +40,10 @@ public class MainnetProtocolScheduleTest {
Assertions.assertThat(sched.getByBlockNumber(4_730_000L).getName()).isEqualTo("Byzantium");
// Constantinople was originally scheduled for 7_080_000, but postponed
Assertions.assertThat(sched.getByBlockNumber(7_080_000L).getName()).isEqualTo("Byzantium");
Assertions.assertThat(sched.getByBlockNumber(Long.MAX_VALUE).getName()).isEqualTo("Byzantium");
Assertions.assertThat(sched.getByBlockNumber(7_280_000L).getName())
.isEqualTo("ConstantinopleFix");
Assertions.assertThat(sched.getByBlockNumber(Long.MAX_VALUE).getName())
.isEqualTo("ConstantinopleFix");
}
@Test
@ -57,7 +60,7 @@ public class MainnetProtocolScheduleTest {
public void createFromConfigWithSettings() {
final JsonObject json =
new JsonObject(
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"chainId\":1234}}");
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 19, \"chainId\":1234}}");
final ProtocolSchedule<Void> sched =
MainnetProtocolSchedule.fromConfig(
GenesisConfigFile.fromConfig(json).getConfigOptions(), PrivacyParameters.noPrivacy());
@ -70,6 +73,22 @@ public class MainnetProtocolScheduleTest {
Assertions.assertThat(sched.getByBlockNumber(15).getName()).isEqualTo("SpuriousDragon");
Assertions.assertThat(sched.getByBlockNumber(16).getName()).isEqualTo("Byzantium");
Assertions.assertThat(sched.getByBlockNumber(18).getName()).isEqualTo("Constantinople");
Assertions.assertThat(sched.getByBlockNumber(19).getName()).isEqualTo("ConstantinopleFix");
}
@Test
public void outOfOrderForksFails() {
final JsonObject json =
new JsonObject(
"{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"constantinopleFixBlock\": 17, \"chainId\":1234}}");
Assertions.assertThatExceptionOfType(RuntimeException.class)
.describedAs(
"Genesis Config Error: 'ConstantinopleFix' is scheduled for block 17 but it must be on or after block 18.")
.isThrownBy(
() ->
MainnetProtocolSchedule.fromConfig(
GenesisConfigFile.fromConfig(json).getConfigOptions(),
PrivacyParameters.noPrivacy()));
}
@Test
@ -86,7 +105,8 @@ public class MainnetProtocolScheduleTest {
Assertions.assertThat(sched.getByBlockNumber(10).getName()).isEqualTo("SpuriousDragon");
Assertions.assertThat(sched.getByBlockNumber(1700000).getName()).isEqualTo("Byzantium");
Assertions.assertThat(sched.getByBlockNumber(4230000).getName()).isEqualTo("Constantinople");
Assertions.assertThat(sched.getByBlockNumber(4939394).getName()).isEqualTo("ConstantinopleFix");
Assertions.assertThat(sched.getByBlockNumber(Long.MAX_VALUE).getName())
.isEqualTo("Constantinople");
.isEqualTo("ConstantinopleFix");
}
}

@ -43,7 +43,7 @@ public class BlockchainReferenceTestTools {
System.getProperty(
"test.ethereum.blockchain.eips",
"FrontierToHomesteadAt5,HomesteadToEIP150At5,HomesteadToDaoAt5,EIP158ToByzantiumAt5,"
+ "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople");
+ "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix");
NETWORKS_TO_RUN = Arrays.asList(networks.split(","));
}
@ -64,7 +64,8 @@ public class BlockchainReferenceTestTools {
params.blacklist("RevertPrecompiledTouch_d0g0v0_(EIP158|Byzantium)");
// Consumes a huge amount of memory
params.blacklist("static_Call1MB1024Calldepth_d1g0v0_(Byzantium|Constantinople)");
params.blacklist(
"static_Call1MB1024Calldepth_d1g0v0_(Byzantium|Constantinople|ConstantinopleFix)");
}
public static Collection<Object[]> generateTestParametersForConfig(final String[] filePath) {

@ -51,7 +51,7 @@ public class GeneralStateReferenceTestTools {
final String eips =
System.getProperty(
"test.ethereum.state.eips",
"Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople");
"Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix");
EIPS_TO_RUN = Arrays.asList(eips.split(","));
}
@ -84,7 +84,7 @@ public class GeneralStateReferenceTestTools {
// Gas integer value is too large to construct a valid transaction.
params.blacklist("OverflowGasRequire");
// Consumes a huge amount of memory
params.blacklist("static_Call1MB1024Calldepth-(Byzantium|Constantinople)");
params.blacklist("static_Call1MB1024Calldepth-(Byzantium|Constantinople|ConstantinopleFix)");
}
public static Collection<Object[]> generateTestParametersForConfig(final String[] filePath) {

@ -47,6 +47,9 @@ public class ReferenceTestProtocolSchedules {
builder.put("Byzantium", createSchedule(new StubGenesisConfigOptions().byzantiumBlock(0)));
builder.put(
"Constantinople", createSchedule(new StubGenesisConfigOptions().constantinopleBlock(0)));
builder.put(
"ConstantinopleFix",
createSchedule(new StubGenesisConfigOptions().constantinopleFixBlock(0)));
return new ReferenceTestProtocolSchedules(builder.build());
}

Loading…
Cancel
Save