[NC-1578] EIP-1052 EXTCODEHASH Operation (#19)

Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>
pull/2/head
Adrian Sutton 6 years ago committed by GitHub
parent d01b29f150
commit c65938c1e4
  1. 16
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/core/AbstractWorldUpdater.java
  2. 7
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/core/Account.java
  3. 7
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/mainnet/ConstantinopleGasCalculator.java
  4. 6
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/mainnet/FrontierGasCalculator.java
  5. 2
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/mainnet/MainnetEvmRegistries.java
  6. 8
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/vm/GasCalculator.java
  7. 33
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/vm/operations/ExtCodeHashOperation.java
  8. 5
      ethereum/core/src/main/java/net/consensys/pantheon/ethereum/worldstate/DefaultMutableWorldState.java
  9. 15
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/core/HashTest.java
  10. 131
      ethereum/core/src/test/java/net/consensys/pantheon/ethereum/vm/operations/ExtCodeHashOperationTest.java

@ -158,6 +158,7 @@ public abstract class AbstractWorldUpdater<W extends WorldView, A extends Accoun
private Wei balance; private Wei balance;
@Nullable private BytesValue updatedCode; // Null if the underlying code has not been updated. @Nullable private BytesValue updatedCode; // Null if the underlying code has not been updated.
@Nullable private Hash updatedCodeHash;
// Only contains update storage entries, but may contains entry with a value of 0 to signify // Only contains update storage entries, but may contains entry with a value of 0 to signify
// deletion. // deletion.
@ -248,6 +249,21 @@ public abstract class AbstractWorldUpdater<W extends WorldView, A extends Accoun
return updatedCode == null ? account.getCode() : updatedCode; return updatedCode == null ? account.getCode() : updatedCode;
} }
@Override
public Hash getCodeHash() {
if (updatedCode == null) {
// Note that we set code for new account, so it's only null if account isn't.
return account.getCodeHash();
} else {
// Cache the hash of updated code to avoid DOS attacks which repeatedly request hash
// of updated code and cause us to regenerate it.
if (updatedCodeHash == null) {
updatedCodeHash = Hash.hash(updatedCode);
}
return updatedCodeHash;
}
}
@Override @Override
public boolean hasCode() { public boolean hasCode() {
// Note that we set code for new account, so it's only null if account isn't. // Note that we set code for new account, so it's only null if account isn't.

@ -65,6 +65,13 @@ public interface Account {
*/ */
BytesValue getCode(); BytesValue getCode();
/**
* The hash of the EVM bytecode associated with this account.
*
* @return the hash of the account code (which may be {@link Hash#EMPTY}.
*/
Hash getCodeHash();
/** /**
* Whether the account has (non empty) EVM bytecode associated to it. * Whether the account has (non empty) EVM bytecode associated to it.
* *

@ -18,6 +18,8 @@ public class ConstantinopleGasCalculator extends SpuriousDragonGasCalculator {
private static final Gas SSTORE_DIRTY_RETURN_TO_UNUSED_REFUND_AMOUNT = Gas.of(19800); private static final Gas SSTORE_DIRTY_RETURN_TO_UNUSED_REFUND_AMOUNT = Gas.of(19800);
private static final Gas SSTORE_DIRTY_RETURN_TO_ORIGINAL_VALUE_REFUND_AMOUNT = Gas.of(4800); private static final Gas SSTORE_DIRTY_RETURN_TO_ORIGINAL_VALUE_REFUND_AMOUNT = Gas.of(4800);
private static final Gas EXTCODE_HASH_COST = Gas.of(400);
@Override @Override
public Gas create2OperationGasCost(final MessageFrame frame) { public Gas create2OperationGasCost(final MessageFrame frame) {
final UInt256 initCodeLength = frame.getStackItem(2).asUInt256(); final UInt256 initCodeLength = frame.getStackItem(2).asUInt256();
@ -87,4 +89,9 @@ public class ConstantinopleGasCalculator extends SpuriousDragonGasCalculator {
} }
} }
} }
@Override
public Gas extCodeHashOperationGasCost() {
return EXTCODE_HASH_COST;
}
} }

@ -338,6 +338,12 @@ public class FrontierGasCalculator implements GasCalculator {
frame, extCodeBaseGasCost(), COPY_WORD_GAS_COST, offset, length); frame, extCodeBaseGasCost(), COPY_WORD_GAS_COST, offset, length);
} }
@Override
public Gas extCodeHashOperationGasCost() {
throw new UnsupportedOperationException(
"EXTCODEHASH not supported by " + getClass().getSimpleName());
}
@Override @Override
public Gas getExtCodeSizeOperationGasCost() { public Gas getExtCodeSizeOperationGasCost() {
return extCodeBaseGasCost(); return extCodeBaseGasCost();

@ -30,6 +30,7 @@ import net.consensys.pantheon.ethereum.vm.operations.DupOperation;
import net.consensys.pantheon.ethereum.vm.operations.EqOperation; import net.consensys.pantheon.ethereum.vm.operations.EqOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExpOperation; import net.consensys.pantheon.ethereum.vm.operations.ExpOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExtCodeCopyOperation; import net.consensys.pantheon.ethereum.vm.operations.ExtCodeCopyOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExtCodeHashOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExtCodeSizeOperation; import net.consensys.pantheon.ethereum.vm.operations.ExtCodeSizeOperation;
import net.consensys.pantheon.ethereum.vm.operations.GasLimitOperation; import net.consensys.pantheon.ethereum.vm.operations.GasLimitOperation;
import net.consensys.pantheon.ethereum.vm.operations.GasOperation; import net.consensys.pantheon.ethereum.vm.operations.GasOperation;
@ -256,6 +257,7 @@ public abstract class MainnetEvmRegistries {
builder.add(SarOperation::new); builder.add(SarOperation::new);
builder.add(ShlOperation::new); builder.add(ShlOperation::new);
builder.add(ShrOperation::new); builder.add(ShrOperation::new);
builder.add(ExtCodeHashOperation::new);
return builder.build(); return builder.build();
} }

@ -13,6 +13,7 @@ import net.consensys.pantheon.ethereum.vm.operations.BalanceOperation;
import net.consensys.pantheon.ethereum.vm.operations.BlockHashOperation; import net.consensys.pantheon.ethereum.vm.operations.BlockHashOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExpOperation; import net.consensys.pantheon.ethereum.vm.operations.ExpOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExtCodeCopyOperation; import net.consensys.pantheon.ethereum.vm.operations.ExtCodeCopyOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExtCodeHashOperation;
import net.consensys.pantheon.ethereum.vm.operations.ExtCodeSizeOperation; import net.consensys.pantheon.ethereum.vm.operations.ExtCodeSizeOperation;
import net.consensys.pantheon.ethereum.vm.operations.JumpDestOperation; import net.consensys.pantheon.ethereum.vm.operations.JumpDestOperation;
import net.consensys.pantheon.ethereum.vm.operations.LogOperation; import net.consensys.pantheon.ethereum.vm.operations.LogOperation;
@ -252,6 +253,13 @@ public interface GasCalculator {
*/ */
Gas extCodeCopyOperationGasCost(MessageFrame frame, UInt256 offset, UInt256 length); Gas extCodeCopyOperationGasCost(MessageFrame frame, UInt256 offset, UInt256 length);
/**
* Returns the cost for executing a {@link ExtCodeHashOperation}.
*
* @return the cost for executing the external code hash operation
*/
Gas extCodeHashOperationGasCost();
/** /**
* Returns the cost for executing a {@link ExtCodeSizeOperation}. * Returns the cost for executing a {@link ExtCodeSizeOperation}.
* *

@ -0,0 +1,33 @@
package net.consensys.pantheon.ethereum.vm.operations;
import net.consensys.pantheon.ethereum.core.Account;
import net.consensys.pantheon.ethereum.core.Address;
import net.consensys.pantheon.ethereum.core.Gas;
import net.consensys.pantheon.ethereum.vm.AbstractOperation;
import net.consensys.pantheon.ethereum.vm.GasCalculator;
import net.consensys.pantheon.ethereum.vm.MessageFrame;
import net.consensys.pantheon.ethereum.vm.Words;
import net.consensys.pantheon.util.bytes.Bytes32;
public class ExtCodeHashOperation extends AbstractOperation {
public ExtCodeHashOperation(final GasCalculator gasCalculator) {
super(0x3F, "EXTCODEHASH", 1, 1, false, 1, gasCalculator);
}
@Override
public Gas cost(final MessageFrame frame) {
return gasCalculator().extCodeHashOperationGasCost();
}
@Override
public void execute(final MessageFrame frame) {
final Address address = Words.toAddress(frame.popStackItem());
final Account account = frame.getWorldState().get(address);
if (account == null) {
frame.pushStackItem(Bytes32.ZERO);
} else {
frame.pushStackItem(account.getCodeHash());
}
}
}

@ -248,6 +248,11 @@ public class DefaultMutableWorldState implements MutableWorldState {
return !getCode().isEmpty(); return !getCode().isEmpty();
} }
@Override
public Hash getCodeHash() {
return codeHash;
}
@Override @Override
public UInt256 getStorageValue(final UInt256 key) { public UInt256 getStorageValue(final UInt256 key) {
final Optional<BytesValue> val = storageTrie().get(Hash.hash(key.getBytes())); final Optional<BytesValue> val = storageTrie().get(Hash.hash(key.getBytes()));

@ -0,0 +1,15 @@
package net.consensys.pantheon.ethereum.core;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
public class HashTest {
@Test
public void shouldGetExpectedValueForEmptyHash() {
assertThat(Hash.EMPTY)
.isEqualTo(
Hash.fromHexString("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"));
}
}

@ -0,0 +1,131 @@
package net.consensys.pantheon.ethereum.vm.operations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import net.consensys.pantheon.ethereum.chain.Blockchain;
import net.consensys.pantheon.ethereum.core.Address;
import net.consensys.pantheon.ethereum.core.AddressHelpers;
import net.consensys.pantheon.ethereum.core.BlockHeaderTestFixture;
import net.consensys.pantheon.ethereum.core.Gas;
import net.consensys.pantheon.ethereum.core.Hash;
import net.consensys.pantheon.ethereum.core.Wei;
import net.consensys.pantheon.ethereum.core.WorldUpdater;
import net.consensys.pantheon.ethereum.db.WorldStateArchive;
import net.consensys.pantheon.ethereum.mainnet.ConstantinopleGasCalculator;
import net.consensys.pantheon.ethereum.vm.Code;
import net.consensys.pantheon.ethereum.vm.MessageFrame;
import net.consensys.pantheon.ethereum.vm.MessageFrame.Type;
import net.consensys.pantheon.ethereum.vm.Words;
import net.consensys.pantheon.ethereum.worldstate.KeyValueStorageWorldStateStorage;
import net.consensys.pantheon.services.kvstore.InMemoryKeyValueStorage;
import net.consensys.pantheon.util.bytes.Bytes32;
import net.consensys.pantheon.util.bytes.BytesValue;
import net.consensys.pantheon.util.uint.UInt256;
import java.util.ArrayDeque;
import org.junit.Test;
public class ExtCodeHashOperationTest {
private static final Address ADDRESS1 = AddressHelpers.ofValue(11111111);
private static final Address REQUESTED_ADDRESS = AddressHelpers.ofValue(22222222);
private final Blockchain blockchain = mock(Blockchain.class);
private final WorldStateArchive worldStateArchive =
new WorldStateArchive(new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()));
private final WorldUpdater worldStateUpdater = worldStateArchive.getMutable().updater();
private final ExtCodeHashOperation operation =
new ExtCodeHashOperation(new ConstantinopleGasCalculator());
@Test
public void shouldCharge400Gas() {
assertThat(operation.cost(createMessageFrame(REQUESTED_ADDRESS))).isEqualTo(Gas.of(400));
}
@Test
public void shouldReturnZeroWhenAccountDoesNotExist() {
final Bytes32 result = executeOperation(REQUESTED_ADDRESS);
assertThat(result).isEqualTo(Bytes32.ZERO);
}
@Test
public void shouldReturnHashOfEmptyDataWhenAccountExistsButDoesNotHaveCode() {
worldStateUpdater.getOrCreate(REQUESTED_ADDRESS);
assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.EMPTY);
}
@Test
public void shouldReturnZeroWhenPrecompiledContractHasNoBalance() {
assertThat(executeOperation(Address.ECREC)).isEqualTo(Bytes32.ZERO);
}
@Test
public void shouldReturnEmptyCodeHashWhenPrecompileHasBalance() {
// Sending money to a precompile causes it to exist in the world state archive.
worldStateUpdater.getOrCreate(Address.ECREC).setBalance(Wei.of(10));
assertThat(executeOperation(Address.ECREC)).isEqualTo(Hash.EMPTY);
}
@Test
public void shouldGetHashOfAccountCodeWhenCodeIsPresent() {
final BytesValue code = BytesValue.fromHexString("0xabcdef");
worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setCode(code);
assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.hash(code));
}
@Test
public void shouldZeroOutLeftMostBitsToGetAddress() {
// If EXTCODEHASH of A is X, then EXTCODEHASH of A + 2**160 is X.
final BytesValue code = BytesValue.fromHexString("0xabcdef");
worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setCode(code);
final Bytes32 value =
Words.fromAddress(REQUESTED_ADDRESS)
.asUInt256()
.plus(UInt256.of(2).pow(UInt256.of(160)))
.getBytes();
final MessageFrame frame = createMessageFrame(value);
operation.execute(frame);
assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code));
}
private Bytes32 executeOperation(final Address requestedAddress) {
final MessageFrame frame = createMessageFrame(requestedAddress);
operation.execute(frame);
return frame.getStackItem(0);
}
private MessageFrame createMessageFrame(final Address requestedAddress) {
final Bytes32 stackItem = Words.fromAddress(requestedAddress);
return createMessageFrame(stackItem);
}
private MessageFrame createMessageFrame(final Bytes32 stackItem) {
final MessageFrame frame =
new MessageFrame.Builder()
.type(Type.MESSAGE_CALL)
.initialGas(Gas.MAX_VALUE)
.inputData(BytesValue.EMPTY)
.depth(1)
.gasPrice(Wei.ZERO)
.contract(ADDRESS1)
.address(ADDRESS1)
.originator(ADDRESS1)
.sender(ADDRESS1)
.worldState(worldStateUpdater)
.messageFrameStack(new ArrayDeque<>())
.blockHeader(new BlockHeaderTestFixture().buildHeader())
.value(Wei.ZERO)
.apparentValue(Wei.ZERO)
.code(new Code(BytesValue.EMPTY))
.blockchain(blockchain)
.completer(messageFrame -> {})
.build();
frame.pushStackItem(stackItem);
return frame;
}
}
Loading…
Cancel
Save