Skip to content

Commit

Permalink
Support block hash operation in modularized web3 (#10148)
Browse files Browse the repository at this point in the history
* Add a `BlockInfo` singleton to return the last 256 block hashes
* Add a `RunningHashes` singleton to return the running hash
* Add `MirrorOperationTracer` to modularized execution
* Change modularized code to not initialize stacked state frame
* Change to always load requested block in modularized code for use in `BlockInfo` later
* Change to initialize `VersionedConfiguration` once and use it everywhere
* Change to reuse stateless `TransactionExecutor` between requests
* Disable `contracts.sidecars` to avoid generating unnecessary sidecar data
* Fix EVM version being cached for the first request
* Fix some tests by always inserting exchange rate

---------

Signed-off-by: Steven Sheehy <[email protected]>
  • Loading branch information
steven-sheehy authored Jan 17, 2025
1 parent 84db054 commit 468fec2
Show file tree
Hide file tree
Showing 30 changed files with 744 additions and 325 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import com.hedera.mirror.web3.evm.contracts.execution.traceability.OpcodeTracerOptions;
import com.hedera.mirror.web3.evm.store.CachingStateFrame;
import com.hedera.mirror.web3.evm.store.StackedStateFrames;
import com.hedera.mirror.web3.service.model.CallServiceParameters;
import com.hedera.mirror.web3.state.FileReadableKVState;
import com.hedera.mirror.web3.viewmodel.BlockType;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
Expand Down Expand Up @@ -57,6 +59,10 @@ public class ContractCallContext {

@Setter
private List<Opcode> opcodes = new ArrayList<>();

@Setter
private CallServiceParameters callServiceParameters;

/**
* Record file which stores the block timestamp and other historical block details used for filtering of historical
* data.
Expand Down Expand Up @@ -101,7 +107,6 @@ public static <T> T run(Function<ContractCallContext, T> function) {
}

public void reset() {
recordFile = null;
stack = stackBase;
file = Optional.empty();
}
Expand Down Expand Up @@ -147,7 +152,10 @@ public void initializeStackFrames(final StackedStateFrames stackedStateFrames) {
}

public boolean useHistorical() {
return recordFile != null;
if (callServiceParameters != null) {
return callServiceParameters.getBlock() != BlockType.LATEST;
}
return recordFile != null; // Remove recordFile comparison after mono code deletion
}

public void incrementContractActionsCounter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,7 @@ public class RatesAndFeesLoader {

public static final EntityId EXCHANGE_RATE_ENTITY_ID = EntityId.of(0L, 0L, 112L);
public static final EntityId FEE_SCHEDULE_ENTITY_ID = EntityId.of(0L, 0L, 111L);

static final ExchangeRateSet DEFAULT_EXCHANGE_RATE_SET = ExchangeRateSet.newBuilder()
.setCurrentRate(ExchangeRate.newBuilder()
.setCentEquiv(12)
.setExpirationTime(TimestampSeconds.newBuilder().setSeconds(4102444800L))
.setHbarEquiv(1))
.build();
static final CurrentAndNextFeeSchedule DEFAULT_FEE_SCHEDULE = CurrentAndNextFeeSchedule.newBuilder()
public static final CurrentAndNextFeeSchedule DEFAULT_FEE_SCHEDULE = CurrentAndNextFeeSchedule.newBuilder()
.setCurrentFeeSchedule(FeeSchedule.newBuilder()
.setExpiryTime(TimestampSeconds.newBuilder().setSeconds(4102444800L))
.addTransactionFeeSchedule(TransactionFeeSchedule.newBuilder()
Expand Down Expand Up @@ -264,6 +257,12 @@ public class RatesAndFeesLoader {
.setGas(852000)
.build()))))
.build();
static final ExchangeRateSet DEFAULT_EXCHANGE_RATE_SET = ExchangeRateSet.newBuilder()
.setCurrentRate(ExchangeRate.newBuilder()
.setCentEquiv(12)
.setExpirationTime(TimestampSeconds.newBuilder().setSeconds(4102444800L))
.setHbarEquiv(1))
.build();
private static final CurrentAndNextFeeSchedule EMPTY_FEE_SCHEDULE = CurrentAndNextFeeSchedule.getDefaultInstance();
private static final ExchangeRateSet EMPTY_EXCHANGE_RATE_SET = ExchangeRateSet.getDefaultInstance();
private final RetryTemplate retryTemplate = RetryTemplate.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@
import static com.swirlds.common.utility.CommonUtils.unhex;
import static com.swirlds.state.lifecycle.HapiUtils.SEMANTIC_VERSION_COMPARATOR;

import com.google.common.collect.ImmutableSortedMap;
import com.hedera.hapi.node.base.SemanticVersion;
import com.hedera.mirror.common.domain.entity.EntityType;
import com.hedera.mirror.web3.common.ContractCallContext;
import com.hedera.node.app.config.ConfigProviderImpl;
import com.hedera.node.app.service.evm.contracts.execution.EvmProperties;
import com.hedera.node.config.VersionedConfiguration;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
Expand Down Expand Up @@ -64,6 +67,9 @@
@ConfigurationProperties(prefix = "hedera.mirror.web3.evm")
public class MirrorNodeEvmProperties implements EvmProperties {

private static final NavigableMap<Long, SemanticVersion> DEFAULT_EVM_VERSION_MAP =
ImmutableSortedMap.of(0L, EVM_VERSION);

@Getter
private boolean allowTreasuryToOwnNfts = true;

Expand Down Expand Up @@ -176,11 +182,17 @@ public class MirrorNodeEvmProperties implements EvmProperties {
"contracts.chainId",
chainIdBytes32().toBigInteger().toString(),
"contracts.maxRefundPercentOfGasLimit",
String.valueOf(maxGasRefundPercentage()));
String.valueOf(maxGasRefundPercentage()),
"contracts.sidecars",
"");

@Getter(lazy = true)
private final Map<String, String> transactionProperties = buildTransactionProperties();

@Getter(lazy = true)
private final VersionedConfiguration versionedConfiguration =
new ConfigProviderImpl(false, null, getTransactionProperties()).getConfiguration();

@Getter
@Min(1)
private int feesTokenTransferUsageMultiplier = 380;
Expand Down Expand Up @@ -295,7 +307,7 @@ public NavigableMap<Long, SemanticVersion> getEvmVersions() {
return network.evmVersions;
}

return new TreeMap<>(Map.of(0L, EVM_VERSION));
return DEFAULT_EVM_VERSION_MAP;
}

/**
Expand Down Expand Up @@ -323,15 +335,11 @@ public int feesTokenTransferUsageMultiplier() {
}

private Map<String, String> buildTransactionProperties() {
final Map<String, String> mirrorNodeProperties = new HashMap<>(properties);
mirrorNodeProperties.put(
"contracts.evm.version",
"v"
+ getSemanticEvmVersion().major() + "."
+ getSemanticEvmVersion().minor());
var mirrorNodeProperties = new HashMap<>(properties);
mirrorNodeProperties.put("contracts.evm.version", "v" + evmVersion.major() + "." + evmVersion.minor());
mirrorNodeProperties.put(
"ledger.id", Bytes.wrap(getNetwork().getLedgerId()).toHexString());
return mirrorNodeProperties;
return Collections.unmodifiableMap(mirrorNodeProperties);
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
import static org.apache.commons.lang3.StringUtils.EMPTY;

import com.hedera.mirror.web3.evm.exception.EvmException;
import com.hedera.node.app.service.evm.contracts.execution.HederaEvmTransactionProcessingResult;
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
import java.io.Serial;
import java.nio.charset.StandardCharsets;
import lombok.Getter;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.apache.tuweni.bytes.Bytes;

@Getter
@SuppressWarnings("java:S110")
public class MirrorEvmTransactionException extends EvmException {

Expand All @@ -34,33 +37,33 @@ public class MirrorEvmTransactionException extends EvmException {

private final String detail;
private final String data;
private final transient HederaEvmTransactionProcessingResult result;

public MirrorEvmTransactionException(
final ResponseCodeEnum responseCode, final String detail, final String hexData) {
super(responseCode.name());
this.detail = detail;
this.data = hexData;
this(responseCode.name(), detail, hexData, null);
}

public MirrorEvmTransactionException(final String message, final String detail, final String hexData) {
this(message, detail, hexData, null);
}

public MirrorEvmTransactionException(
final String message,
final String detail,
final String hexData,
HederaEvmTransactionProcessingResult result) {
super(message);
this.detail = detail;
this.data = hexData;
this.result = result;
}

public Bytes messageBytes() {
final var message = getMessage();
return Bytes.of(message.getBytes(StandardCharsets.UTF_8));
}

public String getDetail() {
return detail;
}

public String getData() {
return data;
}

@Override
public String toString() {
return "%s(message=%s, detail=%s, data=%s, dataDecoded=%s)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static com.hedera.mirror.web3.evm.config.EvmConfiguration.CACHE_NAME_RECORD_FILE_LATEST_INDEX;

import com.hedera.mirror.common.domain.transaction.RecordFile;
import java.util.List;
import java.util.Optional;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
Expand Down Expand Up @@ -65,4 +66,7 @@ public interface RecordFileRepository extends PagingAndSortingRepository<RecordF
put = @CachePut(cacheNames = CACHE_NAME, cacheManager = CACHE_MANAGER_RECORD_FILE_INDEX))
@Query("select r from RecordFile r where r.consensusEnd >= ?1 order by r.consensusEnd asc limit 1")
Optional<RecordFile> findByTimestamp(long timestamp);

@Query(value = "select * from record_file where index >= ?1 and index <= ?2 order by index asc", nativeQuery = true)
List<RecordFile> findByIndexRange(long startIndex, long endIndex);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static com.hedera.mirror.web3.service.model.CallServiceParameters.CallType.ERROR;
import static org.apache.logging.log4j.util.Strings.EMPTY;

import com.google.common.annotations.VisibleForTesting;
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 @@ -46,13 +47,14 @@ 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;
protected final MirrorNodeEvmProperties mirrorNodeEvmProperties;
private final MeterProvider<Counter> gasLimitCounter;
private final MeterProvider<Counter> gasUsedCounter;
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")
Expand Down Expand Up @@ -80,6 +82,12 @@ protected ContractCallService(
this.transactionExecutionService = transactionExecutionService;
}

@VisibleForTesting
public HederaEvmTransactionProcessingResult callContract(CallServiceParameters params)
throws MirrorEvmTransactionException {
return ContractCallContext.run(context -> callContract(params, context));
}

/**
* This method is responsible for calling a smart contract function. The method is divided into two main parts:
* <p>
Expand All @@ -99,15 +107,22 @@ protected ContractCallService(
*/
protected HederaEvmTransactionProcessingResult callContract(CallServiceParameters params, ContractCallContext ctx)
throws MirrorEvmTransactionException {
// if we have historical call, then set the corresponding record file in the context
if (params.getBlock() != BlockType.LATEST) {
ctx.setCallServiceParameters(params);

if (mirrorNodeEvmProperties.isModularizedServices() || params.getBlock() != BlockType.LATEST) {
ctx.setRecordFile(recordFileService
.findByBlockType(params.getBlock())
.orElseThrow(BlockNumberNotFoundException::new));
}

// initializes the stack frame with the current state or historical state (if the call is historical)
ctx.initializeStackFrames(store.getStackedStateFrames());
return doProcessCall(params, params.getGas(), true);
if (!mirrorNodeEvmProperties.isModularizedServices()) {
ctx.initializeStackFrames(store.getStackedStateFrames());
}

var result = doProcessCall(params, params.getGas(), true);
validateResult(result, params.getCallType());
return result;
}

protected HederaEvmTransactionProcessingResult doProcessCall(
Expand Down Expand Up @@ -152,7 +167,8 @@ protected void validateResult(final HederaEvmTransactionProcessingResult txnResu
updateGasUsedMetric(ERROR, txnResult.getGasUsed(), 1);
var revertReason = txnResult.getRevertReason().orElse(Bytes.EMPTY);
var detail = maybeDecodeSolidityErrorStringToReadableMessage(revertReason);
throw new MirrorEvmTransactionException(getStatusOrDefault(txnResult), detail, revertReason.toHexString());
throw new MirrorEvmTransactionException(
getStatusOrDefault(txnResult).name(), detail, revertReason.toHexString(), txnResult);
} else {
updateGasUsedMetric(type, txnResult.getGasUsed(), 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,9 @@ public String processCall(final ContractExecutionParameters params) {

Bytes result;
if (params.isEstimate()) {
// eth_estimateGas initialization - historical timestamp is Optional.empty()
ctx.initializeStackFrames(store.getStackedStateFrames());
result = estimateGas(params);
result = estimateGas(params, ctx);
} else {
final var ethCallTxnResult = callContract(params, ctx);

validateResult(ethCallTxnResult, params.getCallType());

result = Objects.requireNonNullElse(ethCallTxnResult.getOutput(), Bytes.EMPTY);
}

Expand All @@ -103,8 +98,8 @@ public String processCall(final ContractExecutionParameters params) {
* 2. Finally, if the first step is successful, a binary search is initiated. The lower bound of the search is the
* gas used in the first step, while the upper bound is the inputted gas parameter.
*/
private Bytes estimateGas(final ContractExecutionParameters params) {
final var processingResult = doProcessCall(params, params.getGas(), true);
private Bytes estimateGas(final ContractExecutionParameters params, final ContractCallContext context) {
final var processingResult = callContract(params, context);
validateResult(processingResult, CallType.ETH_ESTIMATE_GAS);

final var gasUsedByInitialCall = processingResult.getGasUsed();
Expand Down
Loading

0 comments on commit 468fec2

Please sign in to comment.