Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Bonsai archive feature #7475

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a5315af
rebase of bonsai archive
jframe Mar 6, 2024
ec8a7ff
Change BonsaiReferenceTestWorldStateStorage getContextSafeCopy() to u…
jframe Mar 8, 2024
b2d9196
Force archive mode for testing
jframe Mar 8, 2024
fd300b0
Rebase on main
matthew1001 Jul 17, 2024
b10b6ae
Create new DiffBasedWorldStateConfig when copying bonsai archive worl…
matthew1001 Jul 19, 2024
6e786f3
Add BONSAI_ARCHIVE storage format
matthew1001 Jul 31, 2024
32771d3
New ACCOUNT_FREEZER_STATE DB segment. New BonsaiArchiveFreezer to lis…
matthew1001 Aug 16, 2024
b1f448d
Add freezer segment for account storage
matthew1001 Aug 21, 2024
06ba76e
Find frozen storage slots correctly, refactor the DB segment names
matthew1001 Sep 4, 2024
1d930f5
Check for blocks to freeze state for on startup. Store the most recen…
matthew1001 Sep 5, 2024
605d590
Make sure genesis world state is created in archive mode
matthew1001 Sep 20, 2024
c7c2ee5
Fix incorrect logs
matthew1001 Sep 23, 2024
990bd87
Ensure deleted storage is returned from live DB segment, not old stor…
matthew1001 Sep 26, 2024
8518428
Adding tests
matthew1001 Sep 26, 2024
a1cb77d
Refactor DB TX with retries
matthew1001 Sep 30, 2024
660d449
Honour code hash or account hash config for storing code. Add tests f…
matthew1001 Sep 30, 2024
4063128
Refactor code to move one block's worth of state/storage changes at a…
matthew1001 Oct 1, 2024
829a98e
Refactoring, fix issues, ensure only 1 batch of blocks is archived at…
matthew1001 Oct 3, 2024
faa8da1
Improve archive progress logs
matthew1001 Oct 4, 2024
0543017
Add archive mode tests to bonsai key/value tests
matthew1001 Oct 4, 2024
bf3d171
Implement snap-serving from a Bonsai Archive node and add tests
matthew1001 Oct 4, 2024
d2e5a24
Parameterize snap-server tests, fix bug in storage slot ranges
matthew1001 Oct 7, 2024
ee7c2fc
Remove todo comments
matthew1001 Oct 7, 2024
38cfdd9
Make BONSAI_ARCHIVE experimental for the first release. Add metrics. …
matthew1001 Oct 7, 2024
35d60dd
Add changelog entry
matthew1001 Oct 8, 2024
b8facef
Don't store every block that is waiting to be archived
matthew1001 Oct 8, 2024
75e57cc
Remove pre-startup catchup. Start on first block import instead
matthew1001 Oct 8, 2024
a6552df
Multiple deletes of the same address in a single block causes Bonsai …
matthew1001 Oct 8, 2024
329da00
Metric should be a gauge not a counter
matthew1001 Oct 9, 2024
e6364b2
Refactor archive tests after code refactor
matthew1001 Oct 9, 2024
8824b21
Merge branch 'main' into multi-version-flat-db-rebase
matthew1001 Oct 9, 2024
46a33ef
Remove commented out sections from tests
matthew1001 Oct 9, 2024
4051eb0
Update the BFT soak test to include a Bonsai archive node
matthew1001 Oct 17, 2024
c28a383
Add archive-specific checks to the BFT soak test
matthew1001 Oct 22, 2024
c1f239f
Merge with main
matthew1001 Nov 4, 2024
c226aa6
Update plugin API hash
matthew1001 Nov 4, 2024
a6123bb
Add javadoc
matthew1001 Nov 5, 2024
c4a9516
Merge fix
matthew1001 Nov 5, 2024
81a0907
Unit test fix
matthew1001 Nov 5, 2024
0036e61
Merge branch 'main' into multi-version-flat-db-rebase
matthew1001 Nov 5, 2024
0b0bed4
More UT fixing
matthew1001 Nov 5, 2024
614e5c2
Tidy up
matthew1001 Nov 6, 2024
5392ef4
Merge branch 'main' into multi-version-flat-db-rebase
matthew1001 Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
- Add support for `chainId` in `CallParameters` [#7720](https://github.com/hyperledger/besu/pull/7720)
- Add `--ephemery` network support for Ephemery Testnet [#7563](https://github.com/hyperledger/besu/pull/7563) thanks to [@gconnect](https://github.com/gconnect)
- Add configuration of Consolidation Request Contract Address via genesis configuration [#7647](https://github.com/hyperledger/besu/pull/7647)
- Experimental Bonsai Archive support [#7475](https://github.com/hyperledger/besu/pull/7475)

### Bug fixes
- Fix mounted data path directory permissions for besu user [#7575](https://github.com/hyperledger/besu/pull/7575)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,9 @@ public BesuNode createQbftNode(
.dataStorageConfiguration(
storageFormat == DataStorageFormat.FOREST
? DataStorageConfiguration.DEFAULT_FOREST_CONFIG
: DataStorageConfiguration.DEFAULT_BONSAI_CONFIG)
: storageFormat == DataStorageFormat.BONSAI
? DataStorageConfiguration.DEFAULT_BONSAI_CONFIG
: DataStorageConfiguration.DEFAULT_BONSAI_ARCHIVE_CONFIG)
.genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig);
if (fixedPort) {
builder.p2pPort(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public BesuNode createBonsaiNodeFixedPort(BesuNodeFactory factory, String name)
return creatorFn.create(factory, name, true, DataStorageFormat.BONSAI);
}

public BesuNode createBonsaiArchiveNodeFixedPort(BesuNodeFactory factory, String name)
throws Exception {
return creatorFn.create(factory, name, true, DataStorageFormat.X_BONSAI_ARCHIVE);
}

public BesuNode createForestNodeFixedPort(BesuNodeFactory factory, String name) throws Exception {
return creatorFn.create(factory, name, true, DataStorageFormat.FOREST);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@
import org.assertj.core.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.tx.exceptions.ContractCallException;

public class BftMiningSoakTest extends ParameterizedBftTestBase {

private static final Logger LOG = LoggerFactory.getLogger(BftMiningSoakTest.class);

private final int NUM_STEPS = 5;

private final int MIN_TEST_TIME_MINS = 60;
Expand Down Expand Up @@ -62,7 +68,7 @@ public void shouldBeStableDuringLongTest(

// Create a mix of Bonsai and Forest DB nodes
final BesuNode minerNode1 = nodeFactory.createBonsaiNodeFixedPort(besu, "miner1");
final BesuNode minerNode2 = nodeFactory.createForestNodeFixedPort(besu, "miner2");
final BesuNode minerNode2 = nodeFactory.createBonsaiArchiveNodeFixedPort(besu, "miner2");
final BesuNode minerNode3 = nodeFactory.createBonsaiNodeFixedPort(besu, "miner3");
final BesuNode minerNode4 = nodeFactory.createForestNodeFixedPort(besu, "miner4");

Expand All @@ -82,6 +88,13 @@ public void shouldBeStableDuringLongTest(
SimpleStorage simpleStorageContract =
minerNode1.execute(contractTransactions.createSmartContract(SimpleStorage.class));

// Create another instance of the contract referencing the same contract address but on the
// archive node. This contract instance should be able to query state from the beginning of
// the test
SimpleStorage simpleStorageArchive =
minerNode2.execute(contractTransactions.createSmartContract(SimpleStorage.class));
simpleStorageArchive.setContractAddress(simpleStorageContract.getContractAddress());

// Check the contract address is as expected for this sender & nonce
contractVerifier
.validTransactionReceipt("0x42699a7612a82f1d9c36148af9c77354759b210b")
Expand All @@ -104,6 +117,9 @@ public void shouldBeStableDuringLongTest(
// Set to something new
simpleStorageContract.set(BigInteger.valueOf(101)).send();

// Save this block height to check on the archive node at the end of the test
BigInteger archiveChainHeight = minerNode1.execute(ethTransactions.blockNumber());

// Check the state of the contract has updated correctly. We'll set & get this several times
// during the test
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(101));
Expand Down Expand Up @@ -132,6 +148,7 @@ public void shouldBeStableDuringLongTest(
// Step 2
// Stop one of the nodes, check that the chain continues mining
// blocks
LOG.info("Stopping node 4 to check the chain continues mining (albeit more slowly)");
stopNode(minerNode4);

nextStepEndTime =
Expand All @@ -151,6 +168,7 @@ public void shouldBeStableDuringLongTest(
// Step 3
// Stop another one of the nodes, check that the chain now stops
// mining blocks
LOG.info("Stopping node 3 to check that the chain stalls");
stopNode(minerNode3);

chainHeight = minerNode1.execute(ethTransactions.blockNumber());
Expand All @@ -170,8 +188,8 @@ public void shouldBeStableDuringLongTest(
// Step 4
// Restart both of the stopped nodes. Check that the chain resumes
// mining blocks
LOG.info("Starting node 3 and node 4 to ensure the chain resumes mining new blocks");
startNode(minerNode3);

startNode(minerNode4);

previousStepEndTime = Instant.now();
Expand Down Expand Up @@ -203,11 +221,16 @@ public void shouldBeStableDuringLongTest(
lastChainHeight = chainHeight;
}

LOG.info("Updating the value in the smart contract from 101 to 201");
// Update our smart contract before upgrading from berlin to london
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(101));
simpleStorageContract.set(BigInteger.valueOf(201)).send();
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(201));

LOG.info(
"Upgrading the entire chain to the London fork one node at a time. The genesis for each node will be updated to londonBlock = "
+ lastChainHeight.intValue()
+ 120);
// Upgrade the chain from berlin to london in 120 blocks time
upgradeToLondon(
minerNode1, minerNode2, minerNode3, minerNode4, lastChainHeight.intValue() + 120);
Expand All @@ -219,6 +242,9 @@ public void shouldBeStableDuringLongTest(
previousStepEndTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES);
lastChainHeight = chainHeight;

// Allow the chain to restart mining blocks
Thread.sleep(THREE_MINUTES);

while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) {
Thread.sleep(ONE_MINUTE);
chainHeight = minerNode1.execute(ethTransactions.blockNumber());
Expand All @@ -229,6 +255,8 @@ public void shouldBeStableDuringLongTest(
lastChainHeight = chainHeight;
}

LOG.info(
"Chain has successfully upgraded to the London fork. Checking the contract state is correct");
// Check that the state of our smart contract is still correct
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(201));

Expand All @@ -237,18 +265,42 @@ public void shouldBeStableDuringLongTest(
assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(301));

// Upgrade the chain to shanghai in 120 seconds. Then try to deploy a shanghai contract
LOG.info(
"Upgrading the entire chain to the Shanghai fork one node at a time. The genesis for each node will be updated to shanghaiTime = "
+ Instant.now().getEpochSecond()
+ 120);
upgradeToShanghai(
minerNode1, minerNode2, minerNode3, minerNode4, Instant.now().getEpochSecond() + 120);

// Allow the chain to restart mining blocks
Thread.sleep(THREE_MINUTES);

LOG.info(
"Deploying a smart contract that should only work if the chain is running on the shanghai fork");
SimpleStorageShanghai simpleStorageContractShanghai =
minerNode1.execute(contractTransactions.createSmartContract(SimpleStorageShanghai.class));

// Check the contract address is as expected for this sender & nonce
contractVerifier
.validTransactionReceipt("0x05d91b9031a655d08e654177336d08543ac4b711")
.validTransactionReceipt("0xfeae27388a65ee984f452f86effed42aabd438fd")
.verify(simpleStorageContractShanghai);

// Archive node test. Check the state of the contract when it was first updated in the test
LOG.info(
"Checking that the archive node shows us the original smart contract value if we set a historic block number");
simpleStorageArchive.setDefaultBlockParameter(
DefaultBlockParameter.valueOf(archiveChainHeight));
assertThat(simpleStorageArchive.get().send()).isEqualTo(BigInteger.valueOf(101));

try {
simpleStorageContract.setDefaultBlockParameter(
DefaultBlockParameter.valueOf(archiveChainHeight));
// Should throw ContractCallException because a non-archive not can't satisfy this request
simpleStorageContract.get().send();
Assertions.fail("Request for historic state from non-archive node should have failed");
} catch (ContractCallException e) {
// Ignore
}
}

private static void updateGenesisConfigToLondon(
Expand Down Expand Up @@ -284,21 +336,26 @@ private void upgradeToLondon(
final int londonBlockNumber)
throws InterruptedException {
// Node 1

LOG.info("Upgrading node 1 to london fork");
stopNode(minerNode1);
updateGenesisConfigToLondon(minerNode1, true, londonBlockNumber);
startNode(minerNode1);

// Node 2
LOG.info("Upgrading node 2 to london fork");
stopNode(minerNode2);
updateGenesisConfigToLondon(minerNode2, true, londonBlockNumber);
startNode(minerNode2);

// Node 3
LOG.info("Upgrading node 3 to london fork");
stopNode(minerNode3);
updateGenesisConfigToLondon(minerNode3, true, londonBlockNumber);
startNode(minerNode3);

// Node 4
LOG.info("Upgrading node 4 to london fork");
stopNode(minerNode4);
updateGenesisConfigToLondon(minerNode4, true, londonBlockNumber);
startNode(minerNode4);
Expand All @@ -312,21 +369,25 @@ private void upgradeToShanghai(
final long shanghaiTime)
throws InterruptedException {
// Node 1
LOG.info("Upgrading node 1 to shanghai fork");
stopNode(minerNode1);
updateGenesisConfigToShanghai(minerNode1, shanghaiTime);
startNode(minerNode1);

// Node 2
LOG.info("Upgrading node 2 to shanghai fork");
stopNode(minerNode2);
updateGenesisConfigToShanghai(minerNode2, shanghaiTime);
startNode(minerNode2);

// Node 3
LOG.info("Upgrading node 3 to shanghai fork");
stopNode(minerNode3);
updateGenesisConfigToShanghai(minerNode3, shanghaiTime);
startNode(minerNode3);

// Node 4
LOG.info("Upgrading node 4 to shanghai fork");
stopNode(minerNode4);
updateGenesisConfigToShanghai(minerNode4, shanghaiTime);
startNode(minerNode4);
Expand Down
7 changes: 6 additions & 1 deletion besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -1933,9 +1933,14 @@ private PrivacyParameters privacyParameters() {
throw new ParameterException(
commandLine, String.format("%s %s", "Checkpoint sync", errorSuffix));
}
if (getDataStorageConfiguration().getDataStorageFormat().equals(DataStorageFormat.BONSAI)) {
if (getDataStorageConfiguration().getDataStorageFormat() == DataStorageFormat.BONSAI) {
throw new ParameterException(commandLine, String.format("%s %s", "Bonsai", errorSuffix));
}
if (getDataStorageConfiguration().getDataStorageFormat()
== DataStorageFormat.X_BONSAI_ARCHIVE) {
throw new ParameterException(
commandLine, String.format("%s %s", "Bonsai archive", errorSuffix));
}

if (Boolean.TRUE.equals(privacyOptionGroup.isPrivacyMultiTenancyEnabled)
&& Boolean.FALSE.equals(jsonRpcConfiguration.isAuthenticationEnabled())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public void run() {
switch (dataStorageFormat) {
case FOREST -> 1;
case BONSAI -> 2;
case X_BONSAI_ARCHIVE -> 3;
};

@JsonSerialize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration;
import org.hyperledger.besu.ethereum.worldstate.ImmutableDiffBasedSubStorageConfiguration;
import org.hyperledger.besu.plugin.services.storage.DataStorageFormat;

import java.io.IOException;
import java.io.PrintWriter;
Expand Down Expand Up @@ -328,8 +327,8 @@ private static TrieLogContext getTrieLogContext() {
BesuController besuController = createBesuController();
final DataStorageConfiguration config = besuController.getDataStorageConfiguration();
checkArgument(
DataStorageFormat.BONSAI.equals(config.getDataStorageFormat()),
"Subcommand only works with data-storage-format=BONSAI");
config.getDataStorageFormat().isBonsaiFormat(),
"Subcommand only works with data-storage-format=BONSAI or X_BONSAI_ARCHIVE");

final StorageProvider storageProvider = besuController.getStorageProvider();
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiArchiver;
import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager;
import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogPruner;
import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive;
Expand Down Expand Up @@ -762,6 +763,19 @@ public BesuController build() {
}
}

if (DataStorageFormat.X_BONSAI_ARCHIVE.equals(
dataStorageConfiguration.getDataStorageFormat())) {
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage =
worldStateStorageCoordinator.getStrategy(BonsaiWorldStateKeyValueStorage.class);
final BonsaiArchiver archiver =
createBonsaiArchiver(
worldStateKeyValueStorage,
blockchain,
scheduler,
((BonsaiWorldStateProvider) worldStateArchive).getTrieLogManager());
blockchain.observeBlockAdded(archiver);
}

final List<Closeable> closeables = new ArrayList<>();
closeables.add(protocolContext.getWorldStateArchive());
closeables.add(storageProvider);
Expand Down Expand Up @@ -829,6 +843,24 @@ private TrieLogPruner createTrieLogPruner(
return trieLogPruner;
}

private BonsaiArchiver createBonsaiArchiver(
final WorldStateKeyValueStorage worldStateStorage,
final Blockchain blockchain,
final EthScheduler scheduler,
final TrieLogManager trieLogManager) {
final BonsaiArchiver archiver =
new BonsaiArchiver(
(BonsaiWorldStateKeyValueStorage) worldStateStorage,
blockchain,
scheduler::executeServiceTask,
trieLogManager,
metricsSystem);

archiver.initialize();
LOG.info("Bonsai archiver initialised");
return archiver;
}

/**
* Create synchronizer synchronizer.
*
Expand Down Expand Up @@ -1118,6 +1150,25 @@ yield new BonsaiWorldStateProvider(
besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null),
evmConfiguration);
}
case X_BONSAI_ARCHIVE -> {
final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage =
worldStateStorageCoordinator.getStrategy(BonsaiWorldStateKeyValueStorage.class);

worldStateKeyValueStorage
.getFlatDbStrategy()
.updateBlockContext(blockchain.getChainHeadHeader());

yield new BonsaiWorldStateProvider(
worldStateKeyValueStorage,
blockchain,
Optional.of(
dataStorageConfiguration
.getDiffBasedSubStorageConfiguration()
.getMaxLayersToLoad()),
bonsaiCachedMerkleTrieLoader,
besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null),
evmConfiguration);
}
case FOREST -> {
final WorldStatePreimageStorage preimageStorage =
storageProvider.createWorldStatePreimageStorage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ public void privacyWithBonsaiExplicitMustError() {
assertThat(commandOutput.toString(UTF_8)).isEmpty();
}

@Test
public void privacyWithBonsaiArchiveExplicitMustError() {
// bypass overridden parseCommand method which specifies bonsai
super.parseCommand("--privacy-enabled", "--data-storage-format", "X_BONSAI_ARCHIVE");

assertThat(commandErrorOutput.toString(UTF_8))
.contains("Bonsai archive cannot be enabled with privacy.");
assertThat(commandOutput.toString(UTF_8)).isEmpty();
}

@Test
public void privacyWithoutPrivacyPublicKeyFails() {
parseCommand("--privacy-enabled", "--privacy-url", ENCLAVE_URI);
Expand Down
Loading
Loading