7702 for devenet-3 (#7444)

* wrapped WorldUpdater into `EVMWorldupdater` to remove the authority code injection from the implementation of the actual world updaters

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* add CHANGELOG entry

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* first draft for 7702 v2

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* change return value of DelegatedCodeGasCostHelper

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* fix upfront gas cost calculation, fix setting code multiple times in MutableDelegatedCodeAccount

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* fix gas refund for delegated code when account already exists, added gas cost deduction for code delegation resolution to ExtCodeSizeOperation

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* allow accounts with delegated code to send transactions

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* add refund for already existing account after nonce check

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* resolve delegated code only the first time to avoid delegation loops

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* handle invalid authorization signatures properly

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* refactored CodeDelegationProcessor to compute authorizer of a code delegation after the chain id check

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* fix canSetDelegatedCode method by checking code how it is in the trie and not the resolved code

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* optimize code hash calculation for empty code, fix check for empty code delegation list

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* check the all code delegation signatures hava a valid s value

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* rename encoder & decoder, handle invalid signature values in T8nExecutor

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* create the signatures for code delegation authorizations in T8nExecutor without checking if they are valid to test them later during the tx execution

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* check that recid is either 0 or 1

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* fixed acceptance tests, renamed the the remaining instances of set code to code delegation

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* fix delegate encoder & encoder unit tests

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* spotless

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* changed empty check for delegated accounts, fixed test

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* fix hasDelegatedCode method when code is null

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* run acceptance tests without deamon

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* disable CodeDelegationTransactionAcceptanceTest to check if it is causing the stuck ci pipeline

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* check if shouldTransferAllEthOfAuthorizerToSponsor is causing pipeline to stall

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* check if shouldCheckNonceAfterNonceIncreaseOfSender is causing pipeline to stall

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* check if closing the cluster after every test is causing pipeline to stall

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

* spotless

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>

---------

Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net>
Co-authored-by: Danno Ferrin <danno@numisight.com>
pull/7586/head
daniellehrner 3 months ago committed by GitHub
parent cf592c48d1
commit 8eee569887
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 66
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.java
  2. 2
      besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java
  3. 2
      crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java
  4. 15
      datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java
  5. 8
      datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java
  6. 19
      datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java
  7. 10
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java
  8. 76
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java
  9. 63
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java
  10. 32
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java
  11. 43
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java
  12. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java
  13. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java
  14. 88
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java
  15. 134
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java
  16. 41
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java
  17. 4
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java
  18. 50
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java
  19. 51
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java
  20. 1
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java
  21. 2
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java
  22. 2
      ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java
  23. 44
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java
  24. 41
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java
  25. 2
      ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java
  26. 2
      ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java
  27. 2
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java
  28. 2
      ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java
  29. 45
      ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java
  30. 31
      evm/src/main/java/org/hyperledger/besu/evm/account/Account.java
  31. 92
      evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java
  32. 54
      evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java
  33. 57
      evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java
  34. 27
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java
  35. 25
      evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java
  36. 11
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java
  37. 13
      evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java
  38. 12
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java
  39. 18
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java
  40. 13
      evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java
  41. 116
      evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java
  42. 80
      evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java
  43. 97
      evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java
  44. 34
      evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java

@ -18,9 +18,9 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.SetCodeAuthorization;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
@ -39,7 +39,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
public class CodeDelegationTransactionAcceptanceTest extends AcceptanceTestBase {
private static final String GENESIS_FILE = "/dev/dev_prague.json";
private static final SECP256K1 secp256k1 = new SECP256K1();
@ -74,7 +74,6 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
@AfterEach
void tearDown() {
besuNode.close();
cluster.close();
}
/**
@ -88,17 +87,18 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException {
// 7702 transaction
final org.hyperledger.besu.datatypes.SetCodeAuthorization authorization =
SetCodeAuthorization.builder()
final CodeDelegation authorization =
org.hyperledger.besu.ethereum.core.CodeDelegation.builder()
.chainId(BigInteger.valueOf(20211))
.address(SEND_ALL_ETH_CONTRACT_ADDRESS)
.nonce(0)
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger())));
final Transaction tx =
Transaction.builder()
.type(TransactionType.SET_CODE)
.type(TransactionType.DELEGATE_CODE)
.chainId(BigInteger.valueOf(20211))
.nonce(0)
.maxPriorityFeePerGas(Wei.of(1000000000))
@ -108,7 +108,7 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
.value(Wei.ZERO)
.payload(Bytes32.leftPad(Bytes.fromHexString(transactionSponsor.getAddress())))
.accessList(List.of())
.setCodeTransactionPayloads(List.of(authorization))
.codeDelegations(List.of(authorization))
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(
@ -134,22 +134,21 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
/**
* The authorizer creates an authorization for a contract that sends all its ETH to any given
* address. But the nonce is 1 and the authorization list is processed before the nonce increase
* of the sender. Therefore, the authorization should be invalid and will be ignored. No balance
* change, except for a decrease for paying the transaction cost should occur.
* address. The nonce is 1 and the authorization list is processed after the nonce increase of the
* sender. Therefore, the authorization should be valid. The authorizer balance should be 0 and
* the transaction sponsor's * balance should be 180000 ETH minus the transaction costs.
*/
@Test
public void shouldCheckNonceBeforeNonceIncreaseOfSender() throws IOException {
public void shouldCheckNonceAfterNonceIncreaseOfSender() throws IOException {
final long GAS_LIMIT = 1000000L;
cluster.verify(authorizer.balanceEquals(Amount.ether(90000)));
final org.hyperledger.besu.datatypes.SetCodeAuthorization authorization =
SetCodeAuthorization.builder()
final CodeDelegation authorization =
org.hyperledger.besu.ethereum.core.CodeDelegation.builder()
.chainId(BigInteger.valueOf(20211))
.nonces(
Optional.of(
1L)) // nonce is 1, but because it is validated before the nonce increase, it
// should be 0
.nonce(
1L) // nonce is 1, but because it is validated before the nonce increase, it should
// be 0
.address(SEND_ALL_ETH_CONTRACT_ADDRESS)
.signAndBuild(
secp256k1.createKeyPair(
@ -157,17 +156,17 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
final Transaction tx =
Transaction.builder()
.type(TransactionType.SET_CODE)
.type(TransactionType.DELEGATE_CODE)
.chainId(BigInteger.valueOf(20211))
.nonce(0)
.maxPriorityFeePerGas(Wei.of(1000000000))
.maxFeePerGas(Wei.fromHexString("0x02540BE400"))
.gasLimit(1000000)
.gasLimit(GAS_LIMIT)
.to(Address.fromHexStringStrict(authorizer.getAddress()))
.value(Wei.ZERO)
.payload(Bytes32.leftPad(Bytes.fromHexString(otherAccount.getAddress())))
.accessList(List.of())
.setCodeTransactionPayloads(List.of(authorization))
.codeDelegations(List.of(authorization))
.signAndBuild(
secp256k1.createKeyPair(
secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger())));
@ -180,14 +179,25 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase {
besuNode.execute(ethTransactions.getTransactionReceipt(txHash));
assertThat(maybeTransactionReceipt).isPresent();
// verify that the balance of the other account has not changed
cluster.verify(otherAccount.balanceEquals(0));
final String gasPriceWithout0x =
maybeTransactionReceipt.get().getEffectiveGasPrice().substring(2);
final BigInteger txCost =
maybeTransactionReceipt.get().getGasUsed().multiply(new BigInteger(gasPriceWithout0x, 16));
BigInteger expectedSenderBalance = new BigInteger("90000000000000000000000").subtract(txCost);
cluster.verify(authorizer.balanceEquals(Amount.wei(expectedSenderBalance)));
final BigInteger gasPrice = new BigInteger(gasPriceWithout0x, 16);
final BigInteger txCost = maybeTransactionReceipt.get().getGasUsed().multiply(gasPrice);
final BigInteger authorizerBalance = besuNode.execute(ethTransactions.getBalance(authorizer));
// The remaining balance of the authorizer should the gas limit multiplied by the gas price
// minus the transaction cost.
// The following executes this calculation in reverse.
assertThat(GAS_LIMIT).isEqualTo(authorizerBalance.add(txCost).divide(gasPrice).longValue());
// The other accounts balance should be the initial 9000 ETH balance from the authorizer minus
// the remaining balance of the authorizer and minus the transaction cost
cluster.verify(
otherAccount.balanceEquals(
Amount.wei(
new BigInteger("90000000000000000000000")
.subtract(authorizerBalance)
.subtract(txCost))));
}
}

@ -413,7 +413,7 @@ public class TransactionPoolOptionsTest
@Test
public void maxPrioritizedTxsPerTypeWrongTxType() {
internalTestFailure(
"Invalid value for option '--tx-pool-max-prioritized-by-type' (MAP<TYPE,INTEGER>): expected one of [FRONTIER, ACCESS_LIST, EIP1559, BLOB, SET_CODE] (case-insensitive) but was 'WRONG_TYPE'",
"Invalid value for option '--tx-pool-max-prioritized-by-type' (MAP<TYPE,INTEGER>): expected one of [FRONTIER, ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE] (case-insensitive) but was 'WRONG_TYPE'",
"--tx-pool-max-prioritized-by-type",
"WRONG_TYPE=1");
}

@ -54,7 +54,7 @@ public class SECPSignature {
* @param s the s
* @param recId the rec id
*/
SECPSignature(final BigInteger r, final BigInteger s, final byte recId) {
public SECPSignature(final BigInteger r, final BigInteger s, final byte recId) {
this.r = r;
this.s = s;
this.recId = recId;

@ -20,10 +20,13 @@ import java.math.BigInteger;
import java.util.Optional;
/**
* SetCodeAuthorization is a data structure that represents the authorization to set code on a EOA
* account.
* CodeDelegation is a data structure that represents the authorization to delegate code of an EOA
* account to another account.
*/
public interface SetCodeAuthorization {
public interface CodeDelegation {
/** The cost of delegating code on an existing account. */
long PER_AUTH_BASE_COST = 2_500L;
/**
* Return the chain id.
*
@ -53,11 +56,11 @@ public interface SetCodeAuthorization {
Optional<Address> authorizer();
/**
* Return a valid nonce or empty otherwise. A nonce is valid if the size of the list is exactly 1
* Return the nonce
*
* @return all the optional nonce
* @return the nonce
*/
Optional<Long> nonce();
long nonce();
/**
* Return the recovery id.

@ -236,16 +236,16 @@ public interface Transaction {
int getSize();
/**
* Returns the set code transaction payload if this transaction is a 7702 transaction.
* Returns the code delegations if this transaction is a 7702 transaction.
*
* @return the set code transaction payloads
* @return the code delegations
*/
Optional<List<SetCodeAuthorization>> getAuthorizationList();
Optional<List<CodeDelegation>> getCodeDelegationList();
/**
* Returns the size of the authorization list.
*
* @return the size of the authorization list
*/
int authorizationListSize();
int codeDelegationListSize();
}

@ -29,10 +29,10 @@ public enum TransactionType {
/** Blob transaction type. */
BLOB(0x03),
/** Eip7702 transaction type. */
SET_CODE(0x04);
DELEGATE_CODE(0x04);
private static final Set<TransactionType> ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES =
Set.of(ACCESS_LIST, EIP1559, BLOB, SET_CODE);
Set.of(ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE);
private static final EnumSet<TransactionType> LEGACY_FEE_MARKET_TRANSACTION_TYPES =
EnumSet.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST);
@ -86,7 +86,7 @@ public enum TransactionType {
TransactionType.ACCESS_LIST,
TransactionType.EIP1559,
TransactionType.BLOB,
TransactionType.SET_CODE
TransactionType.DELEGATE_CODE
})
.filter(transactionType -> transactionType.typeValue == serializedTypeValue)
.findFirst()
@ -132,12 +132,21 @@ public enum TransactionType {
return this.equals(BLOB);
}
/**
* Does transaction type support delegate code.
*
* @return the boolean
*/
public boolean supportsDelegateCode() {
return this.equals(DELEGATE_CODE);
}
/**
* Does transaction type require code.
*
* @return the boolean
*/
public boolean requiresSetCode() {
return this.equals(SET_CODE);
public boolean requiresCodeDelegation() {
return this.equals(DELEGATE_CODE);
}
}

@ -15,7 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.SetCodeAuthorization;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
@ -94,7 +94,7 @@ public class TransactionCompleteResult implements TransactionResult {
private final List<VersionedHash> versionedHashes;
@JsonInclude(JsonInclude.Include.NON_NULL)
private final List<SetCodeAuthorization> authorizationList;
private final List<CodeDelegation> authorizationList;
public TransactionCompleteResult(final TransactionWithMetadata tx) {
final Transaction transaction = tx.getTransaction();
@ -131,7 +131,7 @@ public class TransactionCompleteResult implements TransactionResult {
this.v =
(transactionType == TransactionType.ACCESS_LIST
|| transactionType == TransactionType.EIP1559)
|| transactionType == TransactionType.SET_CODE
|| transactionType == TransactionType.DELEGATE_CODE
? Quantity.create(transaction.getYParity())
: null;
}
@ -139,7 +139,7 @@ public class TransactionCompleteResult implements TransactionResult {
this.r = Quantity.create(transaction.getR());
this.s = Quantity.create(transaction.getS());
this.versionedHashes = transaction.getVersionedHashes().orElse(null);
this.authorizationList = transaction.getAuthorizationList().orElse(null);
this.authorizationList = transaction.getCodeDelegationList().orElse(null);
}
@JsonGetter(value = "accessList")
@ -255,7 +255,7 @@ public class TransactionCompleteResult implements TransactionResult {
}
@JsonGetter(value = "authorizationList")
public List<SetCodeAuthorization> getAuthorizationList() {
public List<CodeDelegation> getAuthorizationList() {
return authorizationList;
}
}

@ -20,11 +20,10 @@ import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.core.encoding.SetCodeTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.CodeDelegationEncoder;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
@ -33,7 +32,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Suppliers;
import org.apache.tuweni.bytes.Bytes;
public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetCodeAuthorization {
public class CodeDelegation implements org.hyperledger.besu.datatypes.CodeDelegation {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
@ -41,7 +40,7 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
private final BigInteger chainId;
private final Address address;
private final Optional<Long> nonce;
private final long nonce;
private final SECPSignature signature;
private Optional<Address> authorizer = Optional.empty();
private boolean isAuthorityComputed = false;
@ -51,13 +50,13 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
*
* @param chainId can be either the current chain id or zero
* @param address the address from which the code will be set into the EOA account
* @param nonce an optional nonce after which this auth expires
* @param nonce the nonce after which this auth expires
* @param signature the signature of the EOA account which will be used to set the code
*/
public SetCodeAuthorization(
public CodeDelegation(
final BigInteger chainId,
final Address address,
final Optional<Long> nonce,
final long nonce,
final SECPSignature signature) {
this.chainId = chainId;
this.address = address;
@ -66,29 +65,26 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
}
/**
* Create access list entry.
* Create code delegation.
*
* @param chainId can be either the current chain id or zero
* @param address the address from which the code will be set into the EOA account
* @param nonces the list of nonces
* @param nonce the nonce
* @param v the recovery id
* @param r the r value of the signature
* @param s the s value of the signature
* @return SetCodeTransactionEntry
* @return CodeDelegation
*/
@JsonCreator
public static org.hyperledger.besu.datatypes.SetCodeAuthorization createSetCodeAuthorizationEntry(
public static org.hyperledger.besu.datatypes.CodeDelegation createCodeDelegation(
@JsonProperty("chainId") final BigInteger chainId,
@JsonProperty("address") final Address address,
@JsonProperty("nonce") final List<Long> nonces,
@JsonProperty("nonce") final long nonce,
@JsonProperty("v") final byte v,
@JsonProperty("r") final BigInteger r,
@JsonProperty("s") final BigInteger s) {
return new SetCodeAuthorization(
chainId,
address,
Optional.ofNullable(nonces.get(0)),
SIGNATURE_ALGORITHM.get().createSignature(r, s, v));
return new CodeDelegation(
chainId, address, nonce, SIGNATURE_ALGORITHM.get().createSignature(r, s, v));
}
@JsonProperty("chainId")
@ -120,7 +116,7 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
}
@Override
public Optional<Long> nonce() {
public long nonce() {
return nonce;
}
@ -144,30 +140,38 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
private Optional<Address> computeAuthority() {
BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput();
SetCodeTransactionEncoder.encodeSingleSetCodeWithoutSignature(this, rlpOutput);
CodeDelegationEncoder.encodeSingleCodeDelegationWithoutSignature(this, rlpOutput);
final Hash hash = Hash.hash(Bytes.concatenate(MAGIC, rlpOutput.encoded()));
return SIGNATURE_ALGORITHM
Optional<Address> authorityAddress;
try {
authorityAddress =
SIGNATURE_ALGORITHM
.get()
.recoverPublicKeyFromSignature(hash, signature)
.map(Address::extract);
} catch (final IllegalArgumentException e) {
authorityAddress = Optional.empty();
}
return authorityAddress;
}
/**
* Create set code authorization with a builder.
* Create a code delegation authorization with a builder.
*
* @return SetCodeAuthorization.Builder
* @return CodeDelegation.Builder
*/
public static Builder builder() {
return new Builder();
}
/** Builder for SetCodeAuthorization. */
/** Builder for CodeDelegation authorizations. */
public static class Builder {
private BigInteger chainId = BigInteger.ZERO;
private Address address;
private Optional<Long> nonce = Optional.empty();
private Long nonce;
private SECPSignature signature;
/** Create a new builder. */
@ -196,12 +200,12 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
}
/**
* Set the optional nonce.
* Set the nonce.
*
* @param nonce the optional nonce.
* @param nonce the nonce.
* @return this builder
*/
public Builder nonces(final Optional<Long> nonce) {
public Builder nonce(final long nonce) {
this.nonce = nonce;
return this;
}
@ -221,16 +225,14 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
* Sign the authorization with the given key pair and return the authorization.
*
* @param keyPair the key pair
* @return SetCodeAuthorization
* @return CodeDelegation
*/
public org.hyperledger.besu.datatypes.SetCodeAuthorization signAndBuild(final KeyPair keyPair) {
public org.hyperledger.besu.datatypes.CodeDelegation signAndBuild(final KeyPair keyPair) {
final BytesValueRLPOutput output = new BytesValueRLPOutput();
output.startList();
output.writeBigIntegerScalar(chainId);
output.writeBytes(address);
output.startList();
nonce.ifPresent(output::writeLongScalar);
output.endList();
output.writeLongScalar(nonce);
output.endList();
signature(
@ -243,18 +245,22 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC
/**
* Build the authorization.
*
* @return SetCodeAuthorization
* @return CodeDelegation
*/
public org.hyperledger.besu.datatypes.SetCodeAuthorization build() {
public org.hyperledger.besu.datatypes.CodeDelegation build() {
if (address == null) {
throw new IllegalStateException("Address must be set");
}
if (nonce == null) {
throw new IllegalStateException("Nonce must be set");
}
if (signature == null) {
throw new IllegalStateException("Signature must be set");
}
return new SetCodeAuthorization(chainId, address, nonce, signature);
return new CodeDelegation(chainId, address, nonce, signature);
}
}
}

@ -28,18 +28,18 @@ import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.KZGProof;
import org.hyperledger.besu.datatypes.SetCodeAuthorization;
import org.hyperledger.besu.datatypes.Sha256Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.BlobTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.CodeDelegationEncoder;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.SetCodeTransactionEncoder;
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder;
import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
@ -124,7 +124,7 @@ public class Transaction
private final Optional<List<VersionedHash>> versionedHashes;
private final Optional<BlobsWithCommitments> blobsWithCommitments;
private final Optional<List<SetCodeAuthorization>> maybeAuthorizationList;
private final Optional<List<CodeDelegation>> maybeCodeDelegationList;
public static Builder builder() {
return new Builder();
@ -181,7 +181,7 @@ public class Transaction
final Optional<BigInteger> chainId,
final Optional<List<VersionedHash>> versionedHashes,
final Optional<BlobsWithCommitments> blobsWithCommitments,
final Optional<List<SetCodeAuthorization>> maybeAuthorizationList) {
final Optional<List<CodeDelegation>> maybeCodeDelegationList) {
if (!forCopy) {
if (transactionType.requiresChainId()) {
@ -218,10 +218,10 @@ public class Transaction
maxFeePerBlobGas.isPresent(), "Must specify max fee per blob gas for blob transaction");
}
if (transactionType.requiresSetCode()) {
if (transactionType.requiresCodeDelegation()) {
checkArgument(
maybeAuthorizationList.isPresent(),
"Must specify set code transaction payload for set code transaction");
maybeCodeDelegationList.isPresent(),
"Must specify code delegation authorizations for code delegation transaction");
}
}
@ -241,7 +241,7 @@ public class Transaction
this.chainId = chainId;
this.versionedHashes = versionedHashes;
this.blobsWithCommitments = blobsWithCommitments;
this.maybeAuthorizationList = maybeAuthorizationList;
this.maybeCodeDelegationList = maybeCodeDelegationList;
}
/**
@ -473,7 +473,7 @@ public class Transaction
payload,
maybeAccessList,
versionedHashes.orElse(null),
maybeAuthorizationList,
maybeCodeDelegationList,
chainId);
}
return hashNoSignature;
@ -681,13 +681,13 @@ public class Transaction
}
@Override
public Optional<List<SetCodeAuthorization>> getAuthorizationList() {
return maybeAuthorizationList;
public Optional<List<CodeDelegation>> getCodeDelegationList() {
return maybeCodeDelegationList;
}
@Override
public int authorizationListSize() {
return maybeAuthorizationList.map(List::size).orElse(0);
public int codeDelegationListSize() {
return maybeCodeDelegationList.map(List::size).orElse(0);
}
/**
@ -714,7 +714,7 @@ public class Transaction
final Bytes payload,
final Optional<List<AccessListEntry>> accessList,
final List<VersionedHash> versionedHashes,
final Optional<List<SetCodeAuthorization>> authorizationList,
final Optional<List<CodeDelegation>> codeDelegationList,
final Optional<BigInteger> chainId) {
if (transactionType.requiresChainId()) {
checkArgument(chainId.isPresent(), "Transaction type %s requires chainId", transactionType);
@ -759,8 +759,8 @@ public class Transaction
new IllegalStateException(
"Developer error: the transaction should be guaranteed to have an access list here")),
chainId);
case SET_CODE ->
setCodePreimage(
case DELEGATE_CODE ->
codeDelegationPreimage(
nonce,
maxPriorityFeePerGas,
maxFeePerGas,
@ -770,10 +770,10 @@ public class Transaction
payload,
chainId,
accessList,
authorizationList.orElseThrow(
codeDelegationList.orElseThrow(
() ->
new IllegalStateException(
"Developer error: the transaction should be guaranteed to have a set code payload here")));
"Developer error: the transaction should be guaranteed to have a code delegations here")));
};
return keccak256(preimage);
}
@ -911,7 +911,7 @@ public class Transaction
return Bytes.concatenate(Bytes.of(TransactionType.ACCESS_LIST.getSerializedType()), encode);
}
private static Bytes setCodePreimage(
private static Bytes codeDelegationPreimage(
final long nonce,
final Wei maxPriorityFeePerGas,
final Wei maxFeePerGas,
@ -921,7 +921,7 @@ public class Transaction
final Bytes payload,
final Optional<BigInteger> chainId,
final Optional<List<AccessListEntry>> accessList,
final List<SetCodeAuthorization> authorizationList) {
final List<CodeDelegation> authorizationList) {
final Bytes encoded =
RLP.encode(
rlpOutput -> {
@ -937,10 +937,10 @@ public class Transaction
chainId,
accessList,
rlpOutput);
SetCodeTransactionEncoder.encodeSetCodeInner(authorizationList, rlpOutput);
CodeDelegationEncoder.encodeCodeDelegationInner(authorizationList, rlpOutput);
rlpOutput.endList();
});
return Bytes.concatenate(Bytes.of(TransactionType.SET_CODE.getSerializedType()), encoded);
return Bytes.concatenate(Bytes.of(TransactionType.DELEGATE_CODE.getSerializedType()), encoded);
}
@Override
@ -1111,7 +1111,7 @@ public class Transaction
chainId,
detachedVersionedHashes,
detachedBlobsWithCommitments,
maybeAuthorizationList);
maybeCodeDelegationList);
// copy also the computed fields, to avoid to recompute them
copiedTx.sender = this.sender;
@ -1179,7 +1179,7 @@ public class Transaction
protected Optional<BigInteger> v = Optional.empty();
protected List<VersionedHash> versionedHashes = null;
private BlobsWithCommitments blobsWithCommitments;
protected Optional<List<SetCodeAuthorization>> setCodeTransactionPayloads = Optional.empty();
protected Optional<List<CodeDelegation>> codeDelegationAuthorizations = Optional.empty();
public Builder copiedFrom(final Transaction toCopy) {
this.transactionType = toCopy.transactionType;
@ -1198,7 +1198,7 @@ public class Transaction
this.chainId = toCopy.chainId;
this.versionedHashes = toCopy.versionedHashes.orElse(null);
this.blobsWithCommitments = toCopy.blobsWithCommitments.orElse(null);
this.setCodeTransactionPayloads = toCopy.maybeAuthorizationList;
this.codeDelegationAuthorizations = toCopy.maybeCodeDelegationList;
return this;
}
@ -1292,8 +1292,8 @@ public class Transaction
transactionType = TransactionType.EIP1559;
} else if (accessList.isPresent()) {
transactionType = TransactionType.ACCESS_LIST;
} else if (setCodeTransactionPayloads.isPresent()) {
transactionType = TransactionType.SET_CODE;
} else if (codeDelegationAuthorizations.isPresent()) {
transactionType = TransactionType.DELEGATE_CODE;
} else {
transactionType = TransactionType.FRONTIER;
}
@ -1324,7 +1324,7 @@ public class Transaction
chainId,
Optional.ofNullable(versionedHashes),
Optional.ofNullable(blobsWithCommitments),
setCodeTransactionPayloads);
codeDelegationAuthorizations);
}
public Transaction signAndBuild(final KeyPair keys) {
@ -1351,7 +1351,7 @@ public class Transaction
payload,
accessList,
versionedHashes,
setCodeTransactionPayloads,
codeDelegationAuthorizations,
chainId),
keys);
}
@ -1376,9 +1376,8 @@ public class Transaction
return this;
}
public Builder setCodeTransactionPayloads(
final List<SetCodeAuthorization> setCodeTransactionEntries) {
this.setCodeTransactionPayloads = Optional.ofNullable(setCodeTransactionEntries);
public Builder codeDelegations(final List<CodeDelegation> codeDelegations) {
this.codeDelegationAuthorizations = Optional.ofNullable(codeDelegations);
return this;
}
}

@ -17,7 +17,7 @@ package org.hyperledger.besu.ethereum.core.encoding;
import static org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder.writeAccessList;
import static org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder.writeSignatureAndRecoveryId;
import org.hyperledger.besu.datatypes.SetCodeAuthorization;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPOutput;
@ -25,28 +25,28 @@ import java.util.List;
import org.apache.tuweni.bytes.Bytes;
public class SetCodeTransactionEncoder {
public class CodeDelegationEncoder {
private SetCodeTransactionEncoder() {
private CodeDelegationEncoder() {
// private constructor
}
public static void encodeSetCodeInner(
final List<SetCodeAuthorization> payloads, final RLPOutput rlpOutput) {
public static void encodeCodeDelegationInner(
final List<CodeDelegation> payloads, final RLPOutput rlpOutput) {
rlpOutput.startList();
payloads.forEach(payload -> encodeSingleSetCode(payload, rlpOutput));
payloads.forEach(payload -> encodeSingleCodeDelegation(payload, rlpOutput));
rlpOutput.endList();
}
public static void encodeSingleSetCodeWithoutSignature(
final SetCodeAuthorization payload, final RLPOutput rlpOutput) {
public static void encodeSingleCodeDelegationWithoutSignature(
final CodeDelegation payload, final RLPOutput rlpOutput) {
rlpOutput.startList();
encodeAuthorizationDetails(payload, rlpOutput);
rlpOutput.endList();
}
public static void encodeSingleSetCode(
final SetCodeAuthorization payload, final RLPOutput rlpOutput) {
public static void encodeSingleCodeDelegation(
final CodeDelegation payload, final RLPOutput rlpOutput) {
rlpOutput.startList();
encodeAuthorizationDetails(payload, rlpOutput);
rlpOutput.writeIntScalar(payload.signature().getRecId());
@ -56,12 +56,10 @@ public class SetCodeTransactionEncoder {
}
private static void encodeAuthorizationDetails(
final SetCodeAuthorization payload, final RLPOutput rlpOutput) {
final CodeDelegation payload, final RLPOutput rlpOutput) {
rlpOutput.writeBigIntegerScalar(payload.chainId());
rlpOutput.writeBytes(payload.address().copy());
rlpOutput.startList();
payload.nonce().ifPresent(rlpOutput::writeLongScalar);
rlpOutput.endList();
rlpOutput.writeLongScalar(payload.nonce());
}
public static void encode(final Transaction transaction, final RLPOutput out) {
@ -75,13 +73,13 @@ public class SetCodeTransactionEncoder {
out.writeUInt256Scalar(transaction.getValue());
out.writeBytes(transaction.getPayload());
writeAccessList(out, transaction.getAccessList());
encodeSetCodeInner(
encodeCodeDelegationInner(
transaction
.getAuthorizationList()
.getCodeDelegationList()
.orElseThrow(
() ->
new IllegalStateException(
"Developer error: the transaction should be guaranteed to have a set code payload here")),
"Developer error: the transaction should be guaranteed to have a code delegation authorizations here")),
out);
writeSignatureAndRecoveryId(transaction, out);
out.endList();

@ -19,23 +19,22 @@ import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.core.SetCodeAuthorization;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import java.math.BigInteger;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
public class SetCodeTransactionDecoder {
public class CodeDelegationTransactionDecoder {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
private SetCodeTransactionDecoder() {
private CodeDelegationTransactionDecoder() {
// private constructor
}
@ -44,7 +43,7 @@ public class SetCodeTransactionDecoder {
final BigInteger chainId = input.readBigIntegerScalar();
final Transaction.Builder builder =
Transaction.builder()
.type(TransactionType.SET_CODE)
.type(TransactionType.DELEGATE_CODE)
.chainId(chainId)
.nonce(input.readLongScalar())
.maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar()))
@ -64,10 +63,7 @@ public class SetCodeTransactionDecoder {
accessListEntryRLPInput.leaveList();
return accessListEntry;
}))
.setCodeTransactionPayloads(
input.readList(
setCodeTransactionPayloadsRLPInput ->
decodeInnerPayload(setCodeTransactionPayloadsRLPInput)));
.codeDelegations(input.readList(CodeDelegationTransactionDecoder::decodeInnerPayload));
final byte recId = (byte) input.readUnsignedByteScalar();
final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger();
@ -75,33 +71,15 @@ public class SetCodeTransactionDecoder {
input.leaveList();
final Transaction transaction =
builder.signature(SIGNATURE_ALGORITHM.get().createSignature(r, s, recId)).build();
return transaction;
return builder.signature(SIGNATURE_ALGORITHM.get().createSignature(r, s, recId)).build();
}
public static org.hyperledger.besu.datatypes.SetCodeAuthorization decodeInnerPayload(
final RLPInput input) {
public static CodeDelegation decodeInnerPayload(final RLPInput input) {
input.enterList();
final BigInteger chainId = input.readBigIntegerScalar();
final Address address = Address.wrap(input.readBytes());
Optional<Long> nonce = Optional.empty();
if (!input.nextIsList()) {
throw new IllegalArgumentException("Optional nonce must be an list, but isn't");
}
final long noncesSize = input.nextSize();
input.enterList();
if (noncesSize == 1) {
nonce = Optional.ofNullable(input.readLongScalar());
} else if (noncesSize > 1) {
throw new IllegalArgumentException("Nonce list may only have 1 member, if any");
}
input.leaveList();
final long nonce = input.readLongScalar();
final byte yParity = (byte) input.readUnsignedByteScalar();
final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger();
@ -111,6 +89,7 @@ public class SetCodeTransactionDecoder {
final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, yParity);
return new SetCodeAuthorization(chainId, address, nonce, signature);
return new org.hyperledger.besu.ethereum.core.CodeDelegation(
chainId, address, nonce, signature);
}
}

@ -41,8 +41,8 @@ public class TransactionDecoder {
EIP1559TransactionDecoder::decode,
TransactionType.BLOB,
BlobTransactionDecoder::decode,
TransactionType.SET_CODE,
SetCodeTransactionDecoder::decode);
TransactionType.DELEGATE_CODE,
CodeDelegationTransactionDecoder::decode);
private static final ImmutableMap<TransactionType, Decoder> POOLED_TRANSACTION_DECODERS =
ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionDecoder::decode);

@ -40,8 +40,8 @@ public class TransactionEncoder {
EIP1559TransactionEncoder::encode,
TransactionType.BLOB,
BlobTransactionEncoder::encode,
TransactionType.SET_CODE,
SetCodeTransactionEncoder::encode);
TransactionType.DELEGATE_CODE,
CodeDelegationEncoder::encode);
private static final ImmutableMap<TransactionType, Encoder> POOLED_TRANSACTION_ENCODERS =
ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionEncoder::encode);

@ -1,88 +0,0 @@
/*
* 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.ethereum.mainnet;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.account.AccountState;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.worldstate.EVMWorldUpdater;
import java.math.BigInteger;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AuthorityProcessor {
private static final Logger LOG = LoggerFactory.getLogger(AuthorityProcessor.class);
private final Optional<BigInteger> maybeChainId;
public AuthorityProcessor(final Optional<BigInteger> maybeChainId) {
this.maybeChainId = maybeChainId;
}
public void addContractToAuthority(
final EVMWorldUpdater evmWorldUpdater, final Transaction transaction) {
transaction
.getAuthorizationList()
.get()
.forEach(
payload ->
payload
.authorizer()
.ifPresent(
authorityAddress -> {
LOG.trace("Set code authority: {}", authorityAddress);
if (maybeChainId.isPresent()
&& !payload.chainId().equals(BigInteger.ZERO)
&& !maybeChainId.get().equals(payload.chainId())) {
return;
}
final Optional<MutableAccount> maybeAccount =
Optional.ofNullable(evmWorldUpdater.getAccount(authorityAddress));
final long accountNonce =
maybeAccount.map(AccountState::getNonce).orElse(0L);
if (payload.nonce().isPresent()
&& !payload.nonce().get().equals(accountNonce)) {
return;
}
if (evmWorldUpdater
.authorizedCodeService()
.hasAuthorizedCode(authorityAddress)) {
return;
}
Optional<Account> codeAccount =
Optional.ofNullable(evmWorldUpdater.get(payload.address()));
final Bytes code;
if (codeAccount.isPresent()) {
code = codeAccount.get().getCode();
} else {
code = Bytes.EMPTY;
}
evmWorldUpdater
.authorizedCodeService()
.addAuthorizedCode(authorityAddress, code);
}));
}
}

@ -0,0 +1,134 @@
/*
* 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.ethereum.mainnet;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.core.CodeDelegation;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.worldstate.EVMWorldUpdater;
import java.math.BigInteger;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeDelegationProcessor {
private static final Logger LOG = LoggerFactory.getLogger(CodeDelegationProcessor.class);
private final Optional<BigInteger> maybeChainId;
public CodeDelegationProcessor(final Optional<BigInteger> maybeChainId) {
this.maybeChainId = maybeChainId;
}
/**
* At the start of executing the transaction, after incrementing the senders nonce, for each
* authorization we do the following:
*
* <ol>
* <li>Verify the chain id is either 0 or the chain's current ID.
* <li>`authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
* <li>Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).)
* <li>Verify the code of `authority` is either empty or already delegated.
* <li>Verify the nonce of `authority` is equal to `nonce`.
* <li>Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if
* `authority` exists in the trie.
* <li>Set the code of `authority` to be `0xef0100 || address`. This is a delegation
* designation.
* <li>Increase the nonce of `authority` by one.
* </ol>
*
* @param evmWorldUpdater The world state updater which is aware of code delegation.
* @param transaction The transaction being processed.
* @return The result of the code delegation processing.
*/
public CodeDelegationResult process(
final EVMWorldUpdater evmWorldUpdater, final Transaction transaction) {
final CodeDelegationResult result = new CodeDelegationResult();
transaction
.getCodeDelegationList()
.get()
.forEach(
codeDelegation ->
processAuthorization(
evmWorldUpdater,
(org.hyperledger.besu.ethereum.core.CodeDelegation) codeDelegation,
result));
return result;
}
private void processAuthorization(
final EVMWorldUpdater evmWorldUpdater,
final CodeDelegation codeDelegation,
final CodeDelegationResult result) {
LOG.trace("Processing code delegation: {}", codeDelegation);
if (maybeChainId.isPresent()
&& !codeDelegation.chainId().equals(BigInteger.ZERO)
&& !maybeChainId.get().equals(codeDelegation.chainId())) {
LOG.trace(
"Invalid chain id for code delegation. Expected: {}, Actual: {}",
maybeChainId.get(),
codeDelegation.chainId());
return;
}
final Optional<Address> authorizer = codeDelegation.authorizer();
if (authorizer.isEmpty()) {
LOG.trace("Invalid signature for code delegation");
return;
}
LOG.trace("Set code delegation for authority: {}", authorizer.get());
final Optional<MutableAccount> maybeAuthorityAccount =
Optional.ofNullable(evmWorldUpdater.getAccount(authorizer.get()));
result.addAccessedDelegatorAddress(authorizer.get());
MutableAccount authority;
boolean authorityDoesAlreadyExist = false;
if (maybeAuthorityAccount.isEmpty()) {
authority = evmWorldUpdater.createAccount(authorizer.get());
} else {
authority = maybeAuthorityAccount.get();
if (!evmWorldUpdater.authorizedCodeService().canSetDelegatedCode(authority)) {
return;
}
authorityDoesAlreadyExist = true;
}
if (codeDelegation.nonce() != authority.getNonce()) {
LOG.trace(
"Invalid nonce for code delegation. Expected: {}, Actual: {}",
authority.getNonce(),
codeDelegation.nonce());
return;
}
if (authorityDoesAlreadyExist) {
result.incremenentAlreadyExistingDelegators();
}
evmWorldUpdater.authorizedCodeService().addDelegatedCode(authority, codeDelegation.address());
authority.incrementNonce();
}
}

@ -0,0 +1,41 @@
/*
* 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.ethereum.mainnet;
import org.hyperledger.besu.collections.trie.BytesTrieSet;
import org.hyperledger.besu.datatypes.Address;
import java.util.Set;
public class CodeDelegationResult {
private final Set<Address> accessedDelegatorAddresses = new BytesTrieSet<>(Address.SIZE);
private long alreadyExistingDelegators = 0L;
public void addAccessedDelegatorAddress(final Address address) {
accessedDelegatorAddresses.add(address);
}
public void incremenentAlreadyExistingDelegators() {
alreadyExistingDelegators += 1;
}
public Set<Address> accessedDelegatorAddresses() {
return accessedDelegatorAddresses;
}
public long alreadyExistingDelegators() {
return alreadyExistingDelegators;
}
}

@ -715,7 +715,7 @@ public abstract class MainnetProtocolSpecs {
evmConfiguration.evmStackSize(),
feeMarket,
CoinbaseFeePriceCalculator.eip1559(),
new AuthorityProcessor(chainId)))
new CodeDelegationProcessor(chainId)))
// change to check for max blob gas per block for EIP-4844
.transactionValidatorFactoryBuilder(
(evm, gasLimitCalculator, feeMarket) ->
@ -813,7 +813,7 @@ public abstract class MainnetProtocolSpecs {
TransactionType.ACCESS_LIST,
TransactionType.EIP1559,
TransactionType.BLOB,
TransactionType.SET_CODE),
TransactionType.DELEGATE_CODE),
evm.getMaxInitcodeSize()))
// EIP-2935 Blockhash processor

@ -81,7 +81,7 @@ public class MainnetTransactionProcessor {
protected final FeeMarket feeMarket;
private final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator;
private final Optional<AuthorityProcessor> maybeAuthorityProcessor;
private final Optional<CodeDelegationProcessor> maybeCodeDelegationProcessor;
public MainnetTransactionProcessor(
final GasCalculator gasCalculator,
@ -116,7 +116,7 @@ public class MainnetTransactionProcessor {
final int maxStackSize,
final FeeMarket feeMarket,
final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator,
final AuthorityProcessor maybeAuthorityProcessor) {
final CodeDelegationProcessor maybeCodeDelegationProcessor) {
this.gasCalculator = gasCalculator;
this.transactionValidatorFactory = transactionValidatorFactory;
this.contractCreationProcessor = contractCreationProcessor;
@ -126,7 +126,7 @@ public class MainnetTransactionProcessor {
this.maxStackSize = maxStackSize;
this.feeMarket = feeMarket;
this.coinbaseFeePriceCalculator = coinbaseFeePriceCalculator;
this.maybeAuthorityProcessor = Optional.ofNullable(maybeAuthorityProcessor);
this.maybeCodeDelegationProcessor = Optional.ofNullable(maybeCodeDelegationProcessor);
}
/**
@ -316,16 +316,7 @@ public class MainnetTransactionProcessor {
operationTracer.tracePrepareTransaction(evmWorldUpdater, transaction);
final Set<Address> addressList = new BytesTrieSet<>(Address.SIZE);
if (transaction.getAuthorizationList().isPresent()) {
if (maybeAuthorityProcessor.isEmpty()) {
throw new RuntimeException("Authority processor is required for 7702 transactions");
}
maybeAuthorityProcessor.get().addContractToAuthority(evmWorldUpdater, transaction);
addressList.addAll(evmWorldUpdater.authorizedCodeService().getAuthorities());
}
final Set<Address> warmAddressList = new BytesTrieSet<>(Address.SIZE);
final long previousNonce = sender.incrementNonce();
LOG.trace(
@ -349,6 +340,20 @@ public class MainnetTransactionProcessor {
previousBalance,
sender.getBalance());
long codeDelegationRefund = 0L;
if (transaction.getCodeDelegationList().isPresent()) {
if (maybeCodeDelegationProcessor.isEmpty()) {
throw new RuntimeException("Code delegation processor is required for 7702 transactions");
}
final CodeDelegationResult codeDelegationResult =
maybeCodeDelegationProcessor.get().process(evmWorldUpdater, transaction);
warmAddressList.addAll(codeDelegationResult.accessedDelegatorAddresses());
codeDelegationRefund =
gasCalculator.calculateDelegateCodeGasRefund(
(codeDelegationResult.alreadyExistingDelegators()));
}
final List<AccessListEntry> accessListEntries = transaction.getAccessList().orElse(List.of());
// we need to keep a separate hash set of addresses in case they specify no storage.
// No-storage is a common pattern, especially for Externally Owned Accounts
@ -356,13 +361,13 @@ public class MainnetTransactionProcessor {
int accessListStorageCount = 0;
for (final var entry : accessListEntries) {
final Address address = entry.address();
addressList.add(address);
warmAddressList.add(address);
final List<Bytes32> storageKeys = entry.storageKeys();
storageList.putAll(address, storageKeys);
accessListStorageCount += storageKeys.size();
}
if (warmCoinbase) {
addressList.add(miningBeneficiary);
warmAddressList.add(miningBeneficiary);
}
final long intrinsicGas =
@ -370,16 +375,17 @@ public class MainnetTransactionProcessor {
transaction.getPayload(), transaction.isContractCreation());
final long accessListGas =
gasCalculator.accessListGasCost(accessListEntries.size(), accessListStorageCount);
final long setCodeGas = gasCalculator.setCodeListGasCost(transaction.authorizationListSize());
final long codeDelegationGas =
gasCalculator.delegateCodeGasCost(transaction.codeDelegationListSize());
final long gasAvailable =
transaction.getGasLimit() - intrinsicGas - accessListGas - setCodeGas;
transaction.getGasLimit() - intrinsicGas - accessListGas - codeDelegationGas;
LOG.trace(
"Gas available for execution {} = {} - {} - {} - {} (limit - intrinsic - accessList - setCode)",
"Gas available for execution {} = {} - {} - {} - {} (limit - intrinsic - accessList - codeDelegation)",
gasAvailable,
transaction.getGasLimit(),
intrinsicGas,
accessListGas,
setCodeGas);
codeDelegationGas);
final WorldUpdater worldUpdater = evmWorldUpdater.updater();
final ImmutableMap.Builder<String, Object> contextVariablesBuilder =
@ -409,7 +415,7 @@ public class MainnetTransactionProcessor {
.miningBeneficiary(miningBeneficiary)
.blockHashLookup(blockHashLookup)
.contextVariables(contextVariablesBuilder.build())
.accessListWarmAddresses(addressList)
.accessListWarmAddresses(warmAddressList)
.accessListWarmStorage(storageList);
if (transaction.getVersionedHashes().isPresent()) {
@ -488,7 +494,8 @@ public class MainnetTransactionProcessor {
// after the other so that if it is the same account somehow, we end up with the right result)
final long selfDestructRefund =
gasCalculator.getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long baseRefundGas = initialFrame.getGasRefund() + selfDestructRefund;
final long baseRefundGas =
initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund;
final long refundedGas = refunded(transaction, initialFrame.getRemainingGas(), baseRefundGas);
final Wei refundedWei = transactionGasPrice.multiply(refundedGas);
final Wei balancePriorToRefund = sender.getBalance();
@ -528,7 +535,6 @@ public class MainnetTransactionProcessor {
final var coinbase = evmWorldUpdater.getOrCreate(miningBeneficiary);
coinbase.incrementBalance(coinbaseWeiDelta);
evmWorldUpdater.authorizedCodeService().resetAuthorities();
operationTracer.traceEndTransaction(
worldUpdater,

@ -20,6 +20,7 @@ import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Blob;
import org.hyperledger.besu.datatypes.BlobsWithCommitments;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.KZGCommitment;
import org.hyperledger.besu.datatypes.TransactionType;
@ -129,9 +130,50 @@ public class MainnetTransactionValidator implements TransactionValidator {
transaction.getPayload().size(), maxInitcodeSize));
}
if (transactionType == TransactionType.DELEGATE_CODE) {
if (isDelegateCodeEmpty(transaction)) {
return ValidationResult.invalid(
TransactionInvalidReason.EMPTY_CODE_DELEGATION,
"transaction code delegation transactions must have a non-empty code delegation list");
}
final BigInteger halfCurveOrder = SignatureAlgorithmFactory.getInstance().getHalfCurveOrder();
final Optional<ValidationResult<TransactionInvalidReason>> validationResult =
transaction
.getCodeDelegationList()
.map(
codeDelegations -> {
for (CodeDelegation codeDelegation : codeDelegations) {
if (codeDelegation.signature().getS().compareTo(halfCurveOrder) > 0) {
return ValidationResult.invalid(
TransactionInvalidReason.INVALID_SIGNATURE,
"Invalid signature for code delegation. S value must be less or equal than the half curve order.");
}
if (codeDelegation.signature().getRecId() != 0
&& codeDelegation.signature().getRecId() != 1) {
return ValidationResult.invalid(
TransactionInvalidReason.INVALID_SIGNATURE,
"Invalid signature for code delegation. RecId value must be 0 or 1.");
}
}
return ValidationResult.valid();
});
if (validationResult.isPresent() && !validationResult.get().isValid()) {
return validationResult.get();
}
}
return validateCostAndFee(transaction, baseFee, blobFee, transactionValidationParams);
}
private static boolean isDelegateCodeEmpty(final Transaction transaction) {
return transaction.getCodeDelegationList().isEmpty()
|| transaction.getCodeDelegationList().get().isEmpty();
}
private ValidationResult<TransactionInvalidReason> validateCostAndFee(
final Transaction transaction,
final Optional<Wei> maybeBaseFee,
@ -190,7 +232,7 @@ public class MainnetTransactionValidator implements TransactionValidator {
gasCalculator.transactionIntrinsicGasCost(
transaction.getPayload(), transaction.isContractCreation())
+ (transaction.getAccessList().map(gasCalculator::accessListGasCost).orElse(0L))
+ gasCalculator.setCodeListGasCost(transaction.authorizationListSize());
+ gasCalculator.delegateCodeGasCost(transaction.codeDelegationListSize());
if (Long.compareUnsigned(intrinsicGasCost, transaction.getGasLimit()) > 0) {
return ValidationResult.invalid(
TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT,
@ -250,7 +292,8 @@ public class MainnetTransactionValidator implements TransactionValidator {
transaction.getNonce(), senderNonce));
}
if (!validationParams.isAllowContractAddressAsSender() && !codeHash.equals(Hash.EMPTY)) {
if (!validationParams.isAllowContractAddressAsSender()
&& !canSendTransaction(sender, codeHash)) {
return ValidationResult.invalid(
TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED,
String.format(
@ -261,6 +304,10 @@ public class MainnetTransactionValidator implements TransactionValidator {
return ValidationResult.valid();
}
private static boolean canSendTransaction(final Account sender, final Hash codeHash) {
return codeHash.equals(Hash.EMPTY) || sender.hasDelegatedCode();
}
private ValidationResult<TransactionInvalidReason> validateTransactionSignature(
final Transaction transaction) {
if (chainId.isPresent()

@ -50,6 +50,7 @@ public enum TransactionInvalidReason {
PLUGIN_TX_POOL_VALIDATOR,
EXECUTION_HALTED,
EOF_CODE_INVALID,
EMPTY_CODE_DELEGATION,
// Private Transaction Invalid Reasons
PRIVATE_TRANSACTION_INVALID,
PRIVATE_TRANSACTION_FAILED,

@ -376,7 +376,7 @@ public class BlockDataGenerator {
case EIP1559 -> eip1559Transaction(payload, to);
case ACCESS_LIST -> accessListTransaction(payload, to);
case BLOB -> blobTransaction(payload, to);
case SET_CODE -> null;
case DELEGATE_CODE -> null;
// no default, all types accounted for.
};
}

@ -92,7 +92,7 @@ public class TransactionTestFixture {
builder.versionedHashes(versionedHashes.get());
}
break;
case SET_CODE:
case DELEGATE_CODE:
break;
}

@ -15,11 +15,10 @@
package org.hyperledger.besu.ethereum.core.encoding;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.SetCodeAuthorization;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import java.math.BigInteger;
@ -27,7 +26,7 @@ import java.math.BigInteger;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;
class SetCodeTransactionDecoderTest {
class CodeDelegationDecoderTest {
@Test
void shouldDecodeInnerPayloadWithNonce() {
@ -36,14 +35,14 @@ class SetCodeTransactionDecoderTest {
final BytesValueRLPInput input =
new BytesValueRLPInput(
Bytes.fromHexString(
"0xf85b0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c18080a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"),
"0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa562a80a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"),
true);
final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input);
final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input);
assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE);
assertThat(authorization.address())
.isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"));
assertThat(authorization.nonce().get()).isEqualTo(0L);
assertThat(authorization.nonce()).isEqualTo(42);
final SECPSignature signature = authorization.signature();
assertThat(signature.getRecId()).isEqualTo((byte) 0);
@ -54,20 +53,20 @@ class SetCodeTransactionDecoderTest {
}
@Test
void shouldDecodeInnerPayloadWithoutNonce() {
void shouldDecodeInnerPayloadWithNonceZero() {
// "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5"
final BytesValueRLPInput input =
new BytesValueRLPInput(
Bytes.fromHexString(
"0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"),
"0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa568001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"),
true);
final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input);
final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input);
assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE);
assertThat(authorization.address())
.isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"));
assertThat(authorization.nonce()).isEmpty();
assertThat(authorization.nonce()).isEqualTo(0);
final SECPSignature signature = authorization.signature();
assertThat(signature.getRecId()).isEqualTo((byte) 1);
@ -78,37 +77,20 @@ class SetCodeTransactionDecoderTest {
}
@Test
void shouldThrowInnerPayloadWithMultipleNonces() {
// "d90194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c20107"
final BytesValueRLPInput input =
new BytesValueRLPInput(
Bytes.fromHexString(
"0xf85c0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c2010201a0401b5d4ebe88306448115d1a46a30e5ad1136f2818b4ebb0733d9c4efffd135aa0753ff1dbce6db504ecb9635a64d8c4506ff887e2d2a0d2b7175baf94c849eccc"),
true);
assertThrows(
IllegalArgumentException.class,
() -> {
SetCodeTransactionDecoder.decodeInnerPayload(input);
});
}
@Test
void shouldDecodeInnerPayloadWithoutNonceAndChainIdZero() {
void shouldDecodeInnerPayloadWithChainIdZero() {
// "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5"
final BytesValueRLPInput input =
new BytesValueRLPInput(
Bytes.fromHexString(
"0xf85a0094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"),
"0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa560501a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"),
true);
final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input);
final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input);
assertThat(authorization.chainId()).isEqualTo(BigInteger.ZERO);
assertThat(authorization.address())
.isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"));
assertThat(authorization.nonce().isEmpty()).isTrue();
assertThat(authorization.nonce()).isEqualTo(5);
final SECPSignature signature = authorization.signature();
assertThat(signature.getRecId()).isEqualTo((byte) 1);

@ -19,11 +19,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.ethereum.core.SetCodeAuthorization;
import org.hyperledger.besu.ethereum.core.CodeDelegation;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import java.math.BigInteger;
import java.util.Optional;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
@ -31,7 +30,7 @@ import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class SetCodeTransactionEncoderTest {
class CodeDelegationEncoderTest {
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
@ -43,14 +42,14 @@ class SetCodeTransactionEncoderTest {
}
@Test
void shouldEncodeSingleSetCodeWithNonce() {
void shouldEncodeSingleCodeDelegationWithNonceAndChainId() {
// "0xd80194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c105"
final SetCodeAuthorization authorization =
new SetCodeAuthorization(
final CodeDelegation authorization =
new CodeDelegation(
BigInteger.ONE,
Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"),
Optional.of(0L),
42,
SIGNATURE_ALGORITHM
.get()
.createSignature(
@ -60,23 +59,23 @@ class SetCodeTransactionEncoderTest {
"3b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99", 16),
(byte) 0));
SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output);
CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output);
assertThat(output.encoded())
.isEqualTo(
Bytes.fromHexString(
"0xf85b0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c18080a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"));
"0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa562a80a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"));
}
@Test
void shouldEncodeSingleSetCodeWithoutNonce() {
void shouldEncodeSingleCodeDelegationWithNonceZero() {
// "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5"
final SetCodeAuthorization authorization =
new SetCodeAuthorization(
final CodeDelegation authorization =
new CodeDelegation(
BigInteger.ONE,
Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"),
Optional.empty(),
0,
SIGNATURE_ALGORITHM
.get()
.createSignature(
@ -86,23 +85,23 @@ class SetCodeTransactionEncoderTest {
"25b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031", 16),
(byte) 1));
SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output);
CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output);
assertThat(output.encoded())
.isEqualTo(
Bytes.fromHexString(
"0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"));
"0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa568001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"));
}
@Test
void shouldEncodeSingleSetCodeWithoutNonceAndChainIdZero() {
void shouldEncodeSingleCodeDelegationWithChainIdZero() {
// "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5"
final SetCodeAuthorization authorization =
new SetCodeAuthorization(
final CodeDelegation authorization =
new CodeDelegation(
BigInteger.ZERO,
Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"),
Optional.empty(),
5,
SIGNATURE_ALGORITHM
.get()
.createSignature(
@ -112,11 +111,11 @@ class SetCodeTransactionEncoderTest {
"3c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df", 16),
(byte) 1));
SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output);
CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output);
assertThat(output.encoded())
.isEqualTo(
Bytes.fromHexString(
"0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"));
"0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa560501a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"));
}
}

@ -89,7 +89,7 @@ class MainnetTransactionProcessorTest {
MAX_STACK_SIZE,
FeeMarket.legacy(),
CoinbaseFeePriceCalculator.frontier(),
new AuthorityProcessor(Optional.of(BigInteger.ONE)));
new CodeDelegationProcessor(Optional.of(BigInteger.ONE)));
}
@Test

@ -147,7 +147,7 @@ public abstract class PendingTransaction
case ACCESS_LIST -> computeAccessListMemorySize();
case EIP1559 -> computeEIP1559MemorySize();
case BLOB -> computeBlobMemorySize();
case SET_CODE -> computeSetCodeMemorySize();
case DELEGATE_CODE -> computeSetCodeMemorySize();
}
+ PENDING_TRANSACTION_MEMORY_SIZE;
}

@ -128,7 +128,7 @@ public class BaseTransactionPoolTest {
final TransactionType txType = TransactionType.values()[randomizeTxType.nextInt(4)];
return switch (txType) {
case FRONTIER, ACCESS_LIST, EIP1559, SET_CODE ->
case FRONTIER, ACCESS_LIST, EIP1559, DELEGATE_CODE ->
createTransaction(txType, nonce, maxGasPrice, payloadSize, keys);
case BLOB ->
createTransaction(

@ -1598,7 +1598,7 @@ public class LayersTest extends BaseTransactionPoolTest {
case ACCESS_LIST -> createAccessListPendingTransaction(sender, nonce);
case EIP1559 -> createEIP1559PendingTransaction(sender, nonce);
case BLOB -> createBlobPendingTransaction(sender, nonce);
case SET_CODE -> throw new UnsupportedOperationException();
case DELEGATE_CODE -> throw new UnsupportedOperationException();
};
liveTxsBySender.get(sender).put(nonce, newPendingTx);
return newPendingTx;

@ -22,10 +22,12 @@ import static org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocol
import org.hyperledger.besu.config.StubGenesisConfigOptions;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.CodeDelegation;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.VersionedHash;
@ -35,7 +37,6 @@ import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.ConsolidationRequest;
import org.hyperledger.besu.ethereum.core.DepositRequest;
import org.hyperledger.besu.ethereum.core.Request;
import org.hyperledger.besu.ethereum.core.SetCodeAuthorization;
import org.hyperledger.besu.ethereum.core.Transaction;
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
import org.hyperledger.besu.ethereum.core.WithdrawalRequest;
@ -223,8 +224,7 @@ public class T8nExecutor {
continue;
}
List<org.hyperledger.besu.datatypes.SetCodeAuthorization> authorizations =
new ArrayList<>(authorizationList.size());
List<CodeDelegation> authorizations = new ArrayList<>(authorizationList.size());
for (JsonNode entryAsJson : authorizationList) {
final BigInteger authorizationChainId =
Bytes.fromHexStringLenient(entryAsJson.get("chainId").textValue())
@ -232,26 +232,8 @@ public class T8nExecutor {
final Address authorizationAddress =
Address.fromHexString(entryAsJson.get("address").textValue());
JsonNode nonces = entryAsJson.get("nonce");
if (nonces == null) {
out.printf(
"TX json node unparseable: expected nonce field to be provided - %s%n",
txNode);
continue;
}
List<Long> authorizationNonces;
if (nonces.isArray()) {
authorizationNonces = new ArrayList<>(nonces.size());
for (JsonNode nonceAsJson : nonces) {
authorizationNonces.add(
Bytes.fromHexStringLenient(nonceAsJson.textValue()).toLong());
}
} else {
authorizationNonces =
List.of(Bytes.fromHexStringLenient(nonces.textValue()).toLong());
}
final long authorizationNonce =
Bytes.fromHexStringLenient(entryAsJson.get("nonce").textValue()).toLong();
final byte authorizationV =
Bytes.fromHexStringLenient(entryAsJson.get("v").textValue())
@ -264,16 +246,17 @@ public class T8nExecutor {
Bytes.fromHexStringLenient(entryAsJson.get("s").textValue())
.toUnsignedBigInteger();
final SECPSignature authorizationSignature =
new SECPSignature(authorizationR, authorizationS, authorizationV);
authorizations.add(
SetCodeAuthorization.createSetCodeAuthorizationEntry(
new org.hyperledger.besu.ethereum.core.CodeDelegation(
authorizationChainId,
authorizationAddress,
authorizationNonces,
authorizationV,
authorizationR,
authorizationS));
authorizationNonce,
authorizationSignature));
}
builder.setCodeTransactionPayloads(authorizations);
builder.codeDelegations(authorizations);
}
if (txNode.has("blobVersionedHashes")) {
@ -328,8 +311,8 @@ public class T8nExecutor {
} else {
out.printf("TX json node unparseable: %s%n", txNode);
}
} catch (IllegalArgumentException iae) {
rejections.add(new RejectedTransaction(i, iae.getMessage()));
} catch (IllegalArgumentException | ArithmeticException e) {
rejections.add(new RejectedTransaction(i, e.getMessage()));
}
i++;
}

@ -17,6 +17,10 @@ package org.hyperledger.besu.evm.account;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
/**
* A world state account.
*
@ -49,4 +53,31 @@ public interface Account extends AccountState {
* is set.
*/
boolean isStorageEmpty();
/**
* Returns the address of the delegated code account if it has one.
*
* @return the address of the delegated code account if it has one otherwise empty.
*/
default Optional<Address> delegatedCodeAddress() {
return Optional.empty();
}
/**
* Returns a boolean to indicate if the account has delegated code.
*
* @return true if the account has delegated code otherwise false.
*/
default boolean hasDelegatedCode() {
return false;
}
/**
* Returns the code as it is stored in the trie even if it's a delegated code account.
*
* @return the code as it is stored in the trie.
*/
default Bytes getUnprocessedCode() {
return getCode();
}
}

@ -0,0 +1,92 @@
/*
* 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.evm.account;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
class BaseDelegatedCodeAccount {
private final WorldUpdater worldUpdater;
/** The address of the account that has delegated code to be loaded into it. */
protected final Address delegatedCodeAddress;
protected BaseDelegatedCodeAccount(
final WorldUpdater worldUpdater, final Address delegatedCodeAddress) {
this.worldUpdater = worldUpdater;
this.delegatedCodeAddress = delegatedCodeAddress;
}
/**
* Returns the delegated code.
*
* @return the delegated code.
*/
protected Bytes getCode() {
return resolveDelegatedCode();
}
/**
* Returns the hash of the delegated code.
*
* @return the hash of the delegated code.
*/
protected Hash getCodeHash() {
final Bytes code = getCode();
return (code == null || code.isEmpty()) ? Hash.EMPTY : Hash.hash(code);
}
/**
* Returns the balance of the delegated account.
*
* @return the balance of the delegated account.
*/
protected Wei getDelegatedBalance() {
return getDelegatedAccount().map(Account::getBalance).orElse(Wei.ZERO);
}
/**
* Returns the nonce of the delegated account.
*
* @return the nonce of the delegated account.
*/
protected long getDelegatedNonce() {
return getDelegatedAccount().map(Account::getNonce).orElse(Account.DEFAULT_NONCE);
}
/**
* Returns the address of the delegated code.
*
* @return the address of the delegated code.
*/
protected Optional<Address> delegatedCodeAddress() {
return Optional.of(delegatedCodeAddress);
}
private Optional<Account> getDelegatedAccount() {
return Optional.ofNullable(worldUpdater.getAccount(delegatedCodeAddress));
}
private Bytes resolveDelegatedCode() {
return getDelegatedAccount().map(Account::getUnprocessedCode).orElse(Bytes.EMPTY);
}
}

@ -17,30 +17,33 @@ package org.hyperledger.besu.evm.account;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.NavigableMap;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
/** Wraps an EOA account and includes authorized code to be run on behalf of it. */
public class AuthorizedCodeAccount implements Account {
private final Account wrappedAccount;
private final Bytes authorizedCode;
/** Wraps an EOA account and includes delegated code to be run on behalf of it. */
public class DelegatedCodeAccount extends BaseDelegatedCodeAccount implements Account {
/** The hash of the authorized code. */
protected Hash codeHash = null;
private final Account wrappedAccount;
/**
* Creates a new AuthorizedCodeAccount.
*
* @param wrappedAccount the account that has authorized code to be loaded into it.
* @param authorizedCode the authorized code.
* @param worldUpdater the world updater.
* @param wrappedAccount the account that has delegated code to be loaded into it.
* @param codeDelegationAddress the address of the delegated code.
*/
public AuthorizedCodeAccount(final Account wrappedAccount, final Bytes authorizedCode) {
public DelegatedCodeAccount(
final WorldUpdater worldUpdater,
final Account wrappedAccount,
final Address codeDelegationAddress) {
super(worldUpdater, codeDelegationAddress);
this.wrappedAccount = wrappedAccount;
this.authorizedCode = authorizedCode;
}
@Override
@ -53,6 +56,11 @@ public class AuthorizedCodeAccount implements Account {
return wrappedAccount.isStorageEmpty();
}
@Override
public Optional<Address> delegatedCodeAddress() {
return super.delegatedCodeAddress();
}
@Override
public Hash getAddressHash() {
return wrappedAccount.getAddressHash();
@ -70,16 +78,17 @@ public class AuthorizedCodeAccount implements Account {
@Override
public Bytes getCode() {
return authorizedCode;
return super.getCode();
}
@Override
public Hash getCodeHash() {
if (codeHash == null) {
codeHash = authorizedCode.equals(Bytes.EMPTY) ? Hash.EMPTY : Hash.hash(authorizedCode);
public Bytes getUnprocessedCode() {
return wrappedAccount.getCode();
}
return codeHash;
@Override
public Hash getCodeHash() {
return super.getCodeHash();
}
@Override
@ -92,9 +101,24 @@ public class AuthorizedCodeAccount implements Account {
return wrappedAccount.getOriginalStorageValue(key);
}
@Override
public boolean isEmpty() {
return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode();
}
@Override
public boolean hasCode() {
return !getCode().isEmpty();
}
@Override
public NavigableMap<Bytes32, AccountStorageEntry> storageEntriesFrom(
final Bytes32 startKeyHash, final int limit) {
return wrappedAccount.storageEntriesFrom(startKeyHash, limit);
}
@Override
public boolean hasDelegatedCode() {
return true;
}
}

@ -17,33 +17,35 @@ package org.hyperledger.besu.evm.account;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
/** Wraps a mutable EOA account and includes authorized code to be run on behalf of it. */
public class MutableAuthorizedCodeAccount implements MutableAccount {
/** Wraps an EOA account and includes delegated code to be run on behalf of it. */
public class MutableDelegatedCodeAccount extends BaseDelegatedCodeAccount
implements MutableAccount {
private final MutableAccount wrappedAccount;
private final Bytes authorizedCode;
/** The hash of the authorized code. */
protected Hash codeHash = null;
/**
* Creates a new MutableAuthorizedCodeAccount.
*
* @param wrappedAccount the account that has authorized code to be loaded into it.
* @param authorizedCode the authorized code.
* @param worldUpdater the world updater.
* @param wrappedAccount the account that has delegated code to be loaded into it.
* @param codeDelegationAddress the address of the delegated code.
*/
public MutableAuthorizedCodeAccount(
final MutableAccount wrappedAccount, final Bytes authorizedCode) {
public MutableDelegatedCodeAccount(
final WorldUpdater worldUpdater,
final MutableAccount wrappedAccount,
final Address codeDelegationAddress) {
super(worldUpdater, codeDelegationAddress);
this.wrappedAccount = wrappedAccount;
this.authorizedCode = authorizedCode;
}
@Override
@ -56,6 +58,11 @@ public class MutableAuthorizedCodeAccount implements MutableAccount {
return wrappedAccount.isStorageEmpty();
}
@Override
public Optional<Address> delegatedCodeAddress() {
return super.delegatedCodeAddress();
}
@Override
public Hash getAddressHash() {
return wrappedAccount.getAddressHash();
@ -73,16 +80,17 @@ public class MutableAuthorizedCodeAccount implements MutableAccount {
@Override
public Bytes getCode() {
return authorizedCode;
return super.getCode();
}
@Override
public Hash getCodeHash() {
if (codeHash == null) {
codeHash = authorizedCode.equals(Bytes.EMPTY) ? Hash.EMPTY : Hash.hash(authorizedCode);
public Bytes getUnprocessedCode() {
return wrappedAccount.getCode();
}
return codeHash;
@Override
public Hash getCodeHash() {
return super.getCodeHash();
}
@Override
@ -95,6 +103,16 @@ public class MutableAuthorizedCodeAccount implements MutableAccount {
return wrappedAccount.getOriginalStorageValue(key);
}
@Override
public boolean isEmpty() {
return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode();
}
@Override
public boolean hasCode() {
return !getCode().isEmpty();
}
@Override
public NavigableMap<Bytes32, AccountStorageEntry> storageEntriesFrom(
final Bytes32 startKeyHash, final int limit) {
@ -113,7 +131,7 @@ public class MutableAuthorizedCodeAccount implements MutableAccount {
@Override
public void setCode(final Bytes code) {
throw new RuntimeException("Cannot set code on an AuthorizedCodeAccount");
wrappedAccount.setCode(code);
}
@Override
@ -135,4 +153,9 @@ public class MutableAuthorizedCodeAccount implements MutableAccount {
public void becomeImmutable() {
wrappedAccount.becomeImmutable();
}
@Override
public boolean hasDelegatedCode() {
return true;
}
}

@ -647,12 +647,33 @@ public interface GasCalculator {
}
/**
* Returns the upfront gas cost for EIP 7702 operation.
* Returns the upfront gas cost for EIP 7702 authorization processing.
*
* @param authorizationListLength The length of the authorization list
* @param delegateCodeListLength The length of the code delegation list
* @return the gas cost
*/
default long setCodeListGasCost(final int authorizationListLength) {
default long delegateCodeGasCost(final int delegateCodeListLength) {
return 0L;
}
/**
* Calculates the refund for proessing the 7702 code delegation list if an delegater account
* already exist in the trie.
*
* @param alreadyExistingAccountSize The number of accounts already in the trie
* @return the gas refund
*/
default long calculateDelegateCodeGasRefund(final long alreadyExistingAccountSize) {
return 0L;
}
/**
* Returns the gas cost for resolving the code of a delegate account.
*
* @param isWarm whether the account is warm
* @return the gas cost
*/
default long delegatedCodeResolutionGasCost(final boolean isWarm) {
return 0L;
}
}

@ -16,19 +16,17 @@ package org.hyperledger.besu.evm.gascalculator;
import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2;
import org.hyperledger.besu.datatypes.CodeDelegation;
/**
* Gas Calculator for Prague
*
* <p>Placeholder for new gas schedule items. If Prague finalzies without changes this can be
* removed
*
* <UL>
* <LI>TBD
* <LI>Gas costs for EIP-7702 (Code Delegation)
* </UL>
*/
public class PragueGasCalculator extends CancunGasCalculator {
static final long PER_CONTRACT_CODE_BASE_COST = 2500L;
final long existingAccountGasRefund;
/** Instantiates a new Prague Gas Calculator. */
public PragueGasCalculator() {
@ -42,10 +40,21 @@ public class PragueGasCalculator extends CancunGasCalculator {
*/
protected PragueGasCalculator(final int maxPrecompile) {
super(maxPrecompile);
this.existingAccountGasRefund = newAccountGasCost() - CodeDelegation.PER_AUTH_BASE_COST;
}
@Override
public long delegateCodeGasCost(final int delegateCodeListLength) {
return newAccountGasCost() * delegateCodeListLength;
}
@Override
public long calculateDelegateCodeGasRefund(final long alreadyExistingAccounts) {
return existingAccountGasRefund * alreadyExistingAccounts;
}
@Override
public long setCodeListGasCost(final int authorizationListLength) {
return PER_CONTRACT_CODE_BASE_COST * authorizationListLength;
public long delegatedCodeResolutionGasCost(final boolean isWarm) {
return isWarm ? getWarmStorageReadCost() : getColdAccountAccessCost();
}
}

@ -15,6 +15,7 @@
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
@ -26,6 +27,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.frame.MessageFrame.State;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import org.apache.tuweni.bytes.Bytes;
@ -190,6 +192,15 @@ public abstract class AbstractCallOperation extends AbstractOperation {
final Account contract = frame.getWorldUpdater().get(to);
if (contract != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), contract);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress());
final Wei balance = account == null ? Wei.ZERO : account.getBalance();
// If the call is sending more value than the account has or the message frame is to deep

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
@ -24,6 +26,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import javax.annotation.Nonnull;
@ -119,6 +122,16 @@ public abstract class AbstractExtCallOperation extends AbstractCallOperation {
}
Address to = Words.toAddress(toBytes);
final Account contract = frame.getWorldUpdater().get(to);
if (contract != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), contract);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
boolean accountCreation = contract == null && !zeroValue;
long cost =
gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength)

@ -16,6 +16,7 @@ package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import static org.hyperledger.besu.evm.internal.Words.clampedToLong;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.EVM;
@ -25,6 +26,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import org.apache.tuweni.bytes.Bytes;
@ -93,6 +95,16 @@ public class ExtCodeCopyOperation extends AbstractOperation {
}
final Account account = frame.getWorldUpdater().get(address);
if (account != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), account);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
final Bytes code = account != null ? account.getCode() : Bytes.EMPTY;
if (enableEIP3540

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.EVM;
@ -25,6 +27,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.OverflowException;
import org.hyperledger.besu.evm.internal.UnderflowException;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import org.apache.tuweni.bytes.Bytes;
@ -78,8 +81,19 @@ public class ExtCodeHashOperation extends AbstractOperation {
final long cost = cost(accountIsWarm);
if (frame.getRemainingGas() < cost) {
return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
} else {
}
final Account account = frame.getWorldUpdater().get(address);
if (account != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), account);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
if (account == null || account.isEmpty()) {
frame.pushStackItem(Bytes.EMPTY);
} else {
@ -94,7 +108,7 @@ public class ExtCodeHashOperation extends AbstractOperation {
}
}
return new OperationResult(cost, null);
}
} catch (final UnderflowException ufe) {
return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS);
} catch (final OverflowException ofe) {

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.evm.operation;
import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.account.Account;
@ -24,6 +26,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.OverflowException;
import org.hyperledger.besu.evm.internal.UnderflowException;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper;
import org.apache.tuweni.bytes.Bytes;
@ -78,6 +81,16 @@ public class ExtCodeSizeOperation extends AbstractOperation {
return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
} else {
final Account account = frame.getWorldUpdater().get(address);
if (account != null) {
final DelegatedCodeGasCostHelper.Result result =
deductDelegatedCodeGasCost(frame, gasCalculator(), account);
if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) {
return new Operation.OperationResult(
result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS);
}
}
Bytes codeSize;
if (account == null) {
codeSize = Bytes.EMPTY;

@ -1,116 +0,0 @@
/*
* 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.evm.worldstate;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.account.AuthorizedCodeAccount;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.account.MutableAuthorizedCodeAccount;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
/** A service that manages the code injection of authorized code. */
public class AuthorizedCodeService {
private final Map<Address, Bytes> authorizedCode = new HashMap<>();
/** Creates a new AuthorizedCodeService. */
public AuthorizedCodeService() {}
/**
* Authorizes to load the code of authorizedCode into the authorizer account.
*
* @param authorizer the address that gives the authorization.
* @param authorizedCode the code which will be loaded.
*/
public void addAuthorizedCode(final Address authorizer, final Bytes authorizedCode) {
this.authorizedCode.put(authorizer, authorizedCode);
}
/**
* Return all the authorities that have given their authorization to load the code of another
* account.
*
* @return the set of authorities.
*/
public Set<Address> getAuthorities() {
return authorizedCode.keySet();
}
/** Resets all the authorized accounts. */
public void resetAuthorities() {
authorizedCode.clear();
}
/**
* Checks if the provided address has set an authorized to load code into an EOA account.
*
* @param authority the address to check.
* @return {@code true} if the address has been authorized, {@code false} otherwise.
*/
public boolean hasAuthorizedCode(final Address authority) {
return authorizedCode.containsKey(authority);
}
/**
* Processes the provided account, injecting the authorized code if authorized.
*
* @param worldUpdater the world updater to retrieve the code account.
* @param originalAccount the account to process.
* @param address the address of the account in case the provided account is null
* @return the processed account, containing the authorized code if authorized.
*/
public Account processAccount(
final WorldUpdater worldUpdater, final Account originalAccount, final Address address) {
if (!authorizedCode.containsKey(address)) {
return originalAccount;
}
Account account = originalAccount;
if (account == null) {
account = worldUpdater.createAccount(address);
}
return new AuthorizedCodeAccount(account, authorizedCode.get(address));
}
/**
* Processes the provided mutable account, injecting the authorized code if authorized.
*
* @param worldUpdater the world updater to retrieve the code account.
* @param originalAccount the mutable account to process.
* @param address the address of the account in case the provided account is null
* @return the processed mutable account, containing the authorized code if authorized.
*/
public MutableAccount processMutableAccount(
final WorldUpdater worldUpdater,
final MutableAccount originalAccount,
final Address address) {
if (!authorizedCode.containsKey(address)) {
return originalAccount;
}
MutableAccount account = originalAccount;
if (account == null) {
account = worldUpdater.createAccount(address);
}
return new MutableAuthorizedCodeAccount(account, authorizedCode.get(address));
}
}

@ -0,0 +1,80 @@
/*
* 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.evm.worldstate;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
/**
* Helper class to deduct gas cost for delegated code resolution.
*
* <p>Delegated code resolution is the process of determining the address of the contract that will
* be executed when a contract has delegated code. This process is necessary to determine the
* contract that will be executed and to ensure that the contract is warm in the cache.
*/
public class DelegatedCodeGasCostHelper {
/** Private constructor to prevent instantiation. */
private DelegatedCodeGasCostHelper() {
// empty constructor
}
/** The status of the operation. */
public enum Status {
/** The operation failed due to insufficient gas. */
INSUFFICIENT_GAS,
/** The operation was successful. */
SUCCESS
}
/**
* The result of the operation.
*
* @param gasCost the gas cost
* @param status of the operation
*/
public record Result(long gasCost, Status status) {}
/**
* Deducts the gas cost for delegated code resolution.
*
* @param frame the message frame
* @param gasCalculator the gas calculator
* @param account the account
* @return the gas cost and result of the operation
*/
public static Result deductDelegatedCodeGasCost(
final MessageFrame frame, final GasCalculator gasCalculator, final Account account) {
if (!account.hasDelegatedCode()) {
return new Result(0, Status.SUCCESS);
}
if (account.delegatedCodeAddress().isEmpty()) {
throw new RuntimeException("A delegated code account must have a delegated code address");
}
final boolean delegatedCodeIsWarm = frame.warmUpAddress(account.delegatedCodeAddress().get());
final long delegatedCodeResolutionGas =
gasCalculator.delegatedCodeResolutionGasCost(delegatedCodeIsWarm);
if (frame.getRemainingGas() < delegatedCodeResolutionGas) {
return new Result(delegatedCodeResolutionGas, Status.INSUFFICIENT_GAS);
}
frame.decrementRemainingGas(delegatedCodeResolutionGas);
return new Result(delegatedCodeResolutionGas, Status.SUCCESS);
}
}

@ -0,0 +1,97 @@
/*
* 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.evm.worldstate;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.account.DelegatedCodeAccount;
import org.hyperledger.besu.evm.account.MutableAccount;
import org.hyperledger.besu.evm.account.MutableDelegatedCodeAccount;
import org.apache.tuweni.bytes.Bytes;
/** A service that manages the code injection of delegated code. */
public class DelegatedCodeService {
private static final Bytes DELEGATED_CODE_PREFIX = Bytes.fromHexString("ef0100");
private static final int DELEGATED_CODE_SIZE = DELEGATED_CODE_PREFIX.size() + Address.SIZE;
/** Creates a new DelegatedCodeService. */
public DelegatedCodeService() {}
/**
* Add the delegated code to the given account.
*
* @param account the account to which the delegated code is added.
* @param delegatedCodeAddress the address of the delegated code.
*/
public void addDelegatedCode(final MutableAccount account, final Address delegatedCodeAddress) {
account.setCode(Bytes.concatenate(DELEGATED_CODE_PREFIX, delegatedCodeAddress));
}
/**
* Returns if the provided account has either no code set or has already delegated code.
*
* @param account the account to check.
* @return {@code true} if the account can set delegated code, {@code false} otherwise.
*/
public boolean canSetDelegatedCode(final Account account) {
return account.getCode().isEmpty() || hasDelegatedCode(account.getUnprocessedCode());
}
/**
* Processes the provided account, resolving the code if delegated.
*
* @param worldUpdater the world updater to retrieve the delegated code.
* @param account the account to process.
* @return the processed account, containing the delegated code if set, the unmodified account
* otherwise.
*/
public Account processAccount(final WorldUpdater worldUpdater, final Account account) {
if (account == null || !hasDelegatedCode(account.getCode())) {
return account;
}
return new DelegatedCodeAccount(
worldUpdater, account, resolveDelegatedAddress(account.getCode()));
}
/**
* Processes the provided mutable account, resolving the code if delegated.
*
* @param worldUpdater the world updater to retrieve the delegated code.
* @param account the mutable account to process.
* @return the processed mutable account, containing the delegated code if set, the unmodified
* mutable account otherwise.
*/
public MutableAccount processMutableAccount(
final WorldUpdater worldUpdater, final MutableAccount account) {
if (account == null || !hasDelegatedCode(account.getCode())) {
return account;
}
return new MutableDelegatedCodeAccount(
worldUpdater, account, resolveDelegatedAddress(account.getCode()));
}
private Address resolveDelegatedAddress(final Bytes code) {
return Address.wrap(code.slice(DELEGATED_CODE_PREFIX.size()));
}
private boolean hasDelegatedCode(final Bytes code) {
return code != null
&& code.size() == DELEGATED_CODE_SIZE
&& code.slice(0, DELEGATED_CODE_PREFIX.size()).equals(DELEGATED_CODE_PREFIX);
}
}

@ -29,7 +29,7 @@ import java.util.Optional;
*/
public class EVMWorldUpdater implements WorldUpdater {
private final WorldUpdater rootWorldUpdater;
private final AuthorizedCodeService authorizedCodeService;
private final DelegatedCodeService delegatedCodeService;
/**
* Instantiates a new EVM world updater.
@ -37,13 +37,13 @@ public class EVMWorldUpdater implements WorldUpdater {
* @param rootWorldUpdater the root world updater
*/
public EVMWorldUpdater(final WorldUpdater rootWorldUpdater) {
this(rootWorldUpdater, new AuthorizedCodeService());
this(rootWorldUpdater, new DelegatedCodeService());
}
private EVMWorldUpdater(
final WorldUpdater rootWorldUpdater, final AuthorizedCodeService authorizedCodeService) {
final WorldUpdater rootWorldUpdater, final DelegatedCodeService delegatedCodeService) {
this.rootWorldUpdater = rootWorldUpdater;
this.authorizedCodeService = authorizedCodeService;
this.delegatedCodeService = delegatedCodeService;
}
/**
@ -51,38 +51,36 @@ public class EVMWorldUpdater implements WorldUpdater {
*
* @return the authorized code service
*/
public AuthorizedCodeService authorizedCodeService() {
return authorizedCodeService;
public DelegatedCodeService authorizedCodeService() {
return delegatedCodeService;
}
@Override
public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) {
return authorizedCodeService.processMutableAccount(
this, rootWorldUpdater.createAccount(address, nonce, balance), address);
return delegatedCodeService.processMutableAccount(
this, rootWorldUpdater.createAccount(address, nonce, balance));
}
@Override
public MutableAccount getAccount(final Address address) {
return authorizedCodeService.processMutableAccount(
this, rootWorldUpdater.getAccount(address), address);
return delegatedCodeService.processMutableAccount(this, rootWorldUpdater.getAccount(address));
}
@Override
public MutableAccount getOrCreate(final Address address) {
return authorizedCodeService.processMutableAccount(
this, rootWorldUpdater.getOrCreate(address), address);
return delegatedCodeService.processMutableAccount(this, rootWorldUpdater.getOrCreate(address));
}
@Override
public MutableAccount getOrCreateSenderAccount(final Address address) {
return authorizedCodeService.processMutableAccount(
this, rootWorldUpdater.getOrCreateSenderAccount(address), address);
return delegatedCodeService.processMutableAccount(
this, rootWorldUpdater.getOrCreateSenderAccount(address));
}
@Override
public MutableAccount getSenderAccount(final MessageFrame frame) {
return authorizedCodeService.processMutableAccount(
this, rootWorldUpdater.getSenderAccount(frame), frame.getSenderAddress());
return delegatedCodeService.processMutableAccount(
this, rootWorldUpdater.getSenderAccount(frame));
}
@Override
@ -117,11 +115,11 @@ public class EVMWorldUpdater implements WorldUpdater {
@Override
public WorldUpdater updater() {
return new EVMWorldUpdater(rootWorldUpdater.updater(), authorizedCodeService);
return new EVMWorldUpdater(rootWorldUpdater.updater(), delegatedCodeService);
}
@Override
public Account get(final Address address) {
return authorizedCodeService.processAccount(this, rootWorldUpdater.get(address), address);
return delegatedCodeService.processAccount(this, rootWorldUpdater.get(address));
}
}

Loading…
Cancel
Save