com.hedera.node.app.service.file.impl.handlers.FileGetInfoHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of app-service-file-impl Show documentation
Show all versions of app-service-file-impl Show documentation
Default Hedera File Service Implementation
/*
* Copyright (C) 2022-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.node.app.service.file.impl.handlers;
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_FILE_ID;
import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
import static com.hedera.hapi.node.base.ResponseType.COST_ANSWER;
import static com.swirlds.common.utility.CommonUtils.hex;
import static java.util.Objects.requireNonNull;
import com.hedera.hapi.node.base.FileID;
import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.hapi.node.base.QueryHeader;
import com.hedera.hapi.node.base.ResponseHeader;
import com.hedera.hapi.node.base.Timestamp;
import com.hedera.hapi.node.file.FileGetInfoQuery;
import com.hedera.hapi.node.file.FileGetInfoResponse;
import com.hedera.hapi.node.file.FileInfo;
import com.hedera.hapi.node.state.file.File;
import com.hedera.hapi.node.transaction.Query;
import com.hedera.hapi.node.transaction.Response;
import com.hedera.node.app.hapi.fees.usage.file.ExtantFileContext;
import com.hedera.node.app.hapi.fees.usage.file.FileOpsUsage;
import com.hedera.node.app.hapi.utils.CommonPbjConverters;
import com.hedera.node.app.service.file.FileMetadata;
import com.hedera.node.app.service.file.ReadableFileStore;
import com.hedera.node.app.service.file.ReadableUpgradeFileStore;
import com.hedera.node.app.service.file.impl.base.FileQueryBase;
import com.hedera.node.app.spi.fees.Fees;
import com.hedera.node.app.spi.workflows.PreCheckException;
import com.hedera.node.app.spi.workflows.QueryContext;
import com.hedera.node.config.data.FilesConfig;
import com.hedera.node.config.data.LedgerConfig;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import com.hederahashgraph.api.proto.java.FeeData;
import com.swirlds.common.crypto.CryptographyHolder;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* This class contains all workflow-related functionality regarding {@link HederaFunctionality#FILE_GET_INFO}.
*/
@Singleton
public class FileGetInfoHandler extends FileQueryBase {
private final FileOpsUsage fileOpsUsage;
/**
* Constructs a {@link FileGetInfoHandler} with the given {@link FileOpsUsage}.
* @param fileOpsUsage the file operations usage to be used for fee calculation
*/
@Inject
public FileGetInfoHandler(final FileOpsUsage fileOpsUsage) {
this.fileOpsUsage = fileOpsUsage;
}
@Override
public @NonNull QueryHeader extractHeader(@NonNull final Query query) {
requireNonNull(query);
return query.fileGetInfoOrThrow().header();
}
@Override
public @NonNull Response createEmptyResponse(@NonNull final ResponseHeader header) {
requireNonNull(header);
final var response = FileGetInfoResponse.newBuilder().header(header);
return Response.newBuilder().fileGetInfo(response).build();
}
@Override
public void validate(@NonNull final QueryContext context) throws PreCheckException {
final var query = context.query();
final FileGetInfoQuery op = query.fileGetInfoOrThrow();
if (!op.hasFileID()) {
throw new PreCheckException(INVALID_FILE_ID);
}
}
@Override
public @NonNull Fees computeFees(@NonNull QueryContext queryContext) {
final var query = queryContext.query();
final var fileStore = queryContext.createStore(ReadableFileStore.class);
final var op = query.fileGetInfoOrThrow();
final var fileId = op.fileIDOrElse(FileID.DEFAULT);
final File file = fileStore.getFileLeaf(fileId);
return queryContext
.feeCalculator()
.legacyCalculate(sigValueObj -> usageGiven(CommonPbjConverters.fromPbj(query), file));
}
@Override
public @NonNull Response findResponse(@NonNull final QueryContext context, @NonNull final ResponseHeader header) {
requireNonNull(header);
final var query = context.query();
final var fileStore = context.createStore(ReadableFileStore.class);
final var upgradeFileStore = context.createStore(ReadableUpgradeFileStore.class);
final var ledgerConfig = context.configuration().getConfigData(LedgerConfig.class);
final var fileServiceConfig = context.configuration().getConfigData(FilesConfig.class);
final var op = query.fileGetInfoOrThrow();
final var responseBuilder = FileGetInfoResponse.newBuilder();
final var file = op.fileIDOrThrow();
final var responseType = op.headerOrElse(QueryHeader.DEFAULT).responseType();
responseBuilder.header(header);
if (header.nodeTransactionPrecheckCode() == OK && responseType != COST_ANSWER) {
final Optional optionalInfo;
try {
optionalInfo = infoForFile(file, fileStore, ledgerConfig, upgradeFileStore, fileServiceConfig);
} catch (IOException e) {
throw new UncheckedIOException("Unable to read file contents", e);
}
if (optionalInfo.isEmpty()) {
responseBuilder.header(header.copyBuilder()
.nodeTransactionPrecheckCode(INVALID_FILE_ID)
.build());
} else {
responseBuilder.fileInfo(optionalInfo.get());
}
}
return Response.newBuilder().fileGetInfo(responseBuilder).build();
}
/**
* Provides information about a file.
* @param fileID the file to get information about
* @param fileStore the file store
* @param ledgerConfig Ledger configuration properties
* @return the information about the file
*/
@SuppressWarnings("java:S5738") // Suppress the warning that we are using deprecated class(CryptographyHolder)
private Optional infoForFile(
@NonNull final FileID fileID,
@NonNull final ReadableFileStore fileStore,
@NonNull final LedgerConfig ledgerConfig,
@NonNull final ReadableUpgradeFileStore upgradeFileStore,
@NonNull final FilesConfig fileServiceConfig)
throws IOException {
FileMetadata meta = null;
long contentSize = 0L;
// upgrade is for the entire network, not a node. It's across shards and realms, however, which is why we ignore
// the shard and realm values.
if (fileID.fileNum() >= fileServiceConfig.softwareUpdateRange().left()
&& fileID.fileNum() <= fileServiceConfig.softwareUpdateRange().right()) {
final var file = upgradeFileStore.peek(fileID);
if (file != null) {
// The "memo" of a special upgrade file is its hexed SHA-384 hash for DevOps convenience
final var contents = upgradeFileStore.getFull(fileID).toByteArray();
contentSize = contents.length;
final var upgradeHash = hex(CryptographyHolder.get().digestBytesSync(contents));
meta = new FileMetadata(
file.fileId(),
Timestamp.newBuilder().seconds(file.expirationSecond()).build(),
file.keys(),
Bytes.EMPTY,
upgradeHash,
file.deleted(),
Timestamp.newBuilder()
.seconds(file.preSystemDeleteExpirationSecond())
.build());
}
} else {
meta = fileStore.getFileMetadata(fileID);
}
if (meta == null) {
return Optional.empty();
} else {
final var info = FileInfo.newBuilder();
info.memo(meta.memo() == null ? "" : meta.memo());
info.fileID(fileID);
info.size((contentSize > 0L ? contentSize : meta.contents().length()));
info.expirationTime(meta.expirationTimestamp());
info.deleted(meta.deleted());
info.keys(meta.keys());
info.ledgerId(ledgerConfig.id());
return Optional.of(info.build());
}
}
private FeeData usageGiven(
@NonNull final com.hederahashgraph.api.proto.java.Query query, @Nullable final File file) {
requireNonNull(query);
if (file == null) {
return FeeData.getDefaultInstance();
}
final com.hederahashgraph.api.proto.java.File details = CommonPbjConverters.fromPbj(file);
final var ctx = ExtantFileContext.newBuilder()
.setCurrentSize(details.getContents().toByteArray().length)
.setCurrentWacl(details.getKeys())
.setCurrentMemo(details.getMemo())
.setCurrentExpiry(details.getExpirationSecond())
.build();
return fileOpsUsage.fileInfoUsage(query, ctx);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy