Datahash opcode (#4933)

Implements new DATAHASH opcode in EVM

Signed-off-by: Justin Florentine <justin+github@florentine.us>
pull/4971/head
Justin Florentine 2 years ago committed by GitHub
parent 3850d49069
commit 42d67de5b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 2
      datatypes/src/main/java/org/hyperledger/besu/datatypes/Hash.java
  3. 3
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java
  4. 3
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java
  5. 2
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactionsTest.java
  6. 3
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/goquorum/GoQuorumSendRawPrivateTransactionTest.java
  7. 41
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java
  8. 3
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/goquorum/GoQuorumBlockProcessor.java
  9. 2
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java
  10. 2
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/MessageWrapperTest.java
  11. 2
      evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java
  12. 26
      evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java
  13. 57
      evm/src/main/java/org/hyperledger/besu/evm/operation/DataHashOperation.java
  14. 92
      evm/src/test/java/org/hyperledger/besu/evm/operations/DataHashOperationTest.java

@ -17,6 +17,7 @@
### Breaking Changes
### Additions and Improvements
- Support for new DATAHASH opcode as part of EIP-4844 [#4823](https://github.com/hyperledger/besu/issues/4823)
- Send only hash announcement for blob transaction type [#4940](https://github.com/hyperledger/besu/pull/4940)
- Add `excess_data_gas` field to block header [#4958](https://github.com/hyperledger/besu/pull/4958)

@ -23,7 +23,7 @@ import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.bytes.DelegatingBytes32;
/** A 32-bytes hash value as used in Ethereum blocks, that is the result of the KEC algorithm. */
/** A 32-bytes hash value as used in Ethereum blocks, usually the result of the KEC algorithm. */
public class Hash extends DelegatingBytes32 implements org.hyperledger.besu.plugin.data.Hash {
/** The constant ZERO. */

@ -193,7 +193,8 @@ public class JsonRpcResponseUtils {
Optional.empty(),
address(fromAddress),
Optional.empty(),
Optional.of(bigInteger(v)));
Optional.of(bigInteger(v)),
Optional.empty());
return new TransactionCompleteResult(
new TransactionWithMetadata(

@ -350,6 +350,7 @@ public class GraphQLDataFetchers {
enclavePayload,
transaction.getSender(),
transaction.getChainId(),
Optional.ofNullable(transaction.getV()))));
Optional.ofNullable(transaction.getV()),
Optional.empty())));
}
}

@ -72,7 +72,7 @@ public class TxPoolBesuPendingTransactionsTest {
final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request);
final Set<TransactionPendingResult> result =
(Set<TransactionPendingResult>) actualResponse.getResult();
assertThat(result.size()).isEqualTo(4);
assertThat(result.size()).isEqualTo(this.pendingTransactions.getPendingTransactions().size());
}
@Test

@ -289,7 +289,8 @@ public class GoQuorumSendRawPrivateTransactionTest {
Bytes.fromHexString(
"0x8411b12666f68ef74cace3615c9d5a377729d03f")), // sender public address
Optional.empty(),
Optional.of(BigInteger.valueOf(37)));
Optional.of(BigInteger.valueOf(37)),
Optional.empty());
publicTransaction.writeTo(bvrlp);
return bvrlp.encoded().toHexString();
}

@ -112,6 +112,7 @@ public class Transaction
private final TransactionType transactionType;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance();
private final Optional<List<Hash>> versionedHashes;
public static Builder builder() {
return new Builder();
@ -164,7 +165,8 @@ public class Transaction
final Optional<List<AccessListEntry>> maybeAccessList,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<BigInteger> v) {
final Optional<BigInteger> v,
final Optional<List<Hash>> versionedHashes) {
if (v.isPresent() && chainId.isPresent()) {
throw new IllegalArgumentException(
String.format("chainId '%s' and v '%s' cannot both be provided", chainId.get(), v.get()));
@ -210,6 +212,7 @@ public class Transaction
this.sender = sender;
this.chainId = chainId;
this.v = v;
this.versionedHashes = versionedHashes;
}
public Transaction(
@ -224,7 +227,8 @@ public class Transaction
final Bytes payload,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<BigInteger> v) {
final Optional<BigInteger> v,
final Optional<List<Hash>> versionedHashes) {
this(
TransactionType.FRONTIER,
nonce,
@ -239,7 +243,8 @@ public class Transaction
Optional.empty(),
sender,
chainId,
v);
v,
versionedHashes);
}
public Transaction(
@ -251,7 +256,8 @@ public class Transaction
final SECPSignature signature,
final Bytes payload,
final Optional<BigInteger> chainId,
final Optional<BigInteger> v) {
final Optional<BigInteger> v,
final Optional<List<Hash>> versionedHashes) {
this(
TransactionType.FRONTIER,
nonce,
@ -266,7 +272,8 @@ public class Transaction
Optional.empty(),
null,
chainId,
v);
v,
versionedHashes);
}
/**
@ -308,6 +315,7 @@ public class Transaction
payload,
sender,
chainId,
Optional.empty(),
Optional.empty());
}
@ -339,7 +347,8 @@ public class Transaction
final Bytes payload,
final Address sender,
final Optional<BigInteger> chainId,
final Optional<BigInteger> v) {
final Optional<BigInteger> v,
final Optional<List<Hash>> versionedHashes) {
this(
nonce,
Optional.of(gasPrice),
@ -352,7 +361,8 @@ public class Transaction
payload,
sender,
chainId,
v);
v,
versionedHashes);
}
/**
@ -699,6 +709,10 @@ public class Transaction
return this.transactionType;
}
public Optional<List<Hash>> getVersionedHashes() {
return this.versionedHashes;
}
/**
* Returns whether or not the transaction is a GoQuorum private transaction. <br>
* <br>
@ -980,6 +994,7 @@ public class Transaction
protected Optional<BigInteger> chainId = Optional.empty();
protected Optional<BigInteger> v = Optional.empty();
protected List<Hash> versionedHashes = null;
public Builder type(final TransactionType transactionType) {
this.transactionType = transactionType;
@ -1051,8 +1066,15 @@ public class Transaction
return this;
}
public Builder versionedHashes(final List<Hash> versionedHashes) {
this.versionedHashes = versionedHashes;
return this;
}
public Builder guessType() {
if (maxPriorityFeePerGas != null || maxFeePerGas != null) {
if (versionedHashes != null && !versionedHashes.isEmpty()) {
transactionType = TransactionType.BLOB;
} else if (maxPriorityFeePerGas != null || maxFeePerGas != null) {
transactionType = TransactionType.EIP1559;
} else if (accessList.isPresent()) {
transactionType = TransactionType.ACCESS_LIST;
@ -1082,7 +1104,8 @@ public class Transaction
accessList,
sender,
chainId,
v);
v,
Optional.ofNullable(versionedHashes));
}
public Transaction signAndBuild(final KeyPair keys) {

@ -292,6 +292,7 @@ public class GoQuorumBlockProcessor extends MainnetBlockProcessor {
// signature of the private transaction will not (and should not) be
// checked again.
transaction.getChainId(),
Optional.of(transaction.getV()));
Optional.of(transaction.getV()),
Optional.empty());
}
}

@ -378,6 +378,7 @@ public class MainnetTransactionProcessor {
.address(contractAddress)
.contract(contractAddress)
.inputData(Bytes.EMPTY)
.versionedHashes(transaction.getVersionedHashes())
.code(
contractCreationProcessor.getCodeFromEVM(
Hash.hash(initCodeBytes), initCodeBytes))
@ -392,6 +393,7 @@ public class MainnetTransactionProcessor {
.address(to)
.contract(to)
.inputData(transaction.getPayload())
.versionedHashes(transaction.getVersionedHashes())
.code(
maybeContract
.map(c -> messageCallProcessor.getCodeFromEVM(c.getCodeHash(), c.getCode()))

@ -245,6 +245,7 @@ public class MessageWrapperTest {
@JsonProperty("r") final String r,
@JsonProperty("s") final String s,
@JsonProperty("hash") final String __) {
super(
Bytes.fromHexStringLenient(nonce).toLong(),
Wei.fromHexString(gasPrice),
@ -258,6 +259,7 @@ public class MessageWrapperTest {
recIdAndChainId(Byte.decode(v)).getKey()),
Bytes.fromHexString(data),
recIdAndChainId(Byte.decode(v)).getValue(),
Optional.empty(),
Optional.empty());
}
}

@ -48,6 +48,7 @@ import org.hyperledger.besu.evm.operation.CodeSizeOperation;
import org.hyperledger.besu.evm.operation.CoinbaseOperation;
import org.hyperledger.besu.evm.operation.Create2Operation;
import org.hyperledger.besu.evm.operation.CreateOperation;
import org.hyperledger.besu.evm.operation.DataHashOperation;
import org.hyperledger.besu.evm.operation.DelegateCallOperation;
import org.hyperledger.besu.evm.operation.DifficultyOperation;
import org.hyperledger.besu.evm.operation.DivOperation;
@ -784,6 +785,7 @@ public class MainnetEVMs {
registry.put(new RelativeJumpVectorOperation(gasCalculator));
registry.put(new CallFOperation(gasCalculator));
registry.put(new RetFOperation(gasCalculator));
registry.put(new DataHashOperation(gasCalculator));
}
/**

@ -238,6 +238,7 @@ public class MessageFrame {
private Optional<Bytes> revertReason;
private final Map<String, Object> contextVariables;
private final Optional<List<Hash>> versionedHashes;
// Miscellaneous fields.
private Optional<ExceptionalHaltReason> exceptionalHaltReason = Optional.empty();
@ -279,7 +280,8 @@ public class MessageFrame {
final Optional<Bytes> revertReason,
final int maxStackSize,
final Set<Address> accessListWarmAddresses,
final Multimap<Address, Bytes32> accessListWarmStorage) {
final Multimap<Address, Bytes32> accessListWarmStorage,
final Optional<List<Hash>> versionedHashes) {
this.type = type;
this.messageFrameStack = messageFrameStack;
this.parentMessageFrame = messageFrameStack.peek();
@ -322,6 +324,7 @@ public class MessageFrame {
this.warmedUpAddresses.add(sender);
this.warmedUpAddresses.add(contract);
this.warmedUpStorage = HashMultimap.create(accessListWarmStorage);
this.versionedHashes = versionedHashes;
// the warmed up addresses will always be a superset of the address keys in the warmed up
// storage, so we can do both warm-ups in one pass
@ -1294,7 +1297,10 @@ public class MessageFrame {
return maybeUpdatedStorage;
}
/** Reset. */
public Optional<List<Hash>> getVersionedHashes() {
return versionedHashes;
}
public void reset() {
maybeUpdatedMemory = Optional.empty();
maybeUpdatedStorage = Optional.empty();
@ -1328,12 +1334,8 @@ public class MessageFrame {
private Set<Address> accessListWarmAddresses = emptySet();
private Multimap<Address, Bytes32> accessListWarmStorage = HashMultimap.create();
/**
* Sets Type.
*
* @param type the type
* @return the builder
*/
private Optional<List<Hash>> versionedHashes;
public Builder type(final Type type) {
this.type = type;
return this;
@ -1592,6 +1594,11 @@ public class MessageFrame {
return this;
}
public Builder versionedHashes(final Optional<List<Hash>> versionedHashes) {
this.versionedHashes = versionedHashes;
return this;
}
private void validate() {
checkState(type != null, "Missing message frame type");
checkState(messageFrameStack != null, "Missing message frame message frame stack");
@ -1645,7 +1652,8 @@ public class MessageFrame {
reason,
maxStackSize,
accessListWarmAddresses,
accessListWarmStorage);
accessListWarmStorage,
versionedHashes);
}
}
}

@ -0,0 +1,57 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.evm.operation;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
public class DataHashOperation extends AbstractOperation {
public static final int OPCODE = 0x49;
public DataHashOperation(final GasCalculator gasCalculator) {
super(OPCODE, "DATAHASH", 1, 1, gasCalculator);
}
@Override
public OperationResult execute(final MessageFrame frame, final EVM evm) {
int blobIndex = frame.popStackItem().toInt();
final Optional<List<Hash>> maybeHashes = frame.getVersionedHashes();
if (frame.getVersionedHashes().isPresent()) {
List<Hash> versionedHashes = maybeHashes.get();
if (blobIndex < versionedHashes.size()) {
Hash requested = versionedHashes.get(blobIndex);
frame.pushStackItem(requested);
} else {
frame.pushStackItem(Bytes.EMPTY);
}
} else {
frame.pushStackItem(Bytes.EMPTY);
}
return new OperationResult(3, null);
}
@Override
public boolean isVirtualOperation() {
return super.isVirtualOperation();
}
}

@ -0,0 +1,92 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.evm.operations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator;
import org.hyperledger.besu.evm.operation.DataHashOperation;
import org.hyperledger.besu.evm.operation.Operation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Test;
public class DataHashOperationTest {
@Test
public void putsHashOnStack() {
Hash version0Hash = Hash.fromHexStringLenient("0xcafebabeb0b0facedeadbeef");
List<Hash> versionedHashes = Arrays.asList(version0Hash);
DataHashOperation getHash = new DataHashOperation(new LondonGasCalculator());
MessageFrame frame = mock(MessageFrame.class);
when(frame.popStackItem()).thenReturn(Bytes.of(0));
when(frame.getVersionedHashes()).thenReturn(Optional.of(versionedHashes));
EVM fakeEVM = mock(EVM.class);
Operation.OperationResult r = getHash.execute(frame, fakeEVM);
assertThat(r.getGasCost()).isEqualTo(3);
assertThat(r.getHaltReason()).isEqualTo(null);
verify(frame).pushStackItem(version0Hash);
}
@Test
public void pushesZeroOnBloblessTx() {
EVM fakeEVM = mock(EVM.class);
DataHashOperation getHash = new DataHashOperation(new LondonGasCalculator());
MessageFrame frame = mock(MessageFrame.class);
when(frame.popStackItem()).thenReturn(Bytes.of(0));
when(frame.getVersionedHashes()).thenReturn(Optional.empty());
Operation.OperationResult failed1 = getHash.execute(frame, fakeEVM);
assertThat(failed1.getGasCost()).isEqualTo(3);
assertThat(failed1.getHaltReason()).isNull();
when(frame.popStackItem()).thenReturn(Bytes.of(0));
when(frame.getVersionedHashes()).thenReturn(Optional.of(new ArrayList<>()));
Operation.OperationResult failed2 = getHash.execute(frame, fakeEVM);
assertThat(failed2.getGasCost()).isEqualTo(3);
assertThat(failed2.getHaltReason()).isNull();
verify(frame, times(2)).pushStackItem(Bytes.EMPTY);
}
@Test
public void pushZeroOnVersionIndexOutOFBounds() {
Hash version0Hash = Hash.fromHexStringLenient("0xcafebabeb0b0facedeadbeef");
List<Hash> versionedHashes = Arrays.asList(version0Hash);
DataHashOperation getHash = new DataHashOperation(new LondonGasCalculator());
MessageFrame frame = mock(MessageFrame.class);
when(frame.popStackItem()).thenReturn(Bytes.of(1));
when(frame.getVersionedHashes()).thenReturn(Optional.of(versionedHashes));
EVM fakeEVM = mock(EVM.class);
Operation.OperationResult r = getHash.execute(frame, fakeEVM);
assertThat(r.getGasCost()).isEqualTo(3);
assertThat(r.getHaltReason()).isNull();
verify(frame).pushStackItem(Bytes.EMPTY);
}
}
Loading…
Cancel
Save