All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hedera.node.app.service.schedule.impl.handlers.ScheduleGetInfoHandler Maven / Gradle / Ivy

The newest version!
/*
 * 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.schedule.impl.handlers;

import static com.hedera.node.app.hapi.utils.CommonPbjConverters.fromPbj;
import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA;

import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.KeyList;
import com.hedera.hapi.node.base.QueryHeader;
import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.base.ResponseHeader;
import com.hedera.hapi.node.base.ResponseType;
import com.hedera.hapi.node.base.ScheduleID;
import com.hedera.hapi.node.base.Timestamp;
import com.hedera.hapi.node.scheduled.ScheduleGetInfoQuery;
import com.hedera.hapi.node.scheduled.ScheduleGetInfoResponse;
import com.hedera.hapi.node.scheduled.ScheduleGetInfoResponse.Builder;
import com.hedera.hapi.node.scheduled.ScheduleInfo;
import com.hedera.hapi.node.state.schedule.Schedule;
import com.hedera.hapi.node.transaction.Query;
import com.hedera.hapi.node.transaction.Response;
import com.hedera.node.app.hapi.fees.usage.schedule.ExtantScheduleContext;
import com.hedera.node.app.hapi.fees.usage.schedule.ScheduleOpsUsage;
import com.hedera.node.app.service.schedule.ReadableScheduleStore;
import com.hedera.node.app.spi.fees.Fees;
import com.hedera.node.app.spi.workflows.PaidQueryHandler;
import com.hedera.node.app.spi.workflows.PreCheckException;
import com.hedera.node.app.spi.workflows.QueryContext;
import com.hedera.node.config.data.LedgerConfig;
import com.hederahashgraph.api.proto.java.FeeData;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * This class provides an implementation of the {@link HederaFunctionality#SCHEDULE_GET_INFO} query.
 */
@Singleton
public class ScheduleGetInfoHandler extends PaidQueryHandler {
    private final ScheduleOpsUsage legacyUsage;

    /**
     * Constructor is used by the Dagger dependency injection framework to provide the necessary dependencies to the handler.
     * The handler is responsible for handling the {@link HederaFunctionality#SCHEDULE_GET_INFO} query.
     *
     * @param legacyUsage the legacy usage
     */
    @Inject
    public ScheduleGetInfoHandler(ScheduleOpsUsage legacyUsage) {
        this.legacyUsage = legacyUsage;
    }

    @Override
    public QueryHeader extractHeader(@NonNull final Query query) {
        Objects.requireNonNull(query);
        final ScheduleGetInfoQuery expectedQuery = query.scheduleGetInfo();
        return expectedQuery != null ? expectedQuery.header() : null;
    }

    @Override
    public Response createEmptyResponse(@NonNull final ResponseHeader header) {
        Objects.requireNonNull(header);
        final var response = ScheduleGetInfoResponse.newBuilder().header(header);
        return Response.newBuilder().scheduleGetInfo(response).build();
    }

    @NonNull
    @Override
    public Fees computeFees(@NonNull final QueryContext context) {
        // Need to work out if this is correct, note we effectively (much) more than double total effort
        // here just to calculate fees based on a single instance of that effort...
        final Schedule found = findSchedule(context);
        if (found != null) {
            final LedgerConfig ledgerConfig = context.configuration().getConfigData(LedgerConfig.class);
            final ScheduleInfo.Builder builder = ScheduleInfo.newBuilder();
            buildFromSchedule(builder, found, ledgerConfig);
            return context.feeCalculator()
                    .legacyCalculate(sigValueObj -> usageGiven(fromPbj(context.query()), fromPbj(builder.build())));
        } else {
            return context.feeCalculator().calculate();
        }
    }

    @Override
    public void validate(@NonNull final QueryContext context) throws PreCheckException {
        Objects.requireNonNull(context);
        final ScheduleGetInfoQuery request = context.query().scheduleGetInfo();
        if (request != null && request.hasHeader()) {
            if (findSchedule(context) == null) {
                throw new PreCheckException(ResponseCodeEnum.INVALID_SCHEDULE_ID);
            }
        } else {
            throw new PreCheckException(ResponseCodeEnum.INVALID_TRANSACTION);
        }
    }

    @Override
    public Response findResponse(@NonNull final QueryContext context, @NonNull final ResponseHeader header) {
        Objects.requireNonNull(context);
        Objects.requireNonNull(header);
        final LedgerConfig ledgerConfig = context.configuration().getConfigData(LedgerConfig.class);
        final Builder infoBuilder = ScheduleGetInfoResponse.newBuilder();
        infoBuilder.header(header);
        if (shouldHandle(context, header)) {
            final Schedule scheduleFound = findSchedule(context);
            if (scheduleFound != null) {
                final ScheduleInfo.Builder builder = ScheduleInfo.newBuilder();
                buildFromSchedule(builder, scheduleFound, ledgerConfig);
                infoBuilder.scheduleInfo(builder);
            } else {
                infoBuilder.header(
                        header.copyBuilder().nodeTransactionPrecheckCode(ResponseCodeEnum.INVALID_SCHEDULE_ID));
            }
        }
        return Response.newBuilder().scheduleGetInfo(infoBuilder).build();
    }

    private boolean shouldHandle(final QueryContext context, final ResponseHeader header) {
        final ScheduleGetInfoQuery query = context.query().scheduleGetInfo();
        return query != null
                && query.header() != null
                && query.hasScheduleID()
                && query.header().responseType() != ResponseType.COST_ANSWER
                && header.nodeTransactionPrecheckCode() == ResponseCodeEnum.OK;
    }

    private void buildFromSchedule(
            final ScheduleInfo.Builder builder, final Schedule scheduleFound, final LedgerConfig config) {
        builder.adminKey(scheduleFound.adminKey());
        builder.creatorAccountID(scheduleFound.schedulerAccountId());
        builder.waitForExpiry(scheduleFound.waitForExpiry());
        builder.scheduleID(scheduleFound.scheduleId());
        builder.memo(scheduleFound.memo());
        builder.payerAccountID(scheduleFound.payerAccountId());
        builder.ledgerId(config.id());
        if (scheduleFound.executed()) {
            builder.executionTime(scheduleFound.resolutionTime());
        }
        if (scheduleFound.deleted()) {
            builder.deletionTime(scheduleFound.resolutionTime());
        }
        builder.scheduledTransactionID(HandlerUtility.transactionIdForScheduled(scheduleFound));
        builder.expirationTime(timestampFromSeconds(scheduleFound.calculatedExpirationSecond()));
        builder.signers(makeKeyList(scheduleFound.signatories()));
        builder.scheduledTransactionBody(scheduleFound.scheduledTransaction());
    }

    private KeyList makeKeyList(final List signatories) {
        return KeyList.newBuilder().keys(signatories).build();
    }

    private Schedule findSchedule(final QueryContext context) {
        final Query contextQuery = context.query();
        final ReadableScheduleStore queryStore = context.createStore(ReadableScheduleStore.class);
        final ScheduleGetInfoQuery scheduleQuery = contextQuery.scheduleGetInfoOrThrow();
        final ScheduleID idToQuery = scheduleQuery.scheduleID();
        return idToQuery != null ? queryStore.get(idToQuery) : null;
    }

    private Timestamp.Builder timestampFromSeconds(long secondsSinceEpoch) {
        return Timestamp.newBuilder().seconds(secondsSinceEpoch).nanos(0);
    }

    public FeeData usageGiven(
            final com.hederahashgraph.api.proto.java.Query query,
            final com.hederahashgraph.api.proto.java.ScheduleInfo info) {
        if (info != null) {
            final var scheduleCtxBuilder = ExtantScheduleContext.newBuilder()
                    .setScheduledTxn(info.getScheduledTransactionBody())
                    .setMemo(info.getMemo())
                    .setNumSigners(info.getSigners().getKeysCount())
                    .setResolved(info.hasExecutionTime() || info.hasDeletionTime());
            if (info.hasAdminKey()) {
                scheduleCtxBuilder.setAdminKey(info.getAdminKey());
            } else {
                scheduleCtxBuilder.setNoAdminKey();
            }
            return legacyUsage.scheduleInfoUsage(query, scheduleCtxBuilder.build());
        } else {
            return CONSTANT_FEE_DATA;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy