Skip to content

Commit

Permalink
Merge branch 'main' into qbft-rc-quorum
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew1001 authored Jan 31, 2025
2 parents 897bc6e + cf7d054 commit 8fd9bab
Show file tree
Hide file tree
Showing 25 changed files with 401 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ public BesuNode createExecutionEngineGenesisNode(final String name, final String
.jsonRpcTxPool()
.engineRpcEnabled(true)
.jsonRpcDebug()
.dataStorageConfiguration(DataStorageConfiguration.DEFAULT_BONSAI_CONFIG)
.build());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright contributors to Hyperledger Besu.
*
* 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.tests.acceptance.dsl.transaction.eth;

import static org.assertj.core.api.Assertions.assertThat;
import static org.web3j.protocol.core.DefaultBlockParameterName.LATEST;

import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;

import java.io.IOException;

import org.apache.tuweni.bytes.Bytes;
import org.web3j.protocol.core.methods.response.EthGetCode;

public class EthGetCodeTransaction implements Transaction<Bytes> {

private final Account account;

public EthGetCodeTransaction(final Account account) {
this.account = account;
}

@Override
public Bytes execute(final NodeRequests node) {
try {
final EthGetCode result = node.eth().ethGetCode(account.getAddress(), LATEST).send();
assertThat(result).isNotNull();
assertThat(result.hasError()).isFalse();

return Bytes.fromHexString(result.getCode());

} catch (final IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public EthGetBalanceTransaction getBalance(final Account account) {
return new EthGetBalanceTransaction(account);
}

public EthGetCodeTransaction getCode(final Account account) {
return new EthGetCodeTransaction(account);
}

public EthGetBalanceAtBlockTransaction getBalanceAtBlock(
final Account account, final BigInteger block) {
return new EthGetBalanceAtBlockTransaction(account, block);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class CodeDelegationTransactionAcceptanceTest extends AcceptanceTestBase
public static final Address SEND_ALL_ETH_CONTRACT_ADDRESS =
Address.fromHexStringStrict("0000000000000000000000000000000000009999");

public static final Address ALWAYS_REVERT_CONTRACT_ADDRESS =
Address.fromHexStringStrict("0000000000000000000000000000000000000666");

private final Account authorizer =
accounts.createAccount(
Address.fromHexStringStrict("8da48afC965480220a3dB9244771bd3afcB5d895"));
Expand Down Expand Up @@ -232,4 +235,124 @@ public void shouldCheckNonceAfterNonceIncreaseOfSender() throws IOException {
assertThat(otherAccountBalanceAfterFirstTx.add(BigInteger.ONE))
.isEqualTo(otherAccountBalanceAfterSecondTx);
}

/**
* EIP-7702 code delegation should be persisted even if the transaction that contains the
* authorization is reverted.
*/
@Test
public void shouldPersistCodeDelegationAfterRevert() throws IOException {
final long GAS_LIMIT = 1_000_000L;

// check the authorizer has no code before the transaction
final Bytes authorizerCodeBeforeCodeDelegation =
besuNode.execute(ethTransactions.getCode(authorizer));
assertThat(authorizerCodeBeforeCodeDelegation).isEqualTo(Bytes.EMPTY);

// valid 7702 code delegation to SEND_ALL_ETH_CONTRACT_ADDRESS
final CodeDelegation codeDelegation =
org.hyperledger.besu.ethereum.core.CodeDelegation.builder()
.chainId(BigInteger.valueOf(20211))
.nonce(0L)
.address(SEND_ALL_ETH_CONTRACT_ADDRESS)
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger())));

// the transaction will revert, because the to address is a contract that always reverts
final Transaction tx =
Transaction.builder()
.type(TransactionType.DELEGATE_CODE)
.chainId(BigInteger.valueOf(20211))
.nonce(0)
.maxPriorityFeePerGas(Wei.of(1_000_000_000))
.maxFeePerGas(Wei.fromHexString("0x02540BE400"))
.gasLimit(GAS_LIMIT)
.to(ALWAYS_REVERT_CONTRACT_ADDRESS)
.value(Wei.ZERO)
.payload(Bytes.EMPTY)
.codeDelegations(List.of(codeDelegation))
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(
TRANSACTION_SPONSOR_PRIVATE_KEY.toUnsignedBigInteger())));

// include the tx in the next block
final String txHash =
besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString()));
testHelper.buildNewBlock();

// check that the transaction was included and has indeed reverted
Optional<TransactionReceipt> maybeTransactionReceipt =
besuNode.execute(ethTransactions.getTransactionReceipt(txHash));
assertThat(maybeTransactionReceipt).isPresent();
assertThat(maybeTransactionReceipt.get().getStatus()).isEqualTo("0x0");

// check the authorizer has the code delegation after the transaction even though it has
// reverted
final Bytes expectedCode =
Bytes.concatenate(Bytes.fromHexString("ef0100"), SEND_ALL_ETH_CONTRACT_ADDRESS);
final Bytes authorizerCode = besuNode.execute(ethTransactions.getCode(authorizer));
assertThat(authorizerCode).isEqualTo(expectedCode);
}

/**
* EIP-7702 code delegation should be persisted even if the transaction that contains the
* authorization is reverted and the transaction sender is the same as the code delegation
* authorizer.
*/
@Test
public void shouldPersistCodeDelegationAfterRevertWhenSelfSponsored() throws IOException {
final long GAS_LIMIT = 1_000_000L;

// check the authorizer has no code before the transaction
final Bytes authorizerCodeBeforeCodeDelegation =
besuNode.execute(ethTransactions.getCode(authorizer));
assertThat(authorizerCodeBeforeCodeDelegation).isEqualTo(Bytes.EMPTY);

// valid 7702 code delegation to SEND_ALL_ETH_CONTRACT_ADDRESS
final CodeDelegation codeDelegation =
org.hyperledger.besu.ethereum.core.CodeDelegation.builder()
.chainId(BigInteger.valueOf(20211))
.nonce(1L)
.address(SEND_ALL_ETH_CONTRACT_ADDRESS)
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger())));

// the transaction will revert, because the to address is a contract that always reverts
final Transaction tx =
Transaction.builder()
.type(TransactionType.DELEGATE_CODE)
.chainId(BigInteger.valueOf(20211))
.nonce(0)
.maxPriorityFeePerGas(Wei.of(1_000_000_000))
.maxFeePerGas(Wei.fromHexString("0x02540BE400"))
.gasLimit(GAS_LIMIT)
.to(ALWAYS_REVERT_CONTRACT_ADDRESS)
.value(Wei.ZERO)
.payload(Bytes.EMPTY)
.codeDelegations(List.of(codeDelegation))
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger())));

// include the tx in the next block
final String txHash =
besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString()));
testHelper.buildNewBlock();

// check that the transaction was included and has indeed reverted
Optional<TransactionReceipt> maybeTransactionReceipt =
besuNode.execute(ethTransactions.getTransactionReceipt(txHash));
assertThat(maybeTransactionReceipt).isPresent();
assertThat(maybeTransactionReceipt.get().getStatus()).isEqualTo("0x0");

// check the authorizer has the code delegation after the transaction even though it has
// reverted
final Bytes expectedCode =
Bytes.concatenate(Bytes.fromHexString("ef0100"), SEND_ALL_ETH_CONTRACT_ADDRESS);
final Bytes authorizerCode = besuNode.execute(ethTransactions.getCode(authorizer));
assertThat(authorizerCode).isEqualTo(expectedCode);
}
}
7 changes: 7 additions & 0 deletions acceptance-tests/tests/src/test/resources/dev/dev_prague.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@
"privateKey": "11f2e7b6a734ab03fa682450e0d4681d18a944f8b83c99bf7b9b4de6c0f35ea1",
"balance": "90000000000000000000000"
},
"0x0000000000000000000000000000000000000666": {
"comment": "Contract reverts immediately when called",
"balance": "0",
"code": "5F5FFD",
"codeDecompiled": "PUSH0 PUSH0 REVERT",
"storage": {}
},
"0x0000000000000000000000000000000000009999": {
"comment": "Contract sends all its Ether to the address provided as a call data.",
"balance": "0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,25 @@ public static RequestType of(final int serializedTypeValue) {
case 0x01 -> WITHDRAWAL;
case 0x02 -> CONSOLIDATION;
default ->
throw new IllegalArgumentException(
throw new InvalidRequestTypeException(
String.format("Unsupported request type: 0x%02X", serializedTypeValue));
};
}

/**
* Exception thrown when an invalid request type is encountered.
*
* <p>This exception is thrown when a serialized type value does not correspond to any {@link
* RequestType}.
*/
public static class InvalidRequestTypeException extends IllegalArgumentException {
/**
* Constructs an {@link InvalidRequestTypeException} with the specified detail message.
*
* @param message the detail message.
*/
public InvalidRequestTypeException(final String message) {
super(message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,15 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext)
final Optional<List<Request>> maybeRequests;
try {
maybeRequests = extractRequests(maybeRequestsParam);
} catch (RuntimeException ex) {
} catch (RequestType.InvalidRequestTypeException ex) {
return respondWithInvalid(
reqId,
blockParam,
mergeCoordinator.getLatestValidAncestor(blockParam.getParentHash()).orElse(null),
INVALID,
"Invalid execution requests");
} catch (Exception ex) {
return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_EXECUTION_REQUESTS_PARAMS);
}

if (!getRequestsValidator(
Expand Down Expand Up @@ -591,14 +593,17 @@ private Optional<List<Request>> extractRequests(final Optional<List<String>> may
if (maybeRequestsParam.isEmpty()) {
return Optional.empty();
}

return maybeRequestsParam.map(
requests ->
requests.stream()
.map(
s -> {
final Bytes request = Bytes.fromHexString(s);
return new Request(RequestType.of(request.get(0)), request.slice(1));
final Bytes requestData = request.slice(1);
if (requestData.isEmpty()) {
throw new IllegalArgumentException("Request data cannot be empty");
}
return new Request(RequestType.of(request.get(0)), requestData);
})
.collect(Collectors.toList()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,20 @@ public org.hyperledger.besu.datatypes.CodeDelegation build() {
return new CodeDelegation(chainId, address, nonce, signature);
}
}

@Override
public String toString() {
return "CodeDelegation{"
+ "chainId="
+ chainId
+ ", address="
+ address
+ ", nonce="
+ nonce
+ ", signature="
+ signature
+ ", authorizerSupplier="
+ authorizerSupplier
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.BlockProcessingOutputs;
Expand Down Expand Up @@ -191,6 +192,7 @@ protected BlockProcessingResult processBlock(
}

blockUpdater.commit();
blockUpdater.markTransactionBoundary();

currentGasUsed += transaction.getGasLimit() - transactionProcessingResult.getGasRemaining();
if (transaction.getVersionedHashes().isPresent()) {
Expand Down Expand Up @@ -250,6 +252,19 @@ protected BlockProcessingResult processBlock(
maybeRequests = Optional.of(requestProcessor.get().process(context));
}

if (maybeRequests.isPresent() && blockHeader.getRequestsHash().isPresent()) {
Hash calculatedRequestHash = BodyValidation.requestsHash(maybeRequests.get());
Hash headerRequestsHash = blockHeader.getRequestsHash().get();
if (!calculatedRequestHash.equals(headerRequestsHash)) {
return new BlockProcessingResult(
Optional.empty(),
"Requests hash mismatch, calculated: "
+ calculatedRequestHash.toHexString()
+ " header: "
+ headerRequestsHash.toHexString());
}
}

if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) {
// no need to log, rewardCoinbase logs the error.
if (worldState instanceof BonsaiWorldState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ private void processCodeDelegation(
MutableAccount authority;
boolean authorityDoesAlreadyExist = false;
if (maybeAuthorityAccount.isEmpty()) {
// only create an account if nonce is valid
if (codeDelegation.nonce() != 0) {
return;
}
authority = evmWorldUpdater.createAccount(authorizer.get());
} else {
authority = maybeAuthorityAccount.get();
Expand All @@ -146,7 +150,7 @@ private void processCodeDelegation(
}

if (authorityDoesAlreadyExist) {
result.incremenentAlreadyExistingDelegators();
result.incrementAlreadyExistingDelegators();
}

evmWorldUpdater
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void addAccessedDelegatorAddress(final Address address) {
accessedDelegatorAddresses.add(address);
}

public void incremenentAlreadyExistingDelegators() {
public void incrementAlreadyExistingDelegators() {
alreadyExistingDelegators += 1;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ public void processBlockHashes(
super.processBlockHashes(mutableWorldState, currentBlockHeader);

WorldUpdater worldUpdater = mutableWorldState.updater();
final MutableAccount historyStorageAccount = worldUpdater.getOrCreate(historyStorageAddress);
final MutableAccount historyStorageAccount = worldUpdater.getAccount(historyStorageAddress);

if (currentBlockHeader.getNumber() > 0) {
if (historyStorageAccount != null
&& historyStorageAccount.getNonce() > 0
&& currentBlockHeader.getNumber() > 0) {
storeParentHash(historyStorageAccount, currentBlockHeader);
}
worldUpdater.commit();
Expand Down
Loading

0 comments on commit 8fd9bab

Please sign in to comment.