From 89ce75a614e1d7c7959f4a6dd9406ea9e5c8b88d Mon Sep 17 00:00:00 2001 From: Danno Ferrin Date: Wed, 7 Dec 2022 14:23:34 -0700 Subject: [PATCH] Speedup modexp (#4780) Increate the speed of ModExp gas calculations by using primitive types. Use a native lib for modexp precompile. Signed-off-by: Danno Ferrin * Native modexp --- CHANGELOG.md | 12 +- .../org/hyperledger/besu/cli/BesuCommand.java | 9 ++ .../unstable/NativeLibraryOptions.java | 13 ++ .../besu/crypto/Blake2bfMessageDigest.java | 11 +- .../hyperledger/besu/crypto/SECP256R1.java | 13 +- docker/graalvm/Dockerfile | 2 +- .../StateTestVersionedTransaction.java | 2 +- evm/build.gradle | 1 + .../gascalculator/BerlinGasCalculator.java | 50 +++---- .../gascalculator/ByzantiumGasCalculator.java | 64 ++++---- .../hyperledger/besu/evm/internal/Words.java | 62 +++++--- .../AbstractAltBnPrecompiledContract.java | 11 +- ...ularExponentiationPrecompiledContract.java | 137 +++++++++++++----- .../MODEXPPrecompiledContractTest.java | 38 ++++- gradle/verification-metadata.xml | 72 +++++---- gradle/versions.gradle | 11 +- 16 files changed, 338 insertions(+), 170 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 578b5914a7..33090be15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 8d0113cf2c..3f79bc4279 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -146,6 +146,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; 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; @@ -1740,6 +1741,14 @@ public class BesuCommand implements DefaultCommandValues, Runnable { 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"); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NativeLibraryOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NativeLibraryOptions.java index 776a582ca8..8b10e50d10 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NativeLibraryOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/NativeLibraryOptions.java @@ -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(); } @@ -60,4 +69,8 @@ public class NativeLibraryOptions { public Boolean getNativeBlake2bf() { return nativeBlake2bf; } + + public Boolean getNativeModExp() { + return nativeModExp; + } } diff --git a/crypto/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java b/crypto/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java index 0090169620..b26ad58bad 100644 --- a/crypto/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java +++ b/crypto/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java @@ -80,7 +80,16 @@ public class Blake2bfMessageDigest extends BCMessageDigest implements Cloneable 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) { diff --git a/crypto/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java b/crypto/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java index 39e744e33a..7157c5b4a9 100644 --- a/crypto/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java +++ b/crypto/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.crypto; 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; @@ -25,15 +26,25 @@ import org.apache.tuweni.bytes.Bytes32; 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 diff --git a/docker/graalvm/Dockerfile b/docker/graalvm/Dockerfile index f0cbf7dde0..0a660876f6 100644 --- a/docker/graalvm/Dockerfile +++ b/docker/graalvm/Dockerfile @@ -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 && \ diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java index 0c02bbba7c..69ad926753 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java @@ -100,7 +100,7 @@ public class StateTestVersionedTransaction { @JsonDeserialize(using = StateTestAccessListDeserializer.class) @JsonProperty("accessLists") final List> 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 = diff --git a/evm/build.gradle b/evm/build.gradle index c11cba16c5..99f676ca1e 100644 --- a/evm/build.gradle +++ b/evm/build.gradle @@ -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' diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java index 9ef385ce66..3d208756bd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java @@ -16,11 +16,14 @@ package org.hyperledger.besu.evm.gascalculator; 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; @@ -218,38 +221,29 @@ public class BerlinGasCalculator extends IstanbulGasCalculator { @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); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java index 06f56c2de3..0aa997f3f0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java @@ -15,6 +15,10 @@ */ 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; @@ -22,58 +26,44 @@ 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); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java index f161ab9d74..fa9a066380 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/Words.java @@ -22,9 +22,7 @@ import org.apache.tuweni.bytes.MutableBytes; import org.apache.tuweni.units.bigints.UInt256; /** Static utility methods to work with VM words (that is, {@link Bytes32} values). */ -public abstract class Words { - private Words() {} - +public interface Words { /** * Creates a new word containing the provided address. * @@ -32,7 +30,7 @@ public abstract class Words { * @return A VM word containing {@code address} (left-padded as according to the VM specification * (Appendix H. of the Yellow paper)). */ - public static UInt256 fromAddress(final Address address) { + static UInt256 fromAddress(final Address address) { return UInt256.fromBytes(Bytes32.leftPad(address)); } @@ -43,18 +41,7 @@ public abstract class Words { * @return An address build from the right-most 160-bits of the {@code bytes} (as according to the * VM specification (Appendix H. of the Yellow paper)). */ - public static Address toAddress(final Bytes32 bytes) { - return Address.wrap(bytes.slice(bytes.size() - Address.SIZE, Address.SIZE)); - } - - /** - * Extract an address from the provided address. - * - * @param bytes The word to extract the address from. - * @return An address build from the right-most 160-bits of the {@code bytes} (as according to the - * VM specification (Appendix H. of the Yellow paper)). - */ - public static Address toAddress(final Bytes bytes) { + static Address toAddress(final Bytes bytes) { final int size = bytes.size(); if (size < 20) { final MutableBytes result = MutableBytes.create(20); @@ -77,7 +64,7 @@ public abstract class Words { * @param input the input to check. * @return the number of (32 bytes) words that {@code input} spans. */ - public static int numWords(final Bytes input) { + static int numWords(final Bytes input) { // m/n round up == (m + n - 1)/n: http://www.cs.nott.ac.uk/~psarb2/G51MPC/slides/NumberLogic.pdf return (input.size() + Bytes32.SIZE - 1) / Bytes32.SIZE; } @@ -89,7 +76,7 @@ public abstract class Words { * @param uint the unsigned integer * @return the least of the integer value or Long.MAX_VALUE */ - public static long clampedToLong(final Bytes uint) { + static long clampedToLong(final Bytes uint) { if (uint.size() <= 8) { final long result = uint.toLong(); return result < 0 ? Long.MAX_VALUE : result; @@ -105,6 +92,23 @@ public abstract class Words { } } + /** + * The value of the long as though it was representing an unsigned integer, however if the value + * is out of range it will return the number at the end of the range. + * + * @param l the signed integer + * @return The int value, or Integer.MAX_VALUE if too large or Integer.MIN_VALUE if to small. + */ + static int clampedToInt(final long l) { + if (l > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else if (l < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } else { + return (int) l; + } + } + /** * Adds a and b, but if an underflow/overflow occurs return the Long max/min value * @@ -112,7 +116,7 @@ public abstract class Words { * @param b second value * @return value of a plus b if no over/underflows or Long.MAX_VALUE/Long.MIN_VALUE otherwise */ - public static long clampedAdd(final long a, final long b) { + static long clampedAdd(final long a, final long b) { try { return Math.addExact(a, b); } catch (final ArithmeticException ae) { @@ -127,7 +131,7 @@ public abstract class Words { * @param b second value * @return value of a times b if no over/underflows or Long.MAX_VALUE/Long.MIN_VALUE otherwise */ - public static long clampedMultiply(final long a, final long b) { + static long clampedMultiply(final long a, final long b) { try { return Math.multiplyExact(a, b); } catch (final ArithmeticException ae) { @@ -135,6 +139,22 @@ public abstract class Words { } } + /** + * Multiplies a and b, but if an underflow/overflow occurs return the Integer max/min value + * + * @param a first value + * @param b second value + * @return value of a times b if no over/underflows or Integer.MAX_VALUE/Integer.MIN_VALUE + * otherwise + */ + static int clampedMultiply(final int a, final int b) { + try { + return Math.multiplyExact(a, b); + } catch (final ArithmeticException ae) { + return ((a ^ b) < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE; + } + } + /** * Returns the lesser of the two values, when compared as an unsigned value * @@ -142,7 +162,7 @@ public abstract class Words { * @param b second value * @return a if, as an unsigned integer, a is less than b; otherwise b. */ - public static long unsignedMin(final long a, final long b) { + static long unsignedMin(final long a, final long b) { return Long.compareUnsigned(a, b) < 0 ? a : b; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java index 79c38cf0df..55d3a68f7b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java @@ -35,7 +35,16 @@ public abstract class AbstractAltBnPrecompiledContract extends AbstractPrecompil private static final Logger LOG = LoggerFactory.getLogger(AbstractAltBnPrecompiledContract.class); // use the native library implementation, if it is available - static boolean useNative = LibEthPairings.ENABLED; + static boolean useNative; + + static { + try { + useNative = LibEthPairings.ENABLED; + } catch (UnsatisfiedLinkError ule) { + LOG.info("altbn128 native precompile not available: {}", ule.getMessage()); + useNative = false; + } + } public static void disableNative() { useNative = false; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BigIntegerModularExponentiationPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BigIntegerModularExponentiationPrecompiledContract.java index 548bfc6b96..b97e11741f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BigIntegerModularExponentiationPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BigIntegerModularExponentiationPrecompiledContract.java @@ -14,39 +14,62 @@ */ package org.hyperledger.besu.evm.precompile; +import static org.hyperledger.besu.evm.internal.Words.clampedMultiply; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.nativelib.arithmetic.LibArithmetic; import java.math.BigInteger; import java.util.Arrays; +import java.util.Optional; import javax.annotation.Nonnull; +import com.sun.jna.ptr.IntByReference; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.MutableBytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; // The big integer modular exponentiation precompiled contract defined in EIP-198. public class BigIntegerModularExponentiationPrecompiledContract extends AbstractPrecompiledContract { - public static final BigInteger BASE_OFFSET = BigInteger.valueOf(96); + private static final Logger LOG = + LoggerFactory.getLogger(BigIntegerModularExponentiationPrecompiledContract.class); + + static boolean useNative; + + static { + try { + useNative = LibArithmetic.ENABLED; + } catch (UnsatisfiedLinkError ule) { + LOG.info("modexp native precompile not available: {}", ule.getMessage()); + useNative = false; + } + } + + public static final int BASE_OFFSET = 96; private static final int PARAMETER_LENGTH = 32; private static final int BASE_LENGTH_OFFSET = 0; private static final int EXPONENT_LENGTH_OFFSET = 32; private static final int MODULUS_LENGTH_OFFSET = 64; - private static final BigInteger BIGINT_4 = BigInteger.valueOf(4); - private static final BigInteger BIGINT_16 = BigInteger.valueOf(16); - private static final BigInteger BIGINT_64 = BigInteger.valueOf(64); - private static final BigInteger BIGINT_96 = BigInteger.valueOf(96); - private static final BigInteger BIGINT_480 = BigInteger.valueOf(480); - private static final BigInteger BIGINT_1024 = BigInteger.valueOf(1_024L); - private static final BigInteger BIGINT_3072 = BigInteger.valueOf(3_072L); - private static final BigInteger BIGINT_199680 = BigInteger.valueOf(199_680L); - public BigIntegerModularExponentiationPrecompiledContract(final GasCalculator gasCalculator) { super("BigIntModExp", gasCalculator); } + public static void disableNative() { + useNative = false; + } + + public static boolean isNative() { + return useNative; + } + @Override public long gasRequirement(final Bytes input) { return gasCalculator().modExpGasCost(input); @@ -56,24 +79,33 @@ public class BigIntegerModularExponentiationPrecompiledContract @Override public PrecompileContractResult computePrecompile( final Bytes input, @Nonnull final MessageFrame messageFrame) { - final BigInteger baseLength = baseLength(input); - final BigInteger exponentLength = exponentLength(input); - final BigInteger modulusLength = modulusLength(input); + if (useNative) { + return computeNative(input); + } else { + return computeDefault(input); + } + } + + @Nonnull + public PrecompileContractResult computeDefault(final Bytes input) { + final int baseLength = clampedToInt(baseLength(input)); + final int exponentLength = clampedToInt(exponentLength(input)); + final int modulusLength = clampedToInt(modulusLength(input)); // If baseLength and modulusLength are zero // we could have a massively overflowing exp because it wouldn't have been filtered out at the // gas cost phase - if (baseLength.equals(BigInteger.ZERO) && modulusLength.equals(BigInteger.ZERO)) { + if ((baseLength == 0) && (modulusLength == 0)) { return PrecompileContractResult.success(Bytes.EMPTY); } - final BigInteger exponentOffset = BASE_OFFSET.add(baseLength); - final BigInteger modulusOffset = exponentOffset.add(exponentLength); - final BigInteger base = extractParameter(input, BASE_OFFSET, baseLength.intValue()); - final BigInteger exp = extractParameter(input, exponentOffset, exponentLength.intValue()); - final BigInteger mod = extractParameter(input, modulusOffset, modulusLength.intValue()); + final int exponentOffset = BASE_OFFSET + baseLength; + final int modulusOffset = exponentOffset + exponentLength; + final BigInteger base = extractParameter(input, BASE_OFFSET, baseLength); + final BigInteger exp = extractParameter(input, exponentOffset, exponentLength); + final BigInteger mod = extractParameter(input, modulusOffset, modulusLength); final Bytes modExp; // Result must be the length of the modulus. - final MutableBytes result = MutableBytes.create(modulusLength.intValue()); + final MutableBytes result = MutableBytes.create(modulusLength); if (mod.compareTo(BigInteger.ZERO) == 0) { modExp = MutableBytes.EMPTY; } else { @@ -87,30 +119,29 @@ public class BigIntegerModularExponentiationPrecompiledContract } // Equation to estimate the multiplication complexity. - public static BigInteger multiplicationComplexity(final BigInteger x) { - if (x.compareTo(BIGINT_64) <= 0) { + public static long multiplicationComplexity(final long x) { + if (x <= 64) { return square(x); - } else if (x.compareTo(BIGINT_1024) <= 0) { - return square(x).divide(BIGINT_4).add(BIGINT_96.multiply(x)).subtract(BIGINT_3072); + } else if (x <= 1024) { + return (square(x) / 4) + (x * 96) - 3072; } else { - return square(x).divide(BIGINT_16).add(BIGINT_480.multiply(x)).subtract(BIGINT_199680); + return (square(x) / 16) + (480 * x) - 199680; } } - public static BigInteger baseLength(final Bytes input) { - return extractParameter(input, BASE_LENGTH_OFFSET, PARAMETER_LENGTH); + public static long baseLength(final Bytes input) { + return extractParameterLong(input, BASE_LENGTH_OFFSET, PARAMETER_LENGTH); } - public static BigInteger exponentLength(final Bytes input) { - return extractParameter(input, EXPONENT_LENGTH_OFFSET, PARAMETER_LENGTH); + public static long exponentLength(final Bytes input) { + return extractParameterLong(input, EXPONENT_LENGTH_OFFSET, PARAMETER_LENGTH); } - public static BigInteger modulusLength(final Bytes input) { - return extractParameter(input, MODULUS_LENGTH_OFFSET, PARAMETER_LENGTH); + public static long modulusLength(final Bytes input) { + return extractParameterLong(input, MODULUS_LENGTH_OFFSET, PARAMETER_LENGTH); } - private static BigInteger extractParameter( - final Bytes input, final int offset, final int length) { + public static BigInteger extractParameter(final Bytes input, final int offset, final int length) { if (offset > input.size() || length == 0) { return BigInteger.ZERO; } @@ -118,15 +149,41 @@ public class BigIntegerModularExponentiationPrecompiledContract return new BigInteger(1, raw); } - public static BigInteger extractParameter( - final Bytes input, final BigInteger offset, final int length) { - if (BigInteger.valueOf(input.size()).compareTo(offset) <= 0) { - return BigInteger.ZERO; + public static long extractParameterLong(final Bytes input, final int offset, final int length) { + if (offset >= input.size() || length == 0) { + return 0; + } + Bytes num; + if (offset + length <= input.size()) { + num = input.slice(offset, length).trimLeadingZeros(); + } else { + // Ethereum's memory is always infinitely full of zeros, but we don't store those zeros, just + // what we write. If we are asked for a range that is outside the written memory create a + // result of the correct size (defaults to zeros) and copy the memory we do have into there. + MutableBytes mut = MutableBytes.create(length); + input.slice(offset).copyTo(mut, 0); + num = mut.trimLeadingZeros(); } - return extractParameter(input, offset.intValue(), length); + return clampedToLong(num); + } + + private static long square(final long n) { + return clampedMultiply(n, n); } - private static BigInteger square(final BigInteger n) { - return n.multiply(n); + public PrecompileContractResult computeNative(final @Nonnull Bytes input) { + final int modulusLength = clampedToInt(modulusLength(input)); + final IntByReference o_len = new IntByReference(modulusLength); + + final byte[] result = new byte[modulusLength]; + final int errorNo = + LibArithmetic.modexp_precompiled(input.toArrayUnsafe(), input.size(), result, o_len); + if (errorNo == 0) { + return PrecompileContractResult.success(Bytes.wrap(result, 0, o_len.getValue())); + } else { + LOG.trace("Error executing precompiled contract {}: {}", getName(), errorNo); + return PrecompileContractResult.halt( + null, Optional.of(ExceptionalHaltReason.PRECOMPILE_ERROR)); + } } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java index bb03805f4b..e7d83ed74c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.precompile; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; @@ -143,6 +144,36 @@ public class MODEXPPrecompiledContractTest { "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", 285900L, 87381L + }, + { + "00000000000000000000000000000000000000000000000000000000000000ff2a1e5300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + null, + Long.MAX_VALUE, + Long.MAX_VALUE + }, + { + "0000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010001", + null, + Long.MAX_VALUE, + Long.MAX_VALUE + }, + { + "00000000000000000000000000000000000000000000000000000000000000e300000000000000000000000000000000000000000000000000", + null, + 1580L, + 280L + }, + { + "00000000008000000000000000000000000000000000000000000000000000000000000400000000000000000000000a", + null, + Long.MAX_VALUE, + Long.MAX_VALUE + }, + { + "0x00000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000", + null, + 28928590731427686L, + Long.MAX_VALUE, } }; } @@ -160,10 +191,13 @@ public class MODEXPPrecompiledContractTest { @Test public void testPrecompiledContract() { + assumeThat(precompiledResult).isNotNull(); final Bytes input = Bytes.fromHexString(this.input); final Bytes expected = Bytes.fromHexString(precompiledResult); - assertThat(byzantiumContract.compute(input, messageFrame)).isEqualTo(expected); - assertThat(berlinContract.compute(input, messageFrame)).isEqualTo(expected); + assertThat(byzantiumContract.computePrecompile(input, messageFrame).getOutput()) + .isEqualTo(expected); + assertThat(berlinContract.computePrecompile(input, messageFrame).getOutput()) + .isEqualTo(expected); } @Test diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 7ecaed10db..a559b5d77f 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3874,6 +3874,11 @@ + + + + + @@ -3953,48 +3958,59 @@ - - - + + + + + + + + + + + + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 1c03023792..c0b4409d71 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -153,10 +153,13 @@ dependencyManagement { dependency 'org.fusesource.jansi:jansi:2.4.0' - dependency 'org.hyperledger.besu:bls12-381:0.6.1' - dependency 'org.hyperledger.besu:secp256k1:0.6.1' - dependency 'org.hyperledger.besu:secp256r1:0.6.1' - dependency 'org.hyperledger.besu:blake2bf:0.6.1' + dependencySet(group: 'org.hyperledger.besu', version: '0.7.1') { + entry 'arithmetic' + entry 'bls12-381' + entry 'secp256k1' + entry 'secp256r1' + entry 'blake2bf' + } dependency 'org.immutables:value-annotations:2.9.0' dependency 'org.immutables:value:2.9.0'