mirror of https://github.com/hyperledger/besu
7702 for devenet-3 (#7444)
* wrapped WorldUpdater into `EVMWorldupdater` to remove the authority code injection from the implementation of the actual world updaters Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * add CHANGELOG entry Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * first draft for 7702 v2 Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * change return value of DelegatedCodeGasCostHelper Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * fix upfront gas cost calculation, fix setting code multiple times in MutableDelegatedCodeAccount Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * fix gas refund for delegated code when account already exists, added gas cost deduction for code delegation resolution to ExtCodeSizeOperation Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * allow accounts with delegated code to send transactions Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * add refund for already existing account after nonce check Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * resolve delegated code only the first time to avoid delegation loops Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * handle invalid authorization signatures properly Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * refactored CodeDelegationProcessor to compute authorizer of a code delegation after the chain id check Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * fix canSetDelegatedCode method by checking code how it is in the trie and not the resolved code Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * optimize code hash calculation for empty code, fix check for empty code delegation list Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * check the all code delegation signatures hava a valid s value Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * rename encoder & decoder, handle invalid signature values in T8nExecutor Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * create the signatures for code delegation authorizations in T8nExecutor without checking if they are valid to test them later during the tx execution Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * check that recid is either 0 or 1 Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * fixed acceptance tests, renamed the the remaining instances of set code to code delegation Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * fix delegate encoder & encoder unit tests Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * spotless Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * changed empty check for delegated accounts, fixed test Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * fix hasDelegatedCode method when code is null Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * run acceptance tests without deamon Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * disable CodeDelegationTransactionAcceptanceTest to check if it is causing the stuck ci pipeline Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * check if shouldTransferAllEthOfAuthorizerToSponsor is causing pipeline to stall Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * check if shouldCheckNonceAfterNonceIncreaseOfSender is causing pipeline to stall Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * check if closing the cluster after every test is causing pipeline to stall Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> * spotless Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> --------- Signed-off-by: Daniel Lehrner <daniel.lehrner@consensys.net> Co-authored-by: Danno Ferrin <danno@numisight.com>pull/7586/head
parent
cf592c48d1
commit
8eee569887
@ -1,88 +0,0 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
import org.hyperledger.besu.evm.account.Account; |
||||
import org.hyperledger.besu.evm.account.AccountState; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.worldstate.EVMWorldUpdater; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class AuthorityProcessor { |
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthorityProcessor.class); |
||||
|
||||
private final Optional<BigInteger> maybeChainId; |
||||
|
||||
public AuthorityProcessor(final Optional<BigInteger> maybeChainId) { |
||||
this.maybeChainId = maybeChainId; |
||||
} |
||||
|
||||
public void addContractToAuthority( |
||||
final EVMWorldUpdater evmWorldUpdater, final Transaction transaction) { |
||||
transaction |
||||
.getAuthorizationList() |
||||
.get() |
||||
.forEach( |
||||
payload -> |
||||
payload |
||||
.authorizer() |
||||
.ifPresent( |
||||
authorityAddress -> { |
||||
LOG.trace("Set code authority: {}", authorityAddress); |
||||
|
||||
if (maybeChainId.isPresent() |
||||
&& !payload.chainId().equals(BigInteger.ZERO) |
||||
&& !maybeChainId.get().equals(payload.chainId())) { |
||||
return; |
||||
} |
||||
|
||||
final Optional<MutableAccount> maybeAccount = |
||||
Optional.ofNullable(evmWorldUpdater.getAccount(authorityAddress)); |
||||
final long accountNonce = |
||||
maybeAccount.map(AccountState::getNonce).orElse(0L); |
||||
|
||||
if (payload.nonce().isPresent() |
||||
&& !payload.nonce().get().equals(accountNonce)) { |
||||
return; |
||||
} |
||||
|
||||
if (evmWorldUpdater |
||||
.authorizedCodeService() |
||||
.hasAuthorizedCode(authorityAddress)) { |
||||
return; |
||||
} |
||||
|
||||
Optional<Account> codeAccount = |
||||
Optional.ofNullable(evmWorldUpdater.get(payload.address())); |
||||
final Bytes code; |
||||
if (codeAccount.isPresent()) { |
||||
code = codeAccount.get().getCode(); |
||||
} else { |
||||
code = Bytes.EMPTY; |
||||
} |
||||
|
||||
evmWorldUpdater |
||||
.authorizedCodeService() |
||||
.addAuthorizedCode(authorityAddress, code); |
||||
})); |
||||
} |
||||
} |
@ -0,0 +1,134 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.ethereum.core.CodeDelegation; |
||||
import org.hyperledger.besu.ethereum.core.Transaction; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.worldstate.EVMWorldUpdater; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Optional; |
||||
|
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class CodeDelegationProcessor { |
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeDelegationProcessor.class); |
||||
|
||||
private final Optional<BigInteger> maybeChainId; |
||||
|
||||
public CodeDelegationProcessor(final Optional<BigInteger> maybeChainId) { |
||||
this.maybeChainId = maybeChainId; |
||||
} |
||||
|
||||
/** |
||||
* At the start of executing the transaction, after incrementing the sender’s nonce, for each |
||||
* authorization we do the following: |
||||
* |
||||
* <ol> |
||||
* <li>Verify the chain id is either 0 or the chain's current ID. |
||||
* <li>`authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]` |
||||
* <li>Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).) |
||||
* <li>Verify the code of `authority` is either empty or already delegated. |
||||
* <li>Verify the nonce of `authority` is equal to `nonce`. |
||||
* <li>Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if |
||||
* `authority` exists in the trie. |
||||
* <li>Set the code of `authority` to be `0xef0100 || address`. This is a delegation |
||||
* designation. |
||||
* <li>Increase the nonce of `authority` by one. |
||||
* </ol> |
||||
* |
||||
* @param evmWorldUpdater The world state updater which is aware of code delegation. |
||||
* @param transaction The transaction being processed. |
||||
* @return The result of the code delegation processing. |
||||
*/ |
||||
public CodeDelegationResult process( |
||||
final EVMWorldUpdater evmWorldUpdater, final Transaction transaction) { |
||||
final CodeDelegationResult result = new CodeDelegationResult(); |
||||
|
||||
transaction |
||||
.getCodeDelegationList() |
||||
.get() |
||||
.forEach( |
||||
codeDelegation -> |
||||
processAuthorization( |
||||
evmWorldUpdater, |
||||
(org.hyperledger.besu.ethereum.core.CodeDelegation) codeDelegation, |
||||
result)); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private void processAuthorization( |
||||
final EVMWorldUpdater evmWorldUpdater, |
||||
final CodeDelegation codeDelegation, |
||||
final CodeDelegationResult result) { |
||||
LOG.trace("Processing code delegation: {}", codeDelegation); |
||||
|
||||
if (maybeChainId.isPresent() |
||||
&& !codeDelegation.chainId().equals(BigInteger.ZERO) |
||||
&& !maybeChainId.get().equals(codeDelegation.chainId())) { |
||||
LOG.trace( |
||||
"Invalid chain id for code delegation. Expected: {}, Actual: {}", |
||||
maybeChainId.get(), |
||||
codeDelegation.chainId()); |
||||
return; |
||||
} |
||||
|
||||
final Optional<Address> authorizer = codeDelegation.authorizer(); |
||||
if (authorizer.isEmpty()) { |
||||
LOG.trace("Invalid signature for code delegation"); |
||||
return; |
||||
} |
||||
|
||||
LOG.trace("Set code delegation for authority: {}", authorizer.get()); |
||||
|
||||
final Optional<MutableAccount> maybeAuthorityAccount = |
||||
Optional.ofNullable(evmWorldUpdater.getAccount(authorizer.get())); |
||||
|
||||
result.addAccessedDelegatorAddress(authorizer.get()); |
||||
|
||||
MutableAccount authority; |
||||
boolean authorityDoesAlreadyExist = false; |
||||
if (maybeAuthorityAccount.isEmpty()) { |
||||
authority = evmWorldUpdater.createAccount(authorizer.get()); |
||||
} else { |
||||
authority = maybeAuthorityAccount.get(); |
||||
|
||||
if (!evmWorldUpdater.authorizedCodeService().canSetDelegatedCode(authority)) { |
||||
return; |
||||
} |
||||
|
||||
authorityDoesAlreadyExist = true; |
||||
} |
||||
|
||||
if (codeDelegation.nonce() != authority.getNonce()) { |
||||
LOG.trace( |
||||
"Invalid nonce for code delegation. Expected: {}, Actual: {}", |
||||
authority.getNonce(), |
||||
codeDelegation.nonce()); |
||||
return; |
||||
} |
||||
|
||||
if (authorityDoesAlreadyExist) { |
||||
result.incremenentAlreadyExistingDelegators(); |
||||
} |
||||
|
||||
evmWorldUpdater.authorizedCodeService().addDelegatedCode(authority, codeDelegation.address()); |
||||
authority.incrementNonce(); |
||||
} |
||||
} |
@ -0,0 +1,41 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.ethereum.mainnet; |
||||
|
||||
import org.hyperledger.besu.collections.trie.BytesTrieSet; |
||||
import org.hyperledger.besu.datatypes.Address; |
||||
|
||||
import java.util.Set; |
||||
|
||||
public class CodeDelegationResult { |
||||
private final Set<Address> accessedDelegatorAddresses = new BytesTrieSet<>(Address.SIZE); |
||||
private long alreadyExistingDelegators = 0L; |
||||
|
||||
public void addAccessedDelegatorAddress(final Address address) { |
||||
accessedDelegatorAddresses.add(address); |
||||
} |
||||
|
||||
public void incremenentAlreadyExistingDelegators() { |
||||
alreadyExistingDelegators += 1; |
||||
} |
||||
|
||||
public Set<Address> accessedDelegatorAddresses() { |
||||
return accessedDelegatorAddresses; |
||||
} |
||||
|
||||
public long alreadyExistingDelegators() { |
||||
return alreadyExistingDelegators; |
||||
} |
||||
} |
@ -0,0 +1,92 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.evm.account; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.evm.worldstate.WorldUpdater; |
||||
|
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
class BaseDelegatedCodeAccount { |
||||
private final WorldUpdater worldUpdater; |
||||
|
||||
/** The address of the account that has delegated code to be loaded into it. */ |
||||
protected final Address delegatedCodeAddress; |
||||
|
||||
protected BaseDelegatedCodeAccount( |
||||
final WorldUpdater worldUpdater, final Address delegatedCodeAddress) { |
||||
this.worldUpdater = worldUpdater; |
||||
this.delegatedCodeAddress = delegatedCodeAddress; |
||||
} |
||||
|
||||
/** |
||||
* Returns the delegated code. |
||||
* |
||||
* @return the delegated code. |
||||
*/ |
||||
protected Bytes getCode() { |
||||
return resolveDelegatedCode(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the hash of the delegated code. |
||||
* |
||||
* @return the hash of the delegated code. |
||||
*/ |
||||
protected Hash getCodeHash() { |
||||
final Bytes code = getCode(); |
||||
return (code == null || code.isEmpty()) ? Hash.EMPTY : Hash.hash(code); |
||||
} |
||||
|
||||
/** |
||||
* Returns the balance of the delegated account. |
||||
* |
||||
* @return the balance of the delegated account. |
||||
*/ |
||||
protected Wei getDelegatedBalance() { |
||||
return getDelegatedAccount().map(Account::getBalance).orElse(Wei.ZERO); |
||||
} |
||||
|
||||
/** |
||||
* Returns the nonce of the delegated account. |
||||
* |
||||
* @return the nonce of the delegated account. |
||||
*/ |
||||
protected long getDelegatedNonce() { |
||||
return getDelegatedAccount().map(Account::getNonce).orElse(Account.DEFAULT_NONCE); |
||||
} |
||||
|
||||
/** |
||||
* Returns the address of the delegated code. |
||||
* |
||||
* @return the address of the delegated code. |
||||
*/ |
||||
protected Optional<Address> delegatedCodeAddress() { |
||||
return Optional.of(delegatedCodeAddress); |
||||
} |
||||
|
||||
private Optional<Account> getDelegatedAccount() { |
||||
return Optional.ofNullable(worldUpdater.getAccount(delegatedCodeAddress)); |
||||
} |
||||
|
||||
private Bytes resolveDelegatedCode() { |
||||
|
||||
return getDelegatedAccount().map(Account::getUnprocessedCode).orElse(Bytes.EMPTY); |
||||
} |
||||
} |
@ -1,116 +0,0 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.evm.worldstate; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.evm.account.Account; |
||||
import org.hyperledger.besu.evm.account.AuthorizedCodeAccount; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.account.MutableAuthorizedCodeAccount; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
/** A service that manages the code injection of authorized code. */ |
||||
public class AuthorizedCodeService { |
||||
private final Map<Address, Bytes> authorizedCode = new HashMap<>(); |
||||
|
||||
/** Creates a new AuthorizedCodeService. */ |
||||
public AuthorizedCodeService() {} |
||||
|
||||
/** |
||||
* Authorizes to load the code of authorizedCode into the authorizer account. |
||||
* |
||||
* @param authorizer the address that gives the authorization. |
||||
* @param authorizedCode the code which will be loaded. |
||||
*/ |
||||
public void addAuthorizedCode(final Address authorizer, final Bytes authorizedCode) { |
||||
this.authorizedCode.put(authorizer, authorizedCode); |
||||
} |
||||
|
||||
/** |
||||
* Return all the authorities that have given their authorization to load the code of another |
||||
* account. |
||||
* |
||||
* @return the set of authorities. |
||||
*/ |
||||
public Set<Address> getAuthorities() { |
||||
return authorizedCode.keySet(); |
||||
} |
||||
|
||||
/** Resets all the authorized accounts. */ |
||||
public void resetAuthorities() { |
||||
authorizedCode.clear(); |
||||
} |
||||
|
||||
/** |
||||
* Checks if the provided address has set an authorized to load code into an EOA account. |
||||
* |
||||
* @param authority the address to check. |
||||
* @return {@code true} if the address has been authorized, {@code false} otherwise. |
||||
*/ |
||||
public boolean hasAuthorizedCode(final Address authority) { |
||||
return authorizedCode.containsKey(authority); |
||||
} |
||||
|
||||
/** |
||||
* Processes the provided account, injecting the authorized code if authorized. |
||||
* |
||||
* @param worldUpdater the world updater to retrieve the code account. |
||||
* @param originalAccount the account to process. |
||||
* @param address the address of the account in case the provided account is null |
||||
* @return the processed account, containing the authorized code if authorized. |
||||
*/ |
||||
public Account processAccount( |
||||
final WorldUpdater worldUpdater, final Account originalAccount, final Address address) { |
||||
if (!authorizedCode.containsKey(address)) { |
||||
return originalAccount; |
||||
} |
||||
|
||||
Account account = originalAccount; |
||||
if (account == null) { |
||||
account = worldUpdater.createAccount(address); |
||||
} |
||||
|
||||
return new AuthorizedCodeAccount(account, authorizedCode.get(address)); |
||||
} |
||||
|
||||
/** |
||||
* Processes the provided mutable account, injecting the authorized code if authorized. |
||||
* |
||||
* @param worldUpdater the world updater to retrieve the code account. |
||||
* @param originalAccount the mutable account to process. |
||||
* @param address the address of the account in case the provided account is null |
||||
* @return the processed mutable account, containing the authorized code if authorized. |
||||
*/ |
||||
public MutableAccount processMutableAccount( |
||||
final WorldUpdater worldUpdater, |
||||
final MutableAccount originalAccount, |
||||
final Address address) { |
||||
if (!authorizedCode.containsKey(address)) { |
||||
return originalAccount; |
||||
} |
||||
|
||||
MutableAccount account = originalAccount; |
||||
if (account == null) { |
||||
account = worldUpdater.createAccount(address); |
||||
} |
||||
|
||||
return new MutableAuthorizedCodeAccount(account, authorizedCode.get(address)); |
||||
} |
||||
} |
@ -0,0 +1,80 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.evm.worldstate; |
||||
|
||||
import org.hyperledger.besu.evm.account.Account; |
||||
import org.hyperledger.besu.evm.frame.MessageFrame; |
||||
import org.hyperledger.besu.evm.gascalculator.GasCalculator; |
||||
|
||||
/** |
||||
* Helper class to deduct gas cost for delegated code resolution. |
||||
* |
||||
* <p>Delegated code resolution is the process of determining the address of the contract that will |
||||
* be executed when a contract has delegated code. This process is necessary to determine the |
||||
* contract that will be executed and to ensure that the contract is warm in the cache. |
||||
*/ |
||||
public class DelegatedCodeGasCostHelper { |
||||
|
||||
/** Private constructor to prevent instantiation. */ |
||||
private DelegatedCodeGasCostHelper() { |
||||
// empty constructor
|
||||
} |
||||
|
||||
/** The status of the operation. */ |
||||
public enum Status { |
||||
/** The operation failed due to insufficient gas. */ |
||||
INSUFFICIENT_GAS, |
||||
/** The operation was successful. */ |
||||
SUCCESS |
||||
} |
||||
|
||||
/** |
||||
* The result of the operation. |
||||
* |
||||
* @param gasCost the gas cost |
||||
* @param status of the operation |
||||
*/ |
||||
public record Result(long gasCost, Status status) {} |
||||
|
||||
/** |
||||
* Deducts the gas cost for delegated code resolution. |
||||
* |
||||
* @param frame the message frame |
||||
* @param gasCalculator the gas calculator |
||||
* @param account the account |
||||
* @return the gas cost and result of the operation |
||||
*/ |
||||
public static Result deductDelegatedCodeGasCost( |
||||
final MessageFrame frame, final GasCalculator gasCalculator, final Account account) { |
||||
if (!account.hasDelegatedCode()) { |
||||
return new Result(0, Status.SUCCESS); |
||||
} |
||||
|
||||
if (account.delegatedCodeAddress().isEmpty()) { |
||||
throw new RuntimeException("A delegated code account must have a delegated code address"); |
||||
} |
||||
|
||||
final boolean delegatedCodeIsWarm = frame.warmUpAddress(account.delegatedCodeAddress().get()); |
||||
final long delegatedCodeResolutionGas = |
||||
gasCalculator.delegatedCodeResolutionGasCost(delegatedCodeIsWarm); |
||||
|
||||
if (frame.getRemainingGas() < delegatedCodeResolutionGas) { |
||||
return new Result(delegatedCodeResolutionGas, Status.INSUFFICIENT_GAS); |
||||
} |
||||
|
||||
frame.decrementRemainingGas(delegatedCodeResolutionGas); |
||||
return new Result(delegatedCodeResolutionGas, Status.SUCCESS); |
||||
} |
||||
} |
@ -0,0 +1,97 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.evm.worldstate; |
||||
|
||||
import org.hyperledger.besu.datatypes.Address; |
||||
import org.hyperledger.besu.evm.account.Account; |
||||
import org.hyperledger.besu.evm.account.DelegatedCodeAccount; |
||||
import org.hyperledger.besu.evm.account.MutableAccount; |
||||
import org.hyperledger.besu.evm.account.MutableDelegatedCodeAccount; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
|
||||
/** A service that manages the code injection of delegated code. */ |
||||
public class DelegatedCodeService { |
||||
private static final Bytes DELEGATED_CODE_PREFIX = Bytes.fromHexString("ef0100"); |
||||
private static final int DELEGATED_CODE_SIZE = DELEGATED_CODE_PREFIX.size() + Address.SIZE; |
||||
|
||||
/** Creates a new DelegatedCodeService. */ |
||||
public DelegatedCodeService() {} |
||||
|
||||
/** |
||||
* Add the delegated code to the given account. |
||||
* |
||||
* @param account the account to which the delegated code is added. |
||||
* @param delegatedCodeAddress the address of the delegated code. |
||||
*/ |
||||
public void addDelegatedCode(final MutableAccount account, final Address delegatedCodeAddress) { |
||||
account.setCode(Bytes.concatenate(DELEGATED_CODE_PREFIX, delegatedCodeAddress)); |
||||
} |
||||
|
||||
/** |
||||
* Returns if the provided account has either no code set or has already delegated code. |
||||
* |
||||
* @param account the account to check. |
||||
* @return {@code true} if the account can set delegated code, {@code false} otherwise. |
||||
*/ |
||||
public boolean canSetDelegatedCode(final Account account) { |
||||
return account.getCode().isEmpty() || hasDelegatedCode(account.getUnprocessedCode()); |
||||
} |
||||
|
||||
/** |
||||
* Processes the provided account, resolving the code if delegated. |
||||
* |
||||
* @param worldUpdater the world updater to retrieve the delegated code. |
||||
* @param account the account to process. |
||||
* @return the processed account, containing the delegated code if set, the unmodified account |
||||
* otherwise. |
||||
*/ |
||||
public Account processAccount(final WorldUpdater worldUpdater, final Account account) { |
||||
if (account == null || !hasDelegatedCode(account.getCode())) { |
||||
return account; |
||||
} |
||||
|
||||
return new DelegatedCodeAccount( |
||||
worldUpdater, account, resolveDelegatedAddress(account.getCode())); |
||||
} |
||||
|
||||
/** |
||||
* Processes the provided mutable account, resolving the code if delegated. |
||||
* |
||||
* @param worldUpdater the world updater to retrieve the delegated code. |
||||
* @param account the mutable account to process. |
||||
* @return the processed mutable account, containing the delegated code if set, the unmodified |
||||
* mutable account otherwise. |
||||
*/ |
||||
public MutableAccount processMutableAccount( |
||||
final WorldUpdater worldUpdater, final MutableAccount account) { |
||||
if (account == null || !hasDelegatedCode(account.getCode())) { |
||||
return account; |
||||
} |
||||
|
||||
return new MutableDelegatedCodeAccount( |
||||
worldUpdater, account, resolveDelegatedAddress(account.getCode())); |
||||
} |
||||
|
||||
private Address resolveDelegatedAddress(final Bytes code) { |
||||
return Address.wrap(code.slice(DELEGATED_CODE_PREFIX.size())); |
||||
} |
||||
|
||||
private boolean hasDelegatedCode(final Bytes code) { |
||||
return code != null |
||||
&& code.size() == DELEGATED_CODE_SIZE |
||||
&& code.slice(0, DELEGATED_CODE_PREFIX.size()).equals(DELEGATED_CODE_PREFIX); |
||||
} |
||||
} |
Loading…
Reference in new issue