Skip to content

Commit

Permalink
chore: Add logic for ChildDispatchScope, UserDispatchScope and `U…
Browse files Browse the repository at this point in the history
…serTxnScope` (#13780)

Signed-off-by: Neeharika-Sompalli <[email protected]>
Co-authored-by: Michael Tinker <[email protected]>
  • Loading branch information
Neeharika-Sompalli and tinker-michaelj authored Jun 12, 2024
1 parent 853a6f1 commit f2d47d2
Show file tree
Hide file tree
Showing 14 changed files with 2,010 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Copyright (C) 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.workflows.handle.flow.dispatch.child.logic;

import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
import static com.hedera.node.app.workflows.prehandle.PreHandleResult.Status.PRE_HANDLE_FAILURE;
import static com.hedera.node.app.workflows.prehandle.PreHandleResult.Status.SO_FAR_SO_GOOD;

import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.signature.DelegateKeyVerifier;
import com.hedera.node.app.signature.KeyVerifier;
import com.hedera.node.app.signature.impl.SignatureVerificationImpl;
import com.hedera.node.app.spi.signatures.SignatureVerification;
import com.hedera.node.app.spi.signatures.VerificationAssistant;
import com.hedera.node.app.spi.workflows.ComputeDispatchFeesAsTopLevel;
import com.hedera.node.app.spi.workflows.HandleContext;
import com.hedera.node.app.spi.workflows.PreCheckException;
import com.hedera.node.app.spi.workflows.record.ExternalizedRecordCustomizer;
import com.hedera.node.app.workflows.dispatcher.TransactionDispatcher;
import com.hedera.node.app.workflows.handle.flow.dispatch.Dispatch;
import com.hedera.node.app.workflows.handle.flow.dispatch.child.ChildDispatchComponent;
import com.hedera.node.app.workflows.handle.record.SingleTransactionRecordBuilderImpl;
import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl;
import com.hedera.node.app.workflows.prehandle.PreHandleContextImpl;
import com.hedera.node.app.workflows.prehandle.PreHandleResult;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Collections;
import java.util.function.Predicate;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;

/**
* A factory for constructing child dispatches.This also gets the pre-handle result for the child transaction,
* and signature verifications for the child transaction.
*/
@Singleton
public class ChildDispatchFactory {
private static final NoOpKeyVerifier NO_OP_KEY_VERIFIER = new NoOpKeyVerifier();

private final ChildTxnInfoFactory childTxnInfoFactory;
private final TransactionDispatcher dispatcher;
private final ChildRecordBuilderFactory recordBuilderFactory;

@Inject
public ChildDispatchFactory(
final ChildTxnInfoFactory childTxnInfoFactory,
final TransactionDispatcher dispatcher,
final ChildRecordBuilderFactory recordBuilderFactory) {
this.childTxnInfoFactory = childTxnInfoFactory;
this.dispatcher = dispatcher;
this.recordBuilderFactory = recordBuilderFactory;
}

/**
* Creates a child dispatch. This method computes the transaction info and initializes record builder for the child
* transaction.
* @param parentDispatch the parent dispatch
* @param txBody the transaction body
* @param callback the key verifier for child dispatch
* @param syntheticPayerId the synthetic payer id
* @param category the transaction category
* @param childDispatchFactory the child dispatch factory
* @param customizer the externalized record customizer
* @param reversingBehavior the reversing behavior
* @return the child dispatch
*/
public Dispatch createChildDispatch(
@NonNull final Dispatch parentDispatch,
@NonNull final TransactionBody txBody,
@Nullable final Predicate<Key> callback,
@NonNull final AccountID syntheticPayerId,
@NonNull final HandleContext.TransactionCategory category,
@NonNull final Provider<ChildDispatchComponent.Factory> childDispatchFactory,
@NonNull final ExternalizedRecordCustomizer customizer,
@NonNull final SingleTransactionRecordBuilderImpl.ReversingBehavior reversingBehavior) {
final var preHandleResult = dispatchPreHandleForChildTxn(parentDispatch, txBody, syntheticPayerId);
final var childTxnInfo = childTxnInfoFactory.getTxnInfoFrom(txBody);
final var recordBuilder = recordBuilderFactory.recordBuilderFor(
childTxnInfo,
parentDispatch.recordListBuilder(),
parentDispatch.handleContext().configuration(),
category,
reversingBehavior,
customizer);

return childDispatchFactory
.get()
.create(
recordBuilder,
childTxnInfo,
isScheduled(category),
syntheticPayerId,
category,
new SavepointStackImpl(parentDispatch.stack().peek()),
preHandleResult,
getKeyVerifier(callback));
}

/**
* Dispatches the pre-handle checks for the child transaction. This runs pureChecks and then dispatches pre-handle
* for child transaction.
* @param parentDispatch the parent dispatch
* @param txBody the transaction body
* @param syntheticPayerId the synthetic payer id
* @return the pre-handle result
*/
private PreHandleResult dispatchPreHandleForChildTxn(
final @NonNull Dispatch parentDispatch,
final @NonNull TransactionBody txBody,
final @NonNull AccountID syntheticPayerId) {
try {
dispatcher.dispatchPureChecks(txBody);
final var preHandleContext = new PreHandleContextImpl(
parentDispatch.readableStoreFactory(),
txBody,
syntheticPayerId,
parentDispatch.handleContext().configuration(),
dispatcher);
dispatcher.dispatchPreHandle(preHandleContext);
return new PreHandleResult(
null,
null,
SO_FAR_SO_GOOD,
OK,
null,
preHandleContext.requiredNonPayerKeys(),
null,
preHandleContext.requiredHollowAccounts(),
null,
null,
0);
} catch (final PreCheckException e) {
return new PreHandleResult(
null,
null,
PRE_HANDLE_FAILURE,
e.responseCode(),
null,
Collections.emptySet(),
null,
Collections.emptySet(),
null,
null,
0);
}
}

/**
* Returns whether the transaction is scheduled or not.
* @param category the transaction category
* @return the compute dispatch fees as top level
*/
@NonNull
private static ComputeDispatchFeesAsTopLevel isScheduled(final HandleContext.TransactionCategory category) {
return category == HandleContext.TransactionCategory.SCHEDULED
? ComputeDispatchFeesAsTopLevel.YES
: ComputeDispatchFeesAsTopLevel.NO;
}

/**
* A {@link KeyVerifier} that always returns {@link SignatureVerificationImpl} with a
* passed verification.
*/
static class NoOpKeyVerifier implements KeyVerifier {
private static final SignatureVerification PASSED_VERIFICATION =
new SignatureVerificationImpl(Key.DEFAULT, Bytes.EMPTY, true);

@NonNull
@Override
public SignatureVerification verificationFor(@NonNull final Key key) {
return PASSED_VERIFICATION;
}

@NonNull
@Override
public SignatureVerification verificationFor(
@NonNull final Key key, @NonNull final VerificationAssistant callback) {
return PASSED_VERIFICATION;
}

@NonNull
@Override
public SignatureVerification verificationFor(@NonNull final Bytes evmAlias) {
return PASSED_VERIFICATION;
}

@Override
public int numSignaturesVerified() {
return 0;
}
}

/**
* Returns a {@link KeyVerifier} based on the callback. If the callback is null, then it returns a
* {@link NoOpKeyVerifier}. Otherwise, it returns a {@link DelegateKeyVerifier} with the callback.
* The callback is null if the signature verification is not required. This is the case for hollow account
* completion and auto account creation.
* @param callback the callback
* @return the key verifier
*/
static KeyVerifier getKeyVerifier(@Nullable Predicate<Key> callback) {
return callback == null
? NO_OP_KEY_VERIFIER
: new KeyVerifier() {
private final KeyVerifier verifier = new DelegateKeyVerifier(callback);

@NonNull
@Override
public SignatureVerification verificationFor(@NonNull final Key key) {
return callback.test(key) ? NoOpKeyVerifier.PASSED_VERIFICATION : verifier.verificationFor(key);
}

@NonNull
@Override
public SignatureVerification verificationFor(
@NonNull final Key key, @NonNull final VerificationAssistant callback) {
throw new UnsupportedOperationException("Should never be called!");
}

@NonNull
@Override
public SignatureVerification verificationFor(@NonNull final Bytes evmAlias) {
throw new UnsupportedOperationException("Should never be called!");
}

@Override
public int numSignaturesVerified() {
return 0;
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (C) 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.workflows.handle.flow.dispatch.child.logic;

import static com.hedera.node.app.spi.workflows.HandleContext.PrecedingTransactionCategory.LIMITED_CHILD_RECORDS;
import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.CHILD;
import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.PRECEDING;
import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.SCHEDULED;
import static java.util.Objects.requireNonNull;

import com.hedera.node.app.spi.workflows.HandleContext;
import com.hedera.node.app.spi.workflows.record.ExternalizedRecordCustomizer;
import com.hedera.node.app.workflows.TransactionInfo;
import com.hedera.node.app.workflows.handle.record.RecordListBuilder;
import com.hedera.node.app.workflows.handle.record.SingleTransactionRecordBuilderImpl;
import com.swirlds.config.api.Configuration;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;

/**
* Provider of the child record builder based on the dispatched child transaction category
*/
@Singleton
public class ChildRecordBuilderFactory {
/**
* Constructs the {@link ChildRecordBuilderFactory} instance.
*/
@Inject
public ChildRecordBuilderFactory() {}

/**
* Provides the record builder for the child transaction category and initializes it.
* The record builder is created based on the child category and the reversing behavior.
* @param txnInfo the transaction info
* @param recordListBuilder the record list builder
* @param configuration the configuration
* @param childCategory the child category
* @param reversingBehavior the reversing behavior
* @param customizer the externalized record customizer
* @return the record builder
*/
public SingleTransactionRecordBuilderImpl recordBuilderFor(
TransactionInfo txnInfo,
final RecordListBuilder recordListBuilder,
final Configuration configuration,
HandleContext.TransactionCategory childCategory,
SingleTransactionRecordBuilderImpl.ReversingBehavior reversingBehavior,
@Nullable final ExternalizedRecordCustomizer customizer) {
final SingleTransactionRecordBuilderImpl recordBuilder;
if (childCategory == PRECEDING) {
recordBuilder = switch (reversingBehavior) {
case REMOVABLE -> recordListBuilder.addRemovablePreceding(configuration);
case REVERSIBLE -> recordListBuilder.addReversiblePreceding(configuration);
case IRREVERSIBLE -> recordListBuilder.addPreceding(configuration, LIMITED_CHILD_RECORDS);};
} else if (childCategory == CHILD) {
recordBuilder = switch (reversingBehavior) {
case REMOVABLE -> recordListBuilder.addRemovableChildWithExternalizationCustomizer(
configuration, requireNonNull(customizer));
case REVERSIBLE -> recordListBuilder.addChild(configuration, childCategory);
case IRREVERSIBLE -> throw new IllegalArgumentException("Unsupported reversing behavior: "
+ reversingBehavior + " for child category: " + childCategory);};
} else if (childCategory == SCHEDULED) {
recordBuilder = recordListBuilder.addChild(configuration, childCategory);
} else {
throw new IllegalArgumentException("Unsupported child category: " + childCategory);
}
initializeRecord(recordBuilder, txnInfo);
return recordBuilder;
}

/**
* Initializes the user record with the transaction information.
* @param recordBuilder the record builder
* @param txnInfo the transaction info
*/
private void initializeRecord(
@NonNull final SingleTransactionRecordBuilderImpl recordBuilder, @NonNull final TransactionInfo txnInfo) {
recordBuilder
.transaction(txnInfo.transaction())
.transactionBytes(txnInfo.signedBytes())
.memo(txnInfo.txBody().memo());
final var transactionID = txnInfo.txBody().transactionID();
if (transactionID != null) {
recordBuilder.transactionID(transactionID);
}
}
}
Loading

0 comments on commit f2d47d2

Please sign in to comment.