Skip to content

Commit

Permalink
Speedup modexp (#4780)
Browse files Browse the repository at this point in the history
Increate the speed of ModExp gas calculations by using primitive types.
Use a native lib for modexp precompile.

Signed-off-by: Danno Ferrin <danno.ferrin@swirldslabs.com>

* Native modexp
  • Loading branch information
shemnon authored Dec 7, 2022
1 parent 4120501 commit 89ce75a
Show file tree
Hide file tree
Showing 16 changed files with 338 additions and 170 deletions.
12 changes: 7 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Changelog
## 22.10.3

### Additions and Improvements
- Implement Eth/68 sub-protocol [#4715](https://github.com/hyperledger/besu/issues/4715)

### Breaking Changes
- Added `--rpc-max-logs-range` CLI option to allow limiting the number of blocks queried by `eth_getLogs` RPC API. Default value: 1000 [#4597](https://github.com/hyperledger/besu/pull/4597)
- The `graalvm` docker variant no longer meets the performance requirements for Ethereum Mainnet. The `openjdk-11` and `openjdk-latest` variants are recommended in its place.

### Additions and Improvements
- Implement Eth/68 sub-protocol [#4715](https://github.com/hyperledger/besu/issues/4715)
- Increase the speed of modexp gas execution and execution. [#4780](https://github.com/hyperledger/besu/pull/4780)

### Bug Fixes

Expand All @@ -18,8 +20,8 @@ This is a hotfix release to resolve a race condition that results in segfaults,
- bugfix for async operations on Snashot worldstates [#4767](https://github.com/hyperledger/besu/pull/4767)

### Download Links
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.tar.gz / sha256: TBA
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.zip / sha256: TBA
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.tar.gz / sha256: cdb36141e3cba6379d35016e0a2de2edba579d4786124b5f7257b1e4a68867a2
https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.10.2/besu-22.10.2.zip / sha256: 4c9208f684762670cb4f2c6ebfb6930e05e339a7c3c586fe8caa9f26462830aa


## 22.10.1
Expand Down
9 changes: 9 additions & 0 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.evm.precompile.AbstractAltBnPrecompiledContract;
import org.hyperledger.besu.evm.precompile.BigIntegerModularExponentiationPrecompiledContract;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.metrics.MetricCategoryRegistryImpl;
import org.hyperledger.besu.metrics.MetricsProtocol;
Expand Down Expand Up @@ -1740,6 +1741,14 @@ private void configureNativeLibs() {
logger.info("Using the Java implementation of alt bn128");
}

if (unstableNativeLibraryOptions.getNativeModExp()
&& BigIntegerModularExponentiationPrecompiledContract.isNative()) {
logger.info("Using the native implementation of modexp");
} else {
BigIntegerModularExponentiationPrecompiledContract.disableNative();
logger.info("Using the Java implementation of modexp");
}

if (unstableNativeLibraryOptions.getNativeSecp()
&& SignatureAlgorithmFactory.getInstance().isNative()) {
logger.info("Using the native implementation of the signature algorithm");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ public class NativeLibraryOptions {
arity = "1")
private final Boolean nativeBlake2bf = Boolean.TRUE;

@CommandLine.Option(
hidden = true,
names = {"--Xmodexp-native-enabled"},
description =
"Per default a native library is used for modexp. "
+ "If the Java implementation should be used instead, this option must be set to false",
arity = "1")
private final Boolean nativeModExp = Boolean.TRUE;

public static NativeLibraryOptions create() {
return new NativeLibraryOptions();
}
Expand All @@ -60,4 +69,8 @@ public Boolean getNativeAltbn128() {
public Boolean getNativeBlake2bf() {
return nativeBlake2bf;
}

public Boolean getNativeModExp() {
return nativeModExp;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,16 @@ public static class Blake2bfDigest implements Digest {
private long rounds; // unsigned integer represented as long

private final long[] v;
private static boolean useNative = LibBlake2bf.ENABLED;
private static boolean useNative;

static {
try {
useNative = LibBlake2bf.ENABLED;
} catch (UnsatisfiedLinkError ule) {
LOG.info("blake2bf native precompile not available: {}", ule.getMessage());
useNative = false;
}
}

Blake2bfDigest() {
if (!useNative) {
Expand Down
13 changes: 12 additions & 1 deletion crypto/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.hyperledger.besu.nativelib.secp256r1.LibSECP256R1;
import org.hyperledger.besu.nativelib.secp256r1.Signature;
import org.hyperledger.besu.nativelib.secp256r1.besuNativeEC.BesuNativeEC;

import java.math.BigInteger;
import java.util.Optional;
Expand All @@ -25,15 +26,25 @@
import org.bouncycastle.crypto.signers.DSAKCalculator;
import org.bouncycastle.crypto.signers.RandomDSAKCalculator;
import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SECP256R1 extends AbstractSECP256 {

private static final Logger LOG = LoggerFactory.getLogger(SECP256R1.class);

public static final String CURVE_NAME = "secp256r1";
private boolean useNative = true;
private boolean useNative;
private final LibSECP256R1 libSECP256R1 = new LibSECP256R1();

public SECP256R1() {
super(CURVE_NAME, SecP256R1Curve.q);
try {
useNative = BesuNativeEC.INSTANCE != null;
} catch (UnsatisfiedLinkError ule) {
LOG.info("secp256r1 native precompile not available: {}", ule.getMessage());
useNative = false;
}
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion docker/graalvm/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

FROM ghcr.io/graalvm/graalvm-ce:ol7-java11
FROM ghcr.io/graalvm/graalvm-ce:ol8-java11
ARG VERSION="dev"

RUN adduser --home /opt/besu besu && \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public StateTestVersionedTransaction(
@JsonDeserialize(using = StateTestAccessListDeserializer.class) @JsonProperty("accessLists")
final List<List<AccessListEntry>> maybeAccessLists) {

this.nonce = Long.decode(nonce);
this.nonce = Bytes.fromHexStringLenient(nonce).toLong();
this.gasPrice = Optional.ofNullable(gasPrice).map(Wei::fromHexString).orElse(null);
this.maxFeePerGas = Optional.ofNullable(maxFeePerGas).map(Wei::fromHexString).orElse(null);
this.maxPriorityFeePerGas =
Expand Down
1 change: 1 addition & 0 deletions evm/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies {
compileOnly 'com.fasterxml.jackson.core:jackson-databind'

implementation 'org.apache.tuweni:tuweni-bytes'
implementation 'org.hyperledger.besu:arithmetic'
implementation 'org.hyperledger.besu:bls12-381'
implementation 'net.java.dev.jna:jna'
implementation 'com.github.ben-manes.caffeine:caffeine'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@

import static org.hyperledger.besu.datatypes.Address.BLAKE2B_F_COMPRESSION;
import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import static org.hyperledger.besu.evm.internal.Words.clampedMultiply;
import static org.hyperledger.besu.evm.internal.Words.clampedToInt;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.account.Account;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.precompile.BigIntegerModularExponentiationPrecompiledContract;

import java.math.BigInteger;
Expand Down Expand Up @@ -218,38 +221,29 @@ public long calculateStorageRefundAmount(

@Override
public long modExpGasCost(final Bytes input) {
final BigInteger baseLength =
BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final BigInteger exponentLength =
final long baseLength = BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final long exponentLength =
BigIntegerModularExponentiationPrecompiledContract.exponentLength(input);
final BigInteger modulusLength =
final long modulusLength =
BigIntegerModularExponentiationPrecompiledContract.modulusLength(input);
final BigInteger exponentOffset =
BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET.add(baseLength);
final int firstExponentBytesCap =
exponentLength.min(ByzantiumGasCalculator.MAX_FIRST_EXPONENT_BYTES).intValue();
final long exponentOffset =
clampedAdd(BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET, baseLength);
final long firstExponentBytesCap =
Math.min(exponentLength, ByzantiumGasCalculator.MAX_FIRST_EXPONENT_BYTES);
final BigInteger firstExpBytes =
BigIntegerModularExponentiationPrecompiledContract.extractParameter(
input, exponentOffset, firstExponentBytesCap);
final BigInteger adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
final BigInteger multiplicationComplexity =
modulusLength
.max(baseLength)
.add(BigInteger.valueOf(7))
.divide(BigInteger.valueOf(8))
.pow(2);

final BigInteger gasRequirement =
multiplicationComplexity
.multiply(adjustedExponentLength.max(BigInteger.ONE))
.divide(BigInteger.valueOf(3));

// Gas price is so large it will not fit in a Gas type, so a
// very very very unlikely high gas price is used instead.
if (gasRequirement.bitLength() > ByzantiumGasCalculator.MAX_GAS_BITS) {
return Long.MAX_VALUE;
} else {
return Math.max(gasRequirement.longValueExact(), 200L);
input, clampedToInt(exponentOffset), clampedToInt(firstExponentBytesCap));
final long adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
long multiplicationComplexity = (Math.max(modulusLength, baseLength) + 7L) / 8L;
multiplicationComplexity =
Words.clampedMultiply(multiplicationComplexity, multiplicationComplexity);

long gasRequirement =
clampedMultiply(multiplicationComplexity, Math.max(adjustedExponentLength, 1L));
if (gasRequirement != Long.MAX_VALUE) {
gasRequirement /= 3;
}

return Math.max(gasRequirement, 200L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,65 +15,55 @@
*/
package org.hyperledger.besu.evm.gascalculator;

import static org.hyperledger.besu.evm.internal.Words.clampedAdd;
import static org.hyperledger.besu.evm.internal.Words.clampedMultiply;
import static org.hyperledger.besu.evm.internal.Words.clampedToInt;

import org.hyperledger.besu.evm.precompile.BigIntegerModularExponentiationPrecompiledContract;

import java.math.BigInteger;

import org.apache.tuweni.bytes.Bytes;

public class ByzantiumGasCalculator extends SpuriousDragonGasCalculator {
private static final BigInteger GQUADDIVISOR = BigInteger.valueOf(20);
private static final BigInteger WORD_SIZE = BigInteger.valueOf(32);
private static final BigInteger BITS_IN_BYTE = BigInteger.valueOf(8);
private static final int GQUADDIVISOR = 20;
private static final int WORD_SIZE = 32;
private static final int BITS_IN_BYTE = 8;

public static final BigInteger MAX_FIRST_EXPONENT_BYTES = BigInteger.valueOf(32);
public static final int MAX_GAS_BITS = 63;
public static final int MAX_FIRST_EXPONENT_BYTES = 32;

@Override
public long modExpGasCost(final Bytes input) {
final BigInteger baseLength =
BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final BigInteger exponentLength =
final long baseLength = BigIntegerModularExponentiationPrecompiledContract.baseLength(input);
final long exponentLength =
BigIntegerModularExponentiationPrecompiledContract.exponentLength(input);
final BigInteger modulusLength =
final long modulusLength =
BigIntegerModularExponentiationPrecompiledContract.modulusLength(input);
final BigInteger exponentOffset =
BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET.add(baseLength);
final int firstExponentBytesCap = exponentLength.min(MAX_FIRST_EXPONENT_BYTES).intValue();
final long exponentOffset =
clampedAdd(BigIntegerModularExponentiationPrecompiledContract.BASE_OFFSET, baseLength);
final long firstExponentBytesCap = Math.min(exponentLength, MAX_FIRST_EXPONENT_BYTES);
final BigInteger firstExpBytes =
BigIntegerModularExponentiationPrecompiledContract.extractParameter(
input, exponentOffset, firstExponentBytesCap);
final BigInteger adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
final BigInteger multiplicationComplexity =
input, clampedToInt(exponentOffset), clampedToInt(firstExponentBytesCap));
final long adjustedExponentLength = adjustedExponentLength(exponentLength, firstExpBytes);
final long multiplicationComplexity =
BigIntegerModularExponentiationPrecompiledContract.multiplicationComplexity(
baseLength.max(modulusLength));
final BigInteger gasRequirement =
multiplicationComplexity
.multiply(adjustedExponentLength.max(BigInteger.ONE))
.divide(GQUADDIVISOR);

// Gas price is so large it will not fit in a Gas type, so a
// very very very unlikely high gas price is used instead.
if (gasRequirement.bitLength() > MAX_GAS_BITS) {
return Long.MAX_VALUE;
} else {
return gasRequirement.longValueExact();
}
Math.max(baseLength, modulusLength));
long numerator = clampedMultiply(multiplicationComplexity, Math.max(adjustedExponentLength, 1));
return (numerator == Long.MAX_VALUE) ? Long.MAX_VALUE : numerator / GQUADDIVISOR;
}

public static BigInteger adjustedExponentLength(
final BigInteger exponentLength, final BigInteger firstExpBytes) {
final BigInteger bitLength = bitLength(firstExpBytes);
if (exponentLength.compareTo(WORD_SIZE) <= 0) {
public static long adjustedExponentLength(
final long exponentLength, final BigInteger firstExpBytes) {
final int bitLength = bitLength(firstExpBytes);
if (exponentLength < WORD_SIZE) {
return bitLength;
} else {
return BITS_IN_BYTE.multiply(exponentLength.subtract(WORD_SIZE)).add(bitLength);
return clampedAdd(clampedMultiply(BITS_IN_BYTE, (exponentLength - WORD_SIZE)), bitLength);
}
}

private static BigInteger bitLength(final BigInteger n) {
return n.compareTo(BigInteger.ZERO) == 0
? BigInteger.ZERO
: BigInteger.valueOf(n.bitLength() - 1L);
private static int bitLength(final BigInteger n) {
return n.compareTo(BigInteger.ZERO) == 0 ? 0 : (n.bitLength() - 1);
}
}
Loading

0 comments on commit 89ce75a

Please sign in to comment.