diff --git a/CHANGELOG.md b/CHANGELOG.md index d20740ea4..958f73b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,13 @@ ## Unreleased ### Breaking Changes +NOTE: This release breaks native Windows compatibility for mainnet ethereum configurations. As the prague(pectra) hardfork require +BLS12-381 precompiles and besu does not currently have a pure java implementation of bls12-381, only platforms which +have support in besu-native can run mainnet ethereum configurations. Windows support via WSL should still continue to work. + - k8s (KUBERNETES) Nat method is removed. Use docker or none instead. [#8289](https://github.com/hyperledger/besu/pull/8289) - Change `Invalid block, unable to parse RLP` RPC error message to `Invalid block param (block not found)` [#8328](https://github.com/hyperledger/besu/pull/8328) +- Mainnet ethereum now REQUIRES native crypto libraries, so only linux and macos(darwin) are supported mainnet configurations [#8418](https://github.com/hyperledger/besu/pull/8418) ### Upcoming Breaking Changes - `MetricSystem::createLabelledGauge` is deprecated and will be removed in a future release, replace it with `MetricSystem::createLabelledSuppliedGauge` 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 d58f226e7..87312489f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.chainimport.Era1BlockImporter; import org.hyperledger.besu.chainimport.JsonBlockImporter; import org.hyperledger.besu.chainimport.RlpBlockImporter; import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.cli.config.NativeRequirement.NativeRequirementResult; import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.cli.config.ProfilesCompletionCandidates; import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; @@ -974,7 +975,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { // explicitly enabled, perform compatibility check VersionMetadata.versionCompatibilityChecks(versionCompatibilityProtection, dataDir()); - configureNativeLibs(); + configureNativeLibs(Optional.ofNullable(network)); besuController = buildController(); besuPluginContext.beforeExternalServices(); @@ -994,7 +995,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { runner.awaitStop(); } catch (final Exception e) { - logger.error("Failed to start Besu", e); + logger.error("Failed to start Besu: {}", e.getMessage()); + logger.debug("Startup failure cause", e); throw new ParameterException(this.commandLine, e.getMessage(), e); } } @@ -1388,7 +1390,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { return Optional.ofNullable(colorEnabled); } - private void configureNativeLibs() { + @VisibleForTesting + void configureNativeLibs(final Optional configuredNetwork) { if (unstableNativeLibraryOptions.getNativeAltbn128() && AbstractAltBnPrecompiledContract.maybeEnableNative()) { logger.info("Using the native implementation of alt bn128"); @@ -1435,6 +1438,37 @@ public class BesuCommand implements DefaultCommandValues, Runnable { this.commandLine, "--kzg-trusted-setup can only be specified on networks with data blobs enabled"); } + // assert required native libraries have been loaded + if (genesisFile == null && configuredNetwork.isPresent()) { + checkRequiredNativeLibraries(configuredNetwork.get()); + } + } + + @VisibleForTesting + void checkRequiredNativeLibraries(final NetworkName configuredNetwork) { + if (configuredNetwork == null) { + return; + } + + // assert native library requirements for named networks: + List failedNativeReqs = + configuredNetwork.getNativeRequirements().stream().filter(r -> !r.present()).toList(); + + if (!failedNativeReqs.isEmpty()) { + String failures = + failedNativeReqs.stream() + .map(r -> r.libname() + " " + r.errorMessage()) + .collect(Collectors.joining("\n\t")); + throw new UnsupportedOperationException( + String.format( + "Failed to load required native libraries for network %s. " + + "Verify whether your platform %s and arch %s are supported by besu. " + + "Failures loading: \n%s", + configuredNetwork.name(), + System.getProperty("os.name"), + System.getProperty("os.arch"), + failures)); + } } private void validateOptions() { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/NativeRequirement.java b/besu/src/main/java/org/hyperledger/besu/cli/config/NativeRequirement.java new file mode 100644 index 000000000..ba3ed2f5e --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/NativeRequirement.java @@ -0,0 +1,69 @@ +/* + * Copyright contributors to Besu. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.config; + +import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.evm.precompile.AltBN128PairingPrecompiledContract; +import org.hyperledger.besu.evm.precompile.BLS12PairingPrecompiledContract; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +/** Encapsulates the native library requirements of given networks. */ +public interface NativeRequirement { + + /** + * Record type to encapsulate the result of native library loading + * + * @param present boolean indicating library loading present or failure. + * @param libname string indicating the required library name. + * @param errorMessage Optional error message suitable to log. + */ + record NativeRequirementResult(Boolean present, String libname, Optional errorMessage) {} + + /** Ethereum mainnet-like performance requirements: */ + Supplier> MAINNET = + () -> { + List requirements = new ArrayList<>(); + var secp256k1 = new SECP256K1(); + requirements.add( + new NativeRequirementResult( + secp256k1.maybeEnableNative(), + "secp256k1", + secp256k1.maybeEnableNative() + ? Optional.empty() + : Optional.of("secp256k1: Native secp256k1 failed to load"))); + + requirements.add( + new NativeRequirementResult( + AltBN128PairingPrecompiledContract.isNative(), + "alt_bn128", + AltBN128PairingPrecompiledContract.isNative() + ? Optional.empty() + : Optional.of("alt_bn128: EC native library failed to load"))); + + requirements.add( + new NativeRequirementResult( + BLS12PairingPrecompiledContract.isAvailable(), + "bls12-381", + BLS12PairingPrecompiledContract.isAvailable() + ? Optional.empty() + : Optional.of("bls12-381: EC native library failed to load"))); + + return requirements; + }; +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java index 67535c8c5..944be33a0 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java @@ -14,31 +14,34 @@ */ package org.hyperledger.besu.cli.config; +import org.hyperledger.besu.cli.config.NativeRequirement.NativeRequirementResult; + import java.math.BigInteger; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.function.Supplier; import org.apache.commons.lang3.StringUtils; /** The enum Network name. */ public enum NetworkName { /** Mainnet network name. */ - MAINNET("/mainnet.json", BigInteger.valueOf(1)), + MAINNET("/mainnet.json", BigInteger.valueOf(1), true, NativeRequirement.MAINNET), /** Sepolia network name. */ - SEPOLIA("/sepolia.json", BigInteger.valueOf(11155111)), + SEPOLIA("/sepolia.json", BigInteger.valueOf(11155111), true, NativeRequirement.MAINNET), /** Holešky network name. */ - HOLESKY("/holesky.json", BigInteger.valueOf(17000)), + HOLESKY("/holesky.json", BigInteger.valueOf(17000), true, NativeRequirement.MAINNET), /** Hoodi network name. */ - HOODI("/hoodi.json", BigInteger.valueOf(560048)), - /** LUKSO mainnet network name. */ - LUKSO("/lukso.json", BigInteger.valueOf(42)), - + HOODI("/hoodi.json", BigInteger.valueOf(560048), true, NativeRequirement.MAINNET), /** * EPHEMERY network name. The actual networkId used is calculated based on this default value and * the current time. https://ephemery.dev/ */ - EPHEMERY("/ephemery.json", BigInteger.valueOf(39438135)), - + EPHEMERY("/ephemery.json", BigInteger.valueOf(39438135), true, NativeRequirement.MAINNET), + /** LUKSO mainnet network name. */ + LUKSO("/lukso.json", BigInteger.valueOf(42)), /** Dev network name. */ DEV("/dev.json", BigInteger.valueOf(2018), false), /** Future EIPs network name. */ @@ -54,17 +57,27 @@ public enum NetworkName { private final BigInteger networkId; private final boolean canSnapSync; private final String deprecationDate; + private final Supplier> nativeRequirements; NetworkName(final String genesisFile, final BigInteger networkId) { this(genesisFile, networkId, true); } NetworkName(final String genesisFile, final BigInteger networkId, final boolean canSnapSync) { + this(genesisFile, networkId, canSnapSync, Collections::emptyList); + } + + NetworkName( + final String genesisFile, + final BigInteger networkId, + final boolean canSnapSync, + final Supplier> nativeRequirements) { this.genesisFile = genesisFile; this.networkId = networkId; this.canSnapSync = canSnapSync; // no deprecations planned this.deprecationDate = null; + this.nativeRequirements = nativeRequirements; } /** @@ -120,4 +133,13 @@ public enum NetworkName { public Optional getDeprecationDate() { return Optional.ofNullable(deprecationDate); } + + /** + * Gets native requirements for this network. + * + * @return result of native library requirements defined for this network, as a list. + */ + public List getNativeRequirements() { + return this.nativeRequirements.get(); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index 7f6c1655e..e345441be 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -16,6 +16,8 @@ package org.hyperledger.besu.cli; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.hyperledger.besu.cli.config.NetworkName.CLASSIC; import static org.hyperledger.besu.cli.config.NetworkName.DEV; import static org.hyperledger.besu.cli.config.NetworkName.EPHEMERY; @@ -42,11 +44,15 @@ import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; import org.hyperledger.besu.BesuInfo; import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.cli.config.NativeRequirement.NativeRequirementResult; import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.config.GenesisConfig; import org.hyperledger.besu.config.MergeConfiguration; @@ -2564,6 +2570,46 @@ public class BesuCommandTest extends CommandTestAbstract { assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } + @Test + public void assertUnnamedNetworkNativeRequirements_Met() throws IOException { + final Path genesisFile = + createFakeGenesisFile(new JsonObject().put("config", new JsonObject())); + parseCommand("--genesis-file", genesisFile.toString()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void assertNativeRequirements_UnMet() throws IOException { + BesuCommand mockCmd = parseCommand("--network=mainnet"); + NetworkName spyMainnet = spy(NetworkName.MAINNET); + when(spyMainnet.getNativeRequirements()) + .thenReturn( + List.of(new NativeRequirementResult(false, "MOCKLIB", Optional.of("Mock error")))); + assertThatExceptionOfType(UnsupportedOperationException.class) + .isThrownBy(() -> mockCmd.checkRequiredNativeLibraries(spyMainnet)) + .withMessageContaining("MOCKLIB") + .withMessageContaining("Mock error") + .withMessageContaining(System.getProperty("os.arch")) + .withMessageContaining(System.getProperty("os.name")); + } + + @Test + public void assertNativeRequirements_UnMetForUnnamedNetwork() throws IOException { + final Path fakeGenesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); + BesuCommand mockCmd = parseCommand("--genesis-file=" + fakeGenesisFile.toString()); + NetworkName spyMainnet = spy(NetworkName.MAINNET); + // assert no error output + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + // assert no exception + assertThatNoException().isThrownBy(() -> mockCmd.configureNativeLibs(Optional.of(spyMainnet))); + // assert we didn't check for native requirements for a custom-genesis + verify(spyMainnet, times(0)).getNativeRequirements(); + } + @Test public void bonsaiFlatDbShouldBeEnabledByDefault() { final TestBesuCommand besuCommand = parseCommand(); diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java index 812d77621..39642cef3 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java @@ -44,7 +44,7 @@ public class SECP256R1 extends AbstractSECP256 { public SECP256R1() { super(CURVE_NAME, SecP256R1Curve.q); try { - useNative = BesuNativeEC.INSTANCE != null; + useNative = BesuNativeEC.ENABLED; } catch (UnsatisfiedLinkError ule) { LOG.info("secp256r1 native precompile not available: {}", ule.getMessage()); useNative = false; @@ -69,7 +69,7 @@ public class SECP256R1 extends AbstractSECP256 { @Override public boolean maybeEnableNative() { try { - useNative = BesuNativeEC.INSTANCE != null; + useNative = BesuNativeEC.ENABLED; } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { LOG.info("Native secp256r1 not available - {}", e.getMessage()); useNative = false; diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 87500376e..987f012fb 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -4387,12 +4387,12 @@ - - - + + + - - + + @@ -4403,44 +4403,52 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + @@ -4664,6 +4672,14 @@ + + + + + + + + @@ -4675,14 +4691,6 @@ - - - - - - - - diff --git a/platform/build.gradle b/platform/build.gradle index fc8829f10..4a46605d7 100644 --- a/platform/build.gradle +++ b/platform/build.gradle @@ -139,12 +139,13 @@ dependencies { api 'org.hibernate.validator:hibernate-validator:8.0.2.Final' - api 'org.hyperledger.besu:arithmetic:1.1.2' - api 'org.hyperledger.besu:blake2bf:1.1.2' - api 'org.hyperledger.besu:gnark:1.1.2' - api 'org.hyperledger.besu:ipa-multipoint:1.1.2' - api 'org.hyperledger.besu:secp256k1:1.1.2' - api 'org.hyperledger.besu:secp256r1:1.1.2' + api 'org.hyperledger.besu:besu-native-common:1.3.0' + api 'org.hyperledger.besu:arithmetic:1.3.0' + api 'org.hyperledger.besu:blake2bf:1.3.0' + api 'org.hyperledger.besu:gnark:1.3.0' + api 'org.hyperledger.besu:ipa-multipoint:1.3.0' + api 'org.hyperledger.besu:secp256k1:1.3.0' + api 'org.hyperledger.besu:secp256r1:1.3.0' api 'org.hyperledger.besu:besu-errorprone-checks:1.0.0'