Skip to content

Commit

Permalink
Restrict downgrade (hyperledger#6307)
Browse files Browse the repository at this point in the history
* Add Besu version to DB metadata. Check for downgrades and reject if version < version recorded in DB metadata.

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Add --allow-downgrade CLI arg. If set it allows the downgrade and updates the Besu version in the metadata file to the downgraded version.

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Update gradle verification XML

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Add and update tests

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Refactoring

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Remove versioning from RocksDB, now in separate VERSION_DATADATA.json

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Tidy up and tests for the new class

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Move downgrade logic into VersionMetadata as BesuCommand is already very big

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Add more tests

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Refactor the naming of the option to version-compatibility-protection

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Remove remaining references to allow-downgrade

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Rename test

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Update comments

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Metadata verification update

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* gradle fix

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Enable version downgrade protection by default for non-named networks

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Fix default logic

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

* Update ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java

Co-authored-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Signed-off-by: Matt Whitehead <matthew1001@hotmail.com>

* Update ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java

Co-authored-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Signed-off-by: Matt Whitehead <matthew1001@hotmail.com>

* mock-maker-inline no longer needed

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>

---------

Signed-off-by: Matthew Whitehead <matthew1001@gmail.com>
Signed-off-by: Matt Whitehead <matthew.whitehead@kaleido.io>
Signed-off-by: Matt Whitehead <matthew1001@hotmail.com>
Co-authored-by: Fabio Di Fabio <fabio.difabio@consensys.net>
  • Loading branch information
2 people authored and suraneti committed Feb 28, 2024
1 parent cde8304 commit 82626fc
Show file tree
Hide file tree
Showing 9 changed files with 455 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
- Upgrade reference tests to version 13.1 [#6574](https://github.com/hyperledger/besu/pull/6574)
- Extend `BesuConfiguration` service [#6584](https://github.com/hyperledger/besu/pull/6584)
- Add `ethereum_min_gas_price` and `ethereum_min_priority_fee` metrics to track runtime values of `min-gas-price` and `min-priority-fee` [#6587](https://github.com/hyperledger/besu/pull/6587)
- Option to perform version incompatibility checks when starting Besu. In this first release of the feature, if `--version-compatibility-protection` is set to true it checks that the version of Besu being started is the same or higher than the previous version. [6307](https://github.com/hyperledger/besu/pull/6307)


### Bug fixes
- Fix the way an advertised host configured with `--p2p-host` is treated when communicating with the originator of a PING packet [#6225](https://github.com/hyperledger/besu/pull/6225)
Expand Down
30 changes: 30 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 @@ -120,6 +120,7 @@
import org.hyperledger.besu.ethereum.core.MiningParameters;
import org.hyperledger.besu.ethereum.core.MiningParametersMetrics;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.VersionMetadata;
import org.hyperledger.besu.ethereum.eth.sync.SyncMode;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration;
Expand Down Expand Up @@ -562,6 +563,12 @@ private InetAddress autoDiscoverDefaultIP() {
arity = "1")
private final Path kzgTrustedSetupFile = null;

@Option(
names = {"--version-compatibility-protection"},
description =
"Perform compatibility checks between the version of Besu being started and the version of Besu that last started with this data directory. (default: ${DEFAULT-VALUE})")
private Boolean versionCompatibilityProtection = null;

@CommandLine.ArgGroup(validate = false, heading = "@|bold GraphQL Options|@%n")
GraphQlOptions graphQlOptions = new GraphQlOptions();

Expand Down Expand Up @@ -1078,7 +1085,13 @@ public void run() {
vertx = createVertx(createVertxOptions(metricsSystem.get()));

validateOptions();

configure();

// If we're not running against a named network, or if version compat protection has been
// explicitly enabled, perform compatibility check
VersionMetadata.versionCompatibilityChecks(versionCompatibilityProtection, dataDir());

configureNativeLibs();
besuController = buildController();

Expand Down Expand Up @@ -1648,6 +1661,7 @@ private void configure() throws Exception {
checkPortClash();
checkIfRequiredPortsAreAvailable();
syncMode = getDefaultSyncModeIfNotSet();
versionCompatibilityProtection = getDefaultVersionCompatibilityProtectionIfNotSet();

ethNetworkConfig = updateNetworkConfig(network);

Expand Down Expand Up @@ -2556,6 +2570,16 @@ String getLogLevel() {
return loggingLevelOption.getLogLevel();
}

/**
* Returns the flag indicating that version compatiblity checks will be made.
*
* @return true if compatibility checks should be made, otherwise false
*/
@VisibleForTesting
public Boolean getVersionCompatibilityProtection() {
return versionCompatibilityProtection;
}

private void instantiateSignatureAlgorithmFactory() {
if (SignatureAlgorithmFactory.isInstanceSet()) {
return;
Expand Down Expand Up @@ -2664,6 +2688,12 @@ private SyncMode getDefaultSyncModeIfNotSet() {
: SyncMode.FULL);
}

private Boolean getDefaultVersionCompatibilityProtectionIfNotSet() {
// Version compatibility protection is enabled by default for non-named networks
return Optional.ofNullable(versionCompatibilityProtection)
.orElse(commandLine.getParseResult().hasMatchedOption("network") ? false : true);
}

private String generateConfigurationOverview() {
final ConfigurationOverviewBuilder builder = new ConfigurationOverviewBuilder(logger);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,13 @@ public void dnsEnabledOptionIsParsedCorrectly() {
assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isFalse();
}

@Test
public void versionCompatibilityProtectionTrueOptionIsParsedCorrectly() {
final TestBesuCommand besuCommand = parseCommand("--version-compatibility-protection", "true");

assertThat(besuCommand.getVersionCompatibilityProtection()).isTrue();
}

@Test
public void dnsUpdateEnabledOptionIsParsedCorrectly() {
final TestBesuCommand besuCommand =
Expand Down
2 changes: 2 additions & 0 deletions besu/src/test/resources/everything_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ node-private-key-file="./path/to/privateKey"
pid-path="~/.pid"
reorg-logging-threshold=0
static-nodes-file="~/besudata/static-nodes.json"
version-compatibility-protection=true

profile="NONE"
# Security Module plugin to use
security-module="localfile"
Expand Down
1 change: 1 addition & 0 deletions ethereum/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.google.guava:guava'
implementation 'com.google.dagger:dagger'
implementation 'org.apache.maven:maven-artifact'
annotationProcessor 'com.google.dagger:dagger-compiler'
implementation 'io.opentelemetry:opentelemetry-api'
implementation 'io.vertx:vertx-core'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright Hyperledger Besu contributors.
*
* 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.ethereum.core;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class VersionMetadata {
private static final Logger LOG = LoggerFactory.getLogger(VersionMetadata.class);

/** Represents an unknown Besu version in the version metadata file */
public static final String BESU_VERSION_UNKNOWN = "UNKNOWN";

private static final String METADATA_FILENAME = "VERSION_METADATA.json";
private static final ObjectMapper MAPPER = new ObjectMapper();
private final String besuVersion;

/**
* Get the version of Besu that is running.
*
* @return the version of Besu
*/
public static String getRuntimeVersion() {
return VersionMetadata.class.getPackage().getImplementationVersion() == null
? BESU_VERSION_UNKNOWN
: VersionMetadata.class.getPackage().getImplementationVersion();
}

@JsonCreator
public VersionMetadata(@JsonProperty("besuVersion") final String besuVersion) {
this.besuVersion = besuVersion;
}

public String getBesuVersion() {
return besuVersion;
}

public static VersionMetadata lookUpFrom(final Path dataDir) throws IOException {
LOG.info("Lookup version metadata file in data directory: {}", dataDir);
return resolveVersionMetadata(getDefaultMetadataFile(dataDir));
}

public void writeToDirectory(final Path dataDir) throws IOException {
MAPPER.writeValue(getDefaultMetadataFile(dataDir), this);
}

private static File getDefaultMetadataFile(final Path dataDir) {
return dataDir.resolve(METADATA_FILENAME).toFile();
}

private static VersionMetadata resolveVersionMetadata(final File metadataFile)
throws IOException {
VersionMetadata versionMetadata;
try {
versionMetadata = MAPPER.readValue(metadataFile, VersionMetadata.class);
LOG.info("Existing version data detected. Besu version {}", versionMetadata.besuVersion);
} catch (FileNotFoundException fnfe) {
versionMetadata = new VersionMetadata(BESU_VERSION_UNKNOWN);
} catch (JsonProcessingException jpe) {
throw new IllegalStateException(
String.format("Invalid metadata file %s", metadataFile.getAbsolutePath()), jpe);
}
return versionMetadata;
}

/**
* This function is designed to protect a Besu instance from being unintentionally started at a
* version of Besu that might be incompatible with the version that last modified the specified
* data directory. Currently this check is limited to checking that the version is >= the previous
* version, to avoid accidentally running a lower version of Besu and potentially corrupting data,
* but the method could be extended to perform any other version-to-version compatibility checks
* necessary. If the --version-compatibility-protection flag is set to true and the compatibilty
* checks pass, the version metadata is updated to the current version of Besu.
*/
public static void versionCompatibilityChecks(
final boolean enforceCompatibilityProtection, final Path dataDir) throws IOException {
final VersionMetadata versionMetaData = VersionMetadata.lookUpFrom(dataDir);
if (versionMetaData.getBesuVersion().equals(VersionMetadata.BESU_VERSION_UNKNOWN)) {
// The version isn't known, potentially because the file doesn't exist. Write the latest
// version to the metadata file.
LOG.info(
"No version data detected. Writing Besu version {} to metadata file",
VersionMetadata.getRuntimeVersion());
new VersionMetadata(VersionMetadata.getRuntimeVersion()).writeToDirectory(dataDir);
} else {
// Check the runtime version against the most recent version as recorded in the version
// metadata file
final String installedVersion = VersionMetadata.getRuntimeVersion().split("-", 2)[0];
final String metadataVersion = versionMetaData.getBesuVersion().split("-", 2)[0];
final int versionComparison =
new ComparableVersion(installedVersion).compareTo(new ComparableVersion(metadataVersion));
if (versionComparison == 0) {
// Versions match - no-op
} else if (versionComparison < 0) {
if (!enforceCompatibilityProtection) {
LOG.warn(
"Besu version {} is lower than version {} that last started. Allowing startup because --version-compatibility-protection has been disabled.",
installedVersion,
metadataVersion);
// We've allowed startup at an older version of Besu. Since the version in the metadata
// file records the latest version of
// Besu to write to the database we'll update the metadata version to this
// downgraded-version.
new VersionMetadata(VersionMetadata.getRuntimeVersion()).writeToDirectory(dataDir);
} else {
final String message =
"Besu version "
+ installedVersion
+ " is lower than version "
+ metadataVersion
+ " that last started. Remove --version-compatibility-protection option to allow Besu to start at "
+ " the lower version (warning - this may have unrecoverable effects on the database).";
LOG.error(message, installedVersion, metadataVersion);
throw new IllegalStateException(message);
}
} else {
LOG.info(
"Besu version {} is higher than version {} that last started. Updating version metadata.",
installedVersion,
metadataVersion);
new VersionMetadata(VersionMetadata.getRuntimeVersion()).writeToDirectory(dataDir);
}
}
}
}
Loading

0 comments on commit 82626fc

Please sign in to comment.