Skip to content

Commit

Permalink
chore: cherry pick #16789 - Implement hss functions (#16803)
Browse files Browse the repository at this point in the history
Signed-off-by: Luke Lee <[email protected]>
  • Loading branch information
lukelee-sl authored Dec 2, 2024
1 parent 725f59f commit 6529fd3
Show file tree
Hide file tree
Showing 33 changed files with 1,273 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
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 static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.emptySortedSet;
import static java.util.Collections.unmodifiableSortedSet;
import static java.util.Objects.requireNonNull;
Expand Down Expand Up @@ -169,7 +168,7 @@ public Dispatch createChildDispatch(
requireNonNull(options);

final var preHandleResult = preHandleChild(options.body(), options.payerId(), config, readableStoreFactory);
final var childVerifier = getKeyVerifier(options.effectiveKeyVerifier(), config, emptySet());
final var childVerifier = getKeyVerifier(options.effectiveKeyVerifier(), config, options.authorizingKeys());
final var childTxnInfo = getTxnInfoFrom(options.payerId(), options.body());
final var streamMode = config.getConfigData(BlockStreamConfig.class).streamMode();
final var childStack = SavepointStackImpl.newChildStack(
Expand Down Expand Up @@ -438,8 +437,7 @@ public SignatureVerification verificationFor(@NonNull final Key key) {
@Override
public SignatureVerification verificationFor(
@NonNull final Key key, @NonNull final VerificationAssistant callback) {
// We do not yet support signing scheduled transactions from within the EVM
throw new UnsupportedOperationException();
return verifier.verificationFor(key, callback);
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,27 @@ void keyVerifierWithNullCallbackAndAuthorizingKeysAsExpected() {
}

@Test
void keyVerifierOnlySupportsKeyVerification() {
final var derivedVerifier = ChildDispatchFactory.getKeyVerifier(verifierCallback, DEFAULT_CONFIG, emptySet());
assertThatThrownBy(() -> derivedVerifier.verificationFor(Key.DEFAULT, assistant))
void keyVerifierWithCallbackAndAuthorizingKeysAsExpected() {
final var derivedVerifier =
ChildDispatchFactory.getKeyVerifier(a -> true, DEFAULT_CONFIG, Set.of(A_CONTRACT_ID_KEY));
assertThat(derivedVerifier.verificationFor(Key.DEFAULT).passed()).isTrue();
assertThat(derivedVerifier.verificationFor(Key.DEFAULT, (k, v) -> true).passed())
.isTrue();
assertThatThrownBy(() -> derivedVerifier.verificationFor(Bytes.EMPTY))
.isInstanceOf(UnsupportedOperationException.class);
assertThat(derivedVerifier.numSignaturesVerified()).isZero();
assertThat(derivedVerifier.authorizingSimpleKeys()).containsExactly(A_CONTRACT_ID_KEY);
assertThat(derivedVerifier.authorizingSimpleKeys()).isNotEmpty();
}

@Test
void noOpVerifierFailsVerification() {
final var derivedVerifier = ChildDispatchFactory.getKeyVerifier(verifierCallback, DEFAULT_CONFIG, emptySet());
assertThat(derivedVerifier.verificationFor(Key.DEFAULT, assistant).passed())
.isFalse();
assertThatThrownBy(() -> derivedVerifier.verificationFor(Bytes.EMPTY))
.isInstanceOf(UnsupportedOperationException.class);
assertThat(derivedVerifier.numSignaturesVerified()).isEqualTo(0L);
assertThat(derivedVerifier.numSignaturesVerified()).isZero();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,14 @@ public record ContractsConfig(
boolean precompileHrcFacadeAssociateEnabled,
@ConfigProperty(value = "systemContract.accountService.enabled", defaultValue = "true") @NetworkProperty
boolean systemContractAccountServiceEnabled,
@ConfigProperty(value = "systemContract.scheduleService.enabled", defaultValue = "false") @NetworkProperty
@ConfigProperty(value = "systemContract.scheduleService.enabled", defaultValue = "true") @NetworkProperty
boolean systemContractScheduleServiceEnabled,
@ConfigProperty(value = "systemContract.scheduleService.signSchedule.enabled", defaultValue = "true")
@NetworkProperty
boolean systemContractSignScheduleEnabled,
@ConfigProperty(value = "systemContract.scheduleService.authorizeSchedule.enabled", defaultValue = "false")
@NetworkProperty
boolean systemContractAuthorizeScheduleEnabled,
@ConfigProperty(value = "systemContract.accountService.isAuthorizedRawEnabled", defaultValue = "true")
@NetworkProperty
boolean systemContractAccountServiceIsAuthorizedRawEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,18 @@ private InvolvedParties computeInvolvedParties(
return parties;
}

/**
* Returns whether the given account facade is required to exist. This only applies to account and contract facade
* as tokens and schedule txn facades are not required to exist.
*
* @param to descendant of {@link HederaEvmAccount} that is the target of this call
* @param config the current node configuration
* @return whether the contract is not required to exist.
*/
private boolean contractNotRequired(@Nullable final HederaEvmAccount to, @NonNull final Configuration config) {
final var maybeGrandfatheredNumber =
(to == null) ? null : to.isTokenFacade() ? null : to.hederaId().accountNumOrThrow();

final var maybeGrandfatheredNumber = (to == null || to.isTokenFacade() || to.isScheduleTxnFacade())
? null
: to.hederaId().accountNumOrThrow();
return featureFlags.isAllowCallsToNonContractAccountsEnabled(
config.getConfigData(ContractsConfig.class), maybeGrandfatheredNumber);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ public enum DispatchType {
/**
* Dispatch for Hedera token reject functionality with resource prices on a non-fungible token.
*/
TOKEN_REJECT_NFT(HederaFunctionality.TOKEN_REJECT, TOKEN_NON_FUNGIBLE_UNIQUE);
TOKEN_REJECT_NFT(HederaFunctionality.TOKEN_REJECT, TOKEN_NON_FUNGIBLE_UNIQUE),
/**
* Dispatch for Hedera schedule sign functionality with default resource prices.
*/
SCHEDULE_SIGN(HederaFunctionality.SCHEDULE_SIGN, DEFAULT);

private final HederaFunctionality functionality;
private final SubType subtype;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
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.SortedSet;
import javax.inject.Inject;
import org.hyperledger.besu.evm.frame.MessageFrame;

Expand Down Expand Up @@ -177,4 +178,10 @@ public boolean checkForCustomFees(@NonNull final CryptoTransferTransactionBody o
final var tokenServiceApi = context.storeFactory().serviceApi(TokenServiceApi.class);
return tokenServiceApi.checkForCustomFees(op);
}

@Override
@NonNull
public SortedSet<Key> authorizingSimpleKeys() {
return context.keyVerifier().authorizingSimpleKeys();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,10 @@ public Transaction syntheticTransactionForNativeCall(
public ExchangeRate currentExchangeRate() {
return context.exchangeRateInfo().activeRate(context.consensusNow());
}

@Override
@Nullable
public Key maybeEthSenderKey() {
return maybeEthSenderKey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.hedera.node.app.service.contract.impl.exec.scope;

import static com.hedera.node.app.spi.key.KeyVerifier.NO_AUTHORIZING_KEYS;
import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.base.AccountID;
Expand All @@ -40,6 +41,7 @@
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.SortedSet;
import org.hyperledger.besu.evm.frame.MessageFrame;

/**
Expand Down Expand Up @@ -261,4 +263,12 @@ ResponseCodeEnum transferWithReceiverSigCheck(
* @return true if the given transaction body has custom fees, false otherwise
*/
boolean checkForCustomFees(@NonNull CryptoTransferTransactionBody op);

/**
* Returns the {@link SortedSet} of authorizing simple keys for this transaction.
* @return the authorizing simple keys
*/
default SortedSet<Key> authorizingSimpleKeys() {
return NO_AUTHORIZING_KEYS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,10 @@ public Transaction syntheticTransactionForNativeCall(
public ExchangeRate currentExchangeRate() {
return context.exchangeRateInfo().activeRate(instantSource.instant());
}

@Override
@Nullable
public Key maybeEthSenderKey() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.hedera.node.app.service.contract.impl.records.ContractCallStreamBuilder;
import com.hedera.node.app.spi.workflows.record.StreamBuilder;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.tuweni.bytes.Bytes;
Expand Down Expand Up @@ -154,4 +155,10 @@ Transaction syntheticTransactionForNativeCall(
*/
@NonNull
ExchangeRate currentExchangeRate();

/**
* Returns the ecdsa eth key for the sender of current transaction if one exists.
*/
@Nullable
Key maybeEthSenderKey();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import com.hedera.hapi.node.base.ContractID;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractNativeSystemContract;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallFactory;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallFactory;
import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils;
import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.EntityType;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand All @@ -45,7 +45,7 @@ public class HssSystemContract extends AbstractNativeSystemContract implements H
public static final ContractID HSS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HSS_EVM_ADDRESS));

@Inject
public HssSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HasCallFactory callFactory) {
public HssSystemContract(@NonNull final GasCalculator gasCalculator, @NonNull final HssCallFactory callFactory) {
super(HSS_SYSTEM_CONTRACT_NAME, callFactory, HSS_CONTRACT_ID, gasCalculator);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,22 @@ public boolean isSelectorIfConfigEnabled(final boolean configEnabled, @NonNull f
return configEnabled && isSelector(functions);
}

/**
* Returns whether this call attempt is a selector for any of the given functions.
* @param functionSelector bytes of the function selector
* @param input input bytes
* @return true if the function selector at the start of the input bytes
*/
private boolean isRedirectSelector(@NonNull final byte[] functionSelector, @NonNull final byte[] input) {
return Arrays.equals(input, 0, functionSelector.length, functionSelector, 0, functionSelector.length);
}

/**
* Returns whether only delegate contract keys are active.
*
* @return true if only delegate contract keys are active
*/
public boolean isOnlyDelegatableContractKeysActive() {
return onlyDelegatableContractKeysActive;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2023-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.contract.impl.exec.systemcontracts.hss;

import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY;
import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS;
import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT;
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult;
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly;
import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc;
import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf;

import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.service.contract.impl.exec.gas.DispatchGasCalculator;
import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator;
import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCall;
import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater;
import com.hedera.node.app.service.contract.impl.records.ContractCallStreamBuilder;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Objects;
import java.util.Set;
import org.hyperledger.besu.evm.frame.MessageFrame;

/**
* An HSS call that simply dispatches a synthetic transaction body and returns a result that is
* an encoded {@link ResponseCodeEnum}.
*/
public class DispatchForResponseCodeHssCall extends AbstractCall {

private final AccountID senderId;

@Nullable
private final TransactionBody syntheticBody;

private final VerificationStrategy verificationStrategy;
private final DispatchGasCalculator dispatchGasCalculator;
private final Set<Key> authorizingKeys;

/**
* Convenience overload that slightly eases construction for the most common case.
*
* @param attempt the attempt to translate to a dispatching
* @param syntheticBody the synthetic body to dispatch
* @param dispatchGasCalculator the dispatch gas calculator to use
*/
public DispatchForResponseCodeHssCall(
@NonNull final HssCallAttempt attempt,
@Nullable final TransactionBody syntheticBody,
@NonNull final DispatchGasCalculator dispatchGasCalculator,
@NonNull final Set<Key> authorizingKeys) {
this(
attempt.enhancement(),
attempt.systemContractGasCalculator(),
attempt.addressIdConverter().convertSender(attempt.senderAddress()),
syntheticBody,
attempt.defaultVerificationStrategy(),
dispatchGasCalculator,
authorizingKeys);
}

/**
* More general constructor, for cases where perhaps a custom {@link VerificationStrategy} is needed.
*
* @param enhancement the enhancement to use
* @param senderId the id of the spender
* @param syntheticBody the synthetic body to dispatch
* @param verificationStrategy the verification strategy to use
* @param dispatchGasCalculator the dispatch gas calculator to use
*/
// too many parameters
@SuppressWarnings("java:S107")
public DispatchForResponseCodeHssCall(
@NonNull final HederaWorldUpdater.Enhancement enhancement,
@NonNull final SystemContractGasCalculator gasCalculator,
@NonNull final AccountID senderId,
@Nullable final TransactionBody syntheticBody,
@NonNull final VerificationStrategy verificationStrategy,
@NonNull final DispatchGasCalculator dispatchGasCalculator,
@NonNull final Set<Key> authorizingKeys) {
super(gasCalculator, enhancement, false);
this.senderId = Objects.requireNonNull(senderId);
this.syntheticBody = syntheticBody;
this.verificationStrategy = Objects.requireNonNull(verificationStrategy);
this.dispatchGasCalculator = Objects.requireNonNull(dispatchGasCalculator);
this.authorizingKeys = authorizingKeys;
}

@Override
public @NonNull PricedResult execute(@NonNull final MessageFrame frame) {
if (syntheticBody == null) {
return gasOnly(
haltResult(
ERROR_DECODING_PRECOMPILE_INPUT,
contractsConfigOf(frame).precompileHtsDefaultGasCost()),
INVALID_TRANSACTION_BODY,
false);
}
final var recordBuilder = systemContractOperations()
.dispatch(
syntheticBody,
verificationStrategy,
senderId,
ContractCallStreamBuilder.class,
authorizingKeys);
final var gasRequirement =
dispatchGasCalculator.gasRequirement(syntheticBody, gasCalculator, enhancement, senderId);
var status = recordBuilder.status();
if (status != SUCCESS) {
recordBuilder.status(status);
}
return completionWith(gasRequirement, recordBuilder, encodedRc(recordBuilder.status()));
}
}
Loading

0 comments on commit 6529fd3

Please sign in to comment.