From 8eee5698878af80a543362d136a01c51099d48d6 Mon Sep 17 00:00:00 2001 From: daniellehrner Date: Fri, 6 Sep 2024 12:04:41 +0200 Subject: [PATCH] 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 * add CHANGELOG entry Signed-off-by: Daniel Lehrner * first draft for 7702 v2 Signed-off-by: Daniel Lehrner * change return value of DelegatedCodeGasCostHelper Signed-off-by: Daniel Lehrner * fix upfront gas cost calculation, fix setting code multiple times in MutableDelegatedCodeAccount Signed-off-by: Daniel Lehrner * 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 * allow accounts with delegated code to send transactions Signed-off-by: Daniel Lehrner * add refund for already existing account after nonce check Signed-off-by: Daniel Lehrner * resolve delegated code only the first time to avoid delegation loops Signed-off-by: Daniel Lehrner * handle invalid authorization signatures properly Signed-off-by: Daniel Lehrner * refactored CodeDelegationProcessor to compute authorizer of a code delegation after the chain id check Signed-off-by: Daniel Lehrner * fix canSetDelegatedCode method by checking code how it is in the trie and not the resolved code Signed-off-by: Daniel Lehrner * optimize code hash calculation for empty code, fix check for empty code delegation list Signed-off-by: Daniel Lehrner * check the all code delegation signatures hava a valid s value Signed-off-by: Daniel Lehrner * rename encoder & decoder, handle invalid signature values in T8nExecutor Signed-off-by: Daniel Lehrner * 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 * check that recid is either 0 or 1 Signed-off-by: Daniel Lehrner * fixed acceptance tests, renamed the the remaining instances of set code to code delegation Signed-off-by: Daniel Lehrner * fix delegate encoder & encoder unit tests Signed-off-by: Daniel Lehrner * spotless Signed-off-by: Daniel Lehrner * changed empty check for delegated accounts, fixed test Signed-off-by: Daniel Lehrner * fix hasDelegatedCode method when code is null Signed-off-by: Daniel Lehrner * run acceptance tests without deamon Signed-off-by: Daniel Lehrner * disable CodeDelegationTransactionAcceptanceTest to check if it is causing the stuck ci pipeline Signed-off-by: Daniel Lehrner * check if shouldTransferAllEthOfAuthorizerToSponsor is causing pipeline to stall Signed-off-by: Daniel Lehrner * check if shouldCheckNonceAfterNonceIncreaseOfSender is causing pipeline to stall Signed-off-by: Daniel Lehrner * check if closing the cluster after every test is causing pipeline to stall Signed-off-by: Daniel Lehrner * spotless Signed-off-by: Daniel Lehrner --------- Signed-off-by: Daniel Lehrner Co-authored-by: Danno Ferrin --- ...eDelegationTransactionAcceptanceTest.java} | 66 +++++---- .../options/TransactionPoolOptionsTest.java | 2 +- .../besu/crypto/SECPSignature.java | 2 +- ...Authorization.java => CodeDelegation.java} | 15 +- .../besu/datatypes/Transaction.java | 8 +- .../besu/datatypes/TransactionType.java | 19 ++- .../results/TransactionCompleteResult.java | 10 +- ...Authorization.java => CodeDelegation.java} | 82 ++++++----- .../besu/ethereum/core/Transaction.java | 63 ++++---- ...ncoder.java => CodeDelegationEncoder.java} | 32 ++--- ... => CodeDelegationTransactionDecoder.java} | 43 ++---- .../core/encoding/TransactionDecoder.java | 4 +- .../core/encoding/TransactionEncoder.java | 4 +- .../ethereum/mainnet/AuthorityProcessor.java | 88 ------------ .../mainnet/CodeDelegationProcessor.java | 134 ++++++++++++++++++ .../mainnet/CodeDelegationResult.java | 41 ++++++ .../mainnet/MainnetProtocolSpecs.java | 4 +- .../mainnet/MainnetTransactionProcessor.java | 50 ++++--- .../mainnet/MainnetTransactionValidator.java | 51 ++++++- .../transaction/TransactionInvalidReason.java | 1 + .../ethereum/core/BlockDataGenerator.java | 2 +- .../ethereum/core/TransactionTestFixture.java | 2 +- ...st.java => CodeDelegationDecoderTest.java} | 44 ++---- ...st.java => CodeDelegationEncoderTest.java} | 41 +++--- .../MainnetTransactionProcessorTest.java | 2 +- .../eth/transactions/PendingTransaction.java | 2 +- .../layered/BaseTransactionPoolTest.java | 2 +- .../eth/transactions/layered/LayersTest.java | 2 +- .../hyperledger/besu/evmtool/T8nExecutor.java | 45 ++---- .../hyperledger/besu/evm/account/Account.java | 31 ++++ .../evm/account/BaseDelegatedCodeAccount.java | 92 ++++++++++++ ...Account.java => DelegatedCodeAccount.java} | 56 +++++--- ....java => MutableDelegatedCodeAccount.java} | 59 +++++--- .../besu/evm/gascalculator/GasCalculator.java | 27 +++- .../gascalculator/PragueGasCalculator.java | 25 ++-- .../evm/operation/AbstractCallOperation.java | 11 ++ .../operation/AbstractExtCallOperation.java | 13 ++ .../evm/operation/ExtCodeCopyOperation.java | 12 ++ .../evm/operation/ExtCodeHashOperation.java | 40 ++++-- .../evm/operation/ExtCodeSizeOperation.java | 13 ++ .../evm/worldstate/AuthorizedCodeService.java | 116 --------------- .../DelegatedCodeGasCostHelper.java | 80 +++++++++++ .../evm/worldstate/DelegatedCodeService.java | 97 +++++++++++++ .../besu/evm/worldstate/EVMWorldUpdater.java | 34 +++-- 44 files changed, 999 insertions(+), 568 deletions(-) rename acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/{SetCodeTransactionAcceptanceTest.java => CodeDelegationTransactionAcceptanceTest.java} (76%) rename datatypes/src/main/java/org/hyperledger/besu/datatypes/{SetCodeAuthorization.java => CodeDelegation.java} (82%) rename ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/{SetCodeAuthorization.java => CodeDelegation.java} (76%) rename ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/{SetCodeTransactionEncoder.java => CodeDelegationEncoder.java} (75%) rename ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/{SetCodeTransactionDecoder.java => CodeDelegationTransactionDecoder.java} (73%) delete mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java rename ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/{SetCodeTransactionDecoderTest.java => CodeDelegationDecoderTest.java} (67%) rename ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/{SetCodeTransactionEncoderTest.java => CodeDelegationEncoderTest.java} (75%) create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java rename evm/src/main/java/org/hyperledger/besu/evm/account/{AuthorizedCodeAccount.java => DelegatedCodeAccount.java} (64%) rename evm/src/main/java/org/hyperledger/besu/evm/account/{MutableAuthorizedCodeAccount.java => MutableDelegatedCodeAccount.java} (69%) delete mode 100644 evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.java similarity index 76% rename from acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java rename to acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.java index b134b1f5c0..6000915b44 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.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)))); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java index 8d23e7794a..eb48a3f2a5 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java @@ -413,7 +413,7 @@ public class TransactionPoolOptionsTest @Test public void maxPrioritizedTxsPerTypeWrongTxType() { internalTestFailure( - "Invalid value for option '--tx-pool-max-prioritized-by-type' (MAP): 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): 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"); } diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java index 96f3ca6a93..5524faac3f 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java @@ -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; diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java similarity index 82% rename from datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java rename to datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java index b12e65de41..7b9e3d7d44 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java @@ -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
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 nonce(); + long nonce(); /** * Return the recovery id. diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java index d6751852bc..e614901300 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java @@ -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> getAuthorizationList(); + Optional> getCodeDelegationList(); /** * Returns the size of the authorization list. * * @return the size of the authorization list */ - int authorizationListSize(); + int codeDelegationListSize(); } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java index df4a07193b..bf1d4e7792 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java @@ -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 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 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); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java index 05b323cacb..e8262021fd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java @@ -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 versionedHashes; @JsonInclude(JsonInclude.Include.NON_NULL) - private final List authorizationList; + private final List 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 getAuthorizationList() { + public List getAuthorizationList() { return authorizationList; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java similarity index 76% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java index ea9cae2fcb..68fa958a8d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java @@ -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 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 nonce; + private final long nonce; private final SECPSignature signature; private Optional
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 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 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 nonce() { + public long nonce() { return nonce; } @@ -144,30 +140,38 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC private Optional
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 - .get() - .recoverPublicKeyFromSignature(hash, signature) - .map(Address::extract); + Optional
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 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 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); } } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java index 513cfb8319..0da8f8f28a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java @@ -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> versionedHashes; private final Optional blobsWithCommitments; - private final Optional> maybeAuthorizationList; + private final Optional> maybeCodeDelegationList; public static Builder builder() { return new Builder(); @@ -181,7 +181,7 @@ public class Transaction final Optional chainId, final Optional> versionedHashes, final Optional blobsWithCommitments, - final Optional> maybeAuthorizationList) { + final Optional> 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> getAuthorizationList() { - return maybeAuthorizationList; + public Optional> 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> accessList, final List versionedHashes, - final Optional> authorizationList, + final Optional> codeDelegationList, final Optional 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 chainId, final Optional> accessList, - final List authorizationList) { + final List 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 v = Optional.empty(); protected List versionedHashes = null; private BlobsWithCommitments blobsWithCommitments; - protected Optional> setCodeTransactionPayloads = Optional.empty(); + protected Optional> 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 setCodeTransactionEntries) { - this.setCodeTransactionPayloads = Optional.ofNullable(setCodeTransactionEntries); + public Builder codeDelegations(final List codeDelegations) { + this.codeDelegationAuthorizations = Optional.ofNullable(codeDelegations); return this; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java similarity index 75% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java index 05808f3129..4cedf93adc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java @@ -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 payloads, final RLPOutput rlpOutput) { + public static void encodeCodeDelegationInner( + final List 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(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java similarity index 73% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java index 80d59de83b..8961431c9c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java @@ -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 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 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); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java index 2a63d8f24b..7261aecbea 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java @@ -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 POOLED_TRANSACTION_DECODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionDecoder::decode); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java index 4995975621..26bad56c6d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java @@ -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 POOLED_TRANSACTION_ENCODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionEncoder::encode); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java deleted file mode 100644 index 1d25d2d34f..0000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java +++ /dev/null @@ -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 maybeChainId; - - public AuthorityProcessor(final Optional 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 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 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); - })); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java new file mode 100644 index 0000000000..58f66b6afb --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java @@ -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 maybeChainId; + + public CodeDelegationProcessor(final Optional maybeChainId) { + this.maybeChainId = maybeChainId; + } + + /** + * At the start of executing the transaction, after incrementing the sender’s nonce, for each + * authorization we do the following: + * + *
    + *
  1. Verify the chain id is either 0 or the chain's current ID. + *
  2. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]` + *
  3. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).) + *
  4. Verify the code of `authority` is either empty or already delegated. + *
  5. Verify the nonce of `authority` is equal to `nonce`. + *
  6. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if + * `authority` exists in the trie. + *
  7. Set the code of `authority` to be `0xef0100 || address`. This is a delegation + * designation. + *
  8. Increase the nonce of `authority` by one. + *
+ * + * @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
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 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(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java new file mode 100644 index 0000000000..20d75a6757 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java @@ -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
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
accessedDelegatorAddresses() { + return accessedDelegatorAddresses; + } + + public long alreadyExistingDelegators() { + return alreadyExistingDelegators; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index ddc8b49b4f..e6d7a88cbd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -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 diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index b008a8d5f6..91c964525e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -81,7 +81,7 @@ public class MainnetTransactionProcessor { protected final FeeMarket feeMarket; private final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator; - private final Optional maybeAuthorityProcessor; + private final Optional 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
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
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 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 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 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, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 481ce70e05..4f4c554f3f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -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 = + 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 validateCostAndFee( final Transaction transaction, final Optional 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 validateTransactionSignature( final Transaction transaction) { if (chainId.isPresent() diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index a6c2cb69b2..8273c556d9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -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, diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java index 2709b94e29..ea62d7f1fa 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java @@ -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. }; } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java index 3f7049be39..602116749b 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java @@ -92,7 +92,7 @@ public class TransactionTestFixture { builder.versionedHashes(versionedHashes.get()); } break; - case SET_CODE: + case DELEGATE_CODE: break; } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java similarity index 67% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java index 610b5906f5..d6ff585b4f 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java @@ -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); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java similarity index 75% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java index 89211f4ba3..34b7c6d449 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java @@ -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 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")); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java index b2f5718dad..d8d9f3777e 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java @@ -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 diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java index e15fe417a8..91b4efd7b2 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java @@ -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; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java index 864c8c9181..690ab02a95 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java @@ -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( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java index a7c6556e73..b5503ba81c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java @@ -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; diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java index 6f222166b3..fb0ccf4912 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java @@ -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 authorizations = - new ArrayList<>(authorizationList.size()); + List 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 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++; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java index 8f20eba628..8a65adf617 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java @@ -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
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(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java new file mode 100644 index 0000000000..0e5219d835 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java @@ -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
delegatedCodeAddress() { + return Optional.of(delegatedCodeAddress); + } + + private Optional getDelegatedAccount() { + return Optional.ofNullable(worldUpdater.getAccount(delegatedCodeAddress)); + } + + private Bytes resolveDelegatedCode() { + + return getDelegatedAccount().map(Account::getUnprocessedCode).orElse(Bytes.EMPTY); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java similarity index 64% rename from evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java rename to evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java index 46acac74f9..1eba364c19 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java @@ -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
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 storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { return wrappedAccount.storageEntriesFrom(startKeyHash, limit); } + + @Override + public boolean hasDelegatedCode() { + return true; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java similarity index 69% rename from evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java rename to evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java index 6d4e30a9c2..0e1e1145dd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java @@ -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
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 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; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index 0af023851c..fd453deac5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -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; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java index 0c223d780b..33fe98b996 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java @@ -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 * - *

Placeholder for new gas schedule items. If Prague finalzies without changes this can be - * removed - * *

    - *
  • TBD + *
  • Gas costs for EIP-7702 (Code Delegation) *
*/ 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(); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 12cb06eaa6..932bb4c391 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -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 diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java index 7b58e956f5..c82e8c1633 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java @@ -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) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java index 2f1c139182..ed1e469777 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java @@ -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 diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index c08331b006..9a8cfe8386 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -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,23 +81,34 @@ public class ExtCodeHashOperation extends AbstractOperation { final long cost = cost(accountIsWarm); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + 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 { - final Account account = frame.getWorldUpdater().get(address); - if (account == null || account.isEmpty()) { - frame.pushStackItem(Bytes.EMPTY); + final Bytes code = account.getCode(); + if (enableEIP3540 + && code.size() >= 2 + && code.get(0) == EOFLayout.EOF_PREFIX_BYTE + && code.get(1) == 0) { + frame.pushStackItem(EOF_REPLACEMENT_HASH); } else { - final Bytes code = account.getCode(); - if (enableEIP3540 - && code.size() >= 2 - && code.get(0) == EOFLayout.EOF_PREFIX_BYTE - && code.get(1) == 0) { - frame.pushStackItem(EOF_REPLACEMENT_HASH); - } else { - frame.pushStackItem(account.getCodeHash()); - } + frame.pushStackItem(account.getCodeHash()); } - return new OperationResult(cost, null); } + return new OperationResult(cost, null); + } catch (final UnderflowException ufe) { return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); } catch (final OverflowException ofe) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index 1779175f15..c2795f364b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -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; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java deleted file mode 100644 index ff5d94343b..0000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java +++ /dev/null @@ -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 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
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)); - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java new file mode 100644 index 0000000000..fc67770314 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java @@ -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. + * + *

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); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java new file mode 100644 index 0000000000..1c89fad8bc --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java @@ -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); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java index bac21e73fc..928118acfe 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java @@ -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)); } }