Skip to content

Commit

Permalink
Bonsai snapshot worldstate (#4409)
Browse files Browse the repository at this point in the history
* use optimistictransactiondb for mutable isolated snapshots
* plumbing necessary to have a snapshot specific updater.
* snapshot rolling working
* implement AutoCloseable on BonsaiSnapshotWorldState to ensure we can correctly dispose of snapshots
* add snapshot transaction cloning, change snapshot based worldstate to extend persisted worldstate rather than in-memory worldstate

Signed-off-by: garyschulte <garyschulte@gmail.com>
  • Loading branch information
garyschulte authored Oct 13, 2022
1 parent e0b26d2 commit d73ce21
Show file tree
Hide file tree
Showing 29 changed files with 1,220 additions and 69 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Increased level of detail in JSON-RPC parameter error log messages [#4510](https://github.com/hyperledger/besu/pull/4510)
- New unstable configuration options to set the maximum time, in milliseconds, a PoS block creation jobs is allowed to run [#4519](https://github.com/hyperledger/besu/pull/4519)
- Tune EthScheduler thread pools to avoid to recreate too many threads [#4529](https://github.com/hyperledger/besu/pull/4529)
- RocksDB snapshot based worldstate and plugin-api addition of Snapshot interfaces [#4409](https://github.com/hyperledger/besu/pull/4409)

### Bug Fixes
- Corrects emission of blockadded events when rewinding during a re-org. Fix for [#4495](https://github.com/hyperledger/besu/issues/4495)
Expand Down Expand Up @@ -88,6 +89,7 @@ https://hyperledger.jfrog.io/hyperledger/besu-binaries/besu/22.7.5/besu-22.7.5.t
### Additions and Improvements
- Allow free gas networks in the London fee market [#4061](https://github.com/hyperledger/besu/issues/4061)
- Upgrade besu-native to 0.6.0 and use Blake2bf native implementation if available by default [#4264](https://github.com/hyperledger/besu/pull/4264)
<<<<<<< HEAD
- Resets engine QoS timer with every call to the engine API instead of only when ExchangeTransitionConfiguration is called [#4411](https://github.com/hyperledger/besu/issues/4411)
- ExchangeTransitionConfiguration mismatch will only submit a debug log not a warning anymore [#4411](https://github.com/hyperledger/besu/issues/4411)
- Upgrade besu-native to 0.6.1 and include linux arm64 build of bls12-381 [#4416](https://github.com/hyperledger/besu/pull/4416)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import com.fasterxml.jackson.databind.node.ObjectNode;

Expand All @@ -32,6 +33,10 @@ public String getAddress() {
return address;
}

public Optional<String> getPrivateKey() {
return Optional.ofNullable(JsonUtil.getString(data, "privatekey", null));
}

public String getBalance() {
return JsonUtil.getValueAsString(data, "balance", "0");
}
Expand Down
4 changes: 4 additions & 0 deletions ethereum/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ dependencies {

testImplementation project(path: ':config', configuration: 'testSupportArtifacts')
testImplementation project(path: ':ethereum:api')
testImplementation project(path: ':ethereum:blockcreation')
testImplementation project(path: ':ethereum:referencetests')
testImplementation project(path: ':ethereum:eth')
testImplementation project(':testutil')
testImplementation project(path: ':plugins:rocksdb')


testImplementation 'junit:junit'
testImplementation 'org.apache.logging.log4j:log4j-core'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public Hash rootHash() {
}

public Hash rootHash(final BonsaiWorldStateUpdater localUpdater) {
final BonsaiWorldStateKeyValueStorage.Updater updater = worldStateStorage.updater();
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = worldStateStorage.updater();
try {
final Hash calculatedRootHash = calculateRootHash(updater, localUpdater);
return Hash.wrap(calculatedRootHash);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public BonsaiWorldStateArchive getArchive() {

@Override
public MutableWorldState copy() {
// TODO: consider returning a snapshot rather than a copy here.
BonsaiInMemoryWorldStateKeyValueStorage bonsaiInMemoryWorldStateKeyValueStorage =
new BonsaiInMemoryWorldStateKeyValueStorage(
worldStateStorage.accountStorage,
Expand Down Expand Up @@ -102,7 +103,7 @@ public BonsaiWorldStateKeyValueStorage getWorldStateStorage() {
}

protected Hash calculateRootHash(
final BonsaiWorldStateKeyValueStorage.Updater stateUpdater,
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
// first clear storage
for (final Address address : worldStateUpdater.getStorageToClear()) {
Expand Down Expand Up @@ -241,7 +242,7 @@ public void persist(final BlockHeader blockHeader) {
boolean success = false;

final BonsaiWorldStateUpdater localUpdater = updater.copy();
final BonsaiWorldStateKeyValueStorage.Updater stateUpdater = worldStateStorage.updater();
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater = worldStateStorage.updater();

try {
final Hash newWorldStateRootHash = calculateRootHash(stateUpdater, localUpdater);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;

import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.MutableWorldState;
import org.hyperledger.besu.ethereum.core.SnapshotMutableWorldState;
import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;

/**
* This class takes a snapshot of the worldstate as the basis of a mutable worldstate. It is able to
* commit/perist as a trielog layer only. This is useful for async blockchain opperations like block
* creation and/or point-in-time queries since the snapshot worldstate is fully isolated from the
* main BonsaiPersistedWorldState.
*/
public class BonsaiSnapshotWorldState extends BonsaiInMemoryWorldState
implements SnapshotMutableWorldState {

private final SnappedKeyValueStorage accountSnap;
private final SnappedKeyValueStorage codeSnap;
private final SnappedKeyValueStorage storageSnap;
private final SnappedKeyValueStorage trieBranchSnap;

private BonsaiSnapshotWorldState(
final BonsaiWorldStateArchive archive,
final BonsaiSnapshotWorldStateKeyValueStorage snapshotWorldStateStorage) {
super(archive, snapshotWorldStateStorage);
this.accountSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.accountStorage;
this.codeSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.codeStorage;
this.storageSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.storageStorage;
this.trieBranchSnap = (SnappedKeyValueStorage) snapshotWorldStateStorage.trieBranchStorage;
}

public static BonsaiSnapshotWorldState create(
final BonsaiWorldStateArchive archive,
final BonsaiWorldStateKeyValueStorage parentWorldStateStorage) {
return new BonsaiSnapshotWorldState(
archive,
new BonsaiSnapshotWorldStateKeyValueStorage(
((SnappableKeyValueStorage) parentWorldStateStorage.accountStorage).takeSnapshot(),
((SnappableKeyValueStorage) parentWorldStateStorage.codeStorage).takeSnapshot(),
((SnappableKeyValueStorage) parentWorldStateStorage.storageStorage).takeSnapshot(),
((SnappableKeyValueStorage) parentWorldStateStorage.trieBranchStorage).takeSnapshot(),
parentWorldStateStorage.trieLogStorage));
}

@Override
public void persist(final BlockHeader blockHeader) {
super.persist(blockHeader);
// persist roothash to snapshot tx
trieBranchSnap
.getSnapshotTransaction()
.put(
BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY,
worldStateRootHash.toArrayUnsafe());
}

@Override
public MutableWorldState copy() {
// return a clone-based copy of worldstate storage
return new BonsaiSnapshotWorldState(
archive,
new BonsaiSnapshotWorldStateKeyValueStorage(
accountSnap.cloneFromSnapshot(),
codeSnap.cloneFromSnapshot(),
storageSnap.cloneFromSnapshot(),
trieBranchSnap.cloneFromSnapshot(),
worldStateStorage.trieLogStorage));
}

@Override
public void close() throws Exception {
accountSnap.close();
codeSnap.close();
storageSnap.close();
trieBranchSnap.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.bonsai;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.trie.MerklePatriciaTrie;
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorage;
import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction;
import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;

public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKeyValueStorage {

public BonsaiSnapshotWorldStateKeyValueStorage(
final SnappedKeyValueStorage accountStorage,
final SnappedKeyValueStorage codeStorage,
final SnappedKeyValueStorage storageStorage,
final SnappedKeyValueStorage trieBranchStorage,
final KeyValueStorage trieLogStorage) {
super(accountStorage, codeStorage, storageStorage, trieBranchStorage, trieLogStorage);
}

@Override
public BonsaiUpdater updater() {
return new SnapshotUpdater(
(SnappedKeyValueStorage) accountStorage,
(SnappedKeyValueStorage) codeStorage,
(SnappedKeyValueStorage) storageStorage,
(SnappedKeyValueStorage) trieBranchStorage,
trieLogStorage);
}

public static class SnapshotUpdater implements BonsaiWorldStateKeyValueStorage.BonsaiUpdater {
// private static final Logger LOG =
// LoggerFactory.getLogger(BonsaiSnapshotWorldStateKeyValueStorage.class);

private final SnappedKeyValueStorage accountStorage;
private final SnappedKeyValueStorage codeStorage;
private final SnappedKeyValueStorage storageStorage;
private final SnappedKeyValueStorage trieBranchStorage;
private final KeyValueStorageTransaction trieLogStorageTransaction;

public SnapshotUpdater(
final SnappedKeyValueStorage accountStorage,
final SnappedKeyValueStorage codeStorage,
final SnappedKeyValueStorage storageStorage,
final SnappedKeyValueStorage trieBranchStorage,
final KeyValueStorage trieLogStorage) {
this.accountStorage = accountStorage;
this.codeStorage = codeStorage;
this.storageStorage = storageStorage;
this.trieBranchStorage = trieBranchStorage;
this.trieLogStorageTransaction = trieLogStorage.startTransaction();
}

@Override
public BonsaiUpdater removeCode(final Hash accountHash) {
codeStorage.getSnapshotTransaction().remove(accountHash.toArrayUnsafe());
return this;
}

@Override
public WorldStateStorage.Updater putCode(
final Hash accountHash, final Bytes32 nodeHash, final Bytes code) {
if (code.size() == 0) {
// Don't save empty values
return this;
}
codeStorage.getSnapshotTransaction().put(accountHash.toArrayUnsafe(), code.toArrayUnsafe());
return this;
}

@Override
public BonsaiUpdater removeAccountInfoState(final Hash accountHash) {
accountStorage.getSnapshotTransaction().remove(accountHash.toArrayUnsafe());
return this;
}

@Override
public BonsaiUpdater putAccountInfoState(final Hash accountHash, final Bytes accountValue) {
if (accountValue.size() == 0) {
// Don't save empty values
return this;
}
accountStorage
.getSnapshotTransaction()
.put(accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe());
return this;
}

@Override
public BonsaiUpdater putStorageValueBySlotHash(
final Hash accountHash, final Hash slotHash, final Bytes storage) {
storageStorage
.getSnapshotTransaction()
.put(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(), storage.toArrayUnsafe());
return this;
}

@Override
public void removeStorageValueBySlotHash(final Hash accountHash, final Hash slotHash) {
storageStorage
.getSnapshotTransaction()
.remove(Bytes.concatenate(accountHash, slotHash).toArrayUnsafe());
}

@Override
public KeyValueStorageTransaction getTrieBranchStorageTransaction() {
return trieBranchStorage.getSnapshotTransaction();
}

@Override
public KeyValueStorageTransaction getTrieLogStorageTransaction() {
return trieLogStorageTransaction;
}

@Override
public WorldStateStorage.Updater saveWorldState(
final Bytes blockHash, final Bytes32 nodeHash, final Bytes node) {
trieBranchStorage
.getSnapshotTransaction()
.put(Bytes.EMPTY.toArrayUnsafe(), node.toArrayUnsafe());
trieBranchStorage.getSnapshotTransaction().put(WORLD_ROOT_HASH_KEY, nodeHash.toArrayUnsafe());
trieBranchStorage
.getSnapshotTransaction()
.put(WORLD_BLOCK_HASH_KEY, blockHash.toArrayUnsafe());
return this;
}

@Override
public WorldStateStorage.Updater putAccountStateTrieNode(
final Bytes location, final Bytes32 nodeHash, final Bytes node) {
if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) {
// Don't save empty nodes
return this;
}
trieBranchStorage
.getSnapshotTransaction()
.put(location.toArrayUnsafe(), node.toArrayUnsafe());
return this;
}

@Override
public WorldStateStorage.Updater removeAccountStateTrieNode(
final Bytes location, final Bytes32 nodeHash) {
trieBranchStorage.getSnapshotTransaction().remove(location.toArrayUnsafe());
return this;
}

@Override
public WorldStateStorage.Updater putAccountStorageTrieNode(
final Hash accountHash, final Bytes location, final Bytes32 nodeHash, final Bytes node) {
if (nodeHash.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) {
// Don't save empty nodes
return this;
}
trieBranchStorage
.getSnapshotTransaction()
.put(Bytes.concatenate(accountHash, location).toArrayUnsafe(), node.toArrayUnsafe());
return this;
}

@Override
public void commit() {
// only commit the trielog layer transaction, leave the snapshot transactions open:
trieLogStorageTransaction.commit();
}

@Override
public void rollback() {
// no-op
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ public T getUpdated() {
return updated;
}

public void setPrior(final T prior) {
public BonsaiValue<T> setPrior(final T prior) {
this.prior = prior;
return this;
}

public void setUpdated(final T updated) {
public BonsaiValue<T> setUpdated(final T updated) {
this.cleared = updated == null;
this.updated = updated;
return this;
}

void writeRlp(final RLPOutput output, final BiConsumer<RLPOutput, T> writer) {
Expand Down
Loading

0 comments on commit d73ce21

Please sign in to comment.