Skip to content

Commit

Permalink
TrieLog shipping prep (hyperledger#5317)
Browse files Browse the repository at this point in the history
* trielog save event, observer, test
* add zero read slots to Accumulator storageToUpdate, omit zero read slots from trielog generation
* add account zero reads, mark self-destructed accounts, code and storage as cleared

Signed-off-by: garyschulte <[email protected]>
  • Loading branch information
garyschulte authored and eum602 committed Nov 3, 2023
1 parent da43b8d commit e13a153
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import java.util.Objects;
import java.util.function.BiConsumer;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class BonsaiValue<T> {
private T prior;
private T updated;
Expand Down Expand Up @@ -80,6 +83,10 @@ public boolean isUnchanged() {
return Objects.equals(updated, prior);
}

public void setCleared() {
this.cleared = true;
}

public boolean isCleared() {
return cleared;
}
Expand All @@ -95,4 +102,25 @@ public String toString() {
+ cleared
+ '}';
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BonsaiValue<?> that = (BonsaiValue<?>) o;
return new EqualsBuilder()
.append(cleared, that.cleared)
.append(prior, that.prior)
.append(updated, that.updated)
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(prior).append(updated).append(cleared).toHashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
import org.hyperledger.besu.ethereum.bonsai.cache.CachedBonsaiWorldView;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage;
import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiUpdater;
import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogAddedEvent.TrieLogAddedObserver;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState;
import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.util.Subscribers;

import java.util.Map;
import java.util.Optional;
Expand All @@ -36,12 +38,12 @@
public abstract class AbstractTrieLogManager implements TrieLogManager {
private static final Logger LOG = LoggerFactory.getLogger(AbstractTrieLogManager.class);
public static final long RETAINED_LAYERS = 512; // at least 256 + typical rollbacks

protected final Blockchain blockchain;
protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage;

protected final Map<Bytes32, CachedBonsaiWorldView> cachedWorldStatesByHash;
protected final long maxLayersToLoad;
private final Subscribers<TrieLogAddedObserver> trieLogAddedObservers = Subscribers.create();

protected AbstractTrieLogManager(
final Blockchain blockchain,
Expand Down Expand Up @@ -69,6 +71,10 @@ public synchronized void saveTrieLog(
try {
final TrieLogLayer trieLog = prepareTrieLog(forBlockHeader, localUpdater);
persistTrieLog(forBlockHeader, forWorldStateRootHash, trieLog, stateUpdater);

// notify trie log added observers, synchronously
trieLogAddedObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog)));

success = true;
} finally {
if (success) {
Expand Down Expand Up @@ -137,4 +143,14 @@ public long getMaxLayersToLoad() {
public Optional<TrieLogLayer> getTrieLogLayer(final Hash blockHash) {
return rootWorldStateStorage.getTrieLog(blockHash).map(TrieLogLayer::fromBytes);
}

@Override
public synchronized long subscribe(final TrieLogAddedObserver sub) {
return trieLogAddedObservers.subscribe(sub);
}

@Override
public synchronized void unsubscribe(final long id) {
trieLogAddedObservers.unsubscribe(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.ethereum.bonsai.trielog;

public class TrieLogAddedEvent {

private final TrieLogLayer layer;

public TrieLogAddedEvent(final TrieLogLayer layer) {
this.layer = layer;
}

public TrieLogLayer getLayer() {
return layer;
}

@FunctionalInterface
interface TrieLogAddedObserver {

void onTrieLogAdded(TrieLogAddedEvent event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import java.util.function.Function;
import java.util.stream.Stream;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;

Expand Down Expand Up @@ -70,26 +72,29 @@ public Hash getBlockHash() {
return blockHash;
}

public void setBlockHash(final Hash blockHash) {
public TrieLogLayer setBlockHash(final Hash blockHash) {
checkState(!frozen, "Layer is Frozen");
this.blockHash = blockHash;
return this;
}

public void addAccountChange(
public TrieLogLayer addAccountChange(
final Address address,
final StateTrieAccountValue oldValue,
final StateTrieAccountValue newValue) {
checkState(!frozen, "Layer is Frozen");
accounts.put(address, new BonsaiValue<>(oldValue, newValue));
return this;
}

public void addCodeChange(
public TrieLogLayer addCodeChange(
final Address address, final Bytes oldValue, final Bytes newValue, final Hash blockHash) {
checkState(!frozen, "Layer is Frozen");
code.put(
address,
new BonsaiValue<>(
oldValue == null ? Bytes.EMPTY : oldValue, newValue == null ? Bytes.EMPTY : newValue));
return this;
}

public void addStorageChange(
Expand Down Expand Up @@ -317,4 +322,33 @@ public String dump() {
}
return sb.toString();
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TrieLogLayer that = (TrieLogLayer) o;
return new EqualsBuilder()
.append(frozen, that.frozen)
.append(blockHash, that.blockHash)
.append(accounts, that.accounts)
.append(code, that.code)
.append(storage, that.storage)
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(blockHash)
.append(frozen)
.append(accounts)
.append(code)
.append(storage)
.toHashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,8 @@ Optional<BonsaiWorldState> getHeadWorldState(
void reset();

Optional<TrieLogLayer> getTrieLogLayer(final Hash blockHash);

long subscribe(final TrieLogAddedEvent.TrieLogAddedObserver sub);

void unsubscribe(final long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public class BonsaiWorldStateUpdateAccumulator
private final AccountConsumingMap<BonsaiValue<BonsaiAccount>> accountsToUpdate;
private final Map<Address, BonsaiValue<Bytes>> codeToUpdate = new ConcurrentHashMap<>();
private final Set<Address> storageToClear = Collections.synchronizedSet(new HashSet<>());
private final Set<Bytes> emptySlot = Collections.synchronizedSet(new HashSet<>());

// storage sub mapped by _hashed_ key. This is because in self_destruct calls we need to
// enumerate the old storage and delete it. Those are trie stored by hashed key by spec and the
Expand Down Expand Up @@ -98,7 +97,6 @@ void cloneFromUpdater(final BonsaiWorldStateUpdateAccumulator source) {
storageToUpdate.putAll(source.storageToUpdate);
updatedAccounts.putAll(source.updatedAccounts);
deletedAccounts.addAll(source.deletedAccounts);
emptySlot.addAll(source.emptySlot);
this.isAccumulatorStateChanged = true;
}

Expand Down Expand Up @@ -178,12 +176,14 @@ protected BonsaiAccount loadAccount(
} else {
account = wrappedWorldView().get(address);
}
BonsaiAccount mutableAccount = null;
if (account instanceof BonsaiAccount) {
final BonsaiAccount mutableAccount =
new BonsaiAccount((BonsaiAccount) account, this, true);
mutableAccount = new BonsaiAccount((BonsaiAccount) account, this, true);
accountsToUpdate.put(address, new BonsaiValue<>((BonsaiAccount) account, mutableAccount));
return mutableAccount;
} else {
// add the empty read in accountsToUpdate
accountsToUpdate.put(address, new BonsaiValue<>(null, null));
return null;
}
} else {
Expand Down Expand Up @@ -218,11 +218,11 @@ public void commit() {
final BonsaiValue<BonsaiAccount> accountValue =
accountsToUpdate.computeIfAbsent(
deletedAddress,
__ -> loadAccountFromParent(deletedAddress, new BonsaiValue<>(null, null)));
__ -> loadAccountFromParent(deletedAddress, new BonsaiValue<>(null, null, true)));
storageToClear.add(deletedAddress);
final BonsaiValue<Bytes> codeValue = codeToUpdate.get(deletedAddress);
if (codeValue != null) {
codeValue.setUpdated(null);
codeValue.setUpdated(null).setCleared();
} else {
wrappedWorldView()
.getCode(
Expand All @@ -233,7 +233,7 @@ public void commit() {
.orElse(Hash.EMPTY))
.ifPresent(
deletedCode ->
codeToUpdate.put(deletedAddress, new BonsaiValue<>(deletedCode, null)));
codeToUpdate.put(deletedAddress, new BonsaiValue<>(deletedCode, null, true)));
}

// mark all updated storage as to be cleared
Expand All @@ -251,7 +251,7 @@ public void commit() {
if (updatedSlot.getPrior() == null || updatedSlot.getPrior().isZero()) {
iter.remove();
} else {
updatedSlot.setUpdated(null);
updatedSlot.setUpdated(null).setCleared();
}
}

Expand Down Expand Up @@ -399,39 +399,29 @@ public Optional<UInt256> getStorageValueBySlotHash(final Address address, final
return Optional.ofNullable(value.getUpdated());
}
}
final Bytes slot = Bytes.concatenate(Hash.hash(address), slotHash);
if (emptySlot.contains(slot)) {
return Optional.empty();
} else {
try {
final Optional<UInt256> valueUInt =
(wrappedWorldView() instanceof BonsaiWorldState)
? ((BonsaiWorldState) wrappedWorldView())
.getStorageValueBySlotHash(
() ->
Optional.ofNullable(loadAccount(address, BonsaiValue::getPrior))
.map(BonsaiAccount::getStorageRoot),
address,
slotHash)
: wrappedWorldView().getStorageValueBySlotHash(address, slotHash);
valueUInt.ifPresentOrElse(
v ->
storageToUpdate
.computeIfAbsent(
address,
key ->
new StorageConsumingMap<>(
address, new ConcurrentHashMap<>(), storagePreloader))
.put(slotHash, new BonsaiValue<>(v, v)),
() -> {
emptySlot.add(Bytes.concatenate(Hash.hash(address), slotHash));
});
return valueUInt;
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(address), e.getHash(), e.getLocation());
}
try {
final Optional<UInt256> valueUInt =
(wrappedWorldView() instanceof BonsaiWorldState)
? ((BonsaiWorldState) wrappedWorldView())
.getStorageValueBySlotHash(
() ->
Optional.ofNullable(loadAccount(address, BonsaiValue::getPrior))
.map(BonsaiAccount::getStorageRoot),
address,
slotHash)
: wrappedWorldView().getStorageValueBySlotHash(address, slotHash);
storageToUpdate
.computeIfAbsent(
address,
key ->
new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader))
.put(slotHash, new BonsaiValue<>(valueUInt.orElse(null), valueUInt.orElse(null)));

return valueUInt;
} catch (MerkleTrieException e) {
// need to throw to trigger the heal
throw new MerkleTrieException(
e.getMessage(), Optional.of(address), e.getHash(), e.getLocation());
}
}

Expand Down Expand Up @@ -512,6 +502,10 @@ private void importIntoTrieLog(final TrieLogLayer layer, final Hash blockHash) {
newValue.getBalance(),
newValue.getStorageRoot(),
newValue.getCodeHash());
if (oldValue == null && newValue == null) {
// by default do not persist empty reads of accounts to the trie log
continue;
}
layer.addAccountChange(updatedAccount.getKey(), oldAccount, newAccount);
}

Expand All @@ -528,11 +522,14 @@ private void importIntoTrieLog(final TrieLogLayer layer, final Hash blockHash) {
final Address address = updatesStorage.getKey();
for (final Map.Entry<Hash, BonsaiValue<UInt256>> slotUpdate :
updatesStorage.getValue().entrySet()) {
layer.addStorageChange(
address,
slotUpdate.getKey(),
slotUpdate.getValue().getPrior(),
slotUpdate.getValue().getUpdated());
var val = slotUpdate.getValue();

if (val.getPrior() == null && val.getUpdated() == null) {
// by default do not persist empty reads to the trie log
continue;
}

layer.addStorageChange(address, slotUpdate.getKey(), val.getPrior(), val.getUpdated());
}
}
}
Expand Down Expand Up @@ -817,7 +814,6 @@ public void reset() {
storageToUpdate.clear();
codeToUpdate.clear();
accountsToUpdate.clear();
emptySlot.clear();
resetAccumulatorStateChanged();
super.reset();
}
Expand Down
Loading

0 comments on commit e13a153

Please sign in to comment.