All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tuweni.evm.TransactionalEVMHostContext.kt Maven / Gradle / Ivy

Go to download

Repository for managing, storing and indexing Ethereum domain objects.

There is a newer version: 2.3.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package org.apache.tuweni.evm

import org.apache.tuweni.bytes.Bytes
import org.apache.tuweni.bytes.Bytes32
import org.apache.tuweni.eth.AccountState
import org.apache.tuweni.eth.Address
import org.apache.tuweni.eth.Hash
import org.apache.tuweni.eth.Log
import org.apache.tuweni.eth.repository.BlockchainRepository
import org.apache.tuweni.eth.repository.StateRepository
import org.apache.tuweni.eth.repository.StateRepository.Companion.EMPTY_CODE_HASH
import org.apache.tuweni.eth.repository.StateRepository.Companion.EMPTY_STORAGE_HASH
import org.apache.tuweni.rlp.RLP
import org.apache.tuweni.units.bigints.UInt256
import org.apache.tuweni.units.ethereum.Gas
import org.apache.tuweni.units.ethereum.Wei
import org.slf4j.LoggerFactory
import java.math.BigInteger

/**
 * EVM context that records changes to the world state, so they can be applied atomically.
 */
class TransactionalEVMHostContext(
  val blockchainRepository: BlockchainRepository,
  val transientRepository: StateRepository,
  val ethereumVirtualMachine: EthereumVirtualMachine,
  val sender: Address,
  val destination: Address,
  val value: Bytes,
  val code: Bytes,
  val gas: Gas,
  private val gasPrice: Wei,
  val currentCoinbase: Address,
  val currentNumber: UInt256,
  val currentTimestamp: UInt256,
  val currentGasLimit: Long,
  val currentDifficulty: UInt256,
  val chainId: UInt256,
) : HostContext, ExecutionChanges {

  companion object {
    private val logger = LoggerFactory.getLogger(TransactionalEVMHostContext::class.java)
  }

  private val logs = mutableListOf()
  val accountsToDestroy = mutableListOf
() val warmedUpStorage = HashSet() init { warmedUpStorage.add(sender) warmedUpStorage.add(destination) } override fun getLogs(): List = logs override fun accountsToDestroy(): List
= accountsToDestroy private val refunds = mutableMapOf() /** * Check account existence function. * * * This function is used by the VM to check if there exists an account at given address. * * @param address The address of the account the query is about. * @return true if exists, false otherwise. */ override suspend fun accountExists(address: Address): Boolean { logger.trace("Entering accountExists") return transientRepository.accountsExists(address) } override suspend fun getRepositoryStorage(address: Address, key: Bytes32): Bytes? { logger.trace("Entering getRepositoryStorage") val value = blockchainRepository.getAccountStoreValue(address, key) logger.trace("key $key, value $value") return value } override suspend fun isEmptyAccount(address: Address): Boolean { logger.trace("Entering isEmptyAccount") val accountState = transientRepository.getAccount(address) return null == accountState || (accountState.balance.isZero && accountState.nonce.isZero && EMPTY_CODE_HASH.equals(accountState.codeHash) && EMPTY_STORAGE_HASH.equals(accountState.storageRoot)) } /** * Get storage function. * * * This function is used by a VM to query the given account storage entry. * * @param address The address of the account. * @param key The index of the account's storage entry. * @return The storage value at the given storage key or null bytes if the account does not exist. */ override suspend fun getStorage(address: Address, key: Bytes): Bytes32? { logger.trace("Entering getStorage") val value = transientRepository.getAccountStoreValue(address, Hash.hash(key))?.let { UInt256.fromBytes(RLP.decodeValue(it)) } logger.trace("key $key value $value") return value } /** * Increments the nonce of the account associated with the address. */ override suspend fun incrementNonce(address: Address): UInt256 { val account = transientRepository.getAccount(address) ?: transientRepository.newAccountState() val newNonce = account.nonce.add(1) transientRepository.storeAccount(address, AccountState(newNonce, account.balance, account.storageRoot, account.codeHash)) return newNonce } /** * Set storage function. * * * This function is used by a VM to update the given account storage entry. The VM MUST make * sure that the account exists. This requirement is only a formality because VM implementations * only modify storage of the account of the current execution context (i.e. referenced by * evmc_message::destination). * * @param address The address of the account. * @param key The index of the storage entry. * @param value The value to be stored. * @return The effect on the storage item: * The value of a storage item has been left unchanged: 0 -> 0 and X -> X. * EVMC_STORAGE_UNCHANGED = 0, * The value of a storage item has been modified: X -> Y. * EVMC_STORAGE_MODIFIED = 1, * A storage item has been modified after being modified before: X -> Y -> Z. * EVMC_STORAGE_MODIFIED_AGAIN = 2, * A new storage item has been added: 0 -> X. * EVMC_STORAGE_ADDED = 3, * A storage item has been deleted: X -> 0. * EVMC_STORAGE_DELETED = 4 */ override suspend fun setStorage(address: Address, key: Bytes, value: Bytes): Int { logger.trace("Entering setStorage {} {} {}", address, key, value) val hashKey = Hash.hash(key) val newAccount = transientRepository.accountsExists(address) val repositoryValue = blockchainRepository.getAccountStoreValue(address, hashKey) val oldValue = transientRepository.getAccountStoreValue(address, hashKey) val storageAdded = newAccount || oldValue == null val storageWasModifiedBefore = !(repositoryValue?.equals(oldValue) == true || oldValue == null) val storageModified = !(value == UInt256.ZERO && oldValue == null) && !value.equals(oldValue) if (!storageModified) { return 0 } if (value.isEmpty) { transientRepository.deleteAccountStore(address, hashKey) } else { transientRepository.storeAccountValue(address, hashKey, RLP.encodeValue(value)) } if (value.size() == 0) { return 4 } if (storageModified) { if (storageAdded) { return 3 } if (storageWasModifiedBefore) { return 2 } return 1 } return 0 } /** * Get balance function. * * * This function is used by a VM to query the balance of the given account. * * @param address The address of the account. * @return The balance of the given account or 0 if the account does not exist. */ override suspend fun getBalance(address: Address): Wei { logger.trace("Entering getBalance") val account = transientRepository.getAccount(address) return account?.balance ?: Wei.valueOf(0) } override suspend fun setBalance(address: Address, balance: Wei) { logger.trace("Entering setBalance $address with $balance") val account = transientRepository.getAccount(address) ?: transientRepository.newAccountState() val newAccount = AccountState(account.nonce, balance, account.storageRoot, account.codeHash, account.version) transientRepository.storeAccount(address, newAccount) } /** * Get code size function. * * * This function is used by a VM to get the size of the code stored in the account at the given * address. * * @param address The address of the account. * @return The size of the code in the account or 0 if the account does not exist. */ override suspend fun getCodeSize(address: Address): Int { logger.trace("Entering getCodeSize") val code = transientRepository.getAccountCode(address) return code?.size() ?: 0 } /** * Get code hash function. * * * This function is used by a VM to get the keccak256 hash of the code stored in the account at * the given address. For existing accounts not having a code, this function returns keccak256 * hash of empty data. * * @param address The address of the account. * @return The hash of the code in the account or null bytes if the account does not exist. */ override suspend fun getCodeHash(address: Address): Bytes32 { logger.trace("Entering getCodeHash") val account = transientRepository.getAccount(address) return account?.codeHash ?: Bytes32.ZERO } /** * Get account nonce * * * This function is used by a VM to get the nonce of the account at * the given address. * * @param address The address of the account. * @return The nonce of the accountt. */ override suspend fun getNonce(address: Address): UInt256 { logger.trace("Entering getNonce") val account = transientRepository.getAccount(address) return account?.nonce ?: UInt256.ONE } /** * Copy code function. * * * This function is used by an EVM to request a copy of the code of the given account to the * memory buffer provided by the EVM. The Client MUST copy the requested code, starting with the * given offset, to the provided memory buffer up to the size of the buffer or the size of the * code, whichever is smaller. * * @param address The address of the account. * @return A copy of the requested code. */ override suspend fun getCode(address: Address): Bytes { logger.trace("Entering getCode") val code = transientRepository.getAccountCode(address) return code ?: Bytes.EMPTY } /** * Selfdestruct function. * * * This function is used by an EVM to SELFDESTRUCT given contract. The execution of the * contract will not be stopped, that is up to the EVM. * * @param address The address of the contract to be selfdestructed. * @param beneficiary The address where the remaining ETH is going to be transferred. */ override suspend fun selfdestruct(address: Address, beneficiary: Address) { logger.trace("Entering selfdestruct") accountsToDestroy.add(address) val account = transientRepository.getAccount(address) account?.apply { val beneficiaryAccountState = transientRepository.getAccount(beneficiary) ?: transientRepository.newAccountState() transientRepository.storeAccount( beneficiary, AccountState( beneficiaryAccountState.nonce, beneficiaryAccountState.balance.add(account.balance), beneficiaryAccountState.storageRoot, beneficiaryAccountState.codeHash ) ) val resetBalance = AccountState(this.nonce, Wei.valueOf(0), this.storageRoot, this.codeHash, this.version) transientRepository.storeAccount(address, resetBalance) } logger.trace("Done selfdestruct") } /** * This function supports EVM calls. * * @param msg The call parameters. * @return The result of the call. */ override suspend fun call(evmMessage: EVMMessage): EVMResult { logger.trace("Entering call ${evmMessage.kind}") val code = transientRepository.getAccountCode(evmMessage.contract) val result = ethereumVirtualMachine.executeInternal( evmMessage.origin, evmMessage.sender, evmMessage.destination, evmMessage.contract, evmMessage.value, code ?: Bytes.EMPTY, evmMessage.inputData, evmMessage.gas, evmMessage.kind, depth = evmMessage.depth, hostContext = this ) return result } override suspend fun create(evmMessage: EVMMessage, code: Bytes): EVMResult { logger.trace("Entering create ${evmMessage.kind}") val result = ethereumVirtualMachine.executeInternal( evmMessage.origin, evmMessage.sender, evmMessage.destination, evmMessage.contract, evmMessage.value, code, evmMessage.inputData, evmMessage.gas, evmMessage.kind, depth = evmMessage.depth, hostContext = this, ) return result } /** * Get transaction context function. * * * This function is used by an EVM to retrieve the transaction and block context. * * @return The transaction context. */ override fun getTxContext(): Bytes? { logger.trace("Entering getTxContext") return Bytes.concatenate( gasPrice.toBytes(), sender, currentCoinbase, currentNumber, currentTimestamp, Bytes.ofUnsignedLong(currentGasLimit), currentDifficulty.toBytes(), UInt256.ONE.toBytes() ) } override fun getBlockHash(number: UInt256): Bytes32 { logger.trace("Entering getBlockHash") val listOfCandidates = blockchainRepository.findBlockByHashOrNumber(number) return listOfCandidates.firstOrNull() ?: Bytes32.ZERO } override fun emitLog(address: Address, data: Bytes, topics: List) { logger.trace("Entering emitLog") val log = Log(Address.fromBytes(Bytes.wrap(address)), data, topics) logs.add(log) } override fun warmUpAccount(address: Address): Boolean = !ethereumVirtualMachine.precompiles.contains(address) && warmedUpStorage.add(address) override fun warmUpStorage(address: Address, key: UInt256): Boolean { logger.trace("entering warmUpStorage $address $key") if (ethereumVirtualMachine.precompiles.contains(address)) { return false } return warmedUpStorage.add(Bytes.concatenate(address, Bytes.fromHexString("0x0f"), key)) } override fun getGasPrice() = gasPrice override fun getGasLimit() = currentGasLimit override fun getBlockNumber() = currentNumber override fun getBlockHash() = getBlockHash(currentNumber) override fun getCoinbase() = currentCoinbase override fun timestamp() = currentTimestamp override fun getDifficulty() = currentDifficulty override fun getChaindId(): UInt256 = chainId override fun addRefund(address: Address, refund: Wei) { refunds[address] = (refunds[address] ?: BigInteger.ZERO).add(refund.toBigInteger()) } override fun addRefund(address: Address, refund: Long) { refunds[address] = (refunds[address] ?: BigInteger.ZERO).add(BigInteger.valueOf(refund)) } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy