diff --git a/docs/configuration.md b/docs/configuration.md index f57320b415f..180969349b8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -716,6 +716,7 @@ value, it is recommended to only populate overridden properties in the custom `a | `hedera.mirror.web3.evm.maxTokensPerAccount` | 1000 | Maximum number token associations per account | | `hedera.mirror.web3.evm.maxTokenSymbolUtf8Bytes` | 100 | Maximum size in bytes for token symbol | | `hedera.mirror.web3.evm.minAutoRenewDuration` | 2592000 | Minimum duration for auto-renew account | +| `hedera.mirror.web3.evm.modularizedServices` | false | Flag that indicates if the hedera.app dependency is used. This is under development. It is recommended to be set to false. | | `hedera.mirror.web3.evm.network` | TESTNET | Which Hedera network to use. Can be either `MAINNET`, `PREVIEWNET`, `TESTNET` or `OTHER` | | `hedera.mirror.web3.evm.feesTokenTransferUsageMultiplier` | 380 | Used to calculate token transfer fees | | `hedera.mirror.web3.evm.trace.enabled` | false | Flag enabling tracer | diff --git a/hedera-mirror-web3/build.gradle.kts b/hedera-mirror-web3/build.gradle.kts index ad602bd60e6..90b2f57a986 100644 --- a/hedera-mirror-web3/build.gradle.kts +++ b/hedera-mirror-web3/build.gradle.kts @@ -28,13 +28,16 @@ plugins { id("spring-conventions") } +// We need to use this version in order to be able to decode hex data when using the hedera.app +// dependency +val headlongVersion = "6.1.1" val javaxInjectVersion = "1" dependencies { implementation(platform("org.springframework.cloud:spring-cloud-dependencies")) implementation(project(":common")) implementation("com.bucket4j:bucket4j-core") - implementation("com.esaulpaugh:headlong") + implementation("com.esaulpaugh:headlong:$headlongVersion") implementation("com.hedera.hashgraph:app") { exclude(group = "io.netty") } implementation("com.hedera.evm:hedera-evm") implementation("io.github.mweirauch:micrometer-jvm-extras") diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java index 5b7803e6121..12cc998d168 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/common/ContractCallContext.java @@ -78,16 +78,14 @@ public class ContractCallContext { /** * The TransactionExecutor from the modularized services integration deploys contracts in 2 steps: - * - * 1. The initcode is uploaded and saved as a file using a {@link FileCreateTransactionBody}. - * 2. The returned file id from step 1 is then passed to a {@link ContractCreateTransactionBody}. - * Each step performs a separate transaction. - * For step 2 even if we pass the correct file id, since the mirror node data is readonly, - * the {@link FileReadableKVState} is not able to populate the contract's bytecode from the DB - * since it was never explicitly persisted in the DB. - * - * This is the function of the field "file" to hold temporary the bytecode and the fileId - * during contract deploy. + *

+ * 1. The initcode is uploaded and saved as a file using a {@link FileCreateTransactionBody}. 2. The returned file + * id from step 1 is then passed to a {@link ContractCreateTransactionBody}. Each step performs a separate + * transaction. For step 2 even if we pass the correct file id, since the mirror node data is readonly, the + * {@link FileReadableKVState} is not able to populate the contract's bytecode from the DB since it was never + * explicitly persisted in the DB. + *

+ * This is the function of the field "file" to hold temporary the bytecode and the fileId during contract deploy. */ @Setter private Optional file = Optional.empty(); diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java index ef3dfa0dbd4..25d58f13ecf 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/evm/properties/MirrorNodeEvmProperties.java @@ -36,6 +36,7 @@ import jakarta.validation.constraints.Positive; import java.time.Duration; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; @@ -46,6 +47,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.hibernate.validator.constraints.time.DurationMin; import org.hyperledger.besu.datatypes.Address; @@ -176,10 +178,16 @@ public class MirrorNodeEvmProperties implements EvmProperties { "contracts.maxRefundPercentOfGasLimit", String.valueOf(maxGasRefundPercentage())); + @Getter(lazy = true) + private final Map transactionProperties = buildTransactionProperties(); + @Getter @Min(1) private int feesTokenTransferUsageMultiplier = 380; + @Getter + private boolean modularizedServices; + public boolean shouldAutoRenewAccounts() { return autoRenewTargetTypes.contains(EntityType.ACCOUNT); } @@ -314,6 +322,18 @@ public int feesTokenTransferUsageMultiplier() { return feesTokenTransferUsageMultiplier; } + private Map buildTransactionProperties() { + final Map mirrorNodeProperties = new HashMap<>(properties); + mirrorNodeProperties.put( + "contracts.evm.version", + "v" + + getSemanticEvmVersion().major() + "." + + getSemanticEvmVersion().minor()); + mirrorNodeProperties.put( + "ledger.id", Bytes.wrap(getNetwork().getLedgerId()).toHexString()); + return mirrorNodeProperties; + } + @Getter @RequiredArgsConstructor public enum HederaNetwork { diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java index 1ce95fd1348..a1481c54992 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractCallService.java @@ -24,6 +24,7 @@ import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.mirror.web3.evm.contracts.execution.MirrorEvmTxProcessor; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; import com.hedera.mirror.web3.evm.store.Store; import com.hedera.mirror.web3.exception.BlockNumberNotFoundException; import com.hedera.mirror.web3.exception.MirrorEvmTransactionException; @@ -44,21 +45,26 @@ public abstract class ContractCallService { static final String GAS_LIMIT_METRIC = "hedera.mirror.web3.call.gas.limit"; static final String GAS_USED_METRIC = "hedera.mirror.web3.call.gas.used"; + protected final Store store; private final MeterProvider gasLimitCounter; private final MeterProvider gasUsedCounter; - protected final Store store; private final MirrorEvmTxProcessor mirrorEvmTxProcessor; private final RecordFileService recordFileService; private final ThrottleProperties throttleProperties; private final Bucket gasLimitBucket; + private final MirrorNodeEvmProperties mirrorNodeEvmProperties; + private final TransactionExecutionService transactionExecutionService; + @SuppressWarnings("java:S107") protected ContractCallService( MirrorEvmTxProcessor mirrorEvmTxProcessor, Bucket gasLimitBucket, ThrottleProperties throttleProperties, MeterRegistry meterRegistry, RecordFileService recordFileService, - Store store) { + Store store, + MirrorNodeEvmProperties mirrorNodeEvmProperties, + TransactionExecutionService transactionExecutionService) { this.gasLimitCounter = Counter.builder(GAS_LIMIT_METRIC) .description("The amount of gas limit sent in the request") .withRegistry(meterRegistry); @@ -70,24 +76,26 @@ protected ContractCallService( this.recordFileService = recordFileService; this.throttleProperties = throttleProperties; this.gasLimitBucket = gasLimitBucket; + this.mirrorNodeEvmProperties = mirrorNodeEvmProperties; + this.transactionExecutionService = transactionExecutionService; } /** * This method is responsible for calling a smart contract function. The method is divided into two main parts: *

- * 1. If the call is historical, the method retrieves the corresponding record file and initializes - * the contract call context with the historical state. The method then proceeds to call the contract. + * 1. If the call is historical, the method retrieves the corresponding record file and initializes the contract + * call context with the historical state. The method then proceeds to call the contract. *

*

- * 2. If the call is not historical, the method initializes the contract call context with the current state - * and proceeds to call the contract. + * 2. If the call is not historical, the method initializes the contract call context with the current state and + * proceeds to call the contract. *

* * @param params the call service parameters - * @param ctx the contract call context + * @param ctx the contract call context * @return {@link HederaEvmTransactionProcessingResult} of the contract call - * @throws MirrorEvmTransactionException if any pre-checks - * fail with {@link IllegalStateException} or {@link IllegalArgumentException} + * @throws MirrorEvmTransactionException if any pre-checks fail with {@link IllegalStateException} or + * {@link IllegalArgumentException} */ protected HederaEvmTransactionProcessingResult callContract(CallServiceParameters params, ContractCallContext ctx) throws MirrorEvmTransactionException { @@ -106,7 +114,12 @@ protected HederaEvmTransactionProcessingResult doProcessCall( CallServiceParameters params, long estimatedGas, boolean restoreGasToThrottleBucket) throws MirrorEvmTransactionException { try { - var result = mirrorEvmTxProcessor.execute(params, estimatedGas); + HederaEvmTransactionProcessingResult result; + if (!mirrorNodeEvmProperties.isModularizedServices()) { + result = mirrorEvmTxProcessor.execute(params, estimatedGas); + } else { + result = transactionExecutionService.execute(params, estimatedGas); + } if (!restoreGasToThrottleBucket) { return result; } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java index 21082d13644..e19ead6583f 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractDebugService.java @@ -23,6 +23,7 @@ import com.hedera.mirror.web3.evm.contracts.execution.MirrorEvmTxProcessor; import com.hedera.mirror.web3.evm.contracts.execution.OpcodesProcessingResult; import com.hedera.mirror.web3.evm.contracts.execution.traceability.OpcodeTracerOptions; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; import com.hedera.mirror.web3.evm.store.Store; import com.hedera.mirror.web3.exception.MirrorEvmTransactionException; import com.hedera.mirror.web3.repository.ContractActionRepository; @@ -43,6 +44,7 @@ public class ContractDebugService extends ContractCallService { private final ContractActionRepository contractActionRepository; + @SuppressWarnings("java:S107") public ContractDebugService( ContractActionRepository contractActionRepository, RecordFileService recordFileService, @@ -50,8 +52,18 @@ public ContractDebugService( MirrorEvmTxProcessor mirrorEvmTxProcessor, Bucket gasLimitBucket, ThrottleProperties throttleProperties, - MeterRegistry meterRegistry) { - super(mirrorEvmTxProcessor, gasLimitBucket, throttleProperties, meterRegistry, recordFileService, store); + MeterRegistry meterRegistry, + MirrorNodeEvmProperties mirrorNodeEvmProperties, + TransactionExecutionService transactionExecutionService) { + super( + mirrorEvmTxProcessor, + gasLimitBucket, + throttleProperties, + meterRegistry, + recordFileService, + store, + mirrorNodeEvmProperties, + transactionExecutionService); this.contractActionRepository = contractActionRepository; } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractExecutionService.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractExecutionService.java index cbc504e0e01..f4239ede378 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractExecutionService.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/ContractExecutionService.java @@ -21,6 +21,7 @@ import com.google.common.base.Stopwatch; import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.mirror.web3.evm.contracts.execution.MirrorEvmTxProcessor; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; import com.hedera.mirror.web3.evm.store.Store; import com.hedera.mirror.web3.service.model.ContractExecutionParameters; import com.hedera.mirror.web3.service.utils.BinaryGasEstimator; @@ -38,6 +39,7 @@ public class ContractExecutionService extends ContractCallService { private final BinaryGasEstimator binaryGasEstimator; + @SuppressWarnings("java:S107") public ContractExecutionService( MeterRegistry meterRegistry, BinaryGasEstimator binaryGasEstimator, @@ -45,8 +47,18 @@ public ContractExecutionService( MirrorEvmTxProcessor mirrorEvmTxProcessor, RecordFileService recordFileService, ThrottleProperties throttleProperties, - Bucket gasLimitBucket) { - super(mirrorEvmTxProcessor, gasLimitBucket, throttleProperties, meterRegistry, recordFileService, store); + Bucket gasLimitBucket, + MirrorNodeEvmProperties mirrorNodeEvmProperties, + TransactionExecutionService transactionExecutionService) { + super( + mirrorEvmTxProcessor, + gasLimitBucket, + throttleProperties, + meterRegistry, + recordFileService, + store, + mirrorNodeEvmProperties, + transactionExecutionService); this.binaryGasEstimator = binaryGasEstimator; } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/TransactionExecutionService.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/TransactionExecutionService.java new file mode 100644 index 00000000000..f750a49fc41 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/service/TransactionExecutionService.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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. + */ + +package com.hedera.mirror.web3.service; + +import static com.hedera.mirror.web3.state.Utils.isMirror; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.Duration; +import com.hedera.hapi.node.base.FileID; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.Timestamp; +import com.hedera.hapi.node.base.TransactionID; +import com.hedera.hapi.node.contract.ContractCallTransactionBody; +import com.hedera.hapi.node.contract.ContractCreateTransactionBody; +import com.hedera.hapi.node.contract.ContractFunctionResult; +import com.hedera.hapi.node.file.FileCreateTransactionBody; +import com.hedera.hapi.node.state.file.File; +import com.hedera.hapi.node.state.primitives.ProtoBytes; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.hapi.node.transaction.TransactionRecord; +import com.hedera.mirror.web3.common.ContractCallContext; +import com.hedera.mirror.web3.evm.contracts.execution.traceability.OpcodeTracer; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; +import com.hedera.mirror.web3.service.model.CallServiceParameters; +import com.hedera.mirror.web3.state.AliasesReadableKVState; +import com.hedera.node.app.config.ConfigProviderImpl; +import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult; +import com.hedera.node.app.service.token.TokenService; +import com.hedera.node.app.workflows.standalone.TransactionExecutor; +import com.hedera.node.app.workflows.standalone.TransactionExecutors; +import com.hedera.node.app.workflows.standalone.TransactionExecutors.TracerBinding; +import com.hedera.node.config.data.EntitiesConfig; +import com.swirlds.config.api.Configuration; +import com.swirlds.state.State; +import jakarta.annotation.Nullable; +import jakarta.inject.Named; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.CustomLog; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.tracing.OperationTracer; + +@Named +@CustomLog +public class TransactionExecutionService { + + private static final Configuration DEFAULT_CONFIG = new ConfigProviderImpl().getConfiguration(); + private static final OperationTracer[] EMPTY_OPERATION_TRACER_ARRAY = new OperationTracer[0]; + private static final AccountID TREASURY_ACCOUNT_ID = + AccountID.newBuilder().accountNum(2).build(); + private static final Duration TRANSACTION_DURATION = new Duration(15); + private static final Timestamp TRANSACTION_START = new Timestamp(0, 0); + private static final int INITCODE_SIZE_KB = 6 * 1024; + private final State mirrorNodeState; + private final MirrorNodeEvmProperties mirrorNodeEvmProperties; + private final OpcodeTracer opcodeTracer; + + protected TransactionExecutionService( + State mirrorNodeState, MirrorNodeEvmProperties mirrorNodeEvmProperties, OpcodeTracer opcodeTracer) { + this.mirrorNodeState = mirrorNodeState; + this.mirrorNodeEvmProperties = mirrorNodeEvmProperties; + this.opcodeTracer = opcodeTracer; + } + + public HederaEvmTransactionProcessingResult execute(final CallServiceParameters params, final long estimatedGas) { + final var isContractCreate = params.getReceiver().isZero(); + final var maxLifetime = + DEFAULT_CONFIG.getConfigData(EntitiesConfig.class).maxLifetime(); + var executor = + ExecutorFactory.newExecutor(mirrorNodeState, mirrorNodeEvmProperties.getTransactionProperties(), null); + + TransactionBody transactionBody; + HederaEvmTransactionProcessingResult result; + if (isContractCreate) { + if (params.getCallData().size() < INITCODE_SIZE_KB) { + transactionBody = buildContractCreateTransactionBodyWithInitBytecode(params, estimatedGas, maxLifetime); + } else { + // Upload the init bytecode + transactionBody = buildFileCreateTransactionBody(params, maxLifetime); + var uploadReceipt = executor.execute(transactionBody, Instant.EPOCH); + final var fileID = uploadReceipt + .getFirst() + .transactionRecord() + .receiptOrThrow() + .fileIDOrThrow(); + final var file = File.newBuilder() + .fileId(fileID) + .contents(com.hedera.pbj.runtime.io.buffer.Bytes.wrap( + params.getCallData().toFastHex(false).getBytes())) + .build(); + // Set the context variables for the uploaded contract. + ContractCallContext.get().setFile(Optional.of(file)); + + // Create the contract with the init bytecode + transactionBody = + buildContractCreateTransactionBodyWithFileID(params, fileID, estimatedGas, maxLifetime); + } + } else { + transactionBody = buildContractCallTransactionBody(params, estimatedGas); + } + + var receipt = executor.execute(transactionBody, Instant.EPOCH, getOperationTracers()); + var transactionRecord = receipt.getFirst().transactionRecord(); + if (transactionRecord.receiptOrThrow().status() == ResponseCodeEnum.SUCCESS) { + result = buildSuccessResult(isContractCreate, transactionRecord, params); + } else { + result = buildFailedResult(transactionRecord, isContractCreate); + } + return result; + } + + private ContractFunctionResult getTransactionResult( + final TransactionRecord transactionRecord, boolean isContractCreate) { + return isContractCreate + ? transactionRecord.contractCreateResultOrThrow() + : transactionRecord.contractCallResultOrThrow(); + } + + private HederaEvmTransactionProcessingResult buildSuccessResult( + final boolean isContractCreate, + final TransactionRecord transactionRecord, + final CallServiceParameters params) { + var result = getTransactionResult(transactionRecord, isContractCreate); + + return HederaEvmTransactionProcessingResult.successful( + List.of(), + result.gasUsed(), + 0L, + 0L, + Bytes.wrap(result.contractCallResult().toByteArray()), + params.getReceiver()); + } + + private HederaEvmTransactionProcessingResult buildFailedResult( + final TransactionRecord transactionRecord, final boolean isContractCreate) { + var result = getTransactionResult(transactionRecord, isContractCreate); + var status = transactionRecord.receipt().status(); + + return HederaEvmTransactionProcessingResult.failed( + result.gasUsed(), + 0L, + 0L, + Optional.of(Bytes.wrap(status.protoName().getBytes())), + Optional.empty()); + } + + private TransactionBody.Builder defaultTransactionBodyBuilder(final CallServiceParameters params) { + return TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder() + .transactionValidStart(TRANSACTION_START) + .accountID(getSenderAccountID(params)) + .build()) + .nodeAccountID(TREASURY_ACCOUNT_ID) // We don't really need another account here. + .transactionValidDuration(TRANSACTION_DURATION); + } + + private TransactionBody buildFileCreateTransactionBody(final CallServiceParameters params, long maxLifetime) { + return defaultTransactionBodyBuilder(params) + .fileCreate(FileCreateTransactionBody.newBuilder() + .contents(com.hedera.pbj.runtime.io.buffer.Bytes.wrap( + params.getCallData().toArrayUnsafe())) + .expirationTime(new Timestamp(maxLifetime, 0)) + .build()) + .build(); + } + + private TransactionBody buildContractCreateTransactionBodyWithInitBytecode( + final CallServiceParameters params, long estimatedGas, long maxLifetime) { + return defaultTransactionBodyBuilder(params) + .contractCreateInstance(ContractCreateTransactionBody.newBuilder() + .initcode(com.hedera.pbj.runtime.io.buffer.Bytes.wrap( + params.getCallData().toArrayUnsafe())) + .gas(estimatedGas) + .autoRenewPeriod(new Duration(maxLifetime)) + .build()) + .build(); + } + + private TransactionBody buildContractCreateTransactionBodyWithFileID( + final CallServiceParameters params, final FileID fileID, long estimatedGas, long maxLifetime) { + return defaultTransactionBodyBuilder(params) + .contractCreateInstance(ContractCreateTransactionBody.newBuilder() + .fileID(fileID) + .gas(estimatedGas) + .autoRenewPeriod(new Duration(maxLifetime)) + .build()) + .build(); + } + + private TransactionBody buildContractCallTransactionBody( + final CallServiceParameters params, final long estimatedGas) { + return defaultTransactionBodyBuilder(params) + .contractCall(ContractCallTransactionBody.newBuilder() + .contractID(ContractID.newBuilder() + .evmAddress(com.hedera.pbj.runtime.io.buffer.Bytes.wrap( + params.getReceiver().toArrayUnsafe())) + .build()) + .functionParameters(com.hedera.pbj.runtime.io.buffer.Bytes.wrap( + params.getCallData().toArrayUnsafe())) + .gas(estimatedGas) + .build()) + .build(); + } + + private ProtoBytes convertAddressToProtoBytes(final Address address) { + return ProtoBytes.newBuilder() + .value(com.hedera.pbj.runtime.io.buffer.Bytes.wrap(address.toArrayUnsafe())) + .build(); + } + + private AccountID getSenderAccountID(final CallServiceParameters params) { + if (params.getSender().canonicalAddress().isZero() && params.getValue() == 0L) { + // Set a default account to keep the sender parameter optional. + return TREASURY_ACCOUNT_ID; + } + + final var senderAddress = params.getSender().canonicalAddress(); + if (isMirror(senderAddress)) { + return AccountID.newBuilder() + .accountNum(senderAddress.trimLeadingZeros().toLong()) + .build(); + } + return (AccountID) mirrorNodeState + .getReadableStates(TokenService.NAME) + .get(AliasesReadableKVState.KEY) + .get(convertAddressToProtoBytes(senderAddress)); + } + + private OperationTracer[] getOperationTracers() { + return ContractCallContext.get().getOpcodeTracerOptions() != null + ? new OperationTracer[] {opcodeTracer} + : EMPTY_OPERATION_TRACER_ARRAY; + } + + public static class ExecutorFactory { + + private ExecutorFactory() {} + + public static TransactionExecutor newExecutor( + State mirrorNodeState, + Map properties, + @Nullable final TracerBinding customTracerBinding) { + return TransactionExecutors.TRANSACTION_EXECUTORS.newExecutor( + mirrorNodeState, properties, customTracerBinding); + } + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AbstractReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AbstractReadableKVState.java new file mode 100644 index 00000000000..fb1a281f9a4 --- /dev/null +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AbstractReadableKVState.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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. + */ + +package com.hedera.mirror.web3.state; + +import com.swirlds.state.spi.ReadableKVStateBase; +import edu.umd.cs.findbugs.annotations.NonNull; +import jakarta.annotation.Nullable; +import java.util.Collections; +import java.util.Iterator; + +public abstract class AbstractReadableKVState extends ReadableKVStateBase { + + protected AbstractReadableKVState(@NonNull String stateKey) { + super(stateKey); + } + + @Nullable + @Override + public V get(@NonNull K key) { + return readFromDataSource(key); + } + + @NonNull + @Override + protected Iterator iterateFromDataSource() { + return Collections.emptyIterator(); + } + + @Override + public long size() { + return 0; + } +} diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java index 80ebbc261a2..dce968fd4e5 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AccountReadableKVState.java @@ -43,11 +43,8 @@ import com.hedera.mirror.web3.utils.Suppliers; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.utils.EntityIdUtils; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -60,8 +57,9 @@ * The object, which is read from DB is converted to the PBJ generated format, so that it can properly be utilized by the hedera app components * */ @Named -public class AccountReadableKVState extends ReadableKVStateBase { +public class AccountReadableKVState extends AbstractReadableKVState { + public static final String KEY = "ACCOUNTS"; private final AccountBalanceRepository accountBalanceRepository; private final CommonEntityAccessor commonEntityAccessor; private final CryptoAllowanceRepository cryptoAllowanceRepository; @@ -78,7 +76,7 @@ public AccountReadableKVState( CryptoAllowanceRepository cryptoAllowanceRepository, TokenAccountRepository tokenAccountRepository, AccountBalanceRepository accountBalanceRepository) { - super("ACCOUNTS"); + super(KEY); this.accountBalanceRepository = accountBalanceRepository; this.commonEntityAccessor = commonEntityAccessor; this.cryptoAllowanceRepository = cryptoAllowanceRepository; @@ -98,16 +96,6 @@ protected Account readFromDataSource(@Nonnull AccountID key) { .orElse(null); } - @Override - protected @Nonnull Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } - private Account accountFromEntity(Entity entity, final Optional timestamp) { var tokenAccountBalances = getNumberOfAllAndPositiveBalanceTokenAssociations(entity.getId(), timestamp); byte[] alias = new byte[0]; diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AirdropsReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AirdropsReadableKVState.java index ad2eb4c731e..3fdb6cc904b 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AirdropsReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AirdropsReadableKVState.java @@ -23,19 +23,17 @@ import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.mirror.web3.repository.TokenAirdropRepository; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.Iterator; @Named -public class AirdropsReadableKVState extends ReadableKVStateBase { +public class AirdropsReadableKVState extends AbstractReadableKVState { + public static final String KEY = "PENDING_AIRDROPS"; private final TokenAirdropRepository tokenAirdropRepository; protected AirdropsReadableKVState(final TokenAirdropRepository tokenAirdropRepository) { - super("PENDING_AIRDROPS"); + super(KEY); this.tokenAirdropRepository = tokenAirdropRepository; } @@ -59,17 +57,6 @@ protected AccountPendingAirdrop readFromDataSource(@Nonnull PendingAirdropId key .orElse(null); } - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } - private AccountPendingAirdrop mapToAccountPendingAirdrop(final long amount) { return AccountPendingAirdrop.newBuilder() .pendingAirdropValue(mapToPendingAirdropValue(amount)) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AliasesReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AliasesReadableKVState.java index 2cdd5f675c8..ffdfa0ea9ba 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AliasesReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/AliasesReadableKVState.java @@ -21,19 +21,17 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.mirror.web3.common.ContractCallContext; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.Iterator; @Named -public class AliasesReadableKVState extends ReadableKVStateBase { +public class AliasesReadableKVState extends AbstractReadableKVState { + public static final String KEY = "ALIASES"; private final CommonEntityAccessor commonEntityAccessor; protected AliasesReadableKVState(final CommonEntityAccessor commonEntityAccessor) { - super("ALIASES"); + super(KEY); this.commonEntityAccessor = commonEntityAccessor; } @@ -44,15 +42,4 @@ protected AccountID readFromDataSource(@Nonnull ProtoBytes alias) { return entity.map(e -> toAccountId(e.getShard(), e.getRealm(), e.getNum())) .orElse(null); } - - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractBytecodeReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractBytecodeReadableKVState.java index 43fdc7e0abf..9aa3fca6de2 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractBytecodeReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractBytecodeReadableKVState.java @@ -26,23 +26,21 @@ import com.hedera.mirror.common.util.DomainUtils; import com.hedera.mirror.web3.repository.ContractRepository; import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.Iterator; import java.util.Optional; @Named -public class ContractBytecodeReadableKVState extends ReadableKVStateBase { +public class ContractBytecodeReadableKVState extends AbstractReadableKVState { + public static final String KEY = "BYTECODE"; private final ContractRepository contractRepository; private final CommonEntityAccessor commonEntityAccessor; protected ContractBytecodeReadableKVState( final ContractRepository contractRepository, CommonEntityAccessor commonEntityAccessor) { - super("BYTECODE"); + super(KEY); this.contractRepository = contractRepository; this.commonEntityAccessor = commonEntityAccessor; } @@ -58,17 +56,6 @@ protected Bytecode readFromDataSource(@Nonnull ContractID contractID) { .orElse(null); } - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } - private EntityId toEntityId(@Nonnull final com.hedera.hapi.node.base.ContractID contractID) { if (contractID.hasContractNum()) { return entityIdFromContractId(contractID); diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractStorageReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractStorageReadableKVState.java index f09403bf848..6260db24149 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractStorageReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/ContractStorageReadableKVState.java @@ -22,19 +22,17 @@ import com.hedera.mirror.web3.repository.ContractStateRepository; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.utils.EntityIdUtils; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.Iterator; @Named -public class ContractStorageReadableKVState extends ReadableKVStateBase { +public class ContractStorageReadableKVState extends AbstractReadableKVState { + public static final String KEY = "STORAGE"; private final ContractStateRepository contractStateRepository; protected ContractStorageReadableKVState(final ContractStateRepository contractStateRepository) { - super("STORAGE"); + super(KEY); this.contractStateRepository = contractStateRepository; } @@ -54,15 +52,4 @@ protected SlotValue readFromDataSource(@Nonnull SlotKey slotKey) { .map(byteArr -> new SlotValue(Bytes.wrap(byteArr), Bytes.EMPTY, Bytes.EMPTY)) .orElse(null); } - - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } } diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/FileReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/FileReadableKVState.java index 119b430d34d..64e7d59331c 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/FileReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/FileReadableKVState.java @@ -29,12 +29,9 @@ import com.hedera.mirror.web3.repository.FileDataRepository; import com.hedera.mirror.web3.utils.Suppliers; import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; import java.time.Instant; -import java.util.Collections; -import java.util.Iterator; import java.util.Optional; import java.util.function.Supplier; @@ -44,13 +41,14 @@ * properly be utilized by the hedera app components */ @Named -public class FileReadableKVState extends ReadableKVStateBase { +public class FileReadableKVState extends AbstractReadableKVState { + public static final String KEY = "FILES"; private final FileDataRepository fileDataRepository; private final EntityRepository entityRepository; public FileReadableKVState(final FileDataRepository fileDataRepository, final EntityRepository entityRepository) { - super("FILES"); + super(KEY); this.fileDataRepository = fileDataRepository; this.entityRepository = entityRepository; } @@ -74,17 +72,6 @@ protected File readFromDataSource(@Nonnull FileID key) { .orElse(null); } - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } - private File mapToFile(final FileData fileData, final FileID key, final Optional timestamp) { return File.newBuilder() .contents(Bytes.wrap(fileData.getFileData())) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java index 8154e63d5f3..208b26ebf08 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/MirrorNodeState.java @@ -16,7 +16,6 @@ package com.hedera.mirror.web3.state; -import static com.hedera.node.app.util.FileUtilities.createFileID; import static com.hedera.node.app.workflows.handle.metric.UnavailableMetrics.UNAVAILABLE_METRICS; import static com.hedera.node.app.workflows.standalone.TransactionExecutors.DEFAULT_NODE_INFO; import static com.swirlds.state.StateChangeListener.StateType.MAP; @@ -25,13 +24,11 @@ import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; -import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.base.Key; -import com.hedera.hapi.node.base.KeyList; import com.hedera.hapi.node.base.SignatureMap; -import com.hedera.hapi.node.state.file.File; import com.hedera.hapi.node.transaction.ThrottleDefinitions; import com.hedera.mirror.web3.common.ContractCallContext; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; import com.hedera.mirror.web3.state.core.ListReadableQueueState; import com.hedera.mirror.web3.state.core.ListWritableQueueState; import com.hedera.mirror.web3.state.core.MapReadableKVState; @@ -44,9 +41,8 @@ import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.records.BlockRecordService; import com.hedera.node.app.service.contract.impl.ContractServiceImpl; -import com.hedera.node.app.service.file.FileService; import com.hedera.node.app.service.file.impl.FileServiceImpl; -import com.hedera.node.app.service.file.impl.schemas.V0490FileSchema; +import com.hedera.node.app.service.schedule.impl.ScheduleServiceImpl; import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.services.AppContextImpl; import com.hedera.node.app.services.ServiceMigrator; @@ -59,15 +55,11 @@ import com.hedera.node.app.throttle.ThrottleAccumulator; import com.hedera.node.app.version.ServicesSoftwareVersion; import com.hedera.node.app.workflows.handle.metric.UnavailableMetrics; -import com.hedera.node.config.data.FilesConfig; import com.hedera.node.config.data.VersionConfig; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.config.api.Configuration; import com.swirlds.state.State; import com.swirlds.state.StateChangeListener; import com.swirlds.state.lifecycle.StartupNetworks; import com.swirlds.state.lifecycle.info.NetworkInfo; -import com.swirlds.state.spi.CommittableWritableStates; import com.swirlds.state.spi.EmptyWritableStates; import com.swirlds.state.spi.KVChangeListener; import com.swirlds.state.spi.QueueChangeListener; @@ -115,8 +107,15 @@ public class MirrorNodeState implements State { private final NetworkInfo networkInfo; private final StartupNetworks startupNetworks; + private final MirrorNodeEvmProperties mirrorNodeEvmProperties; + @PostConstruct private void init() { + if (!mirrorNodeEvmProperties.isModularizedServices()) { + // If the flag is not enabled, we don't need to make any further initialization. + return; + } + ContractCallContext.run(ctx -> { registerServices(servicesRegistry); final var bootstrapConfig = new BootstrapConfigProviderImpl().getConfiguration(); @@ -131,20 +130,6 @@ private void init() { networkInfo, UnavailableMetrics.UNAVAILABLE_METRICS, startupNetworks); - - final var fileServiceStates = this.getWritableStates(FileService.NAME); - final var files = fileServiceStates.get(V0490FileSchema.BLOBS_KEY); - genesisContentProviders(bootstrapConfig).forEach((fileNum, provider) -> { - final var fileId = createFileID(fileNum, bootstrapConfig); - files.put( - fileId, - File.newBuilder() - .fileId(fileId) - .keys(KeyList.DEFAULT) - .contents(provider.apply(bootstrapConfig)) - .build()); - }); - ((CommittableWritableStates) fileServiceStates).commit(); return ctx; }); } @@ -392,19 +377,11 @@ private void registerServices(ServicesRegistry servicesRegistry) { new BlockRecordService(), new FeeService(), new CongestionThrottleService(), - new RecordCacheService()) + new RecordCacheService(), + new ScheduleServiceImpl()) .forEach(servicesRegistry::register); } - private Map> genesisContentProviders( - final com.swirlds.config.api.Configuration config) { - final var genesisSchema = new V0490FileSchema(); - final var filesConfig = config.getConfigData(FilesConfig.class); - return Map.of( - filesConfig.feeSchedules(), genesisSchema::genesisFeeSchedules, - filesConfig.exchangeRates(), genesisSchema::genesisExchangeRates); - } - private SignatureVerifier signatureVerifier() { return new SignatureVerifier() { @Override diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/NftReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/NftReadableKVState.java index cdd7c3ddb7e..2ccc153708b 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/NftReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/NftReadableKVState.java @@ -26,11 +26,8 @@ import com.hedera.mirror.web3.repository.NftRepository; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.utils.EntityIdUtils; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.Iterator; /** * This class serves as a repository layer between hedera app services read only state and the Postgres database in @@ -38,12 +35,13 @@ * utilized by the hedera app components */ @Named -public class NftReadableKVState extends ReadableKVStateBase { +public class NftReadableKVState extends AbstractReadableKVState { + public static final String KEY = "NFTS"; private final NftRepository nftRepository; public NftReadableKVState(@Nonnull NftRepository nftRepository) { - super("NFTS"); + super(KEY); this.nftRepository = nftRepository; } @@ -62,17 +60,6 @@ protected Nft readFromDataSource(@Nonnull final NftID key) { .orElse(null); } - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } - private Nft mapToNft(final com.hedera.mirror.common.domain.token.Nft nft, final TokenID tokenID) { return Nft.newBuilder() .metadata(Bytes.wrap(nft.getMetadata())) diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenReadableKVState.java index 9e4cb41812c..72787f7f630 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenReadableKVState.java @@ -45,12 +45,10 @@ import com.hedera.pbj.runtime.OneOf; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.utils.EntityIdUtils; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -58,7 +56,9 @@ import org.springframework.util.CollectionUtils; @Named -public class TokenReadableKVState extends ReadableKVStateBase { +public class TokenReadableKVState extends AbstractReadableKVState { + + public static final String KEY = "TOKENS"; private final CommonEntityAccessor commonEntityAccessor; private final CustomFeeRepository customFeeRepository; private final TokenRepository tokenRepository; @@ -71,7 +71,7 @@ protected TokenReadableKVState( final TokenRepository tokenRepository, final EntityRepository entityRepository, final NftRepository nftRepository) { - super("TOKENS"); + super(KEY); this.commonEntityAccessor = commonEntityAccessor; this.customFeeRepository = customFeeRepository; this.tokenRepository = tokenRepository; @@ -99,16 +99,6 @@ protected Token readFromDataSource(@Nonnull TokenID key) { return tokenFromEntities(entity, token, timestamp); } - @Override - protected @Nonnull Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } - private Token tokenFromEntities( final Entity entity, final com.hedera.mirror.common.domain.token.Token token, diff --git a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenRelationshipReadableKVState.java b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenRelationshipReadableKVState.java index aa7857f5ed1..44e1a37e9e3 100644 --- a/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenRelationshipReadableKVState.java +++ b/hedera-mirror-web3/src/main/java/com/hedera/mirror/web3/state/TokenRelationshipReadableKVState.java @@ -33,17 +33,15 @@ import com.hedera.mirror.web3.repository.TokenBalanceRepository; import com.hedera.mirror.web3.repository.TokenRepository; import com.hedera.mirror.web3.utils.Suppliers; -import com.swirlds.state.spi.ReadableKVStateBase; import jakarta.annotation.Nonnull; import jakarta.inject.Named; -import java.util.Collections; -import java.util.Iterator; import java.util.Optional; import java.util.function.Supplier; @Named -public class TokenRelationshipReadableKVState extends ReadableKVStateBase { +public class TokenRelationshipReadableKVState extends AbstractReadableKVState { + public static final String KEY = "TOKEN_RELS"; private final NftRepository nftRepository; private final TokenAccountRepository tokenAccountRepository; private final TokenBalanceRepository tokenBalanceRepository; @@ -54,7 +52,7 @@ protected TokenRelationshipReadableKVState( final TokenAccountRepository tokenAccountRepository, final TokenBalanceRepository tokenBalanceRepository, final TokenRepository tokenRepository) { - super("TOKEN_RELS"); + super(KEY); this.nftRepository = nftRepository; this.tokenAccountRepository = tokenAccountRepository; this.tokenBalanceRepository = tokenBalanceRepository; @@ -79,17 +77,6 @@ protected TokenRelation readFromDataSource(@Nonnull EntityIDPair key) { .orElse(null); } - @Nonnull - @Override - protected Iterator iterateFromDataSource() { - return Collections.emptyIterator(); - } - - @Override - public long size() { - return 0; - } - private Optional findTokenAccount( final TokenID tokenID, final AccountID accountID, final Optional timestamp) { AbstractTokenAccount.Id id = new AbstractTokenAccount.Id(); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/RecordFileRepositoryTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/RecordFileRepositoryTest.java index 37b45c00e97..5c60323986a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/RecordFileRepositoryTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/RecordFileRepositoryTest.java @@ -40,10 +40,11 @@ void findLatestIndex() { @Test void findEarliest() { - var earliest = domainBuilder.recordFile().persist(); + final var genesisRecordFile = + domainBuilder.recordFile().customize(f -> f.index(0L)).persist(); domainBuilder.recordFile().persist(); - assertThat(recordFileRepository.findEarliest()).get().isEqualTo(earliest); + assertThat(recordFileRepository.findEarliest()).get().isEqualTo(genesisRecordFile); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/TokenBalanceRepositoryTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/TokenBalanceRepositoryTest.java index c93cf29158f..3f65803ba11 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/TokenBalanceRepositoryTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/repository/TokenBalanceRepositoryTest.java @@ -158,17 +158,11 @@ void findHistoricalBalanceIfTokenBalanceIsMissing() { // Test case: account_balance and token_balance entry BEFORE token transfers is missing // usually the account_balance/token_balance gets persisted ~8 mins after the account creation - var accountId = new AccountBalance.Id(domainBuilder.timestamp(), TREASURY_ENTITY_ID); - // persist account - domainBuilder - .entity() - .customize(a -> a.id(accountId.getAccountId().getId())) - .persist(); // not persisted var tokenBalance1 = domainBuilder .tokenBalance() - .customize(tb -> tb.id(new TokenBalance.Id( - domainBuilder.timestamp(), accountId.getAccountId(), domainBuilder.entityId()))) + .customize(tb -> tb.id( + new TokenBalance.Id(domainBuilder.timestamp(), TREASURY_ENTITY_ID, domainBuilder.entityId()))) .get(); long consensusTimestamp = tokenBalance1.getId().getConsensusTimestamp(); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceOpcodeTracerTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceOpcodeTracerTest.java index 3951dfc91ce..39e265d02e5 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceOpcodeTracerTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceOpcodeTracerTest.java @@ -48,6 +48,7 @@ import org.junit.jupiter.api.BeforeEach; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.web3j.tx.Contract; abstract class AbstractContractCallServiceOpcodeTracerTest extends AbstractContractCallServiceHistoricalTest { @@ -55,6 +56,9 @@ abstract class AbstractContractCallServiceOpcodeTracerTest extends AbstractContr @Resource protected ContractDebugService contractDebugService; + @SpyBean + protected TransactionExecutionService transactionExecutionService; + @Captor private ArgumentCaptor paramsCaptor; @@ -69,15 +73,27 @@ abstract class AbstractContractCallServiceOpcodeTracerTest extends AbstractContr @BeforeEach void setUpArgumentCaptors() { - doAnswer(invocation -> { - final var transactionProcessingResult = - (HederaEvmTransactionProcessingResult) invocation.callRealMethod(); - resultCaptor = transactionProcessingResult; - contextCaptor = ContractCallContext.get(); - return transactionProcessingResult; - }) - .when(processor) - .execute(paramsCaptor.capture(), gasCaptor.capture()); + if (!mirrorNodeEvmProperties.isModularizedServices()) { + doAnswer(invocation -> { + final var transactionProcessingResult = + (HederaEvmTransactionProcessingResult) invocation.callRealMethod(); + resultCaptor = transactionProcessingResult; + contextCaptor = ContractCallContext.get(); + return transactionProcessingResult; + }) + .when(processor) + .execute(paramsCaptor.capture(), gasCaptor.capture()); + } else { + doAnswer(invocation -> { + final var transactionProcessingResult = + (HederaEvmTransactionProcessingResult) invocation.callRealMethod(); + resultCaptor = transactionProcessingResult; + contextCaptor = ContractCallContext.get(); + return transactionProcessingResult; + }) + .when(transactionExecutionService) + .execute(paramsCaptor.capture(), gasCaptor.capture()); + } } protected void verifyOpcodeTracerCall( diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java index c97ee6ad582..6548a181a7a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/AbstractContractCallServiceTest.java @@ -29,9 +29,11 @@ import com.hedera.mirror.common.domain.entity.EntityType; import com.hedera.mirror.common.domain.token.TokenFreezeStatusEnum; import com.hedera.mirror.common.domain.token.TokenKycStatusEnum; +import com.hedera.mirror.common.domain.transaction.RecordFile; import com.hedera.mirror.web3.Web3IntegrationTest; import com.hedera.mirror.web3.common.ContractCallContext; import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; +import com.hedera.mirror.web3.evm.utils.EvmTokenUtils; import com.hedera.mirror.web3.service.model.CallServiceParameters.CallType; import com.hedera.mirror.web3.service.model.ContractDebugParameters; import com.hedera.mirror.web3.service.model.ContractExecutionParameters; @@ -42,7 +44,11 @@ import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; import com.hedera.services.store.models.Id; import com.hedera.services.utils.EntityIdUtils; +import com.hederahashgraph.api.proto.java.ExchangeRate; +import com.hederahashgraph.api.proto.java.ExchangeRateSet; import com.hederahashgraph.api.proto.java.Key; +import com.hederahashgraph.api.proto.java.TimestampSeconds; +import com.swirlds.state.State; import jakarta.annotation.Resource; import java.math.BigInteger; import java.util.concurrent.atomic.AtomicLong; @@ -56,7 +62,6 @@ import org.web3j.tx.Contract; @Import(Web3jTestConfiguration.class) -@SuppressWarnings("unchecked") public abstract class AbstractContractCallServiceTest extends Web3IntegrationTest { @Resource @@ -65,6 +70,27 @@ public abstract class AbstractContractCallServiceTest extends Web3IntegrationTes @Resource protected MirrorNodeEvmProperties mirrorNodeEvmProperties; + @Resource + protected State state; + + protected RecordFile genesisRecordFile; + + protected static final String TREASURY_ADDRESS = EvmTokenUtils.toAddress(2).toHexString(); + + protected static final byte[] EXCHANGE_RATES_SET = ExchangeRateSet.newBuilder() + .setCurrentRate(ExchangeRate.newBuilder() + .setCentEquiv(12) + .setHbarEquiv(1) + .setExpirationTime(TimestampSeconds.newBuilder().setSeconds(4102444800L)) + .build()) + .setNextRate(ExchangeRate.newBuilder() + .setCentEquiv(15) + .setHbarEquiv(1) + .setExpirationTime(TimestampSeconds.newBuilder().setSeconds(4102444800L)) + .build()) + .build() + .toByteArray(); + public static Key getKeyWithDelegatableContractId(final Contract contract) { final var contractAddress = Address.fromHexString(contract.getContractAddress()); @@ -82,8 +108,18 @@ public static Key getKeyWithContractId(final Contract contract) { } @BeforeEach - final void setup() { - domainBuilder.recordFile().persist(); + protected void setup() { + genesisRecordFile = + domainBuilder.recordFile().customize(f -> f.index(0L)).persist(); + domainBuilder + .entity() + .customize(e -> e.id(2L).num(2L).balance(5000000000000000000L)) + .persist(); + domainBuilder.entity().customize(e -> e.id(98L).num(98L)).persist(); + domainBuilder + .fileData() + .customize(f -> f.entityId(EntityId.of(112L)).fileData(EXCHANGE_RATES_SET)) + .persist(); testWeb3jService.reset(); } @@ -190,7 +226,15 @@ protected Entity tokenEntityPersist() { protected Entity accountEntityPersist() { return domainBuilder .entity() - .customize(e -> e.type(EntityType.ACCOUNT).deleted(false).balance(1_000_000_000_000L)) + .customize(e -> + e.type(EntityType.ACCOUNT).evmAddress(null).alias(null).balance(1_000_000_000_000L)) + .persist(); + } + + protected Entity accountEntityWithEvmAddressPersist() { + return domainBuilder + .entity() + .customize(e -> e.type(EntityType.ACCOUNT).balance(1_000_000_000_000L)) .persist(); } @@ -210,7 +254,7 @@ protected void tokenAccountPersist(final Entity token, final Long accountId) { } protected String getAddressFromEntity(Entity entity) { - return EntityIdUtils.asHexedEvmAddress(new Id(entity.getShard(), entity.getRealm(), entity.getNum())); + return EvmTokenUtils.toAddress(entity.toEntityId()).toHexString(); } protected String getAliasFromEntity(Entity entity) { diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java index 09bc96ecb8e..4196d34ee7a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallAddressThisTest.java @@ -143,7 +143,7 @@ void contractDeployWithValue() throws Exception { final var request = new ContractCallRequest(); request.setBlock(BlockType.LATEST); request.setData(contract.getContractBinary()); - request.setFrom(Address.ZERO.toHexString()); + request.setFrom(TREASURY_ADDRESS); request.setValue(1000); // When contractCall(request) @@ -158,11 +158,11 @@ void contractDeployWithValue() throws Exception { @Test void deployNestedAddressThisContract() { final var contract = testWeb3jService.deploy(TestNestedAddressThis::deploy); - final var serviceParamaters = testWeb3jService.serviceParametersForTopLevelContractCreate( + final var serviceParameters = testWeb3jService.serviceParametersForTopLevelContractCreate( contract.getContractBinary(), ETH_ESTIMATE_GAS, Address.ZERO); final long actualGas = 95401L; assertThat(isWithinExpectedGasRange( - longValueOf.applyAsLong(contractCallService.processCall(serviceParamaters)), actualGas)) + longValueOf.applyAsLong(contractCallService.processCall(serviceParameters)), actualGas)) .isTrue(); } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallEvmCodesTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallEvmCodesTest.java index 46be59836dd..76c713034f3 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallEvmCodesTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallEvmCodesTest.java @@ -166,31 +166,29 @@ void getBlockPrevrandao() throws Exception { @Test void getBlockHashReturnsCorrectHash() throws Exception { testWeb3jService.setUseContractCallDeploy(true); + domainBuilder.recordFile().persist(); // Set a latest block - needed for the block hash operation final var contract = testWeb3jService.deploy(EvmCodes::deploy); - final var recordFileForBlockHash = - domainBuilder.recordFile().customize(r -> r.index(0L)).persist(); - var result = contract.call_getBlockHash(BigInteger.valueOf(recordFileForBlockHash.getIndex())) + var result = contract.call_getBlockHash(BigInteger.valueOf(genesisRecordFile.getIndex())) .send(); - var expectedResult = ByteString.fromHex(recordFileForBlockHash.getHash().substring(0, 64)) - .toByteArray(); + var expectedResult = + ByteString.fromHex(genesisRecordFile.getHash().substring(0, 64)).toByteArray(); assertThat(result).isEqualTo(expectedResult); } @Test void getGenesisBlockHashReturnsCorrectBlock() throws Exception { testWeb3jService.setUseContractCallDeploy(true); + domainBuilder.recordFile().persist(); // Set a latest block - needed for the block hash operation final var contract = testWeb3jService.deploy(EvmCodes::deploy); - final var genesisRecordFileForBlockHash = - domainBuilder.recordFile().customize(f -> f.index(0L)).persist(); var result = contract.call_getBlockHash(BigInteger.ZERO).send(); - var expectedResult = ByteString.fromHex( - genesisRecordFileForBlockHash.getHash().substring(0, 64)) - .toByteArray(); + var expectedResult = + ByteString.fromHex(genesisRecordFile.getHash().substring(0, 64)).toByteArray(); assertThat(result).isEqualTo(expectedResult); } @Test void getLatestBlockHashIsNotEmpty() throws Exception { + domainBuilder.recordFile().persist(); final var contract = testWeb3jService.deploy(EvmCodes::deploy); var result = contract.call_getLatestBlockHash().send(); var expectedResult = ByteString.fromHex((EMPTY_BLOCK_HASH)).toByteArray(); diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNativePrecompileTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNativePrecompileTest.java index 0532f3c7495..198bdd3efbc 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNativePrecompileTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallNativePrecompileTest.java @@ -39,8 +39,7 @@ class ContractCallNativePrecompileTest extends Web3IntegrationTest { @BeforeEach void setup() { - // Persist needed entities - domainBuilder.recordFile().customize(f -> f.index(0L)).persist(); + domainBuilder.recordFile().persist(); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java index 8c133a1a12a..9fac5f25a85 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileModificationTest.java @@ -80,8 +80,8 @@ class ContractCallServicePrecompileModificationTest extends AbstractContractCall void transferFrom() throws Exception { // Given final var owner = accountEntityPersist(); - final var spender = accountEntityPersist(); - final var recipient = accountEntityPersist(); + final var spender = accountEntityWithEvmAddressPersist(); + final var recipient = accountEntityWithEvmAddressPersist(); final var tokenEntity = domainBuilder.entity().customize(e -> e.type(EntityType.TOKEN)).persist(); @@ -269,7 +269,7 @@ void associateTokenHRC() throws Exception { @ValueSource(booleans = {true, false}) void dissociateToken(final Boolean single) throws Exception { // Given - final var associatedAccount = accountEntityPersist(); + final var associatedAccount = accountEntityWithEvmAddressPersist(); final var tokenEntity = domainBuilder.entity().customize(e -> e.type(EntityType.TOKEN)).persist(); @@ -438,7 +438,7 @@ void burnNFT() throws Exception { @Test void wipeFungibleToken() throws Exception { // Given - final var owner = accountEntityPersist(); + final var owner = accountEntityWithEvmAddressPersist(); final var tokenEntity = tokenEntityPersist(); domainBuilder @@ -462,7 +462,7 @@ void wipeFungibleToken() throws Exception { @Test void wipeNFT() throws Exception { // Given - final var owner = accountEntityPersist(); + final var owner = accountEntityWithEvmAddressPersist(); final var tokenEntity = tokenEntityPersist(); domainBuilder @@ -490,7 +490,7 @@ void wipeNFT() throws Exception { @Test void grantTokenKyc() throws Exception { // Given - final var accountWithoutGrant = accountEntityPersist(); + final var accountWithoutGrant = accountEntityWithEvmAddressPersist(); final var tokenEntity = tokenEntityPersist(); domainBuilder @@ -514,7 +514,7 @@ void grantTokenKyc() throws Exception { @Test void revokeTokenKyc() throws Exception { // Given - final var accountWithGrant = accountEntityPersist(); + final var accountWithGrant = accountEntityWithEvmAddressPersist(); final var tokenEntity = tokenEntityPersist(); domainBuilder @@ -557,7 +557,7 @@ void deleteToken() throws Exception { @Test void freezeToken() throws Exception { // Given - final var accountWithoutFreeze = accountEntityPersist(); + final var accountWithoutFreeze = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); tokenAccountPersist(tokenEntity, accountWithoutFreeze); @@ -575,7 +575,7 @@ void freezeToken() throws Exception { @Test void unfreezeToken() throws Exception { // Given - final var accountWithFreeze = accountEntityPersist(); + final var accountWithFreeze = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); @@ -686,7 +686,7 @@ void createFungibleTokenWithCustomFees() throws Exception { var value = 10000L * 100_000_000L; final var tokenForDenomination = persistFungibleToken(); - final var feeCollector = accountEntityPersist(); + final var feeCollector = accountEntityWithEvmAddressPersist(); final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); @@ -766,7 +766,7 @@ void createNonFungibleTokenWithCustomFees() throws Exception { var value = 10000L * 100_000_000L; final var tokenForDenomination = persistFungibleToken(); - final var feeCollector = accountEntityPersist(); + final var feeCollector = accountEntityWithEvmAddressPersist(); final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); @@ -811,8 +811,8 @@ void createNonFungibleTokenWithCustomFees() throws Exception { @Test void create2ContractAndTransferFromIt() throws Exception { // Given - final var sponsor = accountEntityPersist(); - final var receiver = accountEntityPersist(); + final var sponsor = accountEntityWithEvmAddressPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); final var token = persistFungibleToken(); tokenAccountPersist(token, sponsor); @@ -847,7 +847,7 @@ void notExistingPrecompileCallFails() { void createFungibleTokenWithInheritKeysCall() throws Exception { // Given final var value = 10000 * 100_000_000L; - final var sender = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); // When @@ -1049,9 +1049,9 @@ void transferToken(final String type) throws Exception { // Given final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); final var tokenEntity = persistFungibleToken(); - final var sender = accountEntityPersist(); - final var receiver = accountEntityPersist(); - final var payer = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); + final var payer = accountEntityWithEvmAddressPersist(); tokenAccountPersist(tokenEntity, payer); tokenAccountPersist(tokenEntity, sender); @@ -1085,7 +1085,7 @@ void transferToken(final String type) throws Exception { void transferNft(final String type) throws Exception { // Given final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); - final var sender = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); final var tokenEntity = tokenEntityPersist(); domainBuilder .token() @@ -1095,8 +1095,8 @@ void transferNft(final String type) throws Exception { .nft() .customize(n -> n.tokenId(tokenEntity.getId()).serialNumber(1L).accountId(sender.toEntityId())) .persist(); - final var receiver = accountEntityPersist(); - final var payer = accountEntityPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); + final var payer = accountEntityWithEvmAddressPersist(); tokenAccountPersist(tokenEntity, payer); tokenAccountPersist(tokenEntity, sender); @@ -1130,7 +1130,7 @@ void transferNft(final String type) throws Exception { void transferFromNft() throws Exception { // Given final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); - final var sender = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); final var tokenEntity = tokenEntityPersist(); domainBuilder .token() @@ -1140,8 +1140,8 @@ void transferFromNft() throws Exception { .nft() .customize(n -> n.tokenId(tokenEntity.getId()).serialNumber(1L).accountId(sender.toEntityId())) .persist(); - final var receiver = accountEntityPersist(); - final var payer = accountEntityPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); + final var payer = accountEntityWithEvmAddressPersist(); tokenAccountPersist(tokenEntity, payer); tokenAccountPersist(tokenEntity, sender); @@ -1169,9 +1169,9 @@ void transferFromNft() throws Exception { void cryptoTransferHbars() throws Exception { // Given final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); - final var sender = accountEntityPersist(); - final var receiver = accountEntityPersist(); - final var payer = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); + final var payer = accountEntityWithEvmAddressPersist(); // When testWeb3jService.setSender(getAliasFromEntity(payer)); @@ -1192,10 +1192,10 @@ void cryptoTransferHbars() throws Exception { void cryptoTransferToken() throws Exception { // Given final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); - final var sender = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); - final var receiver = accountEntityPersist(); - final var payer = accountEntityPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); + final var payer = accountEntityWithEvmAddressPersist(); tokenAccountPersist(tokenEntity, payer); tokenAccountPersist(tokenEntity, sender); @@ -1224,10 +1224,10 @@ void cryptoTransferToken() throws Exception { void cryptoTransferHbarsAndToken() throws Exception { // Given final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); - final var sender = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); - final var receiver = accountEntityPersist(); - final var payer = accountEntityPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); + final var payer = accountEntityWithEvmAddressPersist(); tokenAccountPersist(tokenEntity, payer); tokenAccountPersist(tokenEntity, sender); @@ -1259,7 +1259,7 @@ void cryptoTransferHbarsAndToken() throws Exception { void cryptoTransferNft() throws Exception { // Given final var contract = testWeb3jService.deploy(ModificationPrecompileTestContract::deploy); - final var sender = accountEntityPersist(); + final var sender = accountEntityWithEvmAddressPersist(); final var tokenEntity = tokenEntityPersist(); domainBuilder .token() @@ -1269,8 +1269,8 @@ void cryptoTransferNft() throws Exception { .nft() .customize(n -> n.tokenId(tokenEntity.getId()).serialNumber(1L).accountId(sender.toEntityId())) .persist(); - final var receiver = accountEntityPersist(); - final var payer = accountEntityPersist(); + final var receiver = accountEntityWithEvmAddressPersist(); + final var payer = accountEntityWithEvmAddressPersist(); tokenAccountPersist(tokenEntity, payer); tokenAccountPersist(tokenEntity, sender); @@ -1318,7 +1318,7 @@ void updateTokenInfo() throws Exception { @Test void updateTokenExpiry() throws Exception { // Given - final var treasuryAccount = accountEntityPersist(); + final var treasuryAccount = accountEntityWithEvmAddressPersist(); final var tokenWithAutoRenewPair = persistTokenWithAutoRenewAndTreasuryAccounts(TokenTypeEnum.FUNGIBLE_COMMON, treasuryAccount); @@ -1392,7 +1392,7 @@ private void verifyEthCallAndEstimateGas( private HederaToken populateHederaToken( final String contractAddress, final TokenTypeEnum tokenType, final EntityId treasuryAccountId) { - final var autoRenewAccount = accountEntityPersist(); + final var autoRenewAccount = accountEntityWithEvmAddressPersist(); final var tokenEntity = domainBuilder .entity() .customize(e -> e.type(EntityType.TOKEN).autoRenewAccountId(autoRenewAccount.getId())) @@ -1423,7 +1423,7 @@ private HederaToken populateHederaToken( private Pair persistTokenWithAutoRenewAndTreasuryAccounts( final TokenTypeEnum tokenType, final Entity treasuryAccount) { - final var autoRenewAccount = accountEntityPersist(); + final var autoRenewAccount = accountEntityWithEvmAddressPersist(); final var tokenToUpdateEntity = domainBuilder .entity() .customize(e -> e.type(EntityType.TOKEN).autoRenewAccountId(autoRenewAccount.getId())) diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileReadonlyTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileReadonlyTest.java index 22fa320650b..a4fe12b15d5 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileReadonlyTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServicePrecompileReadonlyTest.java @@ -492,7 +492,7 @@ void getTokenKey(final TokenTypeEnum tokenType, final KeyValueType keyValueType, @Test void getCustomFeesForTokenWithFixedFee() throws Exception { // Given - final var collectorAccount = accountEntityPersist(); + final var collectorAccount = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); final var fixedFee = com.hedera.mirror.common.domain.token.FixedFee.builder() .amount(100L) @@ -530,7 +530,7 @@ void getCustomFeesForTokenWithFixedFee() throws Exception { @Test void getCustomFeesForTokenWithFractionalFee() throws Exception { // Given - final var collectorAccount = accountEntityPersist(); + final var collectorAccount = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); final var fractionalFee = FractionalFee.builder() .collectorAccountId(collectorAccount.toEntityId()) @@ -572,7 +572,7 @@ void getCustomFeesForTokenWithFractionalFee() throws Exception { @Test void getCustomFeesForTokenWithRoyaltyFee() throws Exception { // Given - final var collectorAccount = accountEntityPersist(); + final var collectorAccount = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); final var royaltyFee = RoyaltyFee.builder() .collectorAccountId(collectorAccount.toEntityId()) @@ -618,7 +618,7 @@ void getExpiryForToken() throws Exception { // Given final var expiryPeriod = 9999999999999L; final var autoRenewExpiry = 100000000L; - final var autoRenewAccount = accountEntityPersist(); + final var autoRenewAccount = accountEntityWithEvmAddressPersist(); final var tokenEntity = domainBuilder .entity() .customize(e -> e.type(EntityType.TOKEN) @@ -653,8 +653,8 @@ void getExpiryForToken() throws Exception { void getAllowanceForToken() throws Exception { // Given final var amountGranted = 50L; - final var owner = accountEntityPersist(); - final var spender = accountEntityPersist(); + final var owner = accountEntityWithEvmAddressPersist(); + final var spender = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistFungibleToken(); domainBuilder @@ -681,8 +681,8 @@ void getAllowanceForToken() throws Exception { @Test void isApprovedForAllNFT() throws Exception { // Given - final var owner = accountEntityPersist(); - final var spender = accountEntityPersist(); + final var owner = accountEntityWithEvmAddressPersist(); + final var spender = accountEntityWithEvmAddressPersist(); final var tokenEntity = persistNft(); domainBuilder @@ -708,8 +708,8 @@ void isApprovedForAllNFT() throws Exception { @Test void getFungibleTokenInfo() throws Exception { // Given - final var treasury = accountEntityPersist(); - final var feeCollector = accountEntityPersist(); + final var treasury = accountEntityWithEvmAddressPersist(); + final var feeCollector = accountEntityWithEvmAddressPersist(); final var tokenEntity = domainBuilder.entity().customize(e -> e.type(EntityType.TOKEN)).persist(); final var token = domainBuilder @@ -775,8 +775,8 @@ void getFungibleTokenInfo() throws Exception { void getNonFungibleTokenInfo() throws Exception { // Given final var owner = accountEntityPersist(); - final var treasury = accountEntityPersist(); - final var feeCollector = accountEntityPersist(); + final var treasury = accountEntityWithEvmAddressPersist(); + final var feeCollector = accountEntityWithEvmAddressPersist(); final var tokenEntity = domainBuilder.entity().customize(e -> e.type(EntityType.TOKEN)).persist(); final var token = domainBuilder @@ -852,8 +852,8 @@ void getNonFungibleTokenInfo() throws Exception { @EnumSource(TokenTypeEnum.class) void getTokenInfo(final TokenTypeEnum tokenType) throws Exception { // Given - final var treasury = accountEntityPersist(); - final var feeCollector = accountEntityPersist(); + final var treasury = accountEntityWithEvmAddressPersist(); + final var feeCollector = accountEntityWithEvmAddressPersist(); final var tokenEntity = domainBuilder.entity().customize(e -> e.type(EntityType.TOKEN)).persist(); final var token = domainBuilder diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java index 2f6edd1780f..60e7442bfc2 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/ContractCallServiceTest.java @@ -52,6 +52,7 @@ import com.hedera.mirror.web3.service.model.CallServiceParameters.CallType; import com.hedera.mirror.web3.service.model.ContractExecutionParameters; import com.hedera.mirror.web3.service.utils.BinaryGasEstimator; +import com.hedera.mirror.web3.state.MirrorNodeState; import com.hedera.mirror.web3.throttle.ThrottleProperties; import com.hedera.mirror.web3.viewmodel.BlockType; import com.hedera.mirror.web3.web3j.generated.ERCTestContract; @@ -61,10 +62,14 @@ import com.hedera.services.store.models.Id; import com.hedera.services.utils.EntityIdUtils; import io.github.bucket4j.Bucket; +import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; +import java.lang.reflect.Method; import java.math.BigInteger; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; @@ -101,6 +106,9 @@ class ContractCallServiceTest extends AbstractContractCallServiceTest { @Autowired private ThrottleProperties throttleProperties; + @Autowired + private TransactionExecutionService transactionExecutionService; + @Resource private ContractExecutionService contractExecutionService; @@ -130,6 +138,13 @@ private static Stream ercPrecompileCallTypeArgumentsProvider() { .flatMap(callType -> gasLimits.stream().map(gasLimit -> Arguments.of(callType, gasLimit))); } + private static String toHexWith64LeadingZeros(final Long value) { + final String result; + final var paddedHexString = String.format("%064x", value); + result = "0x" + paddedHexString; + return result; + } + @Test void callWithoutDataToAddressWithNoBytecodeReturnsEmptyResult() { // Given @@ -163,6 +178,38 @@ void pureCall() throws Exception { assertGasUsedIsPositive(gasUsedBeforeExecution, ETH_CALL); } + // This test will be removed in the future. Needed only for test coverage right now. + @Test + void pureCallModularizedServices() throws Exception { + // Given + final var modularizedServicesFlag = mirrorNodeEvmProperties.isModularizedServices(); + mirrorNodeEvmProperties.setModularizedServices(true); + Method postConstructMethod = Arrays.stream(MirrorNodeState.class.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(PostConstruct.class)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("@PostConstruct method not found")); + + postConstructMethod.setAccessible(true); // Make the method accessible + postConstructMethod.invoke(state); + + final var backupProperties = mirrorNodeEvmProperties.getProperties(); + final Map propertiesMap = new HashMap<>(); + propertiesMap.put("contracts.maxRefundPercentOfGasLimit", "100"); + propertiesMap.put("contracts.maxGasPerSec", "15000000"); + mirrorNodeEvmProperties.setProperties(propertiesMap); + + final var contract = testWeb3jService.deploy(EthCall::deploy); + meterRegistry.clear(); // Clear it as the contract deploy increases the gas limit metric + + // When + contract.call_multiplySimpleNumbers().send(); + + // Then + // Restore changed property values. + mirrorNodeEvmProperties.setModularizedServices(modularizedServicesFlag); + mirrorNodeEvmProperties.setProperties(backupProperties); + } + @ParameterizedTest @MethodSource("provideBlockTypes") void pureCallWithBlock(BlockType blockType) throws Exception { @@ -715,7 +762,9 @@ void ercPrecompileExceptionalHaltReturnsExpectedGasToBucket(final CallType callT mirrorEvmTxProcessor, recordFileService, throttleProperties, - gasLimitBucket); + gasLimitBucket, + mirrorNodeEvmProperties, + transactionExecutionService); // When try { @@ -748,7 +797,9 @@ void ercPrecompileContractRevertReturnsExpectedGasToBucket(final CallType callTy mirrorEvmTxProcessor, recordFileService, throttleProperties, - gasLimitBucket); + gasLimitBucket, + mirrorNodeEvmProperties, + transactionExecutionService); // When try { @@ -782,7 +833,9 @@ void ercPrecompileSuccessReturnsExpectedGasToBucket(final CallType callType, fin mirrorEvmTxProcessor, recordFileService, throttleProperties, - gasLimitBucket); + gasLimitBucket, + mirrorNodeEvmProperties, + transactionExecutionService); // When try { @@ -977,11 +1030,4 @@ void transferToNonExistingContract() { assertGasLimit(serviceParameters); } } - - private static String toHexWith64LeadingZeros(final Long value) { - final String result; - final var paddedHexString = String.format("%064x", value); - result = "0x" + paddedHexString; - return result; - } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/RecordFileServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/RecordFileServiceTest.java index d890672b5fb..65d990bee93 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/RecordFileServiceTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/RecordFileServiceTest.java @@ -42,11 +42,11 @@ void testFindByTimestamp() { @Test void testFindByBlockTypeEarliest() { - var recordFileEarliest = + final var genesisRecordFile = domainBuilder.recordFile().customize(f -> f.index(0L)).persist(); domainBuilder.recordFile().customize(f -> f.index(1L)).persist(); domainBuilder.recordFile().customize(f -> f.index(2L)).persist(); - assertThat(recordFileService.findByBlockType(BlockType.EARLIEST)).contains(recordFileEarliest); + assertThat(recordFileService.findByBlockType(BlockType.EARLIEST)).contains(genesisRecordFile); } @Test diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/TransactionExecutionServiceTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/TransactionExecutionServiceTest.java new file mode 100644 index 00000000000..ae03196f74b --- /dev/null +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/service/TransactionExecutionServiceTest.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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. + */ + +package com.hedera.mirror.web3.service; + +import static com.hedera.mirror.web3.state.Utils.isMirror; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.contract.ContractFunctionResult; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.hapi.node.transaction.TransactionReceipt; +import com.hedera.hapi.node.transaction.TransactionRecord; +import com.hedera.mirror.web3.common.ContractCallContext; +import com.hedera.mirror.web3.evm.contracts.execution.traceability.OpcodeTracer; +import com.hedera.mirror.web3.evm.contracts.execution.traceability.OpcodeTracerOptions; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; +import com.hedera.mirror.web3.service.TransactionExecutionService.ExecutorFactory; +import com.hedera.mirror.web3.service.model.CallServiceParameters; +import com.hedera.mirror.web3.service.model.CallServiceParameters.CallType; +import com.hedera.mirror.web3.service.model.ContractExecutionParameters; +import com.hedera.mirror.web3.state.AliasesReadableKVState; +import com.hedera.mirror.web3.viewmodel.BlockType; +import com.hedera.mirror.web3.web3j.generated.NestedCalls; +import com.hedera.node.app.service.evm.store.models.HederaEvmAccount; +import com.hedera.node.app.state.SingleTransactionRecord; +import com.hedera.node.app.workflows.standalone.TransactionExecutor; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.state.State; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import java.time.Instant; +import java.util.List; +import java.util.stream.Stream; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TransactionExecutionServiceTest { + private static final Long DEFAULT_GAS = 50000L; + + @Mock + private State mirrorNodeState; + + @Mock + private MirrorNodeEvmProperties mirrorNodeEvmProperties; + + @Mock + private OpcodeTracer opcodeTracer; + + @Mock + private TransactionExecutor transactionExecutor; + + @Mock + private ContractCallContext contractCallContext; + + private TransactionExecutionService transactionExecutionService; + + private static Stream provideCallData() { + return Stream.of( + Arguments.of(org.apache.tuweni.bytes.Bytes.EMPTY), + Arguments.of(org.apache.tuweni.bytes.Bytes.fromHexString(NestedCalls.BINARY))); + } + + @BeforeEach + void setUp() { + transactionExecutionService = + new TransactionExecutionService(mirrorNodeState, mirrorNodeEvmProperties, opcodeTracer); + } + + @ParameterizedTest + @ValueSource( + strings = { + "0x0000000000000000000000000000000000000000", + "0x1234", + "0x627306090abab3a6e1400e9345bc60c78a8bef57" + }) + void testExecuteContractCallSuccess(String senderAddressHex) { + // Given + try (MockedStatic executorFactoryMock = mockStatic(ExecutorFactory.class); + MockedStatic contractCallContextMock = mockStatic(ContractCallContext.class)) { + + // Set up mock behaviors for ExecutorFactory + executorFactoryMock + .when(() -> ExecutorFactory.newExecutor(any(), any(), any())) + .thenReturn(transactionExecutor); + + // Set up mock behaviors for ContractCallContext + contractCallContextMock.when(ContractCallContext::get).thenReturn(contractCallContext); + when(contractCallContext.getOpcodeTracerOptions()).thenReturn(new OpcodeTracerOptions()); + + // Mock the SingleTransactionRecord and TransactionRecord + SingleTransactionRecord singleTransactionRecord = mock(SingleTransactionRecord.class); + TransactionRecord transactionRecord = mock(TransactionRecord.class); + TransactionReceipt transactionReceipt = mock(TransactionReceipt.class); + + // Simulate SUCCESS status in the receipt + when(transactionReceipt.status()).thenReturn(ResponseCodeEnum.SUCCESS); + when(transactionRecord.receiptOrThrow()).thenReturn(transactionReceipt); + when(singleTransactionRecord.transactionRecord()).thenReturn(transactionRecord); + + ContractFunctionResult contractFunctionResult = mock(ContractFunctionResult.class); + when(contractFunctionResult.gasUsed()).thenReturn(DEFAULT_GAS); + when(contractFunctionResult.contractCallResult()).thenReturn(Bytes.EMPTY); + + // Mock the transactionRecord to return the contract call result + when(transactionRecord.contractCallResultOrThrow()).thenReturn(contractFunctionResult); + + final var senderAddress = Address.fromHexString(senderAddressHex); + if (!isMirror(senderAddress)) { + final var readableStates = mock(ReadableStates.class); + when(mirrorNodeState.getReadableStates(any())).thenReturn(readableStates); + + final var aliasesReadableKVState = mock(ReadableKVState.class); + when(readableStates.get(AliasesReadableKVState.KEY)).thenReturn(aliasesReadableKVState); + } + + // Mock the executor to return a List with the mocked SingleTransactionRecord + when(transactionExecutor.execute( + any(TransactionBody.class), any(Instant.class), any(OperationTracer[].class))) + .thenReturn(List.of(singleTransactionRecord)); + + CallServiceParameters callServiceParameters = + buildServiceParams(false, org.apache.tuweni.bytes.Bytes.EMPTY, senderAddress); + + // When + var result = transactionExecutionService.execute(callServiceParameters, DEFAULT_GAS); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getGasUsed()).isEqualTo(DEFAULT_GAS); + assertThat(result.getRevertReason()).isNotPresent(); + } + } + + @Test + void testExecuteContractCallFailure() { + // Given + try (MockedStatic executorFactoryMock = mockStatic(ExecutorFactory.class); + MockedStatic contractCallContextMock = mockStatic(ContractCallContext.class)) { + + // Set up mock behaviors for ExecutorFactory + executorFactoryMock + .when(() -> ExecutorFactory.newExecutor(any(), any(), any())) + .thenReturn(transactionExecutor); + + // Set up mock behaviors for ContractCallContext + contractCallContextMock.when(ContractCallContext::get).thenReturn(contractCallContext); + + // Mock the SingleTransactionRecord and TransactionRecord + SingleTransactionRecord singleTransactionRecord = mock(SingleTransactionRecord.class); + TransactionRecord transactionRecord = mock(TransactionRecord.class); + TransactionReceipt transactionReceipt = mock(TransactionReceipt.class); + + // Simulate CONTRACT_REVERT_EXECUTED status in the receipt + when(transactionReceipt.status()).thenReturn(ResponseCodeEnum.CONTRACT_REVERT_EXECUTED); + when(transactionRecord.receiptOrThrow()).thenReturn(transactionReceipt); + when(transactionRecord.receipt()).thenReturn(transactionReceipt); + when(singleTransactionRecord.transactionRecord()).thenReturn(transactionRecord); + + ContractFunctionResult contractFunctionResult = mock(ContractFunctionResult.class); + when(transactionRecord.contractCallResultOrThrow()).thenReturn(contractFunctionResult); + when(contractFunctionResult.gasUsed()).thenReturn(DEFAULT_GAS); + + // Mock the executor to return a List with the mocked SingleTransactionRecord + when(transactionExecutor.execute( + any(TransactionBody.class), any(Instant.class), any(OperationTracer[].class))) + .thenReturn(List.of(singleTransactionRecord)); + + CallServiceParameters callServiceParameters = + buildServiceParams(false, org.apache.tuweni.bytes.Bytes.EMPTY, Address.ZERO); + + // When + var result = transactionExecutionService.execute(callServiceParameters, DEFAULT_GAS); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getGasUsed()).isEqualTo(DEFAULT_GAS); + assertThat(result.getRevertReason()).isPresent(); + } + } + + // NestedCalls.BINARY + @ParameterizedTest + @MethodSource("provideCallData") + void testExecuteContractCreateSuccess(org.apache.tuweni.bytes.Bytes callData) { + // Given + try (MockedStatic executorFactoryMock = mockStatic(ExecutorFactory.class); + MockedStatic contractCallContextMock = mockStatic(ContractCallContext.class)) { + + // Set up mock behaviors for ExecutorFactory + executorFactoryMock + .when(() -> ExecutorFactory.newExecutor(any(), any(), any())) + .thenReturn(transactionExecutor); + + // Set up mock behaviors for ContractCallContext + contractCallContextMock.when(ContractCallContext::get).thenReturn(contractCallContext); + when(contractCallContext.getOpcodeTracerOptions()).thenReturn(new OpcodeTracerOptions()); + + // Mock the SingleTransactionRecord and TransactionRecord + SingleTransactionRecord singleTransactionRecord = mock(SingleTransactionRecord.class); + TransactionRecord transactionRecord = mock(TransactionRecord.class); + TransactionReceipt transactionReceipt = mock(TransactionReceipt.class); + + when(transactionReceipt.status()).thenReturn(ResponseCodeEnum.SUCCESS); + when(transactionRecord.receiptOrThrow()).thenReturn(transactionReceipt); + when(singleTransactionRecord.transactionRecord()).thenReturn(transactionRecord); + + ContractFunctionResult contractFunctionResult = mock(ContractFunctionResult.class); + when(contractFunctionResult.gasUsed()).thenReturn(DEFAULT_GAS); + when(contractFunctionResult.contractCallResult()).thenReturn(Bytes.EMPTY); + + // Mock the transactionRecord to return the contract call result + when(transactionRecord.contractCreateResultOrThrow()).thenReturn(contractFunctionResult); + + // Mock the executor to return a List with the mocked SingleTransactionRecord + when(transactionExecutor.execute( + any(TransactionBody.class), any(Instant.class), any(OperationTracer[].class))) + .thenReturn(List.of(singleTransactionRecord)); + + CallServiceParameters callServiceParameters = buildServiceParams(true, callData, Address.ZERO); + + // When + var result = transactionExecutionService.execute(callServiceParameters, DEFAULT_GAS); + + // Then + assertThat(result).isNotNull(); + assertThat(result.getGasUsed()).isEqualTo(DEFAULT_GAS); + assertThat(result.getRevertReason()).isNotPresent(); + } + } + + private CallServiceParameters buildServiceParams( + boolean isContractCreate, org.apache.tuweni.bytes.Bytes callData, final Address senderAddress) { + return ContractExecutionParameters.builder() + .block(BlockType.LATEST) + .callData(callData) + .callType(CallType.ETH_CALL) + .gas(DEFAULT_GAS) + .isEstimate(false) + .isStatic(true) + .receiver(isContractCreate ? Address.ZERO : Address.fromHexString("0x1234")) + .sender(new HederaEvmAccount(senderAddress)) + .value(0) + .build(); + } +} diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateIntegrationTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateIntegrationTest.java index 365bad99d84..5a3298566da 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateIntegrationTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateIntegrationTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.hedera.mirror.web3.Web3IntegrationTest; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; import com.hedera.node.app.fees.FeeService; import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.records.BlockRecordService; @@ -46,9 +47,14 @@ public class MirrorNodeStateIntegrationTest extends Web3IntegrationTest { private final MirrorNodeState mirrorNodeState; private final ServicesRegistry servicesRegistry; + private final MirrorNodeEvmProperties mirrorNodeEvmProperties; @Test void verifyMirrorNodeStateHasRegisteredServices() { + if (!mirrorNodeEvmProperties.isModularizedServices()) { + return; + } + Set> expectedServices = new HashSet<>(List.of( EntityIdService.class, TokenServiceImpl.class, @@ -72,6 +78,10 @@ void verifyMirrorNodeStateHasRegisteredServices() { @Test void verifyServicesHaveAssignedDataSources() { + if (!mirrorNodeEvmProperties.isModularizedServices()) { + return; + } + final var states = mirrorNodeState.getStates(); // BlockRecordService @@ -81,7 +91,7 @@ void verifyServicesHaveAssignedDataSources() { verifyServiceDataSources(states, BlockRecordService.NAME, blockRecordServiceDataSources); // FileService - Map> fileServiceDataSources = Map.of("FILES", Map.class); + Map> fileServiceDataSources = Map.of(FileReadableKVState.KEY, Map.class); verifyServiceDataSources(states, FileService.NAME, fileServiceDataSources); // CongestionThrottleService @@ -96,8 +106,8 @@ void verifyServicesHaveAssignedDataSources() { // ContractService Map> contractServiceDataSources = Map.of( - "BYTECODE", Map.class, - "STORAGE", Map.class); + ContractBytecodeReadableKVState.KEY, Map.class, + ContractStorageReadableKVState.KEY, Map.class); verifyServiceDataSources(states, ContractService.NAME, contractServiceDataSources); // RecordCacheService @@ -110,13 +120,20 @@ void verifyServicesHaveAssignedDataSources() { // TokenService Map> tokenServiceDataSources = Map.of( - "ACCOUNTS", Map.class, - "PENDING_AIRDROPS", Map.class, - "ALIASES", Map.class, - "NFTS", Map.class, - "TOKENS", Map.class, - "TOKEN_RELS", Map.class, - "STAKING_NETWORK_REWARDS", AtomicReference.class); + AccountReadableKVState.KEY, + Map.class, + "PENDING_AIRDROPS", + Map.class, + AliasesReadableKVState.KEY, + Map.class, + NftReadableKVState.KEY, + Map.class, + TokenReadableKVState.KEY, + Map.class, + TokenRelationshipReadableKVState.KEY, + Map.class, + "STAKING_NETWORK_REWARDS", + AtomicReference.class); verifyServiceDataSources(states, TokenService.NAME, tokenServiceDataSources); } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java index 64056dc4f2f..cce6fbf9186 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/MirrorNodeStateTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties; import com.hedera.mirror.web3.state.core.MapReadableStates; import com.hedera.mirror.web3.state.core.MapWritableKVState; import com.hedera.mirror.web3.state.core.MapWritableStates; @@ -92,6 +93,9 @@ class MirrorNodeStateTest { @Mock private ServiceMigrator serviceMigrator; + @Mock + private MirrorNodeEvmProperties mirrorNodeEvmProperties; + @Mock private NetworkInfo networkInfo; @@ -121,91 +125,104 @@ void setup() { @Test void testAddService() { - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); - - assertThat(mirrorNodeState.getReadableStates("NEW").contains("FILES")).isFalse(); - final var newState = - mirrorNodeState.addService("NEW", new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState)))); - assertThat(newState.getReadableStates("NEW").contains("FILES")).isTrue(); + when(fileReadableKVState.getStateKey()).thenReturn(FileReadableKVState.KEY); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); + + assertThat(mirrorNodeState.getReadableStates("NEW").contains(FileReadableKVState.KEY)) + .isFalse(); + final var newState = mirrorNodeState.addService( + "NEW", + new HashMap<>(Map.of(FileReadableKVState.KEY, Map.of(FileReadableKVState.KEY, fileReadableKVState)))); + assertThat(newState.getReadableStates("NEW").contains(FileReadableKVState.KEY)) + .isTrue(); } @Test void testRemoveService() { - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); final var testStates = new HashMap<>(Map.of( - "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), - "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + ContractBytecodeReadableKVState.KEY, + Map.of(ContractBytecodeReadableKVState.KEY, contractBytecodeReadableKVState), + ContractStorageReadableKVState.KEY, + Map.of(ContractStorageReadableKVState.KEY, contractStorageReadableKVState))); final var newState = mirrorNodeState.addService("NEW", testStates); - assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isTrue(); - assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); - newState.removeServiceState("NEW", "BYTECODE"); - assertThat(newState.getReadableStates("NEW").contains("BYTECODE")).isFalse(); - assertThat(newState.getReadableStates("NEW").contains("STORAGE")).isTrue(); + assertThat(newState.getReadableStates("NEW").contains(ContractBytecodeReadableKVState.KEY)) + .isTrue(); + assertThat(newState.getReadableStates("NEW").contains(ContractStorageReadableKVState.KEY)) + .isTrue(); + newState.removeServiceState("NEW", ContractBytecodeReadableKVState.KEY); + assertThat(newState.getReadableStates("NEW").contains(ContractBytecodeReadableKVState.KEY)) + .isFalse(); + assertThat(newState.getReadableStates("NEW").contains(ContractStorageReadableKVState.KEY)) + .isTrue(); } @Test void testGetReadableStatesForFileService() { - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + when(fileReadableKVState.getStateKey()).thenReturn(FileReadableKVState.KEY); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); - assertThat(readableStates).isEqualTo(new MapReadableStates(Map.of("FILES", fileReadableKVState))); + assertThat(readableStates) + .isEqualTo(new MapReadableStates(Map.of(FileReadableKVState.KEY, fileReadableKVState))); } @Test void testGetReadableStatesForContractService() { - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( - "BYTECODE", contractBytecodeReadableKVState, "STORAGE", contractStorageReadableKVState))); + ContractBytecodeReadableKVState.KEY, + contractBytecodeReadableKVState, + ContractStorageReadableKVState.KEY, + contractStorageReadableKVState))); } @Test void testGetReadableStatesForTokenService() { - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); - when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); - when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(nftReadableKVState.getStateKey()).thenReturn(NftReadableKVState.KEY); + when(tokenReadableKVState.getStateKey()).thenReturn(TokenReadableKVState.KEY); + when(tokenRelationshipReadableKVState.getStateKey()).thenReturn(TokenRelationshipReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); + when(fileReadableKVState.getStateKey()).thenReturn(FileReadableKVState.KEY); final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); assertThat(readableStates) .isEqualTo(new MapReadableStates(Map.of( - "ACCOUNTS", + AccountReadableKVState.KEY, accountReadableKVState, - "PENDING_AIRDROPS", + AirdropsReadableKVState.KEY, airdropsReadableKVState, - "ALIASES", + AliasesReadableKVState.KEY, aliasesReadableKVState, - "NFTS", + NftReadableKVState.KEY, nftReadableKVState, - "TOKENS", + TokenReadableKVState.KEY, tokenReadableKVState, - "TOKEN_RELS", + TokenRelationshipReadableKVState.KEY, tokenRelationshipReadableKVState))); } @@ -235,85 +252,99 @@ void testGetReadableStatesWithQueue() { @Test void testGetWritableStatesForFileService() { - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + when(fileReadableKVState.getStateKey()).thenReturn(FileReadableKVState.KEY); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) - .isEqualTo(new MapWritableStates( - Map.of("FILES", new MapWritableKVState<>("FILES", readableStates.get("FILES"))))); + .isEqualTo(new MapWritableStates(Map.of( + FileReadableKVState.KEY, + new MapWritableKVState<>( + FileReadableKVState.KEY, readableStates.get(FileReadableKVState.KEY))))); } @Test void testGetWritableStatesForFileServiceWithListeners() { when(listener.stateTypes()).thenReturn(Set.of(StateType.MAP)); - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + when(fileReadableKVState.getStateKey()).thenReturn(FileReadableKVState.KEY); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); mirrorNodeState.registerCommitListener(listener); final var writableStates = mirrorNodeState.getWritableStates(FileService.NAME); final var readableStates = mirrorNodeState.getReadableStates(FileService.NAME); assertThat(writableStates) - .isEqualTo(new MapWritableStates( - Map.of("FILES", new MapWritableKVState<>("FILES", readableStates.get("FILES"))))); + .isEqualTo(new MapWritableStates(Map.of( + FileReadableKVState.KEY, + new MapWritableKVState<>( + FileReadableKVState.KEY, readableStates.get(FileReadableKVState.KEY))))); } @Test void testGetWritableStatesForContractService() { - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); final var writableStates = mirrorNodeState.getWritableStates(ContractService.NAME); final var readableStates = mirrorNodeState.getReadableStates(ContractService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( - "BYTECODE", - new MapWritableKVState<>("BYTECODE", readableStates.get("BYTECODE")), - "STORAGE", - new MapWritableKVState<>("STORAGE", readableStates.get("STORAGE"))))); + ContractBytecodeReadableKVState.KEY, + new MapWritableKVState<>( + ContractBytecodeReadableKVState.KEY, + readableStates.get(ContractBytecodeReadableKVState.KEY)), + ContractStorageReadableKVState.KEY, + new MapWritableKVState<>( + ContractStorageReadableKVState.KEY, + readableStates.get(ContractStorageReadableKVState.KEY))))); } @Test void testGetWritableStatesForTokenService() { - when(accountReadableKVState.getStateKey()).thenReturn("ACCOUNTS"); - when(airdropsReadableKVState.getStateKey()).thenReturn("PENDING_AIRDROPS"); - when(aliasesReadableKVState.getStateKey()).thenReturn("ALIASES"); - when(nftReadableKVState.getStateKey()).thenReturn("NFTS"); - when(tokenReadableKVState.getStateKey()).thenReturn("TOKENS"); - when(tokenRelationshipReadableKVState.getStateKey()).thenReturn("TOKEN_RELS"); - when(contractBytecodeReadableKVState.getStateKey()).thenReturn("BYTECODE"); - when(contractStorageReadableKVState.getStateKey()).thenReturn("STORAGE"); - when(fileReadableKVState.getStateKey()).thenReturn("FILES"); + when(accountReadableKVState.getStateKey()).thenReturn(AccountReadableKVState.KEY); + when(airdropsReadableKVState.getStateKey()).thenReturn(AirdropsReadableKVState.KEY); + when(aliasesReadableKVState.getStateKey()).thenReturn(AliasesReadableKVState.KEY); + when(nftReadableKVState.getStateKey()).thenReturn(NftReadableKVState.KEY); + when(tokenReadableKVState.getStateKey()).thenReturn(TokenReadableKVState.KEY); + when(tokenRelationshipReadableKVState.getStateKey()).thenReturn(TokenRelationshipReadableKVState.KEY); + when(contractBytecodeReadableKVState.getStateKey()).thenReturn(ContractBytecodeReadableKVState.KEY); + when(contractStorageReadableKVState.getStateKey()).thenReturn(ContractStorageReadableKVState.KEY); + when(fileReadableKVState.getStateKey()).thenReturn(FileReadableKVState.KEY); final var writableStates = mirrorNodeState.getWritableStates(TokenService.NAME); final var readableStates = mirrorNodeState.getReadableStates(TokenService.NAME); assertThat(writableStates) .isEqualTo(new MapWritableStates(Map.of( - "ACCOUNTS", - new MapWritableKVState<>("ACCOUNTS", readableStates.get("ACCOUNTS")), - "PENDING_AIRDROPS", - new MapWritableKVState<>("PENDING_AIRDROPS", readableStates.get("PENDING_AIRDROPS")), - "ALIASES", - new MapWritableKVState<>("ALIASES", readableStates.get("ALIASES")), - "NFTS", - new MapWritableKVState<>("NFTS", readableStates.get("NFTS")), - "TOKENS", - new MapWritableKVState<>("TOKENS", readableStates.get("TOKENS")), - "TOKEN_RELS", - new MapWritableKVState<>("TOKEN_RELS", readableStates.get("TOKEN_RELS"))))); + AccountReadableKVState.KEY, + new MapWritableKVState<>( + AccountReadableKVState.KEY, readableStates.get(AccountReadableKVState.KEY)), + AirdropsReadableKVState.KEY, + new MapWritableKVState<>( + AirdropsReadableKVState.KEY, readableStates.get(AirdropsReadableKVState.KEY)), + AliasesReadableKVState.KEY, + new MapWritableKVState<>( + AliasesReadableKVState.KEY, readableStates.get(AliasesReadableKVState.KEY)), + NftReadableKVState.KEY, + new MapWritableKVState<>(NftReadableKVState.KEY, readableStates.get(NftReadableKVState.KEY)), + TokenReadableKVState.KEY, + new MapWritableKVState<>( + TokenReadableKVState.KEY, readableStates.get(TokenReadableKVState.KEY)), + TokenRelationshipReadableKVState.KEY, + new MapWritableKVState<>( + TokenRelationshipReadableKVState.KEY, + readableStates.get(TokenRelationshipReadableKVState.KEY))))); } @Test @@ -425,17 +456,21 @@ void testHashCode() { } private MirrorNodeState initStateAfterMigration() { - final Map fileStateData = new HashMap<>(Map.of("FILES", Map.of("FILES", fileReadableKVState))); + final Map fileStateData = + new HashMap<>(Map.of(FileReadableKVState.KEY, Map.of(FileReadableKVState.KEY, fileReadableKVState))); final Map contractStateData = new HashMap<>(Map.of( - "BYTECODE", Map.of("BYTECODE", contractBytecodeReadableKVState), - "STORAGE", Map.of("STORAGE", contractStorageReadableKVState))); + ContractBytecodeReadableKVState.KEY, + Map.of(ContractBytecodeReadableKVState.KEY, contractBytecodeReadableKVState), + ContractStorageReadableKVState.KEY, + Map.of(ContractStorageReadableKVState.KEY, contractStorageReadableKVState))); final Map tokenStateData = new HashMap<>(Map.of( - "ACCOUNTS", Map.of("ACCOUNTS", accountReadableKVState), - "PENDING_AIRDROPS", Map.of("PENDING_AIRDROPS", airdropsReadableKVState), - "ALIASES", Map.of("ALIASES", aliasesReadableKVState), - "NFTS", Map.of("NFTS", nftReadableKVState), - "TOKENS", Map.of("TOKENS", tokenReadableKVState), - "TOKEN_RELS", Map.of("TOKEN_RELS", tokenRelationshipReadableKVState))); + AccountReadableKVState.KEY, Map.of(AccountReadableKVState.KEY, accountReadableKVState), + AirdropsReadableKVState.KEY, Map.of(AirdropsReadableKVState.KEY, airdropsReadableKVState), + AliasesReadableKVState.KEY, Map.of(AliasesReadableKVState.KEY, aliasesReadableKVState), + NftReadableKVState.KEY, Map.of(NftReadableKVState.KEY, nftReadableKVState), + TokenReadableKVState.KEY, Map.of(TokenReadableKVState.KEY, tokenReadableKVState), + TokenRelationshipReadableKVState.KEY, + Map.of(TokenRelationshipReadableKVState.KEY, tokenRelationshipReadableKVState))); // Add service using the mock data source return buildStateObject() @@ -445,6 +480,12 @@ private MirrorNodeState initStateAfterMigration() { } private MirrorNodeState buildStateObject() { - return new MirrorNodeState(readableKVStates, servicesRegistry, serviceMigrator, networkInfo, startupNetworks); + return new MirrorNodeState( + readableKVStates, + servicesRegistry, + serviceMigrator, + networkInfo, + startupNetworks, + mirrorNodeEvmProperties); } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableKVStateTest.java index abddf9c2b5c..146aa88f7dd 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapReadableKVStateTest.java @@ -20,6 +20,8 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Account; +import com.hedera.mirror.web3.state.AccountReadableKVState; +import com.hedera.mirror.web3.state.AliasesReadableKVState; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,7 +45,7 @@ class MapReadableKVStateTest { @BeforeEach void setup() { accountMap = Map.of(accountID, account); - mapReadableKVState = new MapReadableKVState<>("ACCOUNTS", accountMap); + mapReadableKVState = new MapReadableKVState<>(AccountReadableKVState.KEY, accountMap); } @Test @@ -70,7 +72,7 @@ void testSize() { final var accountID1 = AccountID.newBuilder().accountNum(1L).build(); final var accountID2 = AccountID.newBuilder().accountNum(2L).build(); final var mapReadableKVStateBigger = new MapReadableKVState<>( - "ACCOUNTS", + AccountReadableKVState.KEY, Map.of( accountID1, Account.newBuilder().accountId(accountID1).build(), @@ -96,26 +98,27 @@ void testEqualsWithNull() { @Test void testEqualsSameValues() { - MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + MapReadableKVState other = new MapReadableKVState<>(AccountReadableKVState.KEY, accountMap); assertThat(mapReadableKVState).isEqualTo(other); } @Test void testEqualsDifferentKeys() { - MapReadableKVState other = new MapReadableKVState<>("ALIASES", accountMap); + MapReadableKVState other = new MapReadableKVState<>(AliasesReadableKVState.KEY, accountMap); assertThat(mapReadableKVState).isNotEqualTo(other); } @Test void testEqualsDifferentValues() { final var accountMapOther = Map.of(AccountID.newBuilder().accountNum(3L).build(), account); - MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMapOther); + MapReadableKVState other = + new MapReadableKVState<>(AccountReadableKVState.KEY, accountMapOther); assertThat(mapReadableKVState).isNotEqualTo(other); } @Test void testHashCode() { - MapReadableKVState other = new MapReadableKVState<>("ACCOUNTS", accountMap); + MapReadableKVState other = new MapReadableKVState<>(AccountReadableKVState.KEY, accountMap); assertThat(mapReadableKVState).hasSameHashCodeAs(other); } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableKVStateTest.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableKVStateTest.java index 731091c2fa5..6f70c81e89a 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableKVStateTest.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/state/core/MapWritableKVStateTest.java @@ -22,6 +22,8 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Account; +import com.hedera.mirror.web3.state.AccountReadableKVState; +import com.hedera.mirror.web3.state.AliasesReadableKVState; import com.swirlds.state.spi.ReadableKVState; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; @@ -47,7 +49,7 @@ class MapWritableKVStateTest { @BeforeEach void setup() { - mapWritableKVState = new MapWritableKVState<>("ACCOUNTS", readableKVState); + mapWritableKVState = new MapWritableKVState<>(AccountReadableKVState.KEY, readableKVState); } @Test @@ -113,21 +115,24 @@ void testEqualsWithNull() { @Test void testEqualsDifferentKeys() { - MapWritableKVState other = new MapWritableKVState<>("ALIASES", readableKVState); + MapWritableKVState other = + new MapWritableKVState<>(AliasesReadableKVState.KEY, readableKVState); assertThat(mapWritableKVState).isNotEqualTo(other); } @Test void testEqualsDifferentValues() { final var readableKVStateMock = mock(ReadableKVState.class); - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVStateMock); + MapWritableKVState other = + new MapWritableKVState<>(AccountReadableKVState.KEY, readableKVStateMock); other.put(accountID, account); assertThat(mapWritableKVState).isNotEqualTo(other); } @Test void testHashCode() { - MapWritableKVState other = new MapWritableKVState<>("ACCOUNTS", readableKVState); + MapWritableKVState other = + new MapWritableKVState<>(AccountReadableKVState.KEY, readableKVState); assertThat(mapWritableKVState).hasSameHashCodeAs(other); } } diff --git a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java index ca144549f75..9a105b46224 100644 --- a/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java +++ b/hedera-mirror-web3/src/test/java/com/hedera/mirror/web3/web3j/TestWeb3jService.java @@ -113,7 +113,6 @@ public void reset() { this.isEstimateGas = false; this.contractRuntime = null; this.persistContract = true; - this.sender = Address.ZERO; this.value = 0L; this.sender = Address.fromHexString(""); this.blockType = BlockType.LATEST;