Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Wrap modularized code in transactionExecutionService #9943

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* 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.
* <p>
* This is the function of the field "file" to hold temporary the bytecode and the fileId during contract deploy.
*/
@Setter
private Optional<File> file = Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@
import static com.hedera.mirror.web3.service.model.CallServiceParameters.CallType.ERROR;
import static org.apache.logging.log4j.util.Strings.EMPTY;

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.file.FileCreateTransactionBody;
import com.hedera.hapi.node.state.file.File;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.mirror.web3.common.ContractCallContext;
import com.hedera.mirror.web3.evm.contracts.execution.MirrorEvmTxProcessor;
import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties;
Expand All @@ -43,22 +31,12 @@
import com.hedera.mirror.web3.service.model.CallServiceParameters;
import com.hedera.mirror.web3.throttle.ThrottleProperties;
import com.hedera.mirror.web3.viewmodel.BlockType;
import com.hedera.node.app.config.ConfigProviderImpl;
import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult;
import com.hedera.node.app.state.SingleTransactionRecord;
import com.hedera.node.app.workflows.standalone.TransactionExecutors;
import com.hedera.node.config.data.EntitiesConfig;
import com.swirlds.config.api.Configuration;
import com.swirlds.state.State;
import io.github.bucket4j.Bucket;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter.MeterProvider;
import io.micrometer.core.instrument.MeterRegistry;
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;

Expand All @@ -67,21 +45,15 @@
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<Counter> gasLimitCounter;
private final MeterProvider<Counter> 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 State mirrorNodeState;

private static final Configuration DEFAULT_CONFIG = new ConfigProviderImpl().getConfiguration();
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 final TransactionExecutionService transactionExecutionService;

protected ContractCallService(
MirrorEvmTxProcessor mirrorEvmTxProcessor,
Expand All @@ -91,7 +63,7 @@ protected ContractCallService(
RecordFileService recordFileService,
Store store,
MirrorNodeEvmProperties mirrorNodeEvmProperties,
State mirrorNodeState) {
TransactionExecutionService transactionExecutionService) {
this.gasLimitCounter = Counter.builder(GAS_LIMIT_METRIC)
.description("The amount of gas limit sent in the request")
.withRegistry(meterRegistry);
Expand All @@ -104,25 +76,25 @@ protected ContractCallService(
this.throttleProperties = throttleProperties;
this.gasLimitBucket = gasLimitBucket;
this.mirrorNodeEvmProperties = mirrorNodeEvmProperties;
this.mirrorNodeState = mirrorNodeState;
this.transactionExecutionService = transactionExecutionService;
}

/**
* This method is responsible for calling a smart contract function. The method is divided into two main parts:
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*
* @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 {
Expand All @@ -145,7 +117,7 @@ protected HederaEvmTransactionProcessingResult doProcessCall(
if (!mirrorNodeEvmProperties.isModularizedServices()) {
result = mirrorEvmTxProcessor.execute(params, estimatedGas);
} else {
result = processModularizedCall(params, estimatedGas);
result = transactionExecutionService.execute(params, estimatedGas);
}
if (!restoreGasToThrottleBucket) {
return result;
Expand All @@ -158,145 +130,6 @@ protected HederaEvmTransactionProcessingResult doProcessCall(
}
}

private Map<String, String> buildTransactionExecutorProperties() {
final var mirrorNodeProperties = mirrorNodeEvmProperties.getProperties();
mirrorNodeProperties.put(
"contracts.evm.version",
"v"
+ mirrorNodeEvmProperties.getSemanticEvmVersion().major() + "."
+ mirrorNodeEvmProperties.getSemanticEvmVersion().minor());
mirrorNodeProperties.put(
"ledger.id",
Bytes.wrap(mirrorNodeEvmProperties.getNetwork().getLedgerId()).toHexString());
return mirrorNodeProperties;
}

private HederaEvmTransactionProcessingResult buildSuccessResult(
final boolean isContractCreate,
final List<SingleTransactionRecord> receipt,
final CallServiceParameters params) {
var result = isContractCreate
? receipt.getFirst().transactionRecord().contractCreateResult()
: receipt.getFirst().transactionRecord().contractCallResult();

return HederaEvmTransactionProcessingResult.successful(
List.of(),
result.gasUsed(),
0L,
0L,
Bytes.wrap(result.contractCallResult().toByteArray()),
params.getReceiver());
}

private HederaEvmTransactionProcessingResult buildFailedResult(
final List<SingleTransactionRecord> receipt, final boolean isContractCreate) {
var result = isContractCreate
? receipt.getFirst().transactionRecord().contractCreateResultOrThrow()
: receipt.getFirst().transactionRecord().contractCallResultOrThrow();
var status = receipt.getFirst().transactionRecord().receipt().status();

return HederaEvmTransactionProcessingResult.failed(
result.gasUsed(),
0L,
0L,
Optional.of(Bytes.wrap(status.protoName().getBytes())),
Optional.empty());
}

private TransactionBody buildFileCreateTransactionBody(final CallServiceParameters params, long maxLifetime) {
return TransactionBody.newBuilder()
.fileCreate(FileCreateTransactionBody.newBuilder()
.contents(com.hedera.pbj.runtime.io.buffer.Bytes.wrap(
params.getCallData().toArray()))
.expirationTime(new Timestamp(maxLifetime, 0))
.build())
.transactionID(TransactionID.newBuilder()
.transactionValidStart(TRANSACTION_START)
.accountID(TREASURY_ACCOUNT_ID)
.build())
.transactionValidDuration(TRANSACTION_DURATION)
.build();
}

private TransactionBody buildContractCreateTransactionBody(
final FileID fileID, long estimatedGas, long maxLifetime) {
return TransactionBody.newBuilder()
.contractCreateInstance(ContractCreateTransactionBody.newBuilder()
.fileID(fileID)
.gas(estimatedGas)
.autoRenewPeriod(new Duration(maxLifetime))
.build())
.transactionID(TransactionID.newBuilder()
.transactionValidStart(TRANSACTION_START)
.accountID(TREASURY_ACCOUNT_ID)
.build())
.nodeAccountID(TREASURY_ACCOUNT_ID)
.transactionValidDuration(TRANSACTION_DURATION)
.build();
}

private TransactionBody buildContractCallTransactionBody(
final CallServiceParameters params, final long estimatedGas) {
return TransactionBody.newBuilder()
.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().toArray()))
.gas(estimatedGas)
.build())
.nodeAccountID(TREASURY_ACCOUNT_ID)
.transactionID(TransactionID.newBuilder()
.transactionValidStart(TRANSACTION_START)
.accountID(TREASURY_ACCOUNT_ID)
.build())
.transactionValidDuration(TRANSACTION_DURATION)
.build();
}

private HederaEvmTransactionProcessingResult processModularizedCall(
final CallServiceParameters params, final long estimatedGas) {
final var isContractCreate = params.getReceiver().isZero();
final var maxLifetime =
DEFAULT_CONFIG.getConfigData(EntitiesConfig.class).maxLifetime();
var executor = TransactionExecutors.TRANSACTION_EXECUTORS.newExecutor(
mirrorNodeState, buildTransactionExecutorProperties(), null);

TransactionBody transactionBody;
HederaEvmTransactionProcessingResult result;
if (isContractCreate) {
// 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 = buildContractCreateTransactionBody(fileID, estimatedGas, maxLifetime);
} else {
transactionBody = buildContractCallTransactionBody(params, estimatedGas);
}
var receipt = executor.execute(transactionBody, Instant.EPOCH);
if (receipt.getFirst().transactionRecord().receiptOrThrow().status() == ResponseCodeEnum.SUCCESS) {
result = buildSuccessResult(isContractCreate, receipt, params);
} else {
result = buildFailedResult(receipt, isContractCreate);
}
return result;
}

private void restoreGasToBucket(HederaEvmTransactionProcessingResult result, long gasLimit) {
// If the transaction fails, gasUsed is equal to gasLimit, so restore the configured refund percent
// of the gasLimit value back in the bucket.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.hedera.mirror.web3.service.model.ContractDebugParameters;
import com.hedera.mirror.web3.throttle.ThrottleProperties;
import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult;
import com.swirlds.state.State;
import io.github.bucket4j.Bucket;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.inject.Named;
Expand All @@ -54,7 +53,7 @@ public ContractDebugService(
ThrottleProperties throttleProperties,
MeterRegistry meterRegistry,
MirrorNodeEvmProperties mirrorNodeEvmProperties,
State state) {
TransactionExecutionService transactionExecutionService) {
super(
mirrorEvmTxProcessor,
gasLimitBucket,
Expand All @@ -63,7 +62,7 @@ public ContractDebugService(
recordFileService,
store,
mirrorNodeEvmProperties,
state);
transactionExecutionService);
this.contractActionRepository = contractActionRepository;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import com.hedera.mirror.web3.service.model.ContractExecutionParameters;
import com.hedera.mirror.web3.service.utils.BinaryGasEstimator;
import com.hedera.mirror.web3.throttle.ThrottleProperties;
import com.swirlds.state.State;
import io.github.bucket4j.Bucket;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.inject.Named;
Expand All @@ -49,7 +48,7 @@ public ContractExecutionService(
ThrottleProperties throttleProperties,
Bucket gasLimitBucket,
MirrorNodeEvmProperties mirrorNodeEvmProperties,
State mirrorNodeState) {
TransactionExecutionService transactionExecutionService) {
super(
mirrorEvmTxProcessor,
gasLimitBucket,
Expand All @@ -58,7 +57,7 @@ public ContractExecutionService(
recordFileService,
store,
mirrorNodeEvmProperties,
mirrorNodeState);
transactionExecutionService);
this.binaryGasEstimator = binaryGasEstimator;
}

Expand Down
Loading