Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

[PAN-2903] Implement EIP-1344 ChainID Opcode #1690

Merged
merged 5 commits into from
Jul 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package tech.pegasys.pantheon.ethereum.mainnet;

import static tech.pegasys.pantheon.ethereum.mainnet.MainnetProtocolSpecs.ISTANBUL_ACCOUNT_VERSION;

import tech.pegasys.pantheon.ethereum.core.Account;
import tech.pegasys.pantheon.ethereum.vm.EVM;
import tech.pegasys.pantheon.ethereum.vm.GasCalculator;
Expand All @@ -30,6 +32,7 @@
import tech.pegasys.pantheon.ethereum.vm.operations.CallOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.CallValueOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.CallerOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.ChainIdOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.CodeCopyOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.CodeSizeOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.CoinbaseOperation;
Expand Down Expand Up @@ -91,6 +94,10 @@
import tech.pegasys.pantheon.ethereum.vm.operations.SwapOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.TimestampOperation;
import tech.pegasys.pantheon.ethereum.vm.operations.XorOperation;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;

import java.math.BigInteger;

/** Provides EVMs supporting the appropriate operations for mainnet hard forks. */
abstract class MainnetEvmRegistries {
Expand Down Expand Up @@ -127,10 +134,10 @@ static EVM constantinople(final GasCalculator gasCalculator) {
return new EVM(registry, new InvalidOperation(gasCalculator));
}

static EVM istanbul(final GasCalculator gasCalculator) {
static EVM istanbul(final GasCalculator gasCalculator, final BigInteger chainId) {
final OperationRegistry registry = new OperationRegistry(2);

registerIstanbulOpcodes(registry, gasCalculator, Account.DEFAULT_VERSION);
registerIstanbulOpcodes(registry, gasCalculator, Account.DEFAULT_VERSION, chainId);

return new EVM(registry, new InvalidOperation(gasCalculator));
}
Expand Down Expand Up @@ -257,8 +264,13 @@ private static void registerConstantinopleOpcodes(
private static void registerIstanbulOpcodes(
final OperationRegistry registry,
final GasCalculator gasCalculator,
final int accountVersion) {
final int accountVersion,
final BigInteger chainId) {
registerConstantinopleOpcodes(registry, gasCalculator, accountVersion);
registerConstantinopleOpcodes(registry, gasCalculator, 1);
registerConstantinopleOpcodes(registry, gasCalculator, ISTANBUL_ACCOUNT_VERSION);
registry.put(
new ChainIdOperation(gasCalculator, Bytes32.leftPad(BytesValue.of(chainId.toByteArray()))),
ISTANBUL_ACCOUNT_VERSION);
registerConstantinopleOpcodes(registry, gasCalculator, ISTANBUL_ACCOUNT_VERSION);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package tech.pegasys.pantheon.ethereum.mainnet;

import static com.google.common.base.Preconditions.checkArgument;
import static tech.pegasys.pantheon.ethereum.vm.MessageFrame.DEFAULT_MAX_STACK_SIZE;

import tech.pegasys.pantheon.ethereum.MainnetBlockValidator;
Expand Down Expand Up @@ -285,12 +286,13 @@ public static ProtocolSpecBuilder<Void> istanbulDefinition(
final OptionalInt configContractSizeLimit,
final OptionalInt configStackSizeLimit,
final boolean enableRevertReason) {
checkArgument(chainId.isPresent(), "Istanbul requires the use of chainId");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you trace back to where the chainId is set, it looks like it's only ever missing in test cases. Might be worth going through and making the chainId argument a plain unwrapped BigInteger, in which case we wouldn't need this check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FixedDifficultyProtocolSchedule uses it too. It's at least a 4 file change, larger than this PR, so that level of change would deserve its own PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no requirement that a chain has a chainId - it may be omitted from the genesis config. This is a slightly interesting hole in the ChainID opcode spec. Doesn't apply to any public chain but there may be chains out there it does affect. So maybe ChainID opcode should return 0 when there is no chain ID?

final int contractSizeLimit =
configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT);
final int stackSizeLimit = configStackSizeLimit.orElse(DEFAULT_MAX_STACK_SIZE);
return constantinopleFixDefinition(
chainId, configContractSizeLimit, configStackSizeLimit, enableRevertReason)
.evmBuilder(MainnetEvmRegistries::istanbul)
.evmBuilder(gasCalculator -> MainnetEvmRegistries.istanbul(gasCalculator, chainId.get()))
.precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::istanbul)
.transactionProcessorBuilder(
(gasCalculator,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.vm.operations;

import tech.pegasys.pantheon.ethereum.core.Gas;
import tech.pegasys.pantheon.ethereum.vm.AbstractOperation;
import tech.pegasys.pantheon.ethereum.vm.GasCalculator;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.util.bytes.Bytes32;

public class ChainIdOperation extends AbstractOperation {

private final Bytes32 chainId;

public ChainIdOperation(final GasCalculator gasCalculator, final Bytes32 chainId) {
super(0x46, "CHAINID", 0, 1, false, 1, gasCalculator);
this.chainId = chainId;
}

@Override
public Gas cost(final MessageFrame frame) {
return gasCalculator().getBaseTierGasCost();
}

@Override
public void execute(final MessageFrame frame) {
frame.pushStackItem(chainId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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.vm.operations;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

import tech.pegasys.pantheon.ethereum.core.Gas;
import tech.pegasys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator;
import tech.pegasys.pantheon.ethereum.vm.MessageFrame;
import tech.pegasys.pantheon.util.bytes.Bytes32;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

@RunWith(Parameterized.class)
public class ChainIdOperationTest {

private final Bytes32 chainId;
private final int expectedGas;
private final MessageFrame messageFrame = mock(MessageFrame.class);
private final ChainIdOperation operation;

@Parameters(name = "chainId: {0}, expectedGas: {1}")
public static Object[][] params() {
return new Object[][] {
{"0x01", 2},
{"0x03", 2},
{"0x04", 2},
{"0x05", 2},
};
}

public ChainIdOperationTest(final String chainIdString, final int expectedGas) {
chainId = Bytes32.fromHexString(chainIdString);
this.expectedGas = expectedGas;
operation = new ChainIdOperation(new ConstantinopleGasCalculator(), chainId);
}

@Test
public void shouldReturnChainId() {
final ArgumentCaptor<Bytes32> arg = ArgumentCaptor.forClass(Bytes32.class);
operation.execute(messageFrame);
Mockito.verify(messageFrame).pushStackItem(arg.capture());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably also verify that pushStackItem is only called once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Mockito.verifyNoMoreInteractions(messageFrame);
assertThat(arg.getValue()).isEqualByComparingTo(chainId);
}

@Test
public void shouldCalculateGasPrice() {
assertThat(operation.cost(messageFrame)).isEqualTo(Gas.of(expectedGas));
}
}