mirror of https://github.com/hyperledger/besu
Introduce variables storage (#5471)
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>pull/5540/head
parent
debd53ccc9
commit
8bc939d236
@ -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()); |
||||
} |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -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<Hash> getChainHead(); |
||||
|
||||
Collection<Hash> getForkHeads(); |
||||
|
||||
Optional<Hash> getFinalized(); |
||||
|
||||
Optional<Hash> getSafeBlock(); |
||||
|
||||
Optional<Bytes> getLocalEnrSeqno(); |
||||
|
||||
Updater updater(); |
||||
|
||||
interface Updater { |
||||
|
||||
void setChainHead(Hash blockHash); |
||||
|
||||
void setForkHeads(Collection<Hash> forkHeadHashes); |
||||
|
||||
void setFinalized(Hash blockHash); |
||||
|
||||
void setSafeBlock(Hash blockHash); |
||||
|
||||
void setLocalEnrSeqno(Bytes nodeRecord); |
||||
|
||||
void removeAll(); |
||||
|
||||
void commit(); |
||||
|
||||
void rollback(); |
||||
} |
||||
} |
@ -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<Hash> getChainHead() { |
||||
return getVariable(CHAIN_HEAD_HASH).map(this::bytesToHash); |
||||
} |
||||
|
||||
@Override |
||||
public Collection<Hash> getForkHeads() { |
||||
return getVariable(FORK_HEADS) |
||||
.map(bytes -> RLP.input(bytes).readList(in -> this.bytesToHash(in.readBytes32()))) |
||||
.orElse(Lists.newArrayList()); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getFinalized() { |
||||
return getVariable(FINALIZED_BLOCK_HASH).map(this::bytesToHash); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Hash> getSafeBlock() { |
||||
return getVariable(SAFE_BLOCK_HASH).map(this::bytesToHash); |
||||
} |
||||
|
||||
@Override |
||||
public Optional<Bytes> 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<Bytes> 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<Hash> 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()); |
||||
} |
||||
} |
||||
} |
@ -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<Keys> NOT_PREFIXED_KEYS = EnumSet.of(SEQ_NO_STORE); |
||||
|
||||
public static Map<Keys, Bytes> getSampleVariableValues() { |
||||
final var variableValues = new EnumMap<Keys, Bytes>(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<Keys, Bytes> variableValues) { |
||||
assertVariablesPresentInStorage(kvVariables, Bytes.EMPTY, variableValues); |
||||
} |
||||
|
||||
public static void assertVariablesPresentInBlockchainStorage( |
||||
final KeyValueStorage kvBlockchain, final Map<Keys, Bytes> variableValues) { |
||||
assertVariablesPresentInStorage(kvBlockchain, VARIABLES_PREFIX, variableValues); |
||||
} |
||||
|
||||
public static void assertVariablesPresentInStorage( |
||||
final KeyValueStorage kvStorage, final Bytes prefix, final Map<Keys, Bytes> 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<Keys, Bytes> 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<Keys, Bytes> variableValues) { |
||||
populateStorage(storage, VARIABLES_PREFIX, variableValues); |
||||
} |
||||
|
||||
public static void populateVariablesStorage( |
||||
final KeyValueStorage storage, final Map<Keys, Bytes> variableValues) { |
||||
populateStorage(storage, Bytes.EMPTY, variableValues); |
||||
} |
||||
|
||||
public static void populateStorage( |
||||
final KeyValueStorage storage, final Bytes prefix, final Map<Keys, Bytes> variableValues) { |
||||
populateVariables(storage, prefix, variableValues); |
||||
} |
||||
|
||||
public static void populateVariables( |
||||
final KeyValueStorage storage, final Bytes prefix, final Map<Keys, Bytes> 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)); |
||||
} |
||||
} |
@ -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<Keys, Bytes> 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); |
||||
} |
||||
} |
Loading…
Reference in new issue