Skip to content

Commit

Permalink
Smart contract service metrics, P2 priority.
Browse files Browse the repository at this point in the history
These are the metrics that are interesting now and we can collect in the consensus node, but later we'll
collect in other ways. E.g., from block nodes. (All these metrics are for post-consensus
transactions and will show up in the record/block stream.)

Does _not_ include metrics for ExchangeRate and PNRG system contracts:  Those system contracts
are coded differently (don't use the same base (abstract) classes as HAS/HSS/HTS) and so will need
a bit more work.

Related issue(s):

Fixes #16088

Reviewer notes:

Changes a boatload of files.  Each of the ~60 FooTranslator classes (about one for each system
contract method) needs a small change, but that's a change to the construtor, and so it had
effects on all the unit tests for those classes as well.  You'll quickly see the pattern ...

Signed-off-by: David S Bakin <117694041+david-bakin-sl@users.noreply.github.com>
  • Loading branch information
david-bakin-sl committed Dec 5, 2024
1 parent 63f036f commit fcac14d
Show file tree
Hide file tree
Showing 124 changed files with 1,862 additions and 252 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,14 @@ public interface RpcService extends Service {
*/
@NonNull
Set<RpcServiceDefinition> rpcDefinitions();

/**
* Services may have initialization to be done which can't be done in the constructor (too soon)
* but should/must be done before the system starts processing transactions. This is the hook
* for that.
*
* Called on each Service when `Hedera.onStateInitialized() is called for `InitTrigger.GENESIS`.
* Services module is still single-threaded when this happens.
*/
default void onStateInitializedForGenesis() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@
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.services.ServicesRegistry.Registration;
import com.hedera.node.app.signature.AppSignatureVerifier;
import com.hedera.node.app.signature.impl.SignatureExpanderImpl;
import com.hedera.node.app.signature.impl.SignatureVerifierImpl;
import com.hedera.node.app.spi.AppContext;
import com.hedera.node.app.spi.RpcService;
import com.hedera.node.app.spi.workflows.PreCheckException;
import com.hedera.node.app.state.MerkleStateLifecyclesImpl;
import com.hedera.node.app.state.recordcache.RecordCacheService;
Expand Down Expand Up @@ -603,11 +605,19 @@ public void onStateInitialized(
trigger,
RosterUtils.buildAddressBook(platform.getRoster()),
platform.getContext().getConfiguration());

contractServiceImpl.registerMetrics();
}
// With the States API grounded in the working state, we can create the object graph from it
initializeDagger(state, trigger);

// Tell each service it can do its final initialization (if needed) before the system starts
// processing transactions.
if (trigger == GENESIS) {
servicesRegistry.registrations().stream()
.map(Registration::service)
.filter(RpcService.class::isInstance)
.map(RpcService.class::cast)
.forEach(RpcService::onStateInitializedForGenesis);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

import com.hedera.node.app.service.contract.impl.exec.metrics.ContractMetrics;
import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.has.HasCallAttempt;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hss.HssCallAttempt;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt;
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethodRegistry;
import com.hedera.node.app.service.contract.impl.handlers.ContractHandlers;
import com.hedera.node.app.spi.signatures.SignatureVerifier;
import dagger.BindsInstance;
Expand All @@ -26,6 +31,8 @@
import java.time.InstantSource;
import java.util.List;
import java.util.function.Supplier;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.hyperledger.besu.evm.tracing.OperationTracer;

Expand All @@ -52,7 +59,8 @@ ContractServiceComponent create(
@BindsInstance SignatureVerifier signatureVerifier,
@BindsInstance VerificationStrategies verificationStrategies,
@BindsInstance @Nullable Supplier<List<OperationTracer>> addOnTracers,
@BindsInstance ContractMetrics contractMetrics);
@BindsInstance ContractMetrics contractMetrics,
@BindsInstance SystemContractMethodRegistry systemContractMethodRegistry);
}

/**
Expand All @@ -64,4 +72,18 @@ ContractServiceComponent create(
* @return contract metrics collection, instance
*/
ContractMetrics contractMetrics();

/**
* @return method registry for system contracts
*/
SystemContractMethodRegistry systemContractMethodRegistry();

@Named("HasTranslators")
Provider<List<CallTranslator<HasCallAttempt>>> hasCallTranslators();

@Named("HssTranslators")
Provider<List<CallTranslator<HssCallAttempt>>> hssCallTranslators();

@Named("HtsTranslators")
Provider<List<CallTranslator<HtsCallAttempt>>> htsCallTranslators();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
import com.hedera.node.app.service.contract.impl.exec.metrics.ContractMetrics;
import com.hedera.node.app.service.contract.impl.exec.scope.DefaultVerificationStrategies;
import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallAttempt;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.AbstractCallTranslator;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.CallTranslator;
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethodRegistry;
import com.hedera.node.app.service.contract.impl.handlers.ContractHandlers;
import com.hedera.node.app.service.contract.impl.schemas.V0490ContractSchema;
import com.hedera.node.app.service.contract.impl.schemas.V0500ContractSchema;
Expand All @@ -30,15 +34,23 @@
import com.swirlds.state.lifecycle.SchemaRegistry;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.inject.Singleton;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hyperledger.besu.evm.tracing.OperationTracer;

/**
* Implementation of the {@link ContractService}.
*/
public class ContractServiceImpl implements ContractService {

private static final Logger log = LogManager.getLogger(ContractServiceImpl.class);

/**
* Minimum gas required for contract operations.
*/
Expand Down Expand Up @@ -66,7 +78,10 @@ public ContractServiceImpl(
final var metricsSupplier = requireNonNull(appContext.metricsSupplier());
final Supplier<ContractsConfig> contractsConfigSupplier =
() -> appContext.configSupplier().get().getConfigData(ContractsConfig.class);
final var contractMetrics = new ContractMetrics(metricsSupplier, contractsConfigSupplier);
final var systemContractMethodRegistry = new SystemContractMethodRegistry();
final var contractMetrics =
new ContractMetrics(metricsSupplier, contractsConfigSupplier, systemContractMethodRegistry);

this.component = DaggerContractServiceComponent.factory()
.create(
appContext.instantSource(),
Expand All @@ -75,7 +90,8 @@ public ContractServiceImpl(
appContext.signatureVerifier(),
Optional.ofNullable(verificationStrategies).orElseGet(DefaultVerificationStrategies::new),
addOnTracers,
contractMetrics);
contractMetrics,
systemContractMethodRegistry);
}

@Override
Expand All @@ -84,12 +100,19 @@ public void registerSchemas(@NonNull final SchemaRegistry registry) {
registry.register(new V0500ContractSchema());
}

/**
* Create the metrics for the smart contracts service. This needs to be delayed until _after_
* the metrics are available - which happens after `Hedera.initializeStatesApi`.
*/
public void registerMetrics() {
component.contractMetrics().createContractMetrics();
@Override
public void onStateInitializedForGenesis() {
// Force call translators to be instantiated now, so that all the system contract methods
// will be registered, so the secondary metrics can be created. (Left to its own devices
// Dagger would delay instantiating them until transactions started flowing.)
final var allTranslators = allCallTranslators();

// TESTING
final var msg = "Known call translators:\n" + allTranslatorNames(allTranslators);
// END TESTING

component.contractMetrics().createContractPrimaryMetrics();
component.contractMetrics().createContractSecondaryMetrics();
}

/**
Expand All @@ -98,4 +121,35 @@ public void registerMetrics() {
public ContractHandlers handlers() {
return component.handlers();
}

private @NonNull List<CallTranslator<? extends AbstractCallAttempt<?>>> allCallTranslators() {
final var allCallTranslators = new ArrayList<CallTranslator<? extends AbstractCallAttempt<?>>>();
allCallTranslators.addAll(component.hasCallTranslators().get());
allCallTranslators.addAll(component.hssCallTranslators().get());
allCallTranslators.addAll(component.htsCallTranslators().get());
return allCallTranslators;
}

// -----------------
// For testing only:

private @NonNull String allTranslatorNames(
@NonNull List<CallTranslator<? extends AbstractCallAttempt<?>>> translators) {
return translators.stream().map(this::translatorName).sorted().collect(Collectors.joining("\n"));
}

private @NonNull String translatorName(@NonNull final CallTranslator<? extends AbstractCallAttempt<?>> translator) {
final var simpleName = translator.getClass().getSimpleName();
final var isSingleton = isSingleton(translator.getClass());
final var contractName = translator instanceof AbstractCallTranslator<?> act ? act.kind() : "<UNKNOWN-KIND>";

var name = contractName + "." + simpleName;
if (!isSingleton) name += "(NOT-SINGLETON)";

return name;
}

private boolean isSingleton(Class<?> klass) {
return klass.getDeclaredAnnotation(Singleton.class) != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import com.google.common.annotations.VisibleForTesting;
import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethodRegistry;
import com.hedera.node.config.data.ContractsConfig;
import com.swirlds.metrics.api.Counter;
import com.swirlds.metrics.api.Metric;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class ContractMetrics {
private final Supplier<ContractsConfig> contractsConfigSupplier;
private boolean p1MetricsEnabled;
private boolean p2MetricsEnabled;
private final SystemContractMethodRegistry systemContractMethodRegistry;

private final HashMap<HederaFunctionality, Counter> rejectedTxsCounters = new HashMap<>();
private final HashMap<HederaFunctionality, Counter> rejectedTxsLackingIntrinsicGas = new HashMap<>();
Expand Down Expand Up @@ -81,22 +83,31 @@ public class ContractMetrics {
@Inject
public ContractMetrics(
@NonNull final Supplier<Metrics> metricsSupplier,
@NonNull final Supplier<ContractsConfig> contractsConfigSupplier) {
@NonNull final Supplier<ContractsConfig> contractsConfigSupplier,
@NonNull final SystemContractMethodRegistry systemContractMethodRegistry) {
this.metricsSupplier = requireNonNull(
metricsSupplier, "metrics supplier (from platform via ServicesMain/Hedera must not be null");
this.contractsConfigSupplier =
requireNonNull(contractsConfigSupplier, "contracts configuration supplier must not be null");
this.systemContractMethodRegistry =
requireNonNull(systemContractMethodRegistry, "systemContractMethodRegistry must not be null");
}

public void createContractMetrics() {
// --------------------
// Creating the metrics

/**
* Primary metrics are a fixed set and can be created when `Hedera` initializes the system. But
* it actually must wait until the platform calls `Hedera.onStateInitialized`, and then for
* GENESIS only.
*/
public void createContractPrimaryMetrics() {
final var contractsConfig = requireNonNull(contractsConfigSupplier.get());
this.p1MetricsEnabled = contractsConfig.metricsSmartContractPrimaryEnabled();
this.p2MetricsEnabled = contractsConfig.metricsSmartContractSecondaryEnabled();

final var metrics = requireNonNull(metricsSupplier.get());

if (p1MetricsEnabled) {
final var metrics = requireNonNull(metricsSupplier.get());

// Rejected transactions counters
for (final var txKind : POSSIBLE_FAILING_TX_TYPES.keySet()) {
final var name = toRejectedName(txKind, REJECTED_TXN_SHORT_DESCR);
Expand Down Expand Up @@ -131,12 +142,33 @@ public void createContractMetrics() {
rejectedEthType3Counter = metric;
}
}
}

public void createContractSecondaryMetrics() {

if (systemContractMethodRegistry.size() == 0) {
// Something went wrong with the order in which components were initialized
log.warn("no system contract methods registered when trying to create secondary metrics");

Check warning on line 151 in hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/metrics/ContractMetrics.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/metrics/ContractMetrics.java#L151

Added line #L151 was not covered by tests
}

// TESTING
final var msg = systemContractMethodRegistry.allQualifiedMethods().stream()
.sorted()
.collect(Collectors.joining("\n"));
// END TESTING

final var contractsConfig = requireNonNull(contractsConfigSupplier.get());
this.p2MetricsEnabled = contractsConfig.metricsSmartContractSecondaryEnabled();

if (p2MetricsEnabled) {
// PLACEHOLDER
final var metrics = requireNonNull(metricsSupplier.get());
// TBD
}
}

// ---------------------------------
// P1 metrics: `pureCheck` failures

public void incrementRejectedTx(@NonNull final HederaFunctionality txKind) {
bumpRejectedTx(txKind, 1);
}
Expand All @@ -162,6 +194,12 @@ public void bumpRejectedType3EthTx(final long bumpBy) {
if (p1MetricsEnabled) rejectedEthType3Counter.add(bumpBy);
}

// ---------------------------------------------
// P2 metrics: System contract per-method counts

// -----------------
// Unit test helpers

@VisibleForTesting
public @NonNull Map<String, Long> getAllCounters() {
return Stream.concat(
Expand Down Expand Up @@ -204,6 +242,9 @@ public void bumpRejectedType3EthTx(final long bumpBy) {
.collect(Collectors.joining(", ", "{", "}"));
}

// ---------------------------------
// Helpers for making metrics' names

private @NonNull Counter newCounter(@NonNull final Metrics metrics, @NonNull final Counter.Config config) {
return metrics.getOrCreate(config);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies;
import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy;
import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter;
import com.hedera.node.app.service.contract.impl.exec.utils.SystemContractMethodRegistry;
import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater;
import com.swirlds.config.api.Configuration;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand Down Expand Up @@ -54,6 +55,7 @@ public abstract class AbstractCallAttempt<T extends AbstractCallAttempt<T>> {
private final VerificationStrategies verificationStrategies;
private final SystemContractGasCalculator gasCalculator;
private final List<CallTranslator<T>> callTranslators;
private final SystemContractMethodRegistry systemContractMethodRegistry;
private final boolean isStaticCall;

// If non-null, the address of a non-contract entity (e.g., account or token) whose
Expand Down Expand Up @@ -89,6 +91,7 @@ public AbstractCallAttempt(
@NonNull final SystemContractGasCalculator gasCalculator,
@NonNull final List<CallTranslator<T>> callTranslators,
final boolean isStaticCall,
@NonNull final SystemContractMethodRegistry systemContractMethodRegistry,
@NonNull final com.esaulpaugh.headlong.abi.Function redirectFunction) {
requireNonNull(input);
requireNonNull(redirectFunction);
Expand All @@ -101,6 +104,7 @@ public AbstractCallAttempt(
this.enhancement = requireNonNull(enhancement);
this.verificationStrategies = requireNonNull(verificationStrategies);
this.onlyDelegatableContractKeysActive = onlyDelegatableContractKeysActive;
this.systemContractMethodRegistry = requireNonNull(systemContractMethodRegistry);

if (isRedirectSelector(redirectFunction.selector(), input.toArrayUnsafe())) {
Tuple abiCall = null;
Expand All @@ -127,6 +131,8 @@ public AbstractCallAttempt(
this.isStaticCall = isStaticCall;
}

protected abstract SystemContractMethodRegistry.SystemContract systemContractKind();

protected abstract T self();

/**
Expand Down Expand Up @@ -263,6 +269,26 @@ public boolean isRedirect() {
return redirectAddress != null;
}

public Function asSolidityFunction(@NonNull final String signature, @NonNull final String outputs) {
requireNonNull(signature);
requireNonNull(outputs);

Check warning on line 274 in hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java#L273-L274

Added lines #L273 - L274 were not covered by tests

final var function = Function.parse(signature, outputs);
systemContractMethodRegistry.register(function, systemContractKind());
return function;

Check warning on line 278 in hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java#L276-L278

Added lines #L276 - L278 were not covered by tests
}

public Function asSolidityFunction(
@NonNull final String methodName, @NonNull final String signature, @NonNull final String outputs) {
requireNonNull(methodName);
requireNonNull(signature);
requireNonNull(outputs);

Check warning on line 285 in hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java#L283-L285

Added lines #L283 - L285 were not covered by tests

final var function = Function.parse(signature, outputs);
systemContractMethodRegistry.register(function, systemContractKind(), methodName);
return function;

Check warning on line 289 in hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java#L287-L289

Added lines #L287 - L289 were not covered by tests
}

/**
* Returns whether this call attempt is a selector for any of the given functions.
* @param functions selectors to match against
Expand Down
Loading

0 comments on commit fcac14d

Please sign in to comment.