Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codehash compare #6019

Closed
wants to merge 12 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,29 @@
import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.FORK_HEADS;
import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.SAFE_BLOCK_HASH;
import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.SEQ_NO_STORE;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE_BY_HASH;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE_COMPARE;

import org.hyperledger.besu.cli.BesuCommand;
import org.hyperledger.besu.cli.util.VersionProvider;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.rlp.RLP;
import org.hyperledger.besu.ethereum.storage.StorageProvider;
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction;

import java.io.PrintWriter;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.time.DurationFormatUtils;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine.Command;
Expand All @@ -45,7 +57,10 @@
description = "This command provides storage related actions.",
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class,
subcommands = {StorageSubCommand.RevertVariablesStorage.class})
subcommands = {
StorageSubCommand.RevertVariablesStorage.class,
StorageSubCommand.MigrateCodeStorage.class
})
public class StorageSubCommand implements Runnable {

/** The constant COMMAND_NAME. */
Expand Down Expand Up @@ -75,7 +90,7 @@ public void run() {
spec.commandLine().usage(out);
}

/** The Hash sub command for password. */
/** The revert variables sub command for storage. */
@Command(
name = "revert-variables",
description = "This command revert the modifications done by the variables storage feature.",
Expand Down Expand Up @@ -171,4 +186,98 @@ private void setBlockchainVariable(
Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe());
}
}

/** The revert variables sub command for storage. */
@Command(
name = "compare-code",
description =
"This command compare the code storage to be stored by code hash necessary for snap sync support.",
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class)
static class MigrateCodeStorage implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(MigrateCodeStorage.class);

@SuppressWarnings("unused")
@ParentCommand
private StorageSubCommand parentCommand;

@Override
public void run() {
checkNotNull(parentCommand);

final var storageProvider = getStorageProvider();

migrate(storageProvider);
}

private StorageProvider getStorageProvider() {
// init collection of ignorable segments
parentCommand.parentCommand.setIgnorableStorageSegments();
return parentCommand.parentCommand.getStorageProvider();
}

private void migrate(final StorageProvider storageProvider) {
LOG.info("Starting code storage comparison");
final Instant start = Instant.now();

final SegmentedKeyValueStorage keyValueStorage =
storageProvider.getStorageBySegmentIdentifiers(
List.of(CODE_STORAGE, CODE_STORAGE_BY_HASH, CODE_STORAGE_COMPARE));

keyValueStorage.stream(CODE_STORAGE)
.forEach(
keyValuePair -> {
final Bytes value = Bytes.wrap(keyValuePair.getValue());
final Bytes32 codeHash = Hash.hash(value);
final Optional<byte[]> storageByHashValue =
keyValueStorage.get(CODE_STORAGE_BY_HASH, codeHash.toArray());
if (storageByHashValue.isEmpty()) {
LOG.info(
"Missing code in CODE_STORAGE_BY_HASH for hash={} value={}", codeHash, value);
}
storageByHashValue.ifPresent(
v -> {
if (!Bytes.wrap(v).equals(value)) {
LOG.info(
"Code stored in CODE_STORAGE_BY_HASH has incorrect code value expected={} actual={}",
v,
value);
}
});

// don't need to store the counts. this is just to flatten storage for comparison
final SegmentedKeyValueStorageTransaction transaction =
keyValueStorage.startTransaction();
transaction.put(
CODE_STORAGE_COMPARE, codeHash.toArrayUnsafe(), keyValuePair.getValue());
transaction.commit();
});

keyValueStorage.stream(CODE_STORAGE_BY_HASH)
.forEach(
keyValuePair -> {
final Optional<byte[]> codeStorageCompareValue =
keyValueStorage.get(CODE_STORAGE_COMPARE, keyValuePair.getKey());
if (codeStorageCompareValue.isEmpty()) {
LOG.info(
"Missing code in CODE_STORAGE_COMPARE for hash={} value={}",
Bytes.wrap(keyValuePair.getKey()),
Bytes.wrap(keyValuePair.getValue()));
}
codeStorageCompareValue.ifPresent(
cv -> {
if (!Bytes.wrap(cv).equals(Bytes.wrap(keyValuePair.getValue()))) {
LOG.info(
"Code stored in CODE_STORAGE_BY_HASH has incorrect code value expected={} actual={}",
Bytes.wrap(keyValuePair.getKey()),
Bytes.wrap(cv));
}
});
});

LOG.info(
"Finished code storage comparison in {}",
DurationFormatUtils.formatDurationHMS(Duration.between(start, Instant.now()).toMillis()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public BonsaiUpdater updater() {
return new Updater(
((SnappedKeyValueStorage) composedWorldStateStorage).getSnapshotTransaction(),
trieLogStorage.startTransaction(),
flatDbStrategy);
flatDbStrategy,
composedWorldStateStorage);
}

@Override
Expand All @@ -87,7 +88,7 @@ public Optional<Bytes> getAccount(final Hash accountHash) {
}

@Override
public Optional<Bytes> getCode(final Bytes32 codeHash, final Hash accountHash) {
public Optional<Bytes> getCode(final Hash codeHash, final Hash accountHash) {
return isClosedGet() ? Optional.empty() : super.getCode(codeHash, accountHash);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE_BY_HASH;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE;

import org.hyperledger.besu.datatypes.Hash;
Expand Down Expand Up @@ -85,7 +86,11 @@ public BonsaiWorldStateKeyValueStorage(
this.composedWorldStateStorage =
provider.getStorageBySegmentIdentifiers(
List.of(
ACCOUNT_INFO_STATE, CODE_STORAGE, ACCOUNT_STORAGE_STORAGE, TRIE_BRANCH_STORAGE));
ACCOUNT_INFO_STATE,
CODE_STORAGE,
CODE_STORAGE_BY_HASH,
ACCOUNT_STORAGE_STORAGE,
TRIE_BRANCH_STORAGE));
this.trieLogStorage =
provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE);
this.metricsSystem = metricsSystem;
Expand All @@ -108,19 +113,19 @@ public BonsaiWorldStateKeyValueStorage(
private void loadFlatDbStrategy() {
// derive our flatdb strategy from db or default:
var newFlatDbMode = deriveFlatDbStrategy();

final boolean useLegacyCodeStorage = isLegacyCodeStorageMode();
// if flatDbMode is not loaded or has changed, reload flatDbStrategy
if (this.flatDbMode == null || !this.flatDbMode.equals(newFlatDbMode)) {
this.flatDbMode = newFlatDbMode;
if (flatDbMode == FlatDbMode.FULL) {
this.flatDbStrategy = new FullFlatDbStrategy(metricsSystem);
this.flatDbStrategy = new FullFlatDbStrategy(metricsSystem, useLegacyCodeStorage);
} else {
this.flatDbStrategy = new PartialFlatDbStrategy(metricsSystem);
this.flatDbStrategy = new PartialFlatDbStrategy(metricsSystem, useLegacyCodeStorage);
}
}
}

public FlatDbMode deriveFlatDbStrategy() {
private FlatDbMode deriveFlatDbStrategy() {
var flatDbMode =
FlatDbMode.fromVersion(
composedWorldStateStorage
Expand All @@ -139,6 +144,14 @@ public FlatDbStrategy getFlatDbStrategy() {
return flatDbStrategy;
}

public SegmentedKeyValueStorage getWorldStateStorage() {
return composedWorldStateStorage;
}

public boolean isLegacyCodeStorageMode() {
return composedWorldStateStorage.hasValues(CODE_STORAGE);
}

@Override
public DataStorageFormat getDataStorageFormat() {
return DataStorageFormat.BONSAI;
Expand All @@ -150,7 +163,7 @@ public FlatDbMode getFlatDbMode() {
}

@Override
public Optional<Bytes> getCode(final Bytes32 codeHash, final Hash accountHash) {
public Optional<Bytes> getCode(final Hash codeHash, final Hash accountHash) {
if (codeHash.equals(Hash.EMPTY)) {
return Optional.of(Bytes.EMPTY);
} else {
Expand Down Expand Up @@ -327,7 +340,8 @@ public BonsaiUpdater updater() {
return new Updater(
composedWorldStateStorage.startTransaction(),
trieLogStorage.startTransaction(),
flatDbStrategy);
flatDbStrategy,
composedWorldStateStorage);
}

@Override
Expand All @@ -346,7 +360,7 @@ public void removeNodeAddedListener(final long id) {
}

public interface BonsaiUpdater extends WorldStateStorage.Updater {
BonsaiUpdater removeCode(final Hash accountHash);
BonsaiUpdater removeCode(final Hash accountHash, final Hash codeHash);

BonsaiUpdater removeAccountInfoState(final Hash accountHash);

Expand All @@ -367,30 +381,34 @@ public static class Updater implements BonsaiUpdater {
private final SegmentedKeyValueStorageTransaction composedWorldStateTransaction;
private final KeyValueStorageTransaction trieLogStorageTransaction;
private final FlatDbStrategy flatDbStrategy;
private final SegmentedKeyValueStorage composedWorldStateStorage;

public Updater(
final SegmentedKeyValueStorageTransaction composedWorldStateTransaction,
final KeyValueStorageTransaction trieLogStorageTransaction,
final FlatDbStrategy flatDbStrategy) {

final FlatDbStrategy flatDbStrategy,
final SegmentedKeyValueStorage composedWorldStateStorage) {
this.composedWorldStateTransaction = composedWorldStateTransaction;
this.trieLogStorageTransaction = trieLogStorageTransaction;
this.flatDbStrategy = flatDbStrategy;
this.composedWorldStateStorage = composedWorldStateStorage;
}

@Override
public BonsaiUpdater removeCode(final Hash accountHash) {
flatDbStrategy.removeFlatCode(composedWorldStateTransaction, accountHash);
public BonsaiUpdater removeCode(final Hash accountHash, final Hash codeHash) {
flatDbStrategy.removeFlatCode(
composedWorldStateTransaction, accountHash, codeHash, composedWorldStateStorage);
return this;
}

@Override
public BonsaiUpdater putCode(final Hash accountHash, final Bytes32 codeHash, final Bytes code) {
if (code.size() == 0) {
public BonsaiUpdater putCode(final Hash accountHash, final Hash codeHash, final Bytes code) {
if (code.isEmpty()) {
// Don't save empty values
return this;
}
flatDbStrategy.putFlatCode(composedWorldStateTransaction, accountHash, codeHash, code);
flatDbStrategy.putFlatCode(
composedWorldStateTransaction, accountHash, codeHash, code, composedWorldStateStorage);
return this;
}

Expand All @@ -402,7 +420,7 @@ public BonsaiUpdater removeAccountInfoState(final Hash accountHash) {

@Override
public BonsaiUpdater putAccountInfoState(final Hash accountHash, final Bytes accountValue) {
if (accountValue.size() == 0) {
if (accountValue.isEmpty()) {
// Don't save empty values
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.bonsai.storage.flat;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction;

import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;

public class BothCodeStorageStrategy implements CodeStorageStrategy {

private final DefaultCodeStorageStrategy defaultCodeStorageStrategy;
private final LegacyCodeStorageStrategy legacyCodeStorageStrategy;

public BothCodeStorageStrategy() {
defaultCodeStorageStrategy = new DefaultCodeStorageStrategy();
legacyCodeStorageStrategy = new LegacyCodeStorageStrategy();
}

@Override
public Optional<Bytes> getFlatCode(
final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) {
return defaultCodeStorageStrategy.getFlatCode(codeHash, accountHash, storage);
}

@Override
public void putFlatCode(
final SegmentedKeyValueStorageTransaction transaction,
final Hash accountHash,
final Hash codeHash,
final Bytes code) {
defaultCodeStorageStrategy.putFlatCode(transaction, accountHash, codeHash, code);
legacyCodeStorageStrategy.putFlatCode(transaction, accountHash, codeHash, code);
}

@Override
public void removeFlatCode(
final SegmentedKeyValueStorageTransaction transaction,
final Hash accountHash,
final Hash codeHash) {
defaultCodeStorageStrategy.removeFlatCode(transaction, accountHash, codeHash);
legacyCodeStorageStrategy.removeFlatCode(transaction, accountHash, codeHash);
}

@Override
public void clear(final SegmentedKeyValueStorage storage) {
defaultCodeStorageStrategy.clear(storage);
legacyCodeStorageStrategy.clear(storage);
}
}
Loading