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

Add file id and init bytecode in ContractCallContext #9799

Merged
Show file tree
Hide file tree
Changes from 7 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
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ public Builder copyBuilder() {
numberPendingAirdrops);
}

public Long tinybarBalance() {
public long tinybarBalance() {
return tinybarBalanceSupplier.get();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@

package com.hedera.mirror.web3.common;

import com.hedera.hapi.node.base.FileID;
import com.hedera.hapi.node.contract.ContractCreateTransactionBody;
import com.hedera.hapi.node.file.FileCreateTransactionBody;
import com.hedera.mirror.common.domain.contract.ContractAction;
import com.hedera.mirror.common.domain.transaction.RecordFile;
import com.hedera.mirror.web3.evm.contracts.execution.traceability.Opcode;
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.store.CachingStateFrame;
import com.hedera.mirror.web3.evm.store.StackedStateFrames;
import com.hedera.mirror.web3.state.FileReadableKVState;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;
Expand Down Expand Up @@ -73,19 +78,44 @@ public class ContractCallContext {
@Setter
private Optional<Long> timestamp = Optional.empty();

/**
* 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 fields "fileID" and "initBytecode" - to hold temporary these values
* during contract deploy.
*/
@Setter
private Optional<FileID> fileID = Optional.empty();

@Setter
private Optional<Bytes> initBytecode = Optional.empty();
steven-sheehy marked this conversation as resolved.
Show resolved Hide resolved

private ContractCallContext() {}

public static ContractCallContext get() {
return SCOPED_VALUE.get();
}

public static Optional<Long> getTimestamp() {
return SCOPED_VALUE.isBound() ? SCOPED_VALUE.get().timestamp : Optional.empty();
}

public static <T> T run(Function<ContractCallContext, T> function) {
return ScopedValue.getWhere(SCOPED_VALUE, new ContractCallContext(), () -> function.apply(SCOPED_VALUE.get()));
}

public void reset() {
recordFile = null;
stack = stackBase;
fileID = Optional.empty();
initBytecode = Optional.empty();
}

public int getStackHeight() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public AccountReadableKVState(

@Override
protected Account readFromDataSource(@Nonnull AccountID key) {
final var timestamp = ContractCallContext.get().getTimestamp();
final var timestamp = ContractCallContext.getTimestamp();
return commonEntityAccessor
.get(key, timestamp)
.filter(entity -> entity.getType() != TOKEN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ protected AccountPendingAirdrop readFromDataSource(@Nonnull PendingAirdropId key
.getId();
final var serialNumber =
key.hasNonFungibleToken() ? key.nonFungibleToken().serialNumber() : 0L;
final var timestamp = ContractCallContext.get().getTimestamp();
final var timestamp = ContractCallContext.getTimestamp();

return timestamp
.map(t -> tokenAirdropRepository.findByIdAndTimestamp(senderId, receiverId, tokenId, serialNumber, t))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ protected AliasesReadableKVState(final CommonEntityAccessor commonEntityAccessor

@Override
protected AccountID readFromDataSource(@Nonnull ProtoBytes alias) {
final var timestamp = ContractCallContext.get().getTimestamp();
final var timestamp = ContractCallContext.getTimestamp();
final var entity = commonEntityAccessor.get(alias.value(), timestamp);
return entity.map(e -> toAccountId(e.getShard(), e.getRealm(), e.getNum()))
.orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ protected SlotValue readFromDataSource(@Nonnull SlotKey slotKey) {
return null;
}

final var timestamp = ContractCallContext.get().getTimestamp();
final var timestamp = ContractCallContext.getTimestamp();
final var contractID = slotKey.contractID();
final var entityId = EntityIdUtils.entityIdFromContractId(contractID).getId();
final var keyBytes = slotKey.key().toByteArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,21 @@ public FileReadableKVState(final FileDataRepository fileDataRepository, final En

@Override
protected File readFromDataSource(@Nonnull FileID key) {
final var timestamp = ContractCallContext.get().getTimestamp();
final var fileId = toEntityId(key).getId();
final var timestamp = ContractCallContext.getTimestamp();
final var fileEntityId = toEntityId(key);
final var fileId = fileEntityId.getId();

final var contextFileId = ContractCallContext.get().getFileID();
// If we are in a contract create case, the fileID and the init bytecode are in the ContractCallContext.
if (contextFileId.isPresent()
steven-sheehy marked this conversation as resolved.
Show resolved Hide resolved
&& contextFileId.get().equals(key)
&& ContractCallContext.get().getInitBytecode().isPresent()) {
return File.newBuilder()
.fileId(key)
.contents(ContractCallContext.get().getInitBytecode().get())
.build();
}

return timestamp
.map(t -> fileDataRepository.getFileAtTimestamp(fileId, t))
.orElseGet(() -> fileDataRepository.getFileAtTimestamp(fileId, getCurrentTimestamp()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,49 @@

package com.hedera.mirror.web3.state;

import static com.hedera.node.app.util.FileUtilities.createFileID;
import static com.swirlds.state.StateChangeListener.StateType.MAP;
import static com.swirlds.state.StateChangeListener.StateType.QUEUE;
import static com.swirlds.state.StateChangeListener.StateType.SINGLETON;
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.mirror.web3.state.components.MetricsImpl;
import com.hedera.mirror.web3.state.core.ListReadableQueueState;
import com.hedera.mirror.web3.state.core.ListWritableQueueState;
import com.hedera.mirror.web3.state.core.MapReadableKVState;
import com.hedera.mirror.web3.state.core.MapReadableStates;
import com.hedera.mirror.web3.state.core.MapWritableKVState;
import com.hedera.mirror.web3.state.core.MapWritableStates;
import com.hedera.node.app.config.BootstrapConfigProviderImpl;
import com.hedera.node.app.config.ConfigProviderImpl;
import com.hedera.node.app.fees.FeeService;
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.token.impl.TokenServiceImpl;
import com.hedera.node.app.services.AppContextImpl;
import com.hedera.node.app.services.ServiceMigrator;
import com.hedera.node.app.services.ServicesRegistry;
import com.hedera.node.app.spi.signatures.SignatureVerifier;
import com.hedera.node.app.state.recordcache.RecordCacheService;
import com.hedera.node.app.throttle.CongestionThrottleService;
import com.hedera.node.app.version.ServicesSoftwareVersion;
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.spi.CommittableWritableStates;
import com.swirlds.state.spi.EmptyWritableStates;
import com.swirlds.state.spi.KVChangeListener;
import com.swirlds.state.spi.QueueChangeListener;
Expand All @@ -39,20 +68,29 @@
import com.swirlds.state.spi.WritableQueueStateBase;
import com.swirlds.state.spi.WritableSingletonStateBase;
import com.swirlds.state.spi.WritableStates;
import com.swirlds.state.spi.info.NetworkInfo;
import edu.umd.cs.findbugs.annotations.NonNull;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Named;
import java.time.InstantSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;

@SuppressWarnings({"rawtypes", "unchecked"})
@Named
@RequiredArgsConstructor
public class MirrorNodeState implements State {

private final Map<String, ReadableStates> readableStates = new ConcurrentHashMap<>();
Expand All @@ -62,6 +100,49 @@ public class MirrorNodeState implements State {
private final Map<String, Map<String, Object>> states = new ConcurrentHashMap<>();
private final List<StateChangeListener> listeners = new ArrayList<>();

private final AccountReadableKVState accountReadableKVState;
private final AirdropsReadableKVState airdropsReadableKVState;
private final AliasesReadableKVState aliasesReadableKVState;
private final ContractBytecodeReadableKVState contractBytecodeReadableKVState;
private final ContractStorageReadableKVState contractStorageReadableKVState;
private final FileReadableKVState fileReadableKVState;
private final NftReadableKVState nftReadableKVState;
private final TokenReadableKVState tokenReadableKVState;
private final TokenRelationshipReadableKVState tokenRelationshipReadableKVState;

private final ServicesRegistry servicesRegistry;
private final ServiceMigrator serviceMigrator;
private final NetworkInfo networkInfo;

@PostConstruct
private void init() {
registerServices(servicesRegistry);
final var bootstrapConfig = new BootstrapConfigProviderImpl().getConfiguration();
serviceMigrator.doMigrations(
this,
servicesRegistry,
null,
new ServicesSoftwareVersion(
bootstrapConfig.getConfigData(VersionConfig.class).servicesVersion()),
new ConfigProviderImpl().getConfiguration(),
networkInfo,
new MetricsImpl());

final var fileServiceStates = this.getWritableStates(FileService.NAME);
final var files = fileServiceStates.<FileID, File>get(V0490FileSchema.BLOBS_KEY);
genesisContentProviders(networkInfo, 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();
}

public MirrorNodeState addService(@NonNull final String serviceName, @NonNull final Map<String, ?> dataSources) {
final var serviceStates = this.states.computeIfAbsent(serviceName, k -> new ConcurrentHashMap<>());
dataSources.forEach((k, b) -> {
Expand Down Expand Up @@ -111,7 +192,18 @@ public ReadableStates getReadableStates(@Nonnull String serviceName) {
if (state instanceof Queue queue) {
data.put(stateName, new ListReadableQueueState(stateName, queue));
} else if (state instanceof Map map) {
data.put(stateName, new MapReadableKVState(stateName, map));
switch (stateName) {
case "ACCOUNTS" -> data.put(stateName, accountReadableKVState);
case "PENDING_AIRDROPS" -> data.put(stateName, airdropsReadableKVState);
case "ALIASES" -> data.put(stateName, aliasesReadableKVState);
case "FILES" -> data.put(stateName, fileReadableKVState);
case "BYTECODE" -> data.put(stateName, contractBytecodeReadableKVState);
case "STORAGE" -> data.put(stateName, contractStorageReadableKVState);
case "NFTS" -> data.put(stateName, nftReadableKVState);
case "TOKENS" -> data.put(stateName, tokenReadableKVState);
case "TOKEN_RELS" -> data.put(stateName, tokenRelationshipReadableKVState);
default -> data.put(stateName, new MapReadableKVState(stateName, map));
}
} else if (state instanceof AtomicReference ref) {
data.put(stateName, new ReadableSingletonStateBase<>(stateName, ref::get));
}
Expand All @@ -136,10 +228,14 @@ public WritableStates getWritableStates(@Nonnull String serviceName) {
data.put(
stateName,
withAnyRegisteredListeners(serviceName, new ListWritableQueueState<>(stateName, queue)));
} else if (state instanceof Map<?, ?> map) {
} else if (state instanceof Map<?, ?>) {
data.put(
stateName,
withAnyRegisteredListeners(serviceName, new MapWritableKVState<>(stateName, map)));
withAnyRegisteredListeners(
serviceName,
new MapWritableKVState<>(
stateName,
getReadableStates(serviceName).get(stateName))));
} else if (state instanceof AtomicReference<?> ref) {
data.put(stateName, withAnyRegisteredListeners(serviceName, stateName, ref));
}
Expand Down Expand Up @@ -265,4 +361,57 @@ public int hashCode() {
void setWritableStates(final Map<String, WritableStates> writableStates) {
this.writableStates.putAll(writableStates);
}

@VisibleForTesting
Map<String, Map<String, Object>> getStates() {
return Collections.unmodifiableMap(states);
}

private void registerServices(ServicesRegistry servicesRegistry) {
// Register all service schema RuntimeConstructable factories before platform init
final var appContext = new AppContextImpl(InstantSource.system(), signatureVerifier());
Set.of(
new EntityIdService(),
new TokenServiceImpl(),
new FileServiceImpl(),
new ContractServiceImpl(appContext),
new BlockRecordService(),
new FeeService(),
new CongestionThrottleService(),
new RecordCacheService())
.forEach(servicesRegistry::register);
}

private Map<Long, Function<Configuration, Bytes>> genesisContentProviders(
final NetworkInfo networkInfo, final com.swirlds.config.api.Configuration config) {
final var genesisSchema = new V0490FileSchema();
final var filesConfig = config.getConfigData(FilesConfig.class);
return Map.of(
filesConfig.addressBook(), ignore -> genesisSchema.genesisAddressBook(networkInfo),
filesConfig.nodeDetails(), ignore -> genesisSchema.genesisNodeDetails(networkInfo),
filesConfig.feeSchedules(), genesisSchema::genesisFeeSchedules,
filesConfig.exchangeRates(), genesisSchema::genesisExchangeRates,
filesConfig.networkProperties(), genesisSchema::genesisNetworkProperties,
filesConfig.hapiPermissions(), genesisSchema::genesisHapiPermissions,
filesConfig.throttleDefinitions(), genesisSchema::genesisThrottleDefinitions);
}

private SignatureVerifier signatureVerifier() {
return new SignatureVerifier() {
@Override
public boolean verifySignature(
@Nonnull Key key,
@Nonnull com.hedera.pbj.runtime.io.buffer.Bytes bytes,
@Nonnull com.hedera.node.app.spi.signatures.SignatureVerifier.MessageType messageType,
@Nonnull SignatureMap signatureMap,
@Nullable Function<Key, SimpleKeyStatus> simpleKeyVerifier) {
throw new UnsupportedOperationException("Not implemented");
}

@Override
public KeyCounts countSimpleKeys(@Nonnull Key key) {
throw new UnsupportedOperationException("Not implemented");
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected Nft readFromDataSource(@Nonnull final NftID key) {
return null;
}

final var timestamp = ContractCallContext.get().getTimestamp();
final var timestamp = ContractCallContext.getTimestamp();
final var nftId = EntityIdUtils.toEntityId(key.tokenId()).getId();
return timestamp
.map(t -> nftRepository.findActiveByIdAndTimestamp(nftId, key.serialNumber(), t))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected TokenReadableKVState(

@Override
protected Token readFromDataSource(@Nonnull TokenID key) {
final var timestamp = ContractCallContext.get().getTimestamp();
final var timestamp = ContractCallContext.getTimestamp();
final var entity = commonEntityAccessor.get(key, timestamp).orElse(null);

if (entity == null || entity.getType() != EntityType.TOKEN) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected TokenRelation readFromDataSource(@Nonnull EntityIDPair key) {
return null;
}

final var timestamp = ContractCallContext.get().getTimestamp();
final var timestamp = ContractCallContext.getTimestamp();
// The accountId will always be in the format "shard.realm.num"
return findTokenAccount(tokenId, accountId, timestamp)
.map(ta -> tokenRelationFromEntity(tokenId, accountId, ta, timestamp))
Expand Down
Loading