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

com.hedera.node.app.service.util.impl.handlers.UtilPrngHandler Maven / Gradle / Ivy

There is a newer version: 0.57.0
Show 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.util.impl.handlers;

import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.base.SubType;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.service.util.impl.records.PrngRecordBuilder;
import com.hedera.node.app.spi.fees.FeeContext;
import com.hedera.node.app.spi.fees.Fees;
import com.hedera.node.app.spi.workflows.HandleContext;
import com.hedera.node.app.spi.workflows.PreCheckException;
import com.hedera.node.app.spi.workflows.PreHandleContext;
import com.hedera.node.app.spi.workflows.TransactionHandler;
import com.hedera.node.config.data.UtilPrngConfig;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.nio.ByteBuffer;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * A {@link TransactionHandler} for handling {@link HederaFunctionality#UTIL_PRNG} transactions.
 *
 * 

This transaction uses the n-3 running hash to generate a pseudo-random number. The n-3 running hash is updated * and maintained by the application, based on the record files generated based on preceding transactions. In this way, * the number is both essentially unpredictable and deterministic. While a given node *could* determine the n-3 running * hash that *will be*, after it has run the hashgraph, it will then not be able to craft and insert a transaction that * can take advantage of that information. So for all intents and purposes, the number is unpredictable. */ @Singleton public class UtilPrngHandler implements TransactionHandler { private static final Logger log = LogManager.getLogger(UtilPrngHandler.class); private static final Bytes MISSING_N_MINUS_3_RUNNING_HASH = Bytes.wrap(new byte[48]); @Inject public UtilPrngHandler() { // Dagger2 } /** * {@inheritDoc} */ @Override public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException { // nothing to do } @Override public void pureChecks(@NonNull final TransactionBody txn) throws PreCheckException { // Negative ranges are not allowed if (txn.utilPrngOrThrow().range() < 0) { throw new PreCheckException(ResponseCodeEnum.INVALID_PRNG_RANGE); } } /** * {@inheritDoc} */ @Override @NonNull public Fees calculateFees(@NonNull FeeContext feeContext) { // Determine the fees. If the range is specified (i.e. it isn't 0), then we charge for an additional 4 bytes // (one integer), otherwise we don't charge for any additional bytes. Standard transaction usage has already // been determined and loaded into the calculator. final var range = feeContext.body().utilPrngOrThrow().range(); return feeContext .feeCalculator(SubType.DEFAULT) .addBytesPerTransaction(range > 0 ? Integer.BYTES : 0) .calculate(); } /** * {@inheritDoc} */ @Override public void handle(@NonNull final HandleContext context) { if (!context.configuration().getConfigData(UtilPrngConfig.class).isEnabled()) { // (FUTURE) Should this throw NOT_SUPPORTED instead? As written is the legacy behavior. return; } final var op = context.body().utilPrngOrThrow(); final var range = op.range(); // Get the n-3 running hash. It should never be possible to get an empty n-3 hash, and certainly not in // mainnet. The only way to get one is to handle a transaction immediately after genesis. Even then it is // probably not possible, and if we really wanted to defend against that, we could deterministically // pre-populate the initial running hashes. Or we can return all zeros. Either way is just as safe, so we'll // just return whatever it is we are given. If we *do* happen to get back null, treat as empty zeros. var pseudoRandomBytes = context.blockRecordInfo().getNMinus3RunningHash(); if (pseudoRandomBytes == null || pseudoRandomBytes.length() == 0) { log.info("No n-3 record running hash available. Will use all zeros."); pseudoRandomBytes = MISSING_N_MINUS_3_RUNNING_HASH; } // If `range` is provided then generate a random number in the given range from the pseudoRandomBytes, // otherwise just use the full pseudoRandomBytes as the random number. final var recordBuilder = context.recordBuilder(PrngRecordBuilder.class); if (range > 0) { final var pseudoRandomNumber = randomNumFromBytes(pseudoRandomBytes, range); recordBuilder.entropyNumber(pseudoRandomNumber); } else { recordBuilder.entropyBytes(pseudoRandomBytes); } } /** * Generate a random number from the given bytes in the given range * @param pseudoRandomBytes bytes to generate random number from * @param range range of the random number * @return random number */ private int randomNumFromBytes(@NonNull final Bytes pseudoRandomBytes, final int range) { // Use the initial 4 bytes of the random number to extract the integer value. This might be a negative number, // which when used with the modulus operator will result in a negative number. To avoid this, we mask off the // high bit to make sure we always have a positive number for the mod operator. final var initialBitsValue = ByteBuffer.wrap(pseudoRandomBytes.toByteArray(), 0, 4).getInt(); return (int) mod(initialBitsValue, range); } /** * Returns {@code x mod m}, a non-negative value less than {@code m}. This differs from {@code x % * m}, which might be negative. * * @throws ArithmeticException if {@code m <= 0} */ public static long mod(long x, int m) { if (m <= 0) { throw new ArithmeticException("Modulus must be positive"); } long result = x % m; return (result >= 0) ? result : result + m; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy