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

Adapt storage capturing in OpcodeTracer for modularized execution #10357

Merged
merged 7 commits into from
Feb 12, 2025
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 @@ -23,20 +23,30 @@
import static org.hyperledger.besu.evm.frame.MessageFrame.State.EXCEPTIONAL_HALT;
import static org.hyperledger.besu.evm.frame.MessageFrame.Type.MESSAGE_CALL;

import com.hedera.hapi.node.state.contract.SlotKey;
import com.hedera.hapi.node.state.contract.SlotValue;
import com.hedera.mirror.common.domain.contract.ContractAction;
import com.hedera.mirror.web3.common.ContractCallContext;
import com.hedera.mirror.web3.convert.BytesDecoder;
import com.hedera.mirror.web3.evm.config.PrecompiledContractProvider;
import com.hedera.mirror.web3.evm.properties.MirrorNodeEvmProperties;
import com.hedera.mirror.web3.state.core.MapWritableStates;
import com.hedera.mirror.web3.state.keyvalue.ContractStorageReadableKVState;
import com.hedera.node.app.service.contract.ContractService;
import com.hedera.node.app.service.evm.contracts.operations.HederaExceptionalHaltReason;
import com.hedera.node.app.service.mono.contracts.execution.traceability.HederaOperationTracer;
import com.hedera.services.stream.proto.ContractActionType;
import com.hedera.services.utils.EntityIdUtils;
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
import com.swirlds.state.State;
import jakarta.inject.Named;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import lombok.CustomLog;
Expand All @@ -58,10 +68,17 @@
public class OpcodeTracer implements HederaOperationTracer {

private final Map<Address, PrecompiledContract> hederaPrecompiles;
private final MirrorNodeEvmProperties evmProperties;
private final State mirrorNodeState;

public OpcodeTracer(final PrecompiledContractProvider precompiledContractProvider) {
public OpcodeTracer(
final PrecompiledContractProvider precompiledContractProvider,
MirrorNodeEvmProperties evmProperties,
State mirrorNodeState) {
this.hederaPrecompiles = precompiledContractProvider.getHederaPrecompiles().entrySet().stream()
.collect(Collectors.toMap(e -> Address.fromHexString(e.getKey()), Map.Entry::getValue));
this.evmProperties = evmProperties;
this.mirrorNodeState = mirrorNodeState;
}

@Override
Expand Down Expand Up @@ -177,6 +194,10 @@ private Map<Bytes, Bytes> captureStorage(final MessageFrame frame, OpcodeTracerO
return Collections.emptyMap();
}

if (evmProperties.isModularizedServices()) {
return getModularizedUpdatedStorage(address);
}

return new TreeMap<>(account.getUpdatedStorage());
} catch (final ModificationNotAllowedException e) {
log.warn("Failed to retrieve storage contents", e);
Expand Down Expand Up @@ -210,10 +231,10 @@ private boolean isCallToHederaPrecompile(MessageFrame frame) {
}

/**
* When a contract tries to call a non-existing address
* (resulting in a {@link HederaExceptionalHaltReason#INVALID_SOLIDITY_ADDRESS} failure),
* a synthetic action is created to record this, otherwise the details of the intended call
* (e.g. the targeted invalid address) and sequence of events leading to the failure are lost
* When a contract tries to call a non-existing address (resulting in a
* {@link HederaExceptionalHaltReason#INVALID_SOLIDITY_ADDRESS} failure), a synthetic action is created to record
* this, otherwise the details of the intended call (e.g. the targeted invalid address) and sequence of events
* leading to the failure are lost
*/
private boolean existsSyntheticActionForFrame(MessageFrame frame) {
return (frame.getState() == EXCEPTIONAL_HALT || frame.getState() == COMPLETED_FAILED)
Expand All @@ -223,8 +244,9 @@ private boolean existsSyntheticActionForFrame(MessageFrame frame) {
}

/**
* Formats the revert reason to be consistent with the revert reason format in the EVM.
* <a href="https://besu.hyperledger.org/23.10.2/private-networks/how-to/send-transactions/revert-reason#revert-reason-format">...</a>
* Formats the revert reason to be consistent with the revert reason format in the EVM. <a
* href="https://besu.hyperledger.org/23.10.2/private-networks/how-to/send-transactions/revert-reason#revert-reason-format">...</a>
*
* @param revertReason the revert reason
* @return the formatted revert reason
*/
Expand All @@ -246,4 +268,50 @@ private Bytes formatRevertReason(final Bytes revertReason) {

return BytesDecoder.getAbiEncodedRevertReason(revertReason);
}

private Optional<Map<Bytes, Bytes>> getStorageUpdates(Address accountAddress) {
Map<Bytes, Bytes> storageUpdates = new HashMap<>();
MapWritableStates states = (MapWritableStates) mirrorNodeState.getWritableStates(ContractService.NAME);

try {
Set<SlotKey> modifiedKeys = states.get(ContractStorageReadableKVState.KEY).modifiedKeys().stream()
.filter(SlotKey.class::isInstance)
.map(SlotKey.class::cast)
.filter(SlotKey::hasContractID)
.collect(Collectors.toSet());

if (modifiedKeys.isEmpty()) {
return Optional.empty();
}

for (SlotKey slotKey : modifiedKeys) {
Address contractAddress = EntityIdUtils.asHexedEvmAddress(slotKey.contractID());
if (!accountAddress.equals(contractAddress)) {
continue;
}

SlotValue slotValue = (SlotValue) states.get(ContractStorageReadableKVState.KEY).get(slotKey);
if (slotValue != null) {
storageUpdates.put(
Bytes.of(slotKey.key().toByteArray()),
Bytes.of(slotValue.value().toByteArray()));
}
}
} catch (IllegalArgumentException e) {
log.warn(
"Failed to retrieve modified storage keys for service: {}, key: {}",
ContractService.NAME,
ContractStorageReadableKVState.KEY,
e);
}

return storageUpdates.isEmpty() ? Optional.empty() : Optional.of(storageUpdates);
}

private Map<Bytes, Bytes> getModularizedUpdatedStorage(Address accountAddress) {
return getStorageUpdates(accountAddress)
.map(storageUpdates -> storageUpdates.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, TreeMap::new)))
.orElseGet(TreeMap::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ public static String asHexedEvmAddress(final Id id) {
return CommonUtils.hex(asEvmAddress(id.num()));
}

public static Address asHexedEvmAddress(final com.hedera.hapi.node.base.ContractID id) {
return Address.fromHexString(CommonUtils.hex(asEvmAddress(id.contractNum())));
}

public static boolean isAlias(final AccountID idOrAlias) {
return idOrAlias.getAccountNum() == 0 && !idOrAlias.getAlias().isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import com.hedera.mirror.web3.evm.config.PrecompilesHolder;
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.state.MirrorNodeState;
import com.hedera.node.app.service.evm.contracts.execution.HederaBlockValues;
import com.hedera.node.app.service.evm.contracts.operations.HederaExceptionalHaltReason;
import com.hedera.services.stream.proto.ContractActionType;
Expand All @@ -59,14 +61,20 @@ class MirrorEvmMessageCallProcessorTest extends MirrorEvmMessageCallProcessorBas
@Mock
private PrecompilesHolder precompilesHolder;

@Mock
private MirrorNodeEvmProperties evmProperties;

@Mock
private MirrorNodeState mirrorNodeState;

private OpcodeTracer opcodeTracer;
private MirrorEvmMessageCallProcessor subject;

@BeforeEach
void setUp() {
when(precompilesHolder.getHederaPrecompiles()).thenReturn(hederaPrecompileList);
when(messageFrame.getWorldUpdater()).thenReturn(updater);
opcodeTracer = Mockito.spy(new OpcodeTracer(precompilesHolder));
opcodeTracer = Mockito.spy(new OpcodeTracer(precompilesHolder, evmProperties, mirrorNodeState));
subject = new MirrorEvmMessageCallProcessor(
autoCreationLogic,
entityAddressSequencer,
Expand Down
Loading
Loading