mirror of https://github.com/hyperledger/besu
commit
ccdb980692
@ -0,0 +1,200 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.tests.acceptance.dsl; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Blockchain; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.admin.AdminConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.bft.BftConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.clique.CliqueConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.eth.EthConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.login.LoginConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.net.NetConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.perm.PermissioningConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.priv.PrivConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.process.ExitedWithCode; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.txpool.TxPoolConditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.condition.web3.Web3Conditions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.contract.ContractVerifier; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.Node; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeFactory; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.permissioning.PermissionedNodeBuilder; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.account.AccountTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.admin.AdminTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.bft.BftTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.clique.CliqueTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.contract.ContractTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolTransactions; |
||||
import org.hyperledger.besu.tests.acceptance.dsl.transaction.web3.Web3Transactions; |
||||
|
||||
import java.io.BufferedReader; |
||||
import java.io.IOException; |
||||
import java.io.InputStreamReader; |
||||
import java.math.BigInteger; |
||||
import java.util.concurrent.ExecutorService; |
||||
import java.util.concurrent.Executors; |
||||
|
||||
import org.apache.logging.log4j.ThreadContext; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.TestInfo; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** |
||||
* Superclass for acceptance tests. For now (transition to junit5 is ongoing) this class supports |
||||
* junit5 format. Once the transition is complete, this class can be removed and recombined with |
||||
* AcceptanceTestBase (original). |
||||
*/ |
||||
@ExtendWith(AcceptanceTestBaseTestWatcher.class) |
||||
public class AcceptanceTestBaseJunit5 { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AcceptanceTestBaseJunit5.class); |
||||
|
||||
protected final Accounts accounts; |
||||
protected final AccountTransactions accountTransactions; |
||||
protected final AdminConditions admin; |
||||
protected final AdminTransactions adminTransactions; |
||||
protected final Blockchain blockchain; |
||||
protected final CliqueConditions clique; |
||||
protected final CliqueTransactions cliqueTransactions; |
||||
protected final Cluster cluster; |
||||
protected final ContractVerifier contractVerifier; |
||||
protected final ContractTransactions contractTransactions; |
||||
protected final EthConditions eth; |
||||
protected final EthTransactions ethTransactions; |
||||
protected final BftTransactions bftTransactions; |
||||
protected final BftConditions bft; |
||||
protected final LoginConditions login; |
||||
protected final NetConditions net; |
||||
protected final BesuNodeFactory besu; |
||||
protected final PermissioningConditions perm; |
||||
protected final PermissionedNodeBuilder permissionedNodeBuilder; |
||||
protected final PermissioningTransactions permissioningTransactions; |
||||
protected final MinerTransactions minerTransactions; |
||||
protected final Web3Conditions web3; |
||||
protected final PrivConditions priv; |
||||
protected final PrivacyTransactions privacyTransactions; |
||||
protected final TxPoolConditions txPoolConditions; |
||||
protected final TxPoolTransactions txPoolTransactions; |
||||
protected final ExitedWithCode exitedSuccessfully; |
||||
|
||||
private final ExecutorService outputProcessorExecutor = Executors.newCachedThreadPool(); |
||||
|
||||
protected AcceptanceTestBaseJunit5() { |
||||
ethTransactions = new EthTransactions(); |
||||
accounts = new Accounts(ethTransactions); |
||||
adminTransactions = new AdminTransactions(); |
||||
cliqueTransactions = new CliqueTransactions(); |
||||
bftTransactions = new BftTransactions(); |
||||
accountTransactions = new AccountTransactions(accounts); |
||||
permissioningTransactions = new PermissioningTransactions(); |
||||
privacyTransactions = new PrivacyTransactions(); |
||||
contractTransactions = new ContractTransactions(); |
||||
minerTransactions = new MinerTransactions(); |
||||
|
||||
blockchain = new Blockchain(ethTransactions); |
||||
clique = new CliqueConditions(ethTransactions, cliqueTransactions); |
||||
eth = new EthConditions(ethTransactions); |
||||
bft = new BftConditions(bftTransactions); |
||||
login = new LoginConditions(); |
||||
net = new NetConditions(new NetTransactions()); |
||||
cluster = new Cluster(net); |
||||
perm = new PermissioningConditions(permissioningTransactions); |
||||
priv = new PrivConditions(privacyTransactions); |
||||
admin = new AdminConditions(adminTransactions); |
||||
web3 = new Web3Conditions(new Web3Transactions()); |
||||
besu = new BesuNodeFactory(); |
||||
txPoolTransactions = new TxPoolTransactions(); |
||||
txPoolConditions = new TxPoolConditions(txPoolTransactions); |
||||
contractVerifier = new ContractVerifier(accounts.getPrimaryBenefactor()); |
||||
permissionedNodeBuilder = new PermissionedNodeBuilder(); |
||||
exitedSuccessfully = new ExitedWithCode(0); |
||||
} |
||||
|
||||
@BeforeEach |
||||
public void setUp(final TestInfo testInfo) { |
||||
// log4j is configured to create a file per test
|
||||
// build/acceptanceTestLogs/${ctx:class}.${ctx:test}.log
|
||||
ThreadContext.put("class", this.getClass().getSimpleName()); |
||||
ThreadContext.put("test", testInfo.getTestMethod().get().getName()); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void tearDownAcceptanceTestBase() { |
||||
reportMemory(); |
||||
cluster.close(); |
||||
} |
||||
|
||||
public void reportMemory() { |
||||
String os = System.getProperty("os.name"); |
||||
String[] command = null; |
||||
if (os.contains("Linux")) { |
||||
command = new String[] {"/usr/bin/top", "-n", "1", "-o", "%MEM", "-b", "-c", "-w", "180"}; |
||||
} |
||||
if (os.contains("Mac")) { |
||||
command = new String[] {"/usr/bin/top", "-l", "1", "-o", "mem", "-n", "20"}; |
||||
} |
||||
if (command != null) { |
||||
LOG.info("Memory usage at end of test:"); |
||||
final ProcessBuilder processBuilder = |
||||
new ProcessBuilder(command) |
||||
.redirectErrorStream(true) |
||||
.redirectInput(ProcessBuilder.Redirect.INHERIT); |
||||
try { |
||||
final Process memInfoProcess = processBuilder.start(); |
||||
outputProcessorExecutor.execute(() -> printOutput(memInfoProcess)); |
||||
memInfoProcess.waitFor(); |
||||
LOG.debug("Memory info process exited with code {}", memInfoProcess.exitValue()); |
||||
} catch (final Exception e) { |
||||
LOG.warn("Error running memory information process", e); |
||||
} |
||||
} else { |
||||
LOG.info("Don't know how to report memory for OS {}", os); |
||||
} |
||||
} |
||||
|
||||
private void printOutput(final Process process) { |
||||
try (final BufferedReader in = |
||||
new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8))) { |
||||
String line = in.readLine(); |
||||
while (line != null) { |
||||
LOG.info(line); |
||||
line = in.readLine(); |
||||
} |
||||
} catch (final IOException e) { |
||||
LOG.warn("Failed to read output from memory information process: ", e); |
||||
} |
||||
} |
||||
|
||||
protected void waitForBlockHeight(final Node node, final long blockchainHeight) { |
||||
WaitUtils.waitFor( |
||||
120, |
||||
() -> |
||||
assertThat(node.execute(ethTransactions.blockNumber())) |
||||
.isGreaterThanOrEqualTo(BigInteger.valueOf(blockchainHeight))); |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.tests.acceptance.dsl; |
||||
|
||||
import java.io.File; |
||||
|
||||
import org.junit.jupiter.api.extension.ExtensionContext; |
||||
import org.junit.jupiter.api.extension.TestWatcher; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
public class AcceptanceTestBaseTestWatcher implements TestWatcher { |
||||
private static final Logger LOG = LoggerFactory.getLogger(AcceptanceTestBaseTestWatcher.class); |
||||
|
||||
@Override |
||||
public void testFailed(final ExtensionContext extensionContext, final Throwable e) { |
||||
// add the result at the end of the log, so it is self-sufficient
|
||||
LOG.error( |
||||
"=========================================================================================="); |
||||
LOG.error("Test failed. Reported Throwable at the point of failure:", e); |
||||
LOG.error(e.getMessage()); |
||||
} |
||||
|
||||
@Override |
||||
public void testSuccessful(final ExtensionContext extensionContext) { |
||||
// if so configured, delete logs of successful tests
|
||||
if (!Boolean.getBoolean("acctests.keepLogsOfPassingTests")) { |
||||
try { |
||||
// log4j is configured to create a file per test
|
||||
// build/acceptanceTestLogs/${ctx:class}.${ctx:test}.log
|
||||
String pathname = |
||||
"build/acceptanceTestLogs/" |
||||
+ extensionContext.getTestClass().get().getSimpleName() |
||||
+ "." |
||||
+ extensionContext.getTestMethod().get().getName() |
||||
+ ".log"; |
||||
LOG.info("Test successful, deleting log at {}", pathname); |
||||
final File file = new File(pathname); |
||||
file.delete(); |
||||
} catch (final Exception e) { |
||||
LOG.error("could not delete test file", e); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,361 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.cli.subcommands.storage; |
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument; |
||||
import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.Blockchain; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.ObjectInputStream; |
||||
import java.io.ObjectOutputStream; |
||||
import java.io.PrintWriter; |
||||
import java.nio.file.Path; |
||||
import java.util.ArrayList; |
||||
import java.util.IdentityHashMap; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes32; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** Helper class for counting and pruning trie logs */ |
||||
public class TrieLogHelper { |
||||
private static final String TRIE_LOG_FILE = "trieLogsToRetain"; |
||||
private static final long BATCH_SIZE = 20_000; |
||||
private static final int ROCKSDB_MAX_INSERTS_PER_TRANSACTION = 1000; |
||||
private static final Logger LOG = LoggerFactory.getLogger(TrieLogHelper.class); |
||||
|
||||
static void prune( |
||||
final DataStorageConfiguration config, |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
final MutableBlockchain blockchain, |
||||
final Path dataDirectoryPath) { |
||||
final String batchFileNameBase = |
||||
dataDirectoryPath.resolve(DATABASE_PATH).resolve(TRIE_LOG_FILE).toString(); |
||||
|
||||
validatePruneConfiguration(config); |
||||
|
||||
final long layersToRetain = config.getUnstable().getBonsaiTrieLogRetentionThreshold(); |
||||
|
||||
final long chainHeight = blockchain.getChainHeadBlockNumber(); |
||||
|
||||
final long lastBlockNumberToRetainTrieLogsFor = chainHeight - layersToRetain + 1; |
||||
|
||||
if (!validPruneRequirements(blockchain, chainHeight, lastBlockNumberToRetainTrieLogsFor)) { |
||||
return; |
||||
} |
||||
|
||||
final long numberOfBatches = calculateNumberofBatches(layersToRetain); |
||||
|
||||
processTrieLogBatches( |
||||
rootWorldStateStorage, |
||||
blockchain, |
||||
chainHeight, |
||||
lastBlockNumberToRetainTrieLogsFor, |
||||
numberOfBatches, |
||||
batchFileNameBase); |
||||
|
||||
if (rootWorldStateStorage.streamTrieLogKeys(layersToRetain).count() == layersToRetain) { |
||||
deleteFiles(batchFileNameBase, numberOfBatches); |
||||
LOG.info("Prune ran successfully. Enjoy some disk space back! \uD83D\uDE80"); |
||||
} else { |
||||
LOG.error("Prune failed. Re-run the subcommand to load the trie logs from file."); |
||||
} |
||||
} |
||||
|
||||
private static void processTrieLogBatches( |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
final MutableBlockchain blockchain, |
||||
final long chainHeight, |
||||
final long lastBlockNumberToRetainTrieLogsFor, |
||||
final long numberOfBatches, |
||||
final String batchFileNameBase) { |
||||
|
||||
for (long batchNumber = 1; batchNumber <= numberOfBatches; batchNumber++) { |
||||
|
||||
final long firstBlockOfBatch = chainHeight - ((batchNumber - 1) * BATCH_SIZE); |
||||
|
||||
final long lastBlockOfBatch = |
||||
Math.max(chainHeight - (batchNumber * BATCH_SIZE), lastBlockNumberToRetainTrieLogsFor); |
||||
|
||||
final List<Hash> trieLogKeys = |
||||
getTrieLogKeysForBlocks(blockchain, firstBlockOfBatch, lastBlockOfBatch); |
||||
|
||||
saveTrieLogBatches(batchFileNameBase, rootWorldStateStorage, batchNumber, trieLogKeys); |
||||
} |
||||
|
||||
LOG.info("Clear trie logs..."); |
||||
rootWorldStateStorage.clearTrieLog(); |
||||
|
||||
for (long batchNumber = 1; batchNumber <= numberOfBatches; batchNumber++) { |
||||
restoreTrieLogBatches(rootWorldStateStorage, batchNumber, batchFileNameBase); |
||||
} |
||||
} |
||||
|
||||
private static void saveTrieLogBatches( |
||||
final String batchFileNameBase, |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
final long batchNumber, |
||||
final List<Hash> trieLogKeys) { |
||||
|
||||
LOG.info("Saving trie logs to retain in file (batch {})...", batchNumber); |
||||
|
||||
try { |
||||
saveTrieLogsInFile(trieLogKeys, rootWorldStateStorage, batchNumber, batchFileNameBase); |
||||
} catch (IOException e) { |
||||
LOG.error("Error saving trie logs to file: {}", e.getMessage()); |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private static void restoreTrieLogBatches( |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
final long batchNumber, |
||||
final String batchFileNameBase) { |
||||
|
||||
try { |
||||
LOG.info("Restoring trie logs retained from batch {}...", batchNumber); |
||||
recreateTrieLogs(rootWorldStateStorage, batchNumber, batchFileNameBase); |
||||
} catch (IOException e) { |
||||
LOG.error("Error recreating trie logs from batch {}: {}", batchNumber, e.getMessage()); |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private static void deleteFiles(final String batchFileNameBase, final long numberOfBatches) { |
||||
|
||||
LOG.info("Deleting files..."); |
||||
|
||||
for (long batchNumber = 1; batchNumber <= numberOfBatches; batchNumber++) { |
||||
File file = new File(batchFileNameBase + "-" + batchNumber); |
||||
if (file.exists()) { |
||||
file.delete(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static List<Hash> getTrieLogKeysForBlocks( |
||||
final MutableBlockchain blockchain, |
||||
final long firstBlockOfBatch, |
||||
final long lastBlockOfBatch) { |
||||
final List<Hash> trieLogKeys = new ArrayList<>(); |
||||
for (long i = firstBlockOfBatch; i >= lastBlockOfBatch; i--) { |
||||
final Optional<BlockHeader> header = blockchain.getBlockHeader(i); |
||||
header.ifPresentOrElse( |
||||
blockHeader -> trieLogKeys.add(blockHeader.getHash()), |
||||
() -> LOG.error("Error retrieving block")); |
||||
} |
||||
return trieLogKeys; |
||||
} |
||||
|
||||
private static long calculateNumberofBatches(final long layersToRetain) { |
||||
return layersToRetain / BATCH_SIZE + ((layersToRetain % BATCH_SIZE == 0) ? 0 : 1); |
||||
} |
||||
|
||||
private static boolean validPruneRequirements( |
||||
final MutableBlockchain blockchain, |
||||
final long chainHeight, |
||||
final long lastBlockNumberToRetainTrieLogsFor) { |
||||
if (lastBlockNumberToRetainTrieLogsFor < 0) { |
||||
throw new IllegalArgumentException( |
||||
"Trying to retain more trie logs than chain length (" |
||||
+ chainHeight |
||||
+ "), skipping pruning"); |
||||
} |
||||
|
||||
final Optional<Hash> finalizedBlockHash = blockchain.getFinalized(); |
||||
|
||||
if (finalizedBlockHash.isEmpty()) { |
||||
throw new RuntimeException("No finalized block present, can't safely run trie log prune"); |
||||
} else { |
||||
final Hash finalizedHash = finalizedBlockHash.get(); |
||||
final Optional<BlockHeader> finalizedBlockHeader = blockchain.getBlockHeader(finalizedHash); |
||||
if (finalizedBlockHeader.isPresent() |
||||
&& finalizedBlockHeader.get().getNumber() < lastBlockNumberToRetainTrieLogsFor) { |
||||
throw new IllegalArgumentException( |
||||
"Trying to prune more layers than the finalized block height, skipping pruning"); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
private static void recreateTrieLogs( |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
final long batchNumber, |
||||
final String batchFileNameBase) |
||||
throws IOException { |
||||
// process in chunk to avoid OOM
|
||||
|
||||
IdentityHashMap<byte[], byte[]> trieLogsToRetain = |
||||
readTrieLogsFromFile(batchFileNameBase, batchNumber); |
||||
final int chunkSize = ROCKSDB_MAX_INSERTS_PER_TRANSACTION; |
||||
List<byte[]> keys = new ArrayList<>(trieLogsToRetain.keySet()); |
||||
|
||||
for (int startIndex = 0; startIndex < keys.size(); startIndex += chunkSize) { |
||||
processTransactionChunk(startIndex, chunkSize, keys, trieLogsToRetain, rootWorldStateStorage); |
||||
} |
||||
} |
||||
|
||||
private static void processTransactionChunk( |
||||
final int startIndex, |
||||
final int chunkSize, |
||||
final List<byte[]> keys, |
||||
final IdentityHashMap<byte[], byte[]> trieLogsToRetain, |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage) { |
||||
|
||||
var updater = rootWorldStateStorage.updater(); |
||||
int endIndex = Math.min(startIndex + chunkSize, keys.size()); |
||||
|
||||
for (int i = startIndex; i < endIndex; i++) { |
||||
byte[] key = keys.get(i); |
||||
byte[] value = trieLogsToRetain.get(key); |
||||
updater.getTrieLogStorageTransaction().put(key, value); |
||||
LOG.info("Key({}): {}", i, Bytes32.wrap(key).toShortHexString()); |
||||
} |
||||
|
||||
updater.getTrieLogStorageTransaction().commit(); |
||||
} |
||||
|
||||
private static void validatePruneConfiguration(final DataStorageConfiguration config) { |
||||
checkArgument( |
||||
config.getUnstable().getBonsaiTrieLogRetentionThreshold() |
||||
>= config.getBonsaiMaxLayersToLoad(), |
||||
String.format( |
||||
"--Xbonsai-trie-log-retention-threshold minimum value is %d", |
||||
config.getBonsaiMaxLayersToLoad())); |
||||
checkArgument( |
||||
config.getUnstable().getBonsaiTrieLogPruningLimit() > 0, |
||||
String.format( |
||||
"--Xbonsai-trie-log-pruning-limit=%d must be greater than 0", |
||||
config.getUnstable().getBonsaiTrieLogPruningLimit())); |
||||
checkArgument( |
||||
config.getUnstable().getBonsaiTrieLogPruningLimit() |
||||
> config.getUnstable().getBonsaiTrieLogRetentionThreshold(), |
||||
String.format( |
||||
"--Xbonsai-trie-log-pruning-limit=%d must greater than --Xbonsai-trie-log-retention-threshold=%d", |
||||
config.getUnstable().getBonsaiTrieLogPruningLimit(), |
||||
config.getUnstable().getBonsaiTrieLogRetentionThreshold())); |
||||
} |
||||
|
||||
private static void saveTrieLogsInFile( |
||||
final List<Hash> trieLogsKeys, |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
final long batchNumber, |
||||
final String batchFileNameBase) |
||||
throws IOException { |
||||
|
||||
File file = new File(batchFileNameBase + "-" + batchNumber); |
||||
if (file.exists()) { |
||||
LOG.error("File already exists, skipping file creation"); |
||||
return; |
||||
} |
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) { |
||||
ObjectOutputStream oos = new ObjectOutputStream(fos); |
||||
oos.writeObject(getTrieLogs(trieLogsKeys, rootWorldStateStorage)); |
||||
} catch (IOException e) { |
||||
LOG.error(e.getMessage()); |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private static IdentityHashMap<byte[], byte[]> readTrieLogsFromFile( |
||||
final String batchFileNameBase, final long batchNumber) { |
||||
|
||||
IdentityHashMap<byte[], byte[]> trieLogs; |
||||
try (FileInputStream fis = new FileInputStream(batchFileNameBase + "-" + batchNumber); |
||||
ObjectInputStream ois = new ObjectInputStream(fis)) { |
||||
|
||||
trieLogs = (IdentityHashMap<byte[], byte[]>) ois.readObject(); |
||||
|
||||
} catch (IOException | ClassNotFoundException e) { |
||||
|
||||
LOG.error(e.getMessage()); |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
return trieLogs; |
||||
} |
||||
|
||||
private static IdentityHashMap<byte[], byte[]> getTrieLogs( |
||||
final List<Hash> trieLogKeys, final BonsaiWorldStateKeyValueStorage rootWorldStateStorage) { |
||||
IdentityHashMap<byte[], byte[]> trieLogsToRetain = new IdentityHashMap<>(); |
||||
|
||||
LOG.info("Obtaining trielogs from db, this may take a few minutes..."); |
||||
trieLogKeys.forEach( |
||||
hash -> |
||||
rootWorldStateStorage |
||||
.getTrieLog(hash) |
||||
.ifPresent(trieLog -> trieLogsToRetain.put(hash.toArrayUnsafe(), trieLog))); |
||||
return trieLogsToRetain; |
||||
} |
||||
|
||||
static TrieLogCount getCount( |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
final int limit, |
||||
final Blockchain blockchain) { |
||||
final AtomicInteger total = new AtomicInteger(); |
||||
final AtomicInteger canonicalCount = new AtomicInteger(); |
||||
final AtomicInteger forkCount = new AtomicInteger(); |
||||
final AtomicInteger orphanCount = new AtomicInteger(); |
||||
rootWorldStateStorage |
||||
.streamTrieLogKeys(limit) |
||||
.map(Bytes32::wrap) |
||||
.map(Hash::wrap) |
||||
.forEach( |
||||
hash -> { |
||||
total.getAndIncrement(); |
||||
blockchain |
||||
.getBlockHeader(hash) |
||||
.ifPresentOrElse( |
||||
(header) -> { |
||||
long number = header.getNumber(); |
||||
final Optional<BlockHeader> headerByNumber = |
||||
blockchain.getBlockHeader(number); |
||||
if (headerByNumber.isPresent() |
||||
&& headerByNumber.get().getHash().equals(hash)) { |
||||
canonicalCount.getAndIncrement(); |
||||
} else { |
||||
forkCount.getAndIncrement(); |
||||
} |
||||
}, |
||||
orphanCount::getAndIncrement); |
||||
}); |
||||
|
||||
return new TrieLogCount(total.get(), canonicalCount.get(), forkCount.get(), orphanCount.get()); |
||||
} |
||||
|
||||
static void printCount(final PrintWriter out, final TrieLogCount count) { |
||||
out.printf( |
||||
"trieLog count: %s\n - canonical count: %s\n - fork count: %s\n - orphaned count: %s\n", |
||||
count.total, count.canonicalCount, count.forkCount, count.orphanCount); |
||||
} |
||||
|
||||
record TrieLogCount(int total, int canonicalCount, int forkCount, int orphanCount) {} |
||||
} |
@ -0,0 +1,147 @@ |
||||
/* |
||||
* Copyright Hyperledger Besu Contributors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
package org.hyperledger.besu.cli.subcommands.storage; |
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument; |
||||
import static com.google.common.base.Preconditions.checkNotNull; |
||||
|
||||
import org.hyperledger.besu.cli.util.VersionProvider; |
||||
import org.hyperledger.besu.controller.BesuController; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.storage.StorageProvider; |
||||
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.ethereum.trie.bonsai.trielog.TrieLogPruner; |
||||
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; |
||||
import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; |
||||
|
||||
import java.io.PrintWriter; |
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
|
||||
import org.apache.logging.log4j.Level; |
||||
import org.apache.logging.log4j.core.config.Configurator; |
||||
import org.slf4j.LoggerFactory; |
||||
import picocli.CommandLine; |
||||
import picocli.CommandLine.Command; |
||||
import picocli.CommandLine.ParentCommand; |
||||
|
||||
/** The Trie Log subcommand. */ |
||||
@Command( |
||||
name = "x-trie-log", |
||||
description = "Manipulate trie logs", |
||||
mixinStandardHelpOptions = true, |
||||
versionProvider = VersionProvider.class, |
||||
subcommands = {TrieLogSubCommand.CountTrieLog.class, TrieLogSubCommand.PruneTrieLog.class}) |
||||
public class TrieLogSubCommand implements Runnable { |
||||
|
||||
@SuppressWarnings("UnusedVariable") |
||||
@ParentCommand |
||||
private static StorageSubCommand parentCommand; |
||||
|
||||
@SuppressWarnings("unused") |
||||
@CommandLine.Spec |
||||
private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec
|
||||
|
||||
@Override |
||||
public void run() { |
||||
final PrintWriter out = spec.commandLine().getOut(); |
||||
spec.commandLine().usage(out); |
||||
} |
||||
|
||||
private static BesuController createBesuController() { |
||||
return parentCommand.parentCommand.buildController(); |
||||
} |
||||
|
||||
@Command( |
||||
name = "count", |
||||
description = "This command counts all the trie logs", |
||||
mixinStandardHelpOptions = true, |
||||
versionProvider = VersionProvider.class) |
||||
static class CountTrieLog implements Runnable { |
||||
|
||||
@SuppressWarnings("unused") |
||||
@ParentCommand |
||||
private TrieLogSubCommand parentCommand; |
||||
|
||||
@SuppressWarnings("unused") |
||||
@CommandLine.Spec |
||||
private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec
|
||||
|
||||
@Override |
||||
public void run() { |
||||
TrieLogContext context = getTrieLogContext(); |
||||
|
||||
final PrintWriter out = spec.commandLine().getOut(); |
||||
|
||||
out.println("Counting trie logs..."); |
||||
TrieLogHelper.printCount( |
||||
out, |
||||
TrieLogHelper.getCount( |
||||
context.rootWorldStateStorage, Integer.MAX_VALUE, context.blockchain)); |
||||
} |
||||
} |
||||
|
||||
@Command( |
||||
name = "prune", |
||||
description = |
||||
"This command prunes all trie log layers below the retention threshold, including orphaned trie logs.", |
||||
mixinStandardHelpOptions = true, |
||||
versionProvider = VersionProvider.class) |
||||
static class PruneTrieLog implements Runnable { |
||||
|
||||
@SuppressWarnings("unused") |
||||
@ParentCommand |
||||
private TrieLogSubCommand parentCommand; |
||||
|
||||
@SuppressWarnings("unused") |
||||
@CommandLine.Spec |
||||
private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec
|
||||
|
||||
@Override |
||||
public void run() { |
||||
TrieLogContext context = getTrieLogContext(); |
||||
final Path dataDirectoryPath = |
||||
Paths.get( |
||||
TrieLogSubCommand.parentCommand.parentCommand.dataDir().toAbsolutePath().toString()); |
||||
TrieLogHelper.prune( |
||||
context.config(), |
||||
context.rootWorldStateStorage(), |
||||
context.blockchain(), |
||||
dataDirectoryPath); |
||||
} |
||||
} |
||||
|
||||
record TrieLogContext( |
||||
DataStorageConfiguration config, |
||||
BonsaiWorldStateKeyValueStorage rootWorldStateStorage, |
||||
MutableBlockchain blockchain) {} |
||||
|
||||
private static TrieLogContext getTrieLogContext() { |
||||
Configurator.setLevel(LoggerFactory.getLogger(TrieLogPruner.class).getName(), Level.DEBUG); |
||||
checkNotNull(parentCommand); |
||||
BesuController besuController = createBesuController(); |
||||
final DataStorageConfiguration config = besuController.getDataStorageConfiguration(); |
||||
checkArgument( |
||||
DataStorageFormat.BONSAI.equals(config.getDataStorageFormat()), |
||||
"Subcommand only works with data-storage-format=BONSAI"); |
||||
|
||||
final StorageProvider storageProvider = besuController.getStorageProvider(); |
||||
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage = |
||||
(BonsaiWorldStateKeyValueStorage) |
||||
storageProvider.createWorldStateStorage(DataStorageFormat.BONSAI); |
||||
final MutableBlockchain blockchain = besuController.getProtocolContext().getBlockchain(); |
||||
return new TrieLogContext(config, rootWorldStateStorage, blockchain); |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.ethereum.forkid.ForkId; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
public class RawForkIdTest { |
||||
@Test |
||||
public void testFromRaw() { |
||||
final ForkId forkId = new ForkId(Bytes.ofUnsignedInt(0xfe3366e7L), 1735371L); |
||||
final List<List<Bytes>> forkIdAsBytesList = List.of(forkId.getForkIdAsBytesList()); |
||||
assertThat(ForkId.fromRawForkId(forkIdAsBytesList).get()).isEqualTo(forkId); |
||||
} |
||||
} |
@ -0,0 +1,265 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
||||
* specific language governing permissions and limitations under the License. |
||||
* |
||||
* SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
package org.hyperledger.besu.cli.subcommands.storage; |
||||
|
||||
import static org.hyperledger.besu.ethereum.worldstate.DataStorageFormat.BONSAI; |
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals; |
||||
import static org.junit.jupiter.api.Assertions.assertEquals; |
||||
import static org.junit.jupiter.api.Assertions.assertThrows; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.datatypes.Hash; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeader; |
||||
import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; |
||||
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; |
||||
import org.hyperledger.besu.ethereum.storage.StorageProvider; |
||||
import org.hyperledger.besu.ethereum.trie.bonsai.storage.BonsaiWorldStateKeyValueStorage; |
||||
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; |
||||
import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; |
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.util.Optional; |
||||
|
||||
import org.apache.tuweni.bytes.Bytes; |
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeAll; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.junit.jupiter.api.io.TempDir; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
||||
@ExtendWith(MockitoExtension.class) |
||||
class TrieLogHelperTest { |
||||
|
||||
private static final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); |
||||
private static BonsaiWorldStateKeyValueStorage inMemoryWorldState; |
||||
|
||||
@Mock private MutableBlockchain blockchain; |
||||
|
||||
@TempDir static Path dataDir; |
||||
|
||||
Path test; |
||||
static BlockHeader blockHeader1; |
||||
static BlockHeader blockHeader2; |
||||
static BlockHeader blockHeader3; |
||||
static BlockHeader blockHeader4; |
||||
static BlockHeader blockHeader5; |
||||
|
||||
@BeforeAll |
||||
public static void setup() throws IOException { |
||||
|
||||
blockHeader1 = new BlockHeaderTestFixture().number(1).buildHeader(); |
||||
blockHeader2 = new BlockHeaderTestFixture().number(2).buildHeader(); |
||||
blockHeader3 = new BlockHeaderTestFixture().number(3).buildHeader(); |
||||
blockHeader4 = new BlockHeaderTestFixture().number(4).buildHeader(); |
||||
blockHeader5 = new BlockHeaderTestFixture().number(5).buildHeader(); |
||||
|
||||
inMemoryWorldState = |
||||
new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); |
||||
|
||||
var updater = inMemoryWorldState.updater(); |
||||
updater |
||||
.getTrieLogStorageTransaction() |
||||
.put(blockHeader1.getHash().toArrayUnsafe(), Bytes.fromHexString("0x01").toArrayUnsafe()); |
||||
updater |
||||
.getTrieLogStorageTransaction() |
||||
.put(blockHeader2.getHash().toArrayUnsafe(), Bytes.fromHexString("0x02").toArrayUnsafe()); |
||||
updater |
||||
.getTrieLogStorageTransaction() |
||||
.put(blockHeader3.getHash().toArrayUnsafe(), Bytes.fromHexString("0x03").toArrayUnsafe()); |
||||
updater |
||||
.getTrieLogStorageTransaction() |
||||
.put(blockHeader4.getHash().toArrayUnsafe(), Bytes.fromHexString("0x04").toArrayUnsafe()); |
||||
updater |
||||
.getTrieLogStorageTransaction() |
||||
.put(blockHeader5.getHash().toArrayUnsafe(), Bytes.fromHexString("0x05").toArrayUnsafe()); |
||||
updater.getTrieLogStorageTransaction().commit(); |
||||
} |
||||
|
||||
@BeforeEach |
||||
void createDirectory() throws IOException { |
||||
Files.createDirectories(dataDir.resolve("database")); |
||||
} |
||||
|
||||
@AfterEach |
||||
void deleteDirectory() throws IOException { |
||||
Files.deleteIfExists(dataDir.resolve("database")); |
||||
} |
||||
|
||||
void mockBlockchainBase() { |
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); |
||||
when(blockchain.getFinalized()).thenReturn(Optional.of(blockHeader3.getBlockHash())); |
||||
when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader3)); |
||||
} |
||||
|
||||
@Test |
||||
public void prune() { |
||||
|
||||
DataStorageConfiguration dataStorageConfiguration = |
||||
ImmutableDataStorageConfiguration.builder() |
||||
.dataStorageFormat(BONSAI) |
||||
.bonsaiMaxLayersToLoad(2L) |
||||
.unstable( |
||||
ImmutableDataStorageConfiguration.Unstable.builder() |
||||
.bonsaiTrieLogRetentionThreshold(3) |
||||
.build() |
||||
.withBonsaiTrieLogRetentionThreshold(3)) |
||||
.build(); |
||||
|
||||
mockBlockchainBase(); |
||||
when(blockchain.getBlockHeader(5)).thenReturn(Optional.of(blockHeader5)); |
||||
when(blockchain.getBlockHeader(4)).thenReturn(Optional.of(blockHeader4)); |
||||
when(blockchain.getBlockHeader(3)).thenReturn(Optional.of(blockHeader3)); |
||||
|
||||
// assert trie logs that will be pruned exist before prune call
|
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get(), |
||||
Bytes.fromHexString("0x01").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader2.getHash()).get(), |
||||
Bytes.fromHexString("0x02").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get(), |
||||
Bytes.fromHexString("0x03").toArrayUnsafe()); |
||||
|
||||
TrieLogHelper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir); |
||||
|
||||
// assert pruned trie logs are not in the DB
|
||||
assertEquals(inMemoryWorldState.getTrieLog(blockHeader1.getHash()), Optional.empty()); |
||||
assertEquals(inMemoryWorldState.getTrieLog(blockHeader2.getHash()), Optional.empty()); |
||||
|
||||
// assert retained trie logs are in the DB
|
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get(), |
||||
Bytes.fromHexString("0x03").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader4.getHash()).get(), |
||||
Bytes.fromHexString("0x04").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader5.getHash()).get(), |
||||
Bytes.fromHexString("0x05").toArrayUnsafe()); |
||||
} |
||||
|
||||
@Test |
||||
public void cantPruneIfNoFinalizedIsFound() { |
||||
DataStorageConfiguration dataStorageConfiguration = |
||||
ImmutableDataStorageConfiguration.builder() |
||||
.dataStorageFormat(BONSAI) |
||||
.bonsaiMaxLayersToLoad(2L) |
||||
.unstable( |
||||
ImmutableDataStorageConfiguration.Unstable.builder() |
||||
.bonsaiTrieLogRetentionThreshold(2) |
||||
.build() |
||||
.withBonsaiTrieLogRetentionThreshold(2)) |
||||
.build(); |
||||
|
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); |
||||
when(blockchain.getFinalized()).thenReturn(Optional.empty()); |
||||
|
||||
assertThrows( |
||||
RuntimeException.class, |
||||
() -> |
||||
TrieLogHelper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir)); |
||||
} |
||||
|
||||
@Test |
||||
public void cantPruneIfUserRetainsMoreLayerThanExistingChainLength() { |
||||
DataStorageConfiguration dataStorageConfiguration = |
||||
ImmutableDataStorageConfiguration.builder() |
||||
.dataStorageFormat(BONSAI) |
||||
.bonsaiMaxLayersToLoad(2L) |
||||
.unstable( |
||||
ImmutableDataStorageConfiguration.Unstable.builder() |
||||
.bonsaiTrieLogRetentionThreshold(10) |
||||
.build() |
||||
.withBonsaiTrieLogRetentionThreshold(10)) |
||||
.build(); |
||||
|
||||
when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); |
||||
|
||||
assertThrows( |
||||
IllegalArgumentException.class, |
||||
() -> |
||||
TrieLogHelper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir)); |
||||
} |
||||
|
||||
@Test |
||||
public void cantPruneIfUserRequiredFurtherThanFinalized() { |
||||
|
||||
DataStorageConfiguration dataStorageConfiguration = |
||||
ImmutableDataStorageConfiguration.builder() |
||||
.dataStorageFormat(BONSAI) |
||||
.bonsaiMaxLayersToLoad(2L) |
||||
.unstable( |
||||
ImmutableDataStorageConfiguration.Unstable.builder() |
||||
.bonsaiTrieLogRetentionThreshold(2) |
||||
.build() |
||||
.withBonsaiTrieLogRetentionThreshold(2)) |
||||
.build(); |
||||
|
||||
mockBlockchainBase(); |
||||
|
||||
assertThrows( |
||||
IllegalArgumentException.class, |
||||
() -> |
||||
TrieLogHelper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir)); |
||||
} |
||||
|
||||
@Test |
||||
public void exceptionWhileSavingFileStopsPruneProcess() throws IOException { |
||||
Files.delete(dataDir.resolve("database")); |
||||
|
||||
DataStorageConfiguration dataStorageConfiguration = |
||||
ImmutableDataStorageConfiguration.builder() |
||||
.dataStorageFormat(BONSAI) |
||||
.bonsaiMaxLayersToLoad(2L) |
||||
.unstable( |
||||
ImmutableDataStorageConfiguration.Unstable.builder() |
||||
.bonsaiTrieLogRetentionThreshold(2) |
||||
.build() |
||||
.withBonsaiTrieLogRetentionThreshold(2)) |
||||
.build(); |
||||
|
||||
assertThrows( |
||||
RuntimeException.class, |
||||
() -> |
||||
TrieLogHelper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir)); |
||||
|
||||
// assert all trie logs are still in the DB
|
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get(), |
||||
Bytes.fromHexString("0x01").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader2.getHash()).get(), |
||||
Bytes.fromHexString("0x02").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get(), |
||||
Bytes.fromHexString("0x03").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader4.getHash()).get(), |
||||
Bytes.fromHexString("0x04").toArrayUnsafe()); |
||||
assertArrayEquals( |
||||
inMemoryWorldState.getTrieLog(blockHeader5.getHash()).get(), |
||||
Bytes.fromHexString("0x05").toArrayUnsafe()); |
||||
} |
||||
} |
Loading…
Reference in new issue