Skip to content
Closed
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -105,43 +105,76 @@ public BonsaiWorldStateKeyValueStorage getWorldStateStorage() {
protected Hash calculateRootHash(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
// first clear storage
for (final Address address : worldStateUpdater.getStorageToClear()) {
// because we are clearing persisted values we need the account root as persisted
final BonsaiAccount oldAccount =
worldStateStorage
.getAccount(Hash.hash(address))
.map(bytes -> fromRLP(BonsaiPersistedWorldState.this, address, bytes, true))
.orElse(null);
if (oldAccount == null) {
// This is when an account is both created and deleted within the scope of the same
// block. A not-uncommon DeFi bot pattern.
continue;
clearStorage(stateUpdater, worldStateUpdater);

// This must be done before updating the accounts so
// that we can get the storage state hash
updateAccountStorageState(stateUpdater, worldStateUpdater);

// Third update the code. This has the side effect of ensuring a code hash is calculated.
updateCode(stateUpdater, worldStateUpdater);

// next walk the account trie
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
new StoredMerklePatriciaTrie<>(
this::getAccountStateTrieNode,
worldStateRootHash,
Function.identity(),
Function.identity());

// for manicured tries and composting, collect branches here (not implemented)

addTheAccounts(stateUpdater, worldStateUpdater, accountTrie);

// TODO write to a cache and then generate a layer update from that and the
// DB tx updates. Right now it is just DB updates.
accountTrie.commit(
(location, hash, value) ->
writeTrieNode(stateUpdater.getTrieBranchStorageTransaction(), location, value));
final Bytes32 rootHash = accountTrie.getRootHash();
return Hash.wrap(rootHash);
}

private static void addTheAccounts(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater,
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie) {
for (final Map.Entry<Address, BonsaiValue<BonsaiAccount>> accountUpdate :
worldStateUpdater.getAccountsToUpdate().entrySet()) {
final Bytes accountKey = accountUpdate.getKey();
final BonsaiValue<BonsaiAccount> bonsaiValue = accountUpdate.getValue();
final BonsaiAccount updatedAccount = bonsaiValue.getUpdated();
if (updatedAccount == null) {
final Hash addressHash = Hash.hash(accountKey);
accountTrie.remove(addressHash);
stateUpdater.removeAccountInfoState(addressHash);
} else {
final Hash addressHash = updatedAccount.getAddressHash();
final Bytes accountValue = updatedAccount.serializeAccount();
stateUpdater.putAccountInfoState(Hash.hash(accountKey), accountValue);
accountTrie.put(addressHash, accountValue);
}
final Hash addressHash = Hash.hash(address);
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) -> getStorageTrieNode(addressHash, location, key),
oldAccount.getStorageRoot(),
Function.identity(),
Function.identity());
Map<Bytes32, Bytes> entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
while (!entriesToDelete.isEmpty()) {
entriesToDelete
.keySet()
.forEach(
k -> stateUpdater.removeStorageValueBySlotHash(Hash.hash(address), Hash.wrap(k)));
if (entriesToDelete.size() == 256) {
entriesToDelete.keySet().forEach(storageTrie::remove);
entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
} else {
break;
}
}
}

private static void updateCode(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
for (final Map.Entry<Address, BonsaiValue<Bytes>> codeUpdate :
worldStateUpdater.getCodeToUpdate().entrySet()) {
final Bytes updatedCode = codeUpdate.getValue().getUpdated();
final Hash accountHash = Hash.hash(codeUpdate.getKey());
if (updatedCode == null || updatedCode.size() == 0) {
stateUpdater.removeCode(accountHash);
} else {
stateUpdater.putCode(accountHash, null, updatedCode);
}
}
}

// second update account storage state. This must be done before updating the accounts so
// that we can get the storage state hash
private void updateAccountStorageState(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
for (final Map.Entry<Address, Map<Hash, BonsaiValue<UInt256>>> storageAccountUpdate :
worldStateUpdater.getStorageToUpdate().entrySet()) {
final Address updatedAddress = storageAccountUpdate.getKey();
Expand Down Expand Up @@ -185,54 +218,44 @@ protected Hash calculateRootHash(
}
// for manicured tries and composting, trim and compost here
}
}

// Third update the code. This has the side effect of ensuring a code hash is calculated.
for (final Map.Entry<Address, BonsaiValue<Bytes>> codeUpdate :
worldStateUpdater.getCodeToUpdate().entrySet()) {
final Bytes updatedCode = codeUpdate.getValue().getUpdated();
final Hash accountHash = Hash.hash(codeUpdate.getKey());
if (updatedCode == null || updatedCode.size() == 0) {
stateUpdater.removeCode(accountHash);
} else {
stateUpdater.putCode(accountHash, null, updatedCode);
private void clearStorage(
final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater,
final BonsaiWorldStateUpdater worldStateUpdater) {
for (final Address address : worldStateUpdater.getStorageToClear()) {
// because we are clearing persisted values we need the account root as persisted
final BonsaiAccount oldAccount =
worldStateStorage
.getAccount(Hash.hash(address))
.map(bytes -> fromRLP(BonsaiPersistedWorldState.this, address, bytes, true))
.orElse(null);
if (oldAccount == null) {
// This is when an account is both created and deleted within the scope of the same
// block. A not-uncommon DeFi bot pattern.
continue;
}
}

// next walk the account trie
final StoredMerklePatriciaTrie<Bytes, Bytes> accountTrie =
new StoredMerklePatriciaTrie<>(
this::getAccountStateTrieNode,
worldStateRootHash,
Function.identity(),
Function.identity());

// for manicured tries and composting, collect branches here (not implemented)

// now add the accounts
for (final Map.Entry<Address, BonsaiValue<BonsaiAccount>> accountUpdate :
worldStateUpdater.getAccountsToUpdate().entrySet()) {
final Bytes accountKey = accountUpdate.getKey();
final BonsaiValue<BonsaiAccount> bonsaiValue = accountUpdate.getValue();
final BonsaiAccount updatedAccount = bonsaiValue.getUpdated();
if (updatedAccount == null) {
final Hash addressHash = Hash.hash(accountKey);
accountTrie.remove(addressHash);
stateUpdater.removeAccountInfoState(addressHash);
} else {
final Hash addressHash = updatedAccount.getAddressHash();
final Bytes accountValue = updatedAccount.serializeAccount();
stateUpdater.putAccountInfoState(Hash.hash(accountKey), accountValue);
accountTrie.put(addressHash, accountValue);
final Hash addressHash = Hash.hash(address);
final StoredMerklePatriciaTrie<Bytes, Bytes> storageTrie =
new StoredMerklePatriciaTrie<>(
(location, key) -> getStorageTrieNode(addressHash, location, key),
oldAccount.getStorageRoot(),
Function.identity(),
Function.identity());
Map<Bytes32, Bytes> entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
while (!entriesToDelete.isEmpty()) {
entriesToDelete
.keySet()
.forEach(
k -> stateUpdater.removeStorageValueBySlotHash(Hash.hash(address), Hash.wrap(k)));
entriesToDelete.keySet().forEach(storageTrie::remove);
if (entriesToDelete.size() == 256) {
entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256);
} else {
break;
}
}
}

// TODO write to a cache and then generate a layer update from that and the
// DB tx updates. Right now it is just DB updates.
accountTrie.commit(
(location, hash, value) ->
writeTrieNode(stateUpdater.getTrieBranchStorageTransaction(), location, value));
final Bytes32 rootHash = accountTrie.getRootHash();
return Hash.wrap(rootHash);
}

@Override
Expand Down