diff --git a/CHANGELOG.md b/CHANGELOG.md index dfbf80dd5e9..a43fe5c2fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## 23.4.2 ### Breaking Changes +- Move blockchain related variables in a dedicated storage, to pave the way to future optimizations [#5471](https://github.com/hyperledger/besu/pull/5471). The migration is performed automatically at startup, +and in case a rollback is needed, before installing a previous version, the migration can be reverted, using the subcommand `storage revert-variables` with the same configuration use to run Besu. ### Additions and Improvements - Allow Ethstats connection url to specify ws:// or wss:// scheme. [#5494](https://github.com/hyperledger/besu/issues/5494) 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 6aa087b5229..d77dac0ce9d 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -83,6 +83,7 @@ import org.hyperledger.besu.cli.subcommands.blocks.BlocksSubCommand; import org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommand; import org.hyperledger.besu.cli.subcommands.rlp.RLPSubCommand; +import org.hyperledger.besu.cli.subcommands.storage.StorageSubCommand; import org.hyperledger.besu.cli.util.BesuCommandCustomFactory; import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.cli.util.ConfigOptionSearchAndRunHandler; @@ -140,6 +141,7 @@ import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningConfiguration; import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProvider; import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder; +import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; @@ -1598,6 +1600,8 @@ private void addSubCommands(final InputStream in) { commandLine.addSubcommand( ValidateConfigSubCommand.COMMAND_NAME, new ValidateConfigSubCommand(commandLine, commandLine.getOut())); + commandLine.addSubcommand( + StorageSubCommand.COMMAND_NAME, new StorageSubCommand(commandLine.getOut())); final String generateCompletionSubcommandName = "generate-completion"; commandLine.addSubcommand( generateCompletionSubcommandName, AutoComplete.GenerateCompletion.class); @@ -2979,6 +2983,15 @@ private KeyValueStorageProvider keyValueStorageProvider(final String name) { return this.keyValueStorageProvider; } + /** + * Get the storage provider + * + * @return the storage provider + */ + public StorageProvider getStorageProvider() { + return keyValueStorageProvider(keyValueStorageName); + } + private Optional maybePkiBlockCreationConfiguration() { return pkiBlockCreationOptions .asDomainConfig(commandLine) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java new file mode 100644 index 00000000000..f7a1a6eca11 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java @@ -0,0 +1,172 @@ +/* + * 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.cli.subcommands.storage; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.hyperledger.besu.cli.subcommands.storage.StorageSubCommand.COMMAND_NAME; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.CHAIN_HEAD_HASH; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.FINALIZED_BLOCK_HASH; +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 org.hyperledger.besu.cli.BesuCommand; +import org.hyperledger.besu.cli.util.VersionProvider; +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 java.io.PrintWriter; + +import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.ParentCommand; +import picocli.CommandLine.Spec; + +/** The Storage sub command. */ +@Command( + name = COMMAND_NAME, + description = "This command provides storage related actions.", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class, + subcommands = {StorageSubCommand.RevertVariablesStorage.class}) +public class StorageSubCommand implements Runnable { + + /** The constant COMMAND_NAME. */ + public static final String COMMAND_NAME = "storage"; + + @SuppressWarnings("unused") + @ParentCommand + private BesuCommand parentCommand; + + @SuppressWarnings("unused") + @Spec + private CommandSpec spec; + + private final PrintWriter out; + + /** + * Instantiates a new Storage sub command. + * + * @param out The PrintWriter where the usage will be reported. + */ + public StorageSubCommand(final PrintWriter out) { + this.out = out; + } + + @Override + public void run() { + spec.commandLine().usage(out); + } + + /** The Hash sub command for password. */ + @Command( + name = "revert-variables", + description = "This command revert the modifications done by the variables storage feature.", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class RevertVariablesStorage implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(RevertVariablesStorage.class); + private static final Bytes VARIABLES_PREFIX = Bytes.of(1); + + @SuppressWarnings("unused") + @ParentCommand + private StorageSubCommand parentCommand; + + @Override + public void run() { + checkNotNull(parentCommand); + + final var storageProvider = getStorageProvider(); + + revert(storageProvider); + } + + private StorageProvider getStorageProvider() { + return parentCommand.parentCommand.getStorageProvider(); + } + + private void revert(final StorageProvider storageProvider) { + final var variablesStorage = storageProvider.createVariablesStorage(); + final var blockchainStorage = + getStorageProvider().getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.BLOCKCHAIN); + final var blockchainUpdater = blockchainStorage.startTransaction(); + final var variablesUpdater = variablesStorage.updater(); + + variablesStorage + .getChainHead() + .ifPresent( + v -> { + setBlockchainVariable( + blockchainUpdater, VARIABLES_PREFIX, CHAIN_HEAD_HASH.getBytes(), v); + LOG.info("Reverted variable storage for key {}", CHAIN_HEAD_HASH); + }); + + variablesStorage + .getFinalized() + .ifPresent( + v -> { + setBlockchainVariable( + blockchainUpdater, VARIABLES_PREFIX, FINALIZED_BLOCK_HASH.getBytes(), v); + LOG.info("Reverted variable storage for key {}", FINALIZED_BLOCK_HASH); + }); + + variablesStorage + .getSafeBlock() + .ifPresent( + v -> { + setBlockchainVariable( + blockchainUpdater, VARIABLES_PREFIX, SAFE_BLOCK_HASH.getBytes(), v); + LOG.info("Reverted variable storage for key {}", SAFE_BLOCK_HASH); + }); + + final var forkHeads = variablesStorage.getForkHeads(); + if (!forkHeads.isEmpty()) { + setBlockchainVariable( + blockchainUpdater, + VARIABLES_PREFIX, + FORK_HEADS.getBytes(), + RLP.encode(o -> o.writeList(forkHeads, (val, out) -> out.writeBytes(val)))); + LOG.info("Reverted variable storage for key {}", FORK_HEADS); + } + + variablesStorage + .getLocalEnrSeqno() + .ifPresent( + v -> { + setBlockchainVariable(blockchainUpdater, Bytes.EMPTY, SEQ_NO_STORE.getBytes(), v); + LOG.info("Reverted variable storage for key {}", SEQ_NO_STORE); + }); + + variablesUpdater.removeAll(); + + variablesUpdater.commit(); + blockchainUpdater.commit(); + } + + private void setBlockchainVariable( + final KeyValueStorageTransaction blockchainTransaction, + final Bytes prefix, + final Bytes key, + final Bytes value) { + blockchainTransaction.put( + Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe()); + } + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java index 71c6d80fce5..bb46003b24b 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration; +import org.hyperledger.besu.ethereum.storage.StorageProvider; import java.io.Closeable; import java.io.IOException; @@ -75,6 +76,7 @@ public class BesuController implements java.io.Closeable { private final PluginServiceFactory additionalPluginServices; private final SyncState syncState; private final EthPeers ethPeers; + private final StorageProvider storageProvider; /** * Instantiates a new Besu controller. @@ -111,7 +113,8 @@ public class BesuController implements java.io.Closeable { final NodeKey nodeKey, final List closeables, final PluginServiceFactory additionalPluginServices, - final EthPeers ethPeers) { + final EthPeers ethPeers, + final StorageProvider storageProvider) { this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.ethProtocolManager = ethProtocolManager; @@ -128,6 +131,7 @@ public class BesuController implements java.io.Closeable { this.miningParameters = miningParameters; this.additionalPluginServices = additionalPluginServices; this.ethPeers = ethPeers; + this.storageProvider = storageProvider; } /** @@ -220,6 +224,15 @@ public EthPeers getEthPeers() { return ethPeers; } + /** + * Get the storage provider + * + * @return the storage provider + */ + public StorageProvider getStorageProvider() { + return storageProvider; + } + @Override public void close() { closeables.forEach(this::tryClose); diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 4b7bff0234f..656d39b01e0 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -42,6 +42,7 @@ import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; @@ -561,11 +562,14 @@ public BesuController build() { final ProtocolSchedule protocolSchedule = createProtocolSchedule(); final GenesisState genesisState = GenesisState.fromConfig(genesisConfig, protocolSchedule); + + final VariablesStorage variablesStorage = storageProvider.createVariablesStorage(); + final WorldStateStorage worldStateStorage = storageProvider.createWorldStateStorage(dataStorageConfiguration.getDataStorageFormat()); final BlockchainStorage blockchainStorage = - storageProvider.createBlockchainStorage(protocolSchedule); + storageProvider.createBlockchainStorage(protocolSchedule, variablesStorage); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( @@ -773,7 +777,8 @@ public BesuController build() { nodeKey, closeables, additionalPluginServices, - ethPeers); + ethPeers, + storageProvider); } /** diff --git a/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java index df050af0489..d4c0a79a316 100644 --- a/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java @@ -16,8 +16,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.BLOCKCHAIN; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.VARIABLES; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -163,7 +164,7 @@ public void enodeUrlShouldHaveAdvertisedHostWhenDiscoveryDisabled() { .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(vertx) .dataDir(dataDir.getRoot()) - .storageProvider(mock(KeyValueStorageProvider.class)) + .storageProvider(mock(KeyValueStorageProvider.class, RETURNS_DEEP_STUBS)) .rpcEndpointService(new RpcEndpointServiceImpl()) .build(); runner.startEthereumMainLoop(); @@ -223,7 +224,7 @@ public void movingAcrossProtocolSpecsUpdatesNodeRecord() { inMemoryBlockchain.appendBlock(block, gen.receipts(block)); assertThat( storageProvider - .getStorageBySegmentIdentifier(BLOCKCHAIN) + .getStorageBySegmentIdentifier(VARIABLES) .get("local-enr-seqno".getBytes(StandardCharsets.UTF_8)) .map(Bytes::of) .map(NodeRecordFactory.DEFAULT::fromBytes) @@ -265,7 +266,7 @@ public void whenEngineApiAddedListensOnDefaultPort() { .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) .dataDir(dataDir.getRoot()) - .storageProvider(mock(KeyValueStorageProvider.class)) + .storageProvider(mock(KeyValueStorageProvider.class, RETURNS_DEEP_STUBS)) .rpcEndpointService(new RpcEndpointServiceImpl()) .besuPluginContext(mock(BesuPluginContextImpl.class)) .build(); @@ -307,7 +308,7 @@ public void whenEngineApiAddedWebSocketReadyOnSamePort() { .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) .dataDir(dataDir.getRoot()) - .storageProvider(mock(KeyValueStorageProvider.class)) + .storageProvider(mock(KeyValueStorageProvider.class, RETURNS_DEEP_STUBS)) .rpcEndpointService(new RpcEndpointServiceImpl()) .besuPluginContext(mock(BesuPluginContextImpl.class)) .build(); @@ -348,7 +349,7 @@ public void whenEngineApiAddedEthSubscribeAvailable() { .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) .dataDir(dataDir.getRoot()) - .storageProvider(mock(KeyValueStorageProvider.class)) + .storageProvider(mock(KeyValueStorageProvider.class, RETURNS_DEEP_STUBS)) .rpcEndpointService(new RpcEndpointServiceImpl()) .besuPluginContext(mock(BesuPluginContextImpl.class)) .build(); @@ -390,7 +391,7 @@ public void noEngineApiNoServiceForMethods() { .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) .dataDir(dataDir.getRoot()) - .storageProvider(mock(KeyValueStorageProvider.class)) + .storageProvider(mock(KeyValueStorageProvider.class, RETURNS_DEEP_STUBS)) .rpcEndpointService(new RpcEndpointServiceImpl()) .besuPluginContext(mock(BesuPluginContextImpl.class)) .networkingConfiguration(NetworkingConfiguration.create()) diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommandTest.java new file mode 100644 index 00000000000..a8a1bedc649 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommandTest.java @@ -0,0 +1,117 @@ +/* + * 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.cli.subcommands.storage; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.FINALIZED_BLOCK_HASH; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.SAFE_BLOCK_HASH; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.assertNoVariablesInStorage; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.assertVariablesPresentInBlockchainStorage; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.getSampleVariableValues; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.populateBlockchainStorage; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.populateVariablesStorage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.cli.CommandTestAbstract; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class StorageSubCommandTest extends CommandTestAbstract { + + @Test + public void storageSubCommandExists() { + parseCommand("storage"); + + assertThat(commandOutput.toString(UTF_8)) + .contains("This command provides storage related actions"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void storageRevertVariablesSubCommandExists() { + parseCommand("storage", "revert-variables", "--help"); + + assertThat(commandOutput.toString(UTF_8)) + .contains("This command revert the modifications done by the variables storage feature"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void revertVariables() { + final var kvVariables = new InMemoryKeyValueStorage(); + final var kvBlockchain = new InMemoryKeyValueStorage(); + when(rocksDBStorageFactory.create(eq(KeyValueSegmentIdentifier.VARIABLES), any(), any())) + .thenReturn(kvVariables); + when(rocksDBStorageFactory.create(eq(KeyValueSegmentIdentifier.BLOCKCHAIN), any(), any())) + .thenReturn(kvBlockchain); + + final var variableValues = getSampleVariableValues(); + assertNoVariablesInStorage(kvBlockchain); + populateVariablesStorage(kvVariables, variableValues); + + parseCommand("storage", "revert-variables"); + + assertNoVariablesInStorage(kvVariables); + assertVariablesPresentInBlockchainStorage(kvBlockchain, variableValues); + } + + @Test + public void revertVariablesWhenSomeVariablesDoNotExist() { + final var kvVariables = new InMemoryKeyValueStorage(); + final var kvBlockchain = new InMemoryKeyValueStorage(); + when(rocksDBStorageFactory.create(eq(KeyValueSegmentIdentifier.VARIABLES), any(), any())) + .thenReturn(kvVariables); + when(rocksDBStorageFactory.create(eq(KeyValueSegmentIdentifier.BLOCKCHAIN), any(), any())) + .thenReturn(kvBlockchain); + + final var variableValues = getSampleVariableValues(); + variableValues.remove(FINALIZED_BLOCK_HASH); + variableValues.remove(SAFE_BLOCK_HASH); + assertNoVariablesInStorage(kvBlockchain); + populateVariablesStorage(kvVariables, variableValues); + + parseCommand("storage", "revert-variables"); + + assertNoVariablesInStorage(kvVariables); + assertVariablesPresentInBlockchainStorage(kvBlockchain, variableValues); + } + + @Test + public void doesNothingWhenVariablesAlreadyReverted() { + final var kvVariables = new InMemoryKeyValueStorage(); + final var kvBlockchain = new InMemoryKeyValueStorage(); + when(rocksDBStorageFactory.create(eq(KeyValueSegmentIdentifier.VARIABLES), any(), any())) + .thenReturn(kvVariables); + when(rocksDBStorageFactory.create(eq(KeyValueSegmentIdentifier.BLOCKCHAIN), any(), any())) + .thenReturn(kvBlockchain); + + final var variableValues = getSampleVariableValues(); + assertNoVariablesInStorage(kvVariables); + populateBlockchainStorage(kvBlockchain, variableValues); + + parseCommand("storage", "revert-variables"); + + assertNoVariablesInStorage(kvVariables); + assertVariablesPresentInBlockchainStorage(kvBlockchain, variableValues); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java index edae51a7c95..bd8d06d2a5e 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java @@ -45,6 +45,7 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; @@ -114,10 +115,12 @@ public void setup() { when(ethashConfigOptions.getFixedDifficulty()).thenReturn(OptionalLong.empty()); when(storageProvider.getStorageBySegmentIdentifier(any())) .thenReturn(new InMemoryKeyValueStorage()); - when(storageProvider.createBlockchainStorage(any())) + when(storageProvider.createBlockchainStorage(any(), any())) .thenReturn( new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions())); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions())); when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java index 515ec018e66..15fb3fd3ec2 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java @@ -51,6 +51,7 @@ import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; @@ -119,10 +120,12 @@ public void setup() { when(genesisConfigOptions.getThanosBlockNumber()).thenReturn(OptionalLong.empty()); when(genesisConfigOptions.getTerminalBlockHash()).thenReturn(Optional.of(Hash.ZERO)); when(genesisConfigOptions.getTerminalBlockNumber()).thenReturn(OptionalLong.of(1L)); - when(storageProvider.createBlockchainStorage(any())) + when(storageProvider.createBlockchainStorage(any(), any())) .thenReturn( new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions())); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions())); when(storageProvider.getStorageBySegmentIdentifier(any())) .thenReturn(new InMemoryKeyValueStorage()); when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java index cb2f8080fec..395abeb22d6 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java @@ -47,6 +47,7 @@ import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; @@ -103,10 +104,12 @@ public void setup() { when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); - when(storageProvider.createBlockchainStorage(any())) + when(storageProvider.createBlockchainStorage(any(), any())) .thenReturn( new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions())); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions())); when(storageProvider.createWorldStateStorage(DataStorageFormat.FOREST)) .thenReturn(worldStateStorage); when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 85607973509..4ce430e787d 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -54,6 +54,7 @@ import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.data.AddedBlockContext; @@ -114,7 +115,9 @@ public void setUp() { DefaultBlockchain.createMutable( gen.genesisBlock(), new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()), + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()), new NoOpMetricsSystem(), 0); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java index 3259d873ab7..0628cefadbf 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -60,7 +61,9 @@ public class NewBlockHeadersSubscriptionServiceTest { private final BlockDataGenerator gen = new BlockDataGenerator(); private final BlockchainStorage blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); private final Block genesisBlock = gen.genesisBlock(); private final MutableBlockchain blockchain = DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, new NoOpMetricsSystem(), 0); diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java index f6bba6a165f..81796f89f29 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java @@ -65,7 +65,7 @@ public static OperationBenchmarkHelper create() throws IOException { RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); final ExecutionContextTestFixture executionContext = - ExecutionContextTestFixture.builder().keyValueStorage(keyValueStorage).build(); + ExecutionContextTestFixture.builder().blockchainKeyValueStorage(keyValueStorage).build(); final MutableBlockchain blockchain = executionContext.getBlockchain(); for (int i = 1; i < 256; i++) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/VariablesStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/VariablesStorage.java new file mode 100644 index 00000000000..43a4361a0e4 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/VariablesStorage.java @@ -0,0 +1,88 @@ +/* + * 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.chain; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.hyperledger.besu.datatypes.Hash; + +import java.util.Collection; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public interface VariablesStorage { + enum Keys { + CHAIN_HEAD_HASH("chainHeadHash"), + FORK_HEADS("forkHeads"), + FINALIZED_BLOCK_HASH("finalizedBlockHash"), + SAFE_BLOCK_HASH("safeBlockHash"), + SEQ_NO_STORE("local-enr-seqno"); + + private final String key; + private final byte[] byteArray; + private final Bytes bytes; + + Keys(final String key) { + this.key = key; + this.byteArray = key.getBytes(UTF_8); + this.bytes = Bytes.wrap(byteArray); + } + + public byte[] toByteArray() { + return byteArray; + } + + public Bytes getBytes() { + return bytes; + } + + @Override + public String toString() { + return key; + } + } + + Optional getChainHead(); + + Collection getForkHeads(); + + Optional getFinalized(); + + Optional getSafeBlock(); + + Optional getLocalEnrSeqno(); + + Updater updater(); + + interface Updater { + + void setChainHead(Hash blockHash); + + void setForkHeads(Collection forkHeadHashes); + + void setFinalized(Hash blockHash); + + void setSafeBlock(Hash blockHash); + + void setLocalEnrSeqno(Bytes nodeRecord); + + void removeAll(); + + void commit(); + + void rollback(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java index 74faf91ec10..fea099d5769 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.storage; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; @@ -27,7 +28,10 @@ public interface StorageProvider extends Closeable { - BlockchainStorage createBlockchainStorage(ProtocolSchedule protocolSchedule); + VariablesStorage createVariablesStorage(); + + BlockchainStorage createBlockchainStorage( + ProtocolSchedule protocolSchedule, VariablesStorage variablesStorage); WorldStateStorage createWorldStateStorage(DataStorageFormat dataStorageFormat); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java index 559f1880c8a..43b02f224de 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java @@ -29,10 +29,10 @@ public enum KeyValueSegmentIdentifier implements SegmentIdentifier { ACCOUNT_STORAGE_STORAGE(new byte[] {8}, new int[] {2}), TRIE_BRANCH_STORAGE(new byte[] {9}, new int[] {2}), TRIE_LOG_STORAGE(new byte[] {10}, new int[] {2}), + VARIABLES(new byte[] {11}), // formerly GOQUORUM_PRIVATE_WORLD_STATE // previously supported GoQuorum private states // no longer used but need to be retained for db backward compatibility - GOQUORUM_PRIVATE_WORLD_STATE(new byte[] {11}), GOQUORUM_PRIVATE_STORAGE(new byte[] {12}), BACKWARD_SYNC_HEADERS(new byte[] {13}), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java index ee3a9d5790c..e5c1f11889b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java @@ -14,9 +14,16 @@ */ package org.hyperledger.besu.ethereum.storage.keyvalue; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.CHAIN_HEAD_HASH; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.FINALIZED_BLOCK_HASH; +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 org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; import org.hyperledger.besu.ethereum.chain.TransactionLocation; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; @@ -26,64 +33,61 @@ import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; import java.util.Optional; -import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class KeyValueStoragePrefixedKeyBlockchainStorage implements BlockchainStorage { + private static final Logger LOG = + LoggerFactory.getLogger(KeyValueStoragePrefixedKeyBlockchainStorage.class); - private static final Bytes CHAIN_HEAD_KEY = - Bytes.wrap("chainHeadHash".getBytes(StandardCharsets.UTF_8)); - private static final Bytes FORK_HEADS_KEY = - Bytes.wrap("forkHeads".getBytes(StandardCharsets.UTF_8)); - private static final Bytes FINALIZED_BLOCK_HASH_KEY = - Bytes.wrap("finalizedBlockHash".getBytes(StandardCharsets.UTF_8)); - private static final Bytes SAFE_BLOCK_HASH_KEY = - Bytes.wrap("safeBlockHash".getBytes(StandardCharsets.UTF_8)); - + @Deprecated(since = "23.4.2", forRemoval = true) private static final Bytes VARIABLES_PREFIX = Bytes.of(1); - static final Bytes BLOCK_HEADER_PREFIX = Bytes.of(2); + + private static final Bytes BLOCK_HEADER_PREFIX = Bytes.of(2); private static final Bytes BLOCK_BODY_PREFIX = Bytes.of(3); private static final Bytes TRANSACTION_RECEIPTS_PREFIX = Bytes.of(4); private static final Bytes BLOCK_HASH_PREFIX = Bytes.of(5); private static final Bytes TOTAL_DIFFICULTY_PREFIX = Bytes.of(6); private static final Bytes TRANSACTION_LOCATION_PREFIX = Bytes.of(7); - - final KeyValueStorage storage; + final KeyValueStorage blockchainStorage; + final VariablesStorage variablesStorage; final BlockHeaderFunctions blockHeaderFunctions; public KeyValueStoragePrefixedKeyBlockchainStorage( - final KeyValueStorage storage, final BlockHeaderFunctions blockHeaderFunctions) { - this.storage = storage; + final KeyValueStorage blockchainStorage, + final VariablesStorage variablesStorage, + final BlockHeaderFunctions blockHeaderFunctions) { + this.blockchainStorage = blockchainStorage; + this.variablesStorage = variablesStorage; this.blockHeaderFunctions = blockHeaderFunctions; + migrateVariables(); } @Override public Optional getChainHead() { - return get(VARIABLES_PREFIX, CHAIN_HEAD_KEY).map(this::bytesToHash); + return variablesStorage.getChainHead(); } @Override public Collection getForkHeads() { - return get(VARIABLES_PREFIX, FORK_HEADS_KEY) - .map(bytes -> RLP.input(bytes).readList(in -> this.bytesToHash(in.readBytes32()))) - .orElse(Lists.newArrayList()); + return variablesStorage.getForkHeads(); } @Override public Optional getFinalized() { - return get(VARIABLES_PREFIX, FINALIZED_BLOCK_HASH_KEY).map(this::bytesToHash); + return variablesStorage.getFinalized(); } @Override public Optional getSafeBlock() { - return get(VARIABLES_PREFIX, SAFE_BLOCK_HASH_KEY).map(this::bytesToHash); + return variablesStorage.getSafeBlock(); } @Override @@ -121,7 +125,7 @@ public Optional getTransactionLocation(final Hash transacti @Override public Updater updater() { - return new Updater(storage.startTransaction()); + return new Updater(blockchainStorage.startTransaction(), variablesStorage.updater()); } private List rlpDecodeTransactionReceipts(final Bytes bytes) { @@ -133,15 +137,68 @@ private Hash bytesToHash(final Bytes bytes) { } Optional get(final Bytes prefix, final Bytes key) { - return storage.get(Bytes.concatenate(prefix, key).toArrayUnsafe()).map(Bytes::wrap); + return blockchainStorage.get(Bytes.concatenate(prefix, key).toArrayUnsafe()).map(Bytes::wrap); + } + + public void migrateVariables() { + final var blockchainUpdater = updater(); + final var variablesUpdater = variablesStorage.updater(); + + get(VARIABLES_PREFIX, CHAIN_HEAD_HASH.getBytes()) + .map(this::bytesToHash) + .ifPresent( + ch -> { + variablesUpdater.setChainHead(ch); + LOG.info("Migrated key {} to variables storage", CHAIN_HEAD_HASH); + }); + + get(VARIABLES_PREFIX, FINALIZED_BLOCK_HASH.getBytes()) + .map(this::bytesToHash) + .ifPresent( + fh -> { + variablesUpdater.setFinalized(fh); + LOG.info("Migrated key {} to variables storage", FINALIZED_BLOCK_HASH); + }); + + get(VARIABLES_PREFIX, SAFE_BLOCK_HASH.getBytes()) + .map(this::bytesToHash) + .ifPresent( + sh -> { + variablesUpdater.setSafeBlock(sh); + LOG.info("Migrated key {} to variables storage", SAFE_BLOCK_HASH); + }); + + get(VARIABLES_PREFIX, FORK_HEADS.getBytes()) + .map(bytes -> RLP.input(bytes).readList(in -> this.bytesToHash(in.readBytes32()))) + .ifPresent( + fh -> { + variablesUpdater.setForkHeads(fh); + LOG.info("Migrated key {} to variables storage", FORK_HEADS); + }); + + get(Bytes.EMPTY, SEQ_NO_STORE.getBytes()) + .ifPresent( + sns -> { + variablesUpdater.setLocalEnrSeqno(sns); + LOG.info("Migrated key {} to variables storage", SEQ_NO_STORE); + }); + + blockchainUpdater.removeVariables(); + + variablesUpdater.commit(); + blockchainUpdater.commit(); } public static class Updater implements BlockchainStorage.Updater { - private final KeyValueStorageTransaction transaction; + private final KeyValueStorageTransaction blockchainTransaction; + private final VariablesStorage.Updater variablesUpdater; - Updater(final KeyValueStorageTransaction transaction) { - this.transaction = transaction; + Updater( + final KeyValueStorageTransaction blockchainTransaction, + final VariablesStorage.Updater variablesUpdater) { + this.blockchainTransaction = blockchainTransaction; + this.variablesUpdater = variablesUpdater; } @Override @@ -178,24 +235,22 @@ public void putTotalDifficulty(final Hash blockHash, final Difficulty totalDiffi @Override public void setChainHead(final Hash blockHash) { - set(VARIABLES_PREFIX, CHAIN_HEAD_KEY, blockHash); + variablesUpdater.setChainHead(blockHash); } @Override public void setForkHeads(final Collection forkHeadHashes) { - final Bytes data = - RLP.encode(o -> o.writeList(forkHeadHashes, (val, out) -> out.writeBytes(val))); - set(VARIABLES_PREFIX, FORK_HEADS_KEY, data); + variablesUpdater.setForkHeads(forkHeadHashes); } @Override public void setFinalized(final Hash blockHash) { - set(VARIABLES_PREFIX, FINALIZED_BLOCK_HASH_KEY, blockHash); + variablesUpdater.setFinalized(blockHash); } @Override public void setSafeBlock(final Hash blockHash) { - set(VARIABLES_PREFIX, SAFE_BLOCK_HASH_KEY, blockHash); + variablesUpdater.setSafeBlock(blockHash); } @Override @@ -230,24 +285,35 @@ public void removeTotalDifficulty(final Hash blockHash) { @Override public void commit() { - transaction.commit(); + blockchainTransaction.commit(); + variablesUpdater.commit(); } @Override public void rollback() { - transaction.rollback(); + variablesUpdater.rollback(); + blockchainTransaction.rollback(); } void set(final Bytes prefix, final Bytes key, final Bytes value) { - transaction.put(Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe()); + blockchainTransaction.put( + Bytes.concatenate(prefix, key).toArrayUnsafe(), value.toArrayUnsafe()); } private void remove(final Bytes prefix, final Bytes key) { - transaction.remove(Bytes.concatenate(prefix, key).toArrayUnsafe()); + blockchainTransaction.remove(Bytes.concatenate(prefix, key).toArrayUnsafe()); } private Bytes rlpEncode(final List receipts) { return RLP.encode(o -> o.writeList(receipts, TransactionReceipt::writeToWithRevertReason)); } + + private void removeVariables() { + remove(VARIABLES_PREFIX, CHAIN_HEAD_HASH.getBytes()); + remove(VARIABLES_PREFIX, FINALIZED_BLOCK_HASH.getBytes()); + remove(VARIABLES_PREFIX, SAFE_BLOCK_HASH.getBytes()); + remove(VARIABLES_PREFIX, FORK_HEADS.getBytes()); + remove(Bytes.EMPTY, SEQ_NO_STORE.getBytes()); + } } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java index 77b181b0072..13ad3270f01 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.StorageProvider; @@ -44,18 +45,6 @@ public class KeyValueStorageProvider implements StorageProvider { protected final Map storageInstances = new HashMap<>(); private final ObservableMetricsSystem metricsSystem; - public KeyValueStorageProvider( - final Function storageCreator, - final KeyValueStorage worldStatePreimageStorage, - final boolean segmentIsolationSupported, - final ObservableMetricsSystem metricsSystem) { - this.storageCreator = storageCreator; - this.worldStatePreimageStorage = worldStatePreimageStorage; - this.isWorldStateIterable = segmentIsolationSupported; - this.isWorldStateSnappable = SNAPSHOT_ISOLATION_UNSUPPORTED; - this.metricsSystem = metricsSystem; - } - public KeyValueStorageProvider( final Function storageCreator, final KeyValueStorage worldStatePreimageStorage, @@ -70,9 +59,17 @@ public KeyValueStorageProvider( } @Override - public BlockchainStorage createBlockchainStorage(final ProtocolSchedule protocolSchedule) { + public VariablesStorage createVariablesStorage() { + return new VariablesKeyValueStorage( + getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.VARIABLES)); + } + + @Override + public BlockchainStorage createBlockchainStorage( + final ProtocolSchedule protocolSchedule, final VariablesStorage variablesStorage) { return new KeyValueStoragePrefixedKeyBlockchainStorage( getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.BLOCKCHAIN), + variablesStorage, ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/VariablesKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/VariablesKeyValueStorage.java new file mode 100644 index 00000000000..28e6648a82d --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/VariablesKeyValueStorage.java @@ -0,0 +1,145 @@ +/* + * 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.storage.keyvalue; + +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.CHAIN_HEAD_HASH; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.FINALIZED_BLOCK_HASH; +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 org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; + +import java.util.Collection; +import java.util.Optional; + +import com.google.common.collect.Lists; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class VariablesKeyValueStorage implements VariablesStorage { + final KeyValueStorage variables; + + public VariablesKeyValueStorage(final KeyValueStorage variables) { + this.variables = variables; + } + + @Override + public Optional getChainHead() { + return getVariable(CHAIN_HEAD_HASH).map(this::bytesToHash); + } + + @Override + public Collection getForkHeads() { + return getVariable(FORK_HEADS) + .map(bytes -> RLP.input(bytes).readList(in -> this.bytesToHash(in.readBytes32()))) + .orElse(Lists.newArrayList()); + } + + @Override + public Optional getFinalized() { + return getVariable(FINALIZED_BLOCK_HASH).map(this::bytesToHash); + } + + @Override + public Optional getSafeBlock() { + return getVariable(SAFE_BLOCK_HASH).map(this::bytesToHash); + } + + @Override + public Optional getLocalEnrSeqno() { + return getVariable(SEQ_NO_STORE).map(Bytes::wrap); + } + + @Override + public Updater updater() { + return new Updater(variables.startTransaction()); + } + + private Hash bytesToHash(final Bytes bytes) { + return Hash.wrap(Bytes32.wrap(bytes, 0)); + } + + Optional getVariable(final Keys key) { + return variables.get(key.toByteArray()).map(Bytes::wrap); + } + + public static class Updater implements VariablesStorage.Updater { + + private final KeyValueStorageTransaction variablesTransaction; + + Updater(final KeyValueStorageTransaction variablesTransaction) { + this.variablesTransaction = variablesTransaction; + } + + @Override + public void setChainHead(final Hash blockHash) { + setVariable(CHAIN_HEAD_HASH, blockHash); + } + + @Override + public void setForkHeads(final Collection forkHeadHashes) { + final Bytes data = + RLP.encode(o -> o.writeList(forkHeadHashes, (val, out) -> out.writeBytes(val))); + setVariable(FORK_HEADS, data); + } + + @Override + public void setFinalized(final Hash blockHash) { + setVariable(FINALIZED_BLOCK_HASH, blockHash); + } + + @Override + public void setSafeBlock(final Hash blockHash) { + setVariable(SAFE_BLOCK_HASH, blockHash); + } + + @Override + public void setLocalEnrSeqno(final Bytes nodeRecord) { + setVariable(SEQ_NO_STORE, nodeRecord); + } + + @Override + public void removeAll() { + removeVariable(CHAIN_HEAD_HASH); + removeVariable(FINALIZED_BLOCK_HASH); + removeVariable(SAFE_BLOCK_HASH); + removeVariable(FORK_HEADS); + removeVariable(SEQ_NO_STORE); + } + + @Override + public void commit() { + variablesTransaction.commit(); + } + + @Override + public void rollback() { + variablesTransaction.rollback(); + } + + void setVariable(final Keys key, final Bytes value) { + variablesTransaction.put(key.toByteArray(), value.toArrayUnsafe()); + } + + void removeVariable(final Keys key) { + variablesTransaction.remove(key.toByteArray()); + } + } +} diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java index e712139f29c..bd3b77798ff 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -40,7 +41,8 @@ public class ExecutionContextTestFixture { private final Block genesis; - private final KeyValueStorage keyValueStorage; + private final KeyValueStorage blockchainKeyValueStorage; + private final KeyValueStorage variablesKeyValueStorage; private final MutableBlockchain blockchain; private final WorldStateArchive stateArchive; @@ -49,15 +51,20 @@ public class ExecutionContextTestFixture { private static final GenesisConfigFile genesisConfigFile = GenesisConfigFile.mainnet(); private ExecutionContextTestFixture( - final ProtocolSchedule protocolSchedule, final KeyValueStorage keyValueStorage) { + final ProtocolSchedule protocolSchedule, + final KeyValueStorage blockchainKeyValueStorage, + final KeyValueStorage variablesKeyValueStorage) { final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, protocolSchedule); this.genesis = genesisState.getBlock(); - this.keyValueStorage = keyValueStorage; + this.blockchainKeyValueStorage = blockchainKeyValueStorage; + this.variablesKeyValueStorage = variablesKeyValueStorage; this.blockchain = DefaultBlockchain.createMutable( genesis, new KeyValueStoragePrefixedKeyBlockchainStorage( - keyValueStorage, new MainnetBlockHeaderFunctions()), + blockchainKeyValueStorage, + new VariablesKeyValueStorage(variablesKeyValueStorage), + new MainnetBlockHeaderFunctions()), new NoOpMetricsSystem(), 0); this.stateArchive = createInMemoryWorldStateArchive(); @@ -78,8 +85,12 @@ public Block getGenesis() { return genesis; } - public KeyValueStorage getKeyValueStorage() { - return keyValueStorage; + public KeyValueStorage getBlockchainKeyValueStorage() { + return blockchainKeyValueStorage; + } + + public KeyValueStorage getVariablesKeyValueStorage() { + return variablesKeyValueStorage; } public MutableBlockchain getBlockchain() { @@ -99,12 +110,17 @@ public ProtocolContext getProtocolContext() { } public static class Builder { - - private KeyValueStorage keyValueStorage; + private KeyValueStorage variablesKeyValueStorage; + private KeyValueStorage blockchainKeyValueStorage; private ProtocolSchedule protocolSchedule; - public Builder keyValueStorage(final KeyValueStorage keyValueStorage) { - this.keyValueStorage = keyValueStorage; + public Builder variablesKeyValueStorage(final KeyValueStorage keyValueStorage) { + this.variablesKeyValueStorage = keyValueStorage; + return this; + } + + public Builder blockchainKeyValueStorage(final KeyValueStorage keyValueStorage) { + this.blockchainKeyValueStorage = keyValueStorage; return this; } @@ -125,10 +141,14 @@ public ExecutionContextTestFixture build() { EvmConfiguration.DEFAULT) .createProtocolSchedule(); } - if (keyValueStorage == null) { - keyValueStorage = new InMemoryKeyValueStorage(); + if (blockchainKeyValueStorage == null) { + blockchainKeyValueStorage = new InMemoryKeyValueStorage(); + } + if (variablesKeyValueStorage == null) { + variablesKeyValueStorage = new InMemoryKeyValueStorage(); } - return new ExecutionContextTestFixture(protocolSchedule, keyValueStorage); + return new ExecutionContextTestFixture( + protocolSchedule, variablesKeyValueStorage, blockchainKeyValueStorage); } } } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java index 58852f3b0ba..7c1dd689bcf 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java @@ -19,11 +19,13 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateKeyValueStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; @@ -44,15 +46,30 @@ public InMemoryKeyValueStorageProvider() { } public static MutableBlockchain createInMemoryBlockchain(final Block genesisBlock) { - return createInMemoryBlockchain(genesisBlock, new MainnetBlockHeaderFunctions()); + return createInMemoryBlockchain(genesisBlock, createInMemoryVariablesStorage()); + } + + public static MutableBlockchain createInMemoryBlockchain( + final Block genesisBlock, final VariablesStorage variablesStorage) { + return createInMemoryBlockchain( + genesisBlock, new MainnetBlockHeaderFunctions(), variablesStorage); } public static MutableBlockchain createInMemoryBlockchain( final Block genesisBlock, final BlockHeaderFunctions blockHeaderFunctions) { + return createInMemoryBlockchain( + genesisBlock, blockHeaderFunctions, createInMemoryVariablesStorage()); + } + + public static MutableBlockchain createInMemoryBlockchain( + final Block genesisBlock, + final BlockHeaderFunctions blockHeaderFunctions, + final VariablesStorage variablesStorage) { final InMemoryKeyValueStorage keyValueStorage = new InMemoryKeyValueStorage(); return DefaultBlockchain.createMutable( genesisBlock, - new KeyValueStoragePrefixedKeyBlockchainStorage(keyValueStorage, blockHeaderFunctions), + new KeyValueStoragePrefixedKeyBlockchainStorage( + keyValueStorage, variablesStorage, blockHeaderFunctions), new NoOpMetricsSystem(), 0); } @@ -87,4 +104,8 @@ public static MutableWorldState createInMemoryWorldState() { public static PrivateStateStorage createInMemoryPrivateStateStorage() { return new PrivateStateKeyValueStorage(new InMemoryKeyValueStorage()); } + + public static VariablesStorage createInMemoryVariablesStorage() { + return new VariablesKeyValueStorage(new InMemoryKeyValueStorage()); + } } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/VariablesStorageHelper.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/VariablesStorageHelper.java new file mode 100644 index 00000000000..9744748ef1f --- /dev/null +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/VariablesStorageHelper.java @@ -0,0 +1,153 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.CHAIN_HEAD_HASH; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.FINALIZED_BLOCK_HASH; +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 org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; + +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class VariablesStorageHelper { + public static final Bytes VARIABLES_PREFIX = Bytes.of(1); + public static final Hash SAMPLE_CHAIN_HEAD = Hash.fromHexStringLenient("0x01234"); + public static final Hash SAMPLE_FINALIZED = Hash.fromHexStringLenient("0x56789"); + public static final Hash SAMPLE_SAFE = Hash.fromHexStringLenient("0xabcde"); + public static final Hash FORK1 = Hash.fromHexStringLenient("0xf1357"); + public static final Hash FORK2 = Hash.fromHexStringLenient("0xf2468"); + public static final Bytes SAMPLE_FORK_HEADS = + RLP.encode(o -> o.writeList(List.of(FORK1, FORK2), (val, out) -> out.writeBytes(val))); + public static final Bytes SAMPLE_ERN_SEQNO = Bytes.fromHexStringLenient("0xabc123"); + public static final EnumSet NOT_PREFIXED_KEYS = EnumSet.of(SEQ_NO_STORE); + + public static Map getSampleVariableValues() { + final var variableValues = new EnumMap(Keys.class); + variableValues.put(CHAIN_HEAD_HASH, SAMPLE_CHAIN_HEAD); + variableValues.put(FINALIZED_BLOCK_HASH, SAMPLE_FINALIZED); + variableValues.put(SAFE_BLOCK_HASH, SAMPLE_SAFE); + variableValues.put(FORK_HEADS, SAMPLE_FORK_HEADS); + variableValues.put(SEQ_NO_STORE, SAMPLE_ERN_SEQNO); + return variableValues; + } + + public static void assertNoVariablesInStorage(final KeyValueStorage kvStorage) { + assertThat(kvStorage.streamKeys()).isEmpty(); + } + + public static void assertVariablesPresentInVariablesStorage( + final KeyValueStorage kvVariables, final Map variableValues) { + assertVariablesPresentInStorage(kvVariables, Bytes.EMPTY, variableValues); + } + + public static void assertVariablesPresentInBlockchainStorage( + final KeyValueStorage kvBlockchain, final Map variableValues) { + assertVariablesPresentInStorage(kvBlockchain, VARIABLES_PREFIX, variableValues); + } + + public static void assertVariablesPresentInStorage( + final KeyValueStorage kvStorage, final Bytes prefix, final Map variableValues) { + variableValues.forEach( + (k, v) -> + assertThat( + kvStorage.get( + Bytes.concatenate( + (NOT_PREFIXED_KEYS.contains(k) ? Bytes.EMPTY : prefix), + k.getBytes()) + .toArrayUnsafe())) + .contains(v.toArrayUnsafe())); + } + + public static void assertVariablesReturnedByBlockchainStorage( + final KeyValueStoragePrefixedKeyBlockchainStorage blockchainStorage, + final Map variableValues) { + variableValues.computeIfPresent( + CHAIN_HEAD_HASH, + (k, v) -> { + assertThat(blockchainStorage.getChainHead()).isPresent().contains(bytesToHash(v)); + return v; + }); + + variableValues.computeIfPresent( + FINALIZED_BLOCK_HASH, + (k, v) -> { + assertThat(blockchainStorage.getFinalized()).isPresent().contains(bytesToHash(v)); + return v; + }); + + variableValues.computeIfPresent( + SAFE_BLOCK_HASH, + (k, v) -> { + assertThat(blockchainStorage.getSafeBlock()).isPresent().contains(bytesToHash(v)); + return v; + }); + + variableValues.computeIfPresent( + FORK_HEADS, + (k, v) -> { + assertThat(blockchainStorage.getForkHeads()) + .containsExactlyElementsOf( + RLP.input(v).readList(in -> bytesToHash(in.readBytes32()))); + return v; + }); + } + + public static void populateBlockchainStorage( + final KeyValueStorage storage, final Map variableValues) { + populateStorage(storage, VARIABLES_PREFIX, variableValues); + } + + public static void populateVariablesStorage( + final KeyValueStorage storage, final Map variableValues) { + populateStorage(storage, Bytes.EMPTY, variableValues); + } + + public static void populateStorage( + final KeyValueStorage storage, final Bytes prefix, final Map variableValues) { + populateVariables(storage, prefix, variableValues); + } + + public static void populateVariables( + final KeyValueStorage storage, final Bytes prefix, final Map variableValues) { + final var tx = storage.startTransaction(); + variableValues.forEach( + (k, v) -> putVariable(tx, (NOT_PREFIXED_KEYS.contains(k) ? Bytes.EMPTY : prefix), k, v)); + tx.commit(); + } + + public static void putVariable( + final KeyValueStorageTransaction tx, final Bytes prefix, final Keys key, final Bytes value) { + tx.put(Bytes.concatenate(prefix, key.getBytes()).toArrayUnsafe(), value.toArrayUnsafe()); + } + + public static Hash bytesToHash(final Bytes bytes) { + return Hash.wrap(Bytes32.wrap(bytes, 0)); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index 79609bb3000..00d0797d39b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -38,7 +39,9 @@ public void singleChainPruning() { final BlockDataGenerator gen = new BlockDataGenerator(); final BlockchainStorage blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, @@ -74,7 +77,9 @@ public void forkPruning() { final BlockDataGenerator gen = new BlockDataGenerator(); final BlockchainStorage blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java index 5fece9c6359..b3523e1d9b7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.metrics.MetricsSystemFactory; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; @@ -52,8 +53,10 @@ public void initializeNew() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); assertBlockDataIsStored(blockchain, genesisBlock, Collections.emptyList()); assertBlockIsHead(blockchain, genesisBlock); @@ -67,11 +70,13 @@ public void initializeExisting() { // Write to kv store final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - createMutableBlockchain(kvStore, genesisBlock); + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); // Initialize a new blockchain store with kvStore that already contains data - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); assertBlockDataIsStored(blockchain, genesisBlock, Collections.emptyList()); assertBlockIsHead(blockchain, genesisBlock); @@ -85,11 +90,15 @@ public void initializeExistingWithWrongGenesisBlock() { // Write to kv store final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - createMutableBlockchain(kvStore, genesisBlock); + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); // Initialize a new blockchain store with same kvStore, but different genesis block - assertThatThrownBy(() -> createMutableBlockchain(kvStore, gen.genesisBlock(), "/test/path")) + assertThatThrownBy( + () -> + createMutableBlockchain( + kvStore, kvStoreVariables, gen.genesisBlock(), "/test/path")) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining( "Supplied genesis block does not match chain data stored in /test/path.\n" @@ -101,13 +110,14 @@ public void initializeExistingWithWrongGenesisBlock() { public void initializeReadOnly_withGenesisBlock() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); // Write genesis block to storage - createMutableBlockchain(kvStore, genesisBlock); + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); // Create read only chain - final Blockchain blockchain = createBlockchain(kvStore); + final Blockchain blockchain = createBlockchain(kvStore, kvStoreVariables); assertBlockDataIsStored(blockchain, genesisBlock, Collections.emptyList()); assertBlockIsHead(blockchain, genesisBlock); @@ -118,12 +128,14 @@ public void initializeReadOnly_withGenesisBlock() { public void initializeReadOnly_withSmallChain() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final List blocks = gen.blockSequence(10); final List> blockReceipts = new ArrayList<>(blocks.size()); blockReceipts.add(Collections.emptyList()); // Write small chain to storage - final MutableBlockchain mutableBlockchain = createMutableBlockchain(kvStore, blocks.get(0)); + final MutableBlockchain mutableBlockchain = + createMutableBlockchain(kvStore, kvStoreVariables, blocks.get(0)); for (int i = 1; i < blocks.size(); i++) { final Block block = blocks.get(i); final List receipts = gen.receipts(block); @@ -132,7 +144,7 @@ public void initializeReadOnly_withSmallChain() { } // Create read only chain - final Blockchain blockchain = createBlockchain(kvStore); + final Blockchain blockchain = createBlockchain(kvStore, kvStoreVariables); for (int i = 0; i < blocks.size(); i++) { assertBlockDataIsStored(blockchain, blocks.get(i), blockReceipts.get(i)); @@ -148,12 +160,14 @@ public void initializeReadOnly_withGiantDifficultyAndLiveMetrics() { gen.setBlockOptionsSupplier( () -> BlockOptions.create().setDifficulty(Difficulty.of(Long.MAX_VALUE))); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final List blocks = gen.blockSequence(10); final List> blockReceipts = new ArrayList<>(blocks.size()); blockReceipts.add(Collections.emptyList()); // Write small chain to storage - final MutableBlockchain mutableBlockchain = createMutableBlockchain(kvStore, blocks.get(0)); + final MutableBlockchain mutableBlockchain = + createMutableBlockchain(kvStore, kvStoreVariables, blocks.get(0)); for (int i = 1; i < blocks.size(); i++) { final Block block = blocks.get(i); final List receipts = gen.receipts(block); @@ -164,7 +178,7 @@ public void initializeReadOnly_withGiantDifficultyAndLiveMetrics() { // Create read only chain final Blockchain blockchain = DefaultBlockchain.create( - createStorage(kvStore), + createStorage(kvStore, kvStoreVariables), MetricsSystemFactory.create(MetricsConfiguration.builder().enabled(true).build()), 0); @@ -179,8 +193,8 @@ public void initializeReadOnly_withGiantDifficultyAndLiveMetrics() { @Test public void initializeReadOnly_emptyStorage() { final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - - assertThatThrownBy(() -> createBlockchain(kvStore)) + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + assertThatThrownBy(() -> createBlockchain(kvStore, kvStoreVariables)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Cannot create Blockchain from empty storage"); } @@ -190,8 +204,10 @@ public void appendBlock() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); final BlockDataGenerator.BlockOptions options = new BlockDataGenerator.BlockOptions() @@ -218,8 +234,10 @@ public void appendUnconnectedBlock() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); final BlockDataGenerator.BlockOptions options = new BlockDataGenerator.BlockOptions().setBlockNumber(1L).setParentHash(Hash.ZERO); @@ -234,8 +252,10 @@ public void appendBlockWithMismatchedReceipts() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); final BlockDataGenerator.BlockOptions options = new BlockDataGenerator.BlockOptions() @@ -256,7 +276,9 @@ public void createSmallChain() { chain.stream().map(gen::receipts).collect(Collectors.toList()); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, chain.get(0)); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, chain.get(0)); for (int i = 1; i < chain.size(); i++) { blockchain.appendBlock(chain.get(i), blockReceipts.get(i)); } @@ -281,7 +303,9 @@ public void appendBlockWithReorgToChainAtEqualHeight() { final List> blockReceipts = chain.stream().map(gen::receipts).collect(Collectors.toList()); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, chain.get(0)); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, chain.get(0)); // Listen to block events and add the Logs here List logsWithMetadata = new ArrayList<>(); @@ -358,7 +382,9 @@ public void appendBlockWithReorgToShorterChain() { final List> blockReceipts = chain.stream().map(gen::receipts).collect(Collectors.toList()); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, chain.get(0)); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, chain.get(0)); // Listen to block events and add the Logs here List logsWithMetadata = new ArrayList<>(); blockchain.observeBlockAdded(event -> logsWithMetadata.addAll(event.getLogsWithMetadata())); @@ -477,7 +503,9 @@ public void appendBlockWithReorgToLongerChain() { final List> blockReceipts = chain.stream().map(gen::receipts).collect(Collectors.toList()); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, chain.get(0)); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, chain.get(0)); // Listen to block events and add the Logs here List logsWithMetadata = new ArrayList<>(); blockchain.observeBlockAdded(event -> logsWithMetadata.addAll(event.getLogsWithMetadata())); @@ -587,7 +615,9 @@ public void reorgWithOverlappingTransactions() { final List> blockReceipts = chain.stream().map(gen::receipts).collect(Collectors.toList()); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, chain.get(0)); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, chain.get(0)); // Listen to block events and add the Logs here List logsWithMetadata = new ArrayList<>(); blockchain.observeBlockAdded(event -> logsWithMetadata.addAll(event.getLogsWithMetadata())); @@ -658,7 +688,9 @@ public void rewindChain() { final List> blockReceipts = chain.stream().map(gen::receipts).collect(Collectors.toList()); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, chain.get(0)); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, chain.get(0)); for (int i = 1; i < chain.size(); i++) { blockchain.appendBlock(chain.get(i), blockReceipts.get(i)); } @@ -697,7 +729,9 @@ public void appendBlockForFork() { final List> blockReceipts = chain.stream().map(gen::receipts).collect(Collectors.toList()); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, chain.get(0)); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, chain.get(0)); // Listen to block events and add the Logs here List logsWithMetadata = new ArrayList<>(); blockchain.observeBlockAdded(event -> logsWithMetadata.addAll(event.getLogsWithMetadata())); @@ -782,8 +816,10 @@ public void appendBlockForFork() { public void blockAddedObserver_removeNonexistentObserver() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); assertThat(blockchain.removeObserver(7)).isFalse(); } @@ -793,8 +829,10 @@ public void blockAddedObserver_addRemoveSingle() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); final long observerId = blockchain.observeBlockAdded(__ -> {}); assertThat(blockchain.observerCount()).isEqualTo(1); @@ -808,8 +846,10 @@ public void blockAddedObserver_nullObserver() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); assertThatThrownBy(() -> blockchain.observeBlockAdded(null)) .isInstanceOf(NullPointerException.class); @@ -820,8 +860,10 @@ public void blockAddedObserver_addRemoveMultiple() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); final long observerId1 = blockchain.observeBlockAdded(__ -> {}); assertThat(blockchain.observerCount()).isEqualTo(1); @@ -847,8 +889,10 @@ public void blockAddedObserver_invokedSingle() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); final BlockDataGenerator.BlockOptions options = new BlockDataGenerator.BlockOptions() @@ -870,8 +914,10 @@ public void blockAddedObserver_invokedMultiple() { final BlockDataGenerator gen = new BlockDataGenerator(); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); + final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); final Block genesisBlock = gen.genesisBlock(); - final DefaultBlockchain blockchain = createMutableBlockchain(kvStore, genesisBlock); + final DefaultBlockchain blockchain = + createMutableBlockchain(kvStore, kvStoreVariables, genesisBlock); final BlockDataGenerator.BlockOptions options = new BlockDataGenerator.BlockOptions() @@ -944,26 +990,40 @@ private void assertTotalDifficultiesAreConsistent(final Blockchain blockchain, f assertThat(blockchain.getChainHead().getTotalDifficulty()).isEqualTo(td); } - private BlockchainStorage createStorage(final KeyValueStorage kvStore) { + private BlockchainStorage createStorage( + final KeyValueStorage kvStoreChain, final KeyValueStorage kvStorageVariables) { return new KeyValueStoragePrefixedKeyBlockchainStorage( - kvStore, new MainnetBlockHeaderFunctions()); + kvStoreChain, + new VariablesKeyValueStorage(kvStorageVariables), + new MainnetBlockHeaderFunctions()); } private DefaultBlockchain createMutableBlockchain( - final KeyValueStorage kvStore, final Block genesisBlock) { + final KeyValueStorage kvStore, + final KeyValueStorage kvStorageVariables, + final Block genesisBlock) { return (DefaultBlockchain) DefaultBlockchain.createMutable( - genesisBlock, createStorage(kvStore), new NoOpMetricsSystem(), 0); + genesisBlock, createStorage(kvStore, kvStorageVariables), new NoOpMetricsSystem(), 0); } private DefaultBlockchain createMutableBlockchain( - final KeyValueStorage kvStore, final Block genesisBlock, final String dataDirectory) { + final KeyValueStorage kvStore, + final KeyValueStorage kvStorageVariables, + final Block genesisBlock, + final String dataDirectory) { return (DefaultBlockchain) DefaultBlockchain.createMutable( - genesisBlock, createStorage(kvStore), new NoOpMetricsSystem(), 0, dataDirectory); + genesisBlock, + createStorage(kvStore, kvStorageVariables), + new NoOpMetricsSystem(), + 0, + dataDirectory); } - private Blockchain createBlockchain(final KeyValueStorage kvStore) { - return DefaultBlockchain.create(createStorage(kvStore), new NoOpMetricsSystem(), 0); + private Blockchain createBlockchain( + final KeyValueStorage kvStore, final KeyValueStorage kvStorageVariables) { + return DefaultBlockchain.create( + createStorage(kvStore, kvStorageVariables), new NoOpMetricsSystem(), 0); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorageTest.java new file mode 100644 index 00000000000..cd072780cfc --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorageTest.java @@ -0,0 +1,103 @@ +/* + * 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.storage.keyvalue; + +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.FINALIZED_BLOCK_HASH; +import static org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys.SAFE_BLOCK_HASH; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.assertNoVariablesInStorage; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.assertVariablesPresentInVariablesStorage; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.assertVariablesReturnedByBlockchainStorage; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.getSampleVariableValues; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.populateBlockchainStorage; +import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.populateVariablesStorage; +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.ethereum.chain.VariablesStorage; +import org.hyperledger.besu.ethereum.chain.VariablesStorage.Keys; +import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import java.util.Map; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class KeyValueStoragePrefixedKeyBlockchainStorageTest { + private final BlockHeaderFunctions blockHeaderFunctions = mock(BlockHeaderFunctions.class); + private KeyValueStorage kvBlockchain; + private KeyValueStorage kvVariables; + private VariablesStorage variablesStorage; + private Map variableValues; + + @BeforeEach + public void setup() { + kvBlockchain = new InMemoryKeyValueStorage(); + kvVariables = new InMemoryKeyValueStorage(); + variablesStorage = new VariablesKeyValueStorage(kvVariables); + variableValues = getSampleVariableValues(); + } + + @Test + public void migrationToVariablesStorage() { + populateBlockchainStorage(kvBlockchain, variableValues); + + assertNoVariablesInStorage(kvVariables); + + final var blockchainStorage = + new KeyValueStoragePrefixedKeyBlockchainStorage( + kvBlockchain, variablesStorage, blockHeaderFunctions); + + assertNoVariablesInStorage(kvBlockchain); + assertVariablesPresentInVariablesStorage(kvVariables, variableValues); + + assertVariablesReturnedByBlockchainStorage(blockchainStorage, variableValues); + } + + @Test + public void migrationToVariablesStorageWhenSomeVariablesDoNotExist() { + variableValues.remove(FINALIZED_BLOCK_HASH); + variableValues.remove(SAFE_BLOCK_HASH); + populateBlockchainStorage(kvBlockchain, variableValues); + + assertNoVariablesInStorage(kvVariables); + + final var blockchainStorage = + new KeyValueStoragePrefixedKeyBlockchainStorage( + kvBlockchain, variablesStorage, blockHeaderFunctions); + + assertNoVariablesInStorage(kvBlockchain); + assertVariablesPresentInVariablesStorage(kvVariables, variableValues); + + assertVariablesReturnedByBlockchainStorage(blockchainStorage, variableValues); + } + + @Test + public void doesNothingIfVariablesAlreadyMigrated() { + populateVariablesStorage(kvVariables, variableValues); + + assertNoVariablesInStorage(kvBlockchain); + + final var blockchainStorage = + new KeyValueStoragePrefixedKeyBlockchainStorage( + kvBlockchain, variablesStorage, blockHeaderFunctions); + + assertNoVariablesInStorage(kvBlockchain); + assertVariablesPresentInVariablesStorage(kvVariables, variableValues); + + assertVariablesReturnedByBlockchainStorage(blockchainStorage, variableValues); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/PrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/PrunerTest.java index dfaef9fae1d..fa90bf6a8e2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/PrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/PrunerTest.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.testutil.MockExecutorService; @@ -60,7 +61,9 @@ public class PrunerTest { public void shouldMarkCorrectBlockAndSweep() throws ExecutionException, InterruptedException { final BlockchainStorage blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); final MutableBlockchain blockchain = DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); @@ -81,7 +84,9 @@ public void shouldMarkCorrectBlockAndSweep() throws ExecutionException, Interrup public void shouldOnlySweepAfterBlockConfirmationPeriodAndRetentionPeriodEnds() { final BlockchainStorage blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); final MutableBlockchain blockchain = DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); @@ -107,7 +112,9 @@ public void shouldOnlySweepAfterBlockConfirmationPeriodAndRetentionPeriodEnds() public void abortsPruningWhenFullyMarkedBlockNoLongerOnCanonicalChain() { final BlockchainStorage blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); final MutableBlockchain blockchain = DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); @@ -177,7 +184,9 @@ public void shouldRejectInvalidArguments() { public void shouldCleanUpPruningStrategyOnShutdown() throws InterruptedException { final BlockchainStorage blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); final MutableBlockchain blockchain = DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java index e57a8f87a29..1520edb0679 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.Checkpoint; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -49,7 +50,9 @@ public class CheckPointBlockImportStepTest { public void setup() { blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), new MainnetBlockHeaderFunctions()); + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions()); blockchain = DefaultBlockchain.createMutable( generateBlock(0), blockchainStorage, mock(MetricsSystem.class), 0); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java index a2ce12caa55..fd8f7a8c375 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; @@ -53,6 +54,20 @@ public class DataStoreModule { List.of(KeyValueSegmentIdentifier.values()), RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS)); + @Provides + @Singleton + @Named("variables") + KeyValueStorage provideVariablesKeyValueStorage( + @Named("KeyValueStorageName") final String keyValueStorageName, + final BesuConfiguration commonConfiguration, + final MetricsSystem metricsSystem) { + return constructKeyValueStorage( + keyValueStorageName, + commonConfiguration, + metricsSystem, + KeyValueSegmentIdentifier.VARIABLES); + } + @Provides @Singleton @Named("blockchain") @@ -127,7 +142,9 @@ private KeyValueStorage constructKeyValueStorage( @Singleton static BlockchainStorage provideBlockchainStorage( @Named("blockchain") final KeyValueStorage keyValueStorage, + @Named("variables") final KeyValueStorage variablesKeyValueStorage, final BlockHeaderFunctions blockHashFunction) { - return new KeyValueStoragePrefixedKeyBlockchainStorage(keyValueStorage, blockHashFunction); + return new KeyValueStoragePrefixedKeyBlockchainStorage( + keyValueStorage, new VariablesKeyValueStorage(variablesKeyValueStorage), blockHashFunction); } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java index 6c1812335cd..8af596018a1 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java @@ -15,12 +15,12 @@ package org.hyperledger.besu.ethereum.p2p.discovery; import static com.google.common.base.Preconditions.checkArgument; -import static java.nio.charset.StandardCharsets.UTF_8; import org.hyperledger.besu.crypto.Hash; import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.cryptoservices.NodeKey; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration; import org.hyperledger.besu.ethereum.p2p.discovery.internal.Packet; @@ -34,12 +34,9 @@ import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions; import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent; import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.nat.NatService; import org.hyperledger.besu.plugin.data.EnodeURL; import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; import org.hyperledger.besu.util.NetworkUtility; import java.net.InetSocketAddress; @@ -70,7 +67,6 @@ */ public abstract class PeerDiscoveryAgent { private static final Logger LOG = LoggerFactory.getLogger(PeerDiscoveryAgent.class); - private static final String SEQ_NO_STORE_KEY = "local-enr-seqno"; private static final com.google.common.base.Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); @@ -101,7 +97,7 @@ public abstract class PeerDiscoveryAgent { /* Is discovery enabled? */ private boolean isActive = false; - private final StorageProvider storageProvider; + private final VariablesStorage variablesStorage; private final Supplier> forkIdSupplier; private String advertisedAddress; @@ -130,7 +126,7 @@ protected PeerDiscoveryAgent( this.id = nodeKey.getPublicKey().getEncodedBytes(); - this.storageProvider = storageProvider; + this.variablesStorage = storageProvider.createVariablesStorage(); this.forkIdManager = forkIdManager; this.forkIdSupplier = () -> forkIdManager.getForkIdForChainHead().getForkIdAsBytesList(); this.rlpxAgent = rlpxAgent; @@ -187,14 +183,9 @@ public void updateNodeRecord() { return; } - final KeyValueStorage keyValueStorage = - storageProvider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.BLOCKCHAIN); final NodeRecordFactory nodeRecordFactory = NodeRecordFactory.DEFAULT; final Optional existingNodeRecord = - keyValueStorage - .get(Bytes.of(SEQ_NO_STORE_KEY.getBytes(UTF_8)).toArray()) - .map(Bytes::of) - .map(nodeRecordFactory::fromBytes); + variablesStorage.getLocalEnrSeqno().map(nodeRecordFactory::fromBytes); final Bytes addressBytes = Bytes.of(InetAddresses.forString(advertisedAddress).getAddress()); final Optional maybeEnodeURL = localNode.map(DiscoveryPeer::getEnodeURL); @@ -236,12 +227,10 @@ public void updateNodeRecord() { .slice(0, 64)); LOG.info("Writing node record to disk. {}", nodeRecord); - final KeyValueStorageTransaction keyValueStorageTransaction = - keyValueStorage.startTransaction(); - keyValueStorageTransaction.put( - Bytes.wrap(SEQ_NO_STORE_KEY.getBytes(UTF_8)).toArray(), - nodeRecord.serialize().toArray()); - keyValueStorageTransaction.commit(); + final var variablesUpdater = variablesStorage.updater(); + variablesUpdater.setLocalEnrSeqno(nodeRecord.serialize()); + variablesUpdater.commit(); + return nodeRecord; }); localNode diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java index bbfb1fba786..95cb744ace4 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; @@ -55,6 +56,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; @@ -254,9 +256,12 @@ private static MutableBlockchain createInMemoryBlockchain(final Block genesisBlo private static MutableBlockchain createInMemoryBlockchain( final Block genesisBlock, final BlockHeaderFunctions blockHeaderFunctions) { final InMemoryKeyValueStorage keyValueStorage = new InMemoryKeyValueStorage(); + final VariablesStorage variablesStorage = + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()); return DefaultBlockchain.createMutable( genesisBlock, - new KeyValueStoragePrefixedKeyBlockchainStorage(keyValueStorage, blockHeaderFunctions), + new KeyValueStoragePrefixedKeyBlockchainStorage( + keyValueStorage, variablesStorage, blockHeaderFunctions), new NoOpMetricsSystem(), 100); } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java index cdca868a27e..32075f364ae 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java @@ -218,24 +218,9 @@ void initColumnHandler() throws RocksDBException { } BlockBasedTableConfig createBlockBasedTableConfig(final RocksDBConfiguration config) { - if (config.isHighSpec()) return createBlockBasedTableConfigHighSpec(); - else return createBlockBasedTableConfigDefault(config); - } - - private BlockBasedTableConfig createBlockBasedTableConfigHighSpec() { - final LRUCache cache = new LRUCache(ROCKSDB_BLOCKCACHE_SIZE_HIGH_SPEC); - return new BlockBasedTableConfig() - .setFormatVersion(ROCKSDB_FORMAT_VERSION) - .setBlockCache(cache) - .setFilterPolicy(new BloomFilter(10, false)) - .setPartitionFilters(true) - .setCacheIndexAndFilterBlocks(false) - .setBlockSize(ROCKSDB_BLOCK_SIZE); - } - - private BlockBasedTableConfig createBlockBasedTableConfigDefault( - final RocksDBConfiguration config) { - final LRUCache cache = new LRUCache(config.getCacheCapacity()); + final LRUCache cache = + new LRUCache( + config.isHighSpec() ? ROCKSDB_BLOCKCACHE_SIZE_HIGH_SPEC : config.getCacheCapacity()); return new BlockBasedTableConfig() .setFormatVersion(ROCKSDB_FORMAT_VERSION) .setBlockCache(cache)