Optimize performance of WorldStateUpdater commit method (#4635)

WorldStateUpdater commit method is one of the most consuming methods during block processing (engine_newPayloadV1 call). This PR will focus on parallelizing some parts of this method to make it faster.


Signed-off-by: Ameziane H <ameziane.hamlat@consensys.net>
Signed-off-by: Karim TAAM <karim.t2am@gmail.com>
pull/4659/head
ahamlat 2 years ago committed by GitHub
parent fe0b628e0e
commit fcf640a90b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 155
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateUpdater.java

@ -7,6 +7,7 @@
### Additions and Improvements
- Explain and improve price validation for London and local transactions during block proposal selection [#4602](https://github.com/hyperledger/besu/pull/4602)
- Support for ephemeral testnet Shandong. EIPs are still in flux, besu does not fully sync yet, and the network is subject to restarts. [#//FIXME](https://github.com/hyperledger/besu/pull///FIXME)
- Improve performance of block processing by parallelizing some parts during the "commit" step [#4635](https://github.com/hyperledger/besu/pull/4635)
### Bug Fixes

@ -29,7 +29,6 @@ import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -39,7 +38,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
@ -186,7 +184,7 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
// mark all updated storage as to be cleared
final Map<Hash, BonsaiValue<UInt256>> deletedStorageUpdates =
storageToUpdate.computeIfAbsent(deletedAddress, k -> new HashMap<>());
storageToUpdate.computeIfAbsent(deletedAddress, k -> new ConcurrentHashMap<>());
final Iterator<Map.Entry<Hash, BonsaiValue<UInt256>>> iter =
deletedStorageUpdates.entrySet().iterator();
while (iter.hasNext()) {
@ -219,76 +217,83 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
accountValue.setUpdated(null);
}
for (final UpdateTrackingAccount<BonsaiAccount> tracked : getUpdatedAccounts()) {
final Address updatedAddress = tracked.getAddress();
BonsaiAccount updatedAccount = tracked.getWrappedAccount();
if (updatedAccount == null) {
final BonsaiValue<BonsaiAccount> updatedAccountValue = accountsToUpdate.get(updatedAddress);
updatedAccount = new BonsaiAccount(this, tracked);
tracked.setWrappedAccount(updatedAccount);
if (updatedAccountValue == null) {
accountsToUpdate.put(updatedAddress, new BonsaiValue<>(null, updatedAccount));
codeToUpdate.put(updatedAddress, new BonsaiValue<>(null, updatedAccount.getCode()));
} else {
updatedAccountValue.setUpdated(updatedAccount);
}
} else {
updatedAccount.setBalance(tracked.getBalance());
updatedAccount.setNonce(tracked.getNonce());
if (tracked.codeWasUpdated()) {
updatedAccount.setCode(tracked.getCode());
}
if (tracked.getStorageWasCleared()) {
updatedAccount.clearStorage();
}
tracked.getUpdatedStorage().forEach(updatedAccount::setStorageValue);
}
if (tracked.codeWasUpdated()) {
final BonsaiValue<Bytes> pendingCode =
codeToUpdate.computeIfAbsent(
updatedAddress,
addr -> new BonsaiValue<>(wrappedWorldView().getCode(addr).orElse(null), null));
pendingCode.setUpdated(updatedAccount.getCode());
}
final Map<Hash, BonsaiValue<UInt256>> pendingStorageUpdates =
storageToUpdate.computeIfAbsent(updatedAddress, __ -> new HashMap<>());
if (tracked.getStorageWasCleared()) {
storageToClear.add(updatedAddress);
pendingStorageUpdates.clear();
}
final TreeSet<Map.Entry<UInt256, UInt256>> entries =
new TreeSet<>(
Comparator.comparing(
(Function<Map.Entry<UInt256, UInt256>, UInt256>) Map.Entry::getKey));
entries.addAll(updatedAccount.getUpdatedStorage().entrySet());
for (final Map.Entry<UInt256, UInt256> storageUpdate : entries) {
final UInt256 keyUInt = storageUpdate.getKey();
final Hash slotHash = Hash.hash(keyUInt);
final UInt256 value = storageUpdate.getValue();
final BonsaiValue<UInt256> pendingValue = pendingStorageUpdates.get(slotHash);
if (pendingValue == null) {
pendingStorageUpdates.put(
slotHash, new BonsaiValue<>(updatedAccount.getOriginalStorageValue(keyUInt), value));
} else {
pendingValue.setUpdated(value);
}
}
updatedAccount.getUpdatedStorage().clear();
if (pendingStorageUpdates.isEmpty()) {
storageToUpdate.remove(updatedAddress);
}
if (tracked.getStorageWasCleared()) {
tracked.setStorageWasCleared(false); // storage already cleared for this transaction
}
// TODO maybe add address preimage?
}
getUpdatedAccounts().parallelStream()
.forEach(
tracked -> {
final Address updatedAddress = tracked.getAddress();
final BonsaiAccount updatedAccount;
if (tracked.getWrappedAccount() == null) {
final BonsaiValue<BonsaiAccount> updatedAccountValue =
accountsToUpdate.get(updatedAddress);
updatedAccount = new BonsaiAccount(this, tracked);
tracked.setWrappedAccount(updatedAccount);
if (updatedAccountValue == null) {
accountsToUpdate.put(updatedAddress, new BonsaiValue<>(null, updatedAccount));
codeToUpdate.put(
updatedAddress, new BonsaiValue<>(null, updatedAccount.getCode()));
} else {
updatedAccountValue.setUpdated(updatedAccount);
}
} else {
updatedAccount = tracked.getWrappedAccount();
updatedAccount.setBalance(tracked.getBalance());
updatedAccount.setNonce(tracked.getNonce());
if (tracked.codeWasUpdated()) {
updatedAccount.setCode(tracked.getCode());
}
if (tracked.getStorageWasCleared()) {
updatedAccount.clearStorage();
}
tracked.getUpdatedStorage().forEach(updatedAccount::setStorageValue);
}
if (tracked.codeWasUpdated()) {
final BonsaiValue<Bytes> pendingCode =
codeToUpdate.computeIfAbsent(
updatedAddress,
addr ->
new BonsaiValue<>(wrappedWorldView().getCode(addr).orElse(null), null));
pendingCode.setUpdated(updatedAccount.getCode());
}
final Map<Hash, BonsaiValue<UInt256>> pendingStorageUpdates =
storageToUpdate.computeIfAbsent(updatedAddress, __ -> new ConcurrentHashMap<>());
if (tracked.getStorageWasCleared()) {
storageToClear.add(updatedAddress);
pendingStorageUpdates.clear();
}
final TreeSet<Map.Entry<UInt256, UInt256>> entries =
new TreeSet<>(Map.Entry.comparingByKey());
entries.addAll(updatedAccount.getUpdatedStorage().entrySet());
// parallel stream here may cause database corruption
entries.forEach(
storageUpdate -> {
final UInt256 keyUInt = storageUpdate.getKey();
final Hash slotHash = Hash.hash(keyUInt);
final UInt256 value = storageUpdate.getValue();
final BonsaiValue<UInt256> pendingValue = pendingStorageUpdates.get(slotHash);
if (pendingValue == null) {
pendingStorageUpdates.put(
slotHash,
new BonsaiValue<>(
updatedAccount.getOriginalStorageValue(keyUInt), value));
} else {
pendingValue.setUpdated(value);
}
});
updatedAccount.getUpdatedStorage().clear();
if (pendingStorageUpdates.isEmpty()) {
storageToUpdate.remove(updatedAddress);
}
if (tracked.getStorageWasCleared()) {
tracked.setStorageWasCleared(false); // storage already cleared for this transaction
}
});
}
@Override
@ -611,7 +616,9 @@ public class BonsaiWorldStateUpdater extends AbstractWorldUpdater<BonsaiWorldVie
wrappedWorldView().getStorageValueBySlotHash(address, slotHash);
if (storageValue.isPresent()) {
slotValue = new BonsaiValue<>(storageValue.get(), storageValue.get());
storageToUpdate.computeIfAbsent(address, k -> new HashMap<>()).put(slotHash, slotValue);
storageToUpdate
.computeIfAbsent(address, k -> new ConcurrentHashMap<>())
.put(slotHash, slotValue);
}
}
if (slotValue == null) {

Loading…
Cancel
Save