Skip to content

Commit 268e098

Browse files
authored
snap sync - apply BALs before flat db heal (#10151)
Signed-off-by: Miroslav Kovar <miroslavkovar@protonmail.com>
1 parent a8dce17 commit 268e098

File tree

9 files changed

+249
-33
lines changed

9 files changed

+249
-33
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright contributors to Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.ethereum.mainnet.block.access.list;
16+
17+
import org.hyperledger.besu.datatypes.Address;
18+
import org.hyperledger.besu.datatypes.StorageSlotKey;
19+
import org.hyperledger.besu.datatypes.Wei;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import java.util.Optional;
24+
25+
import org.apache.tuweni.bytes.Bytes;
26+
import org.apache.tuweni.units.bigints.UInt256;
27+
28+
public final class BlockAccessListChanges {
29+
30+
private BlockAccessListChanges() {}
31+
32+
public static List<AccountFinalChanges> latestChanges(final BlockAccessList blockAccessList) {
33+
final List<AccountFinalChanges> accountFinalChanges = new ArrayList<>();
34+
35+
for (final BlockAccessList.AccountChanges accountChanges : blockAccessList.accountChanges()) {
36+
if (!accountChanges.hasAnyChange()) {
37+
continue;
38+
}
39+
40+
final List<StorageFinalChange> storageFinalChanges = new ArrayList<>();
41+
for (final BlockAccessList.SlotChanges slotChanges : accountChanges.storageChanges()) {
42+
final Optional<BlockAccessList.StorageChange> latestStorageChange =
43+
lastOf(slotChanges.changes());
44+
if (latestStorageChange.isPresent()) {
45+
storageFinalChanges.add(
46+
new StorageFinalChange(
47+
slotChanges.slot(),
48+
latestStorageChange.get().newValue() == null
49+
? UInt256.ZERO
50+
: latestStorageChange.get().newValue()));
51+
}
52+
}
53+
54+
accountFinalChanges.add(
55+
new AccountFinalChanges(
56+
accountChanges.address(),
57+
lastOf(accountChanges.balanceChanges())
58+
.map(BlockAccessList.BalanceChange::postBalance),
59+
lastOf(accountChanges.nonceChanges()).map(BlockAccessList.NonceChange::newNonce),
60+
lastOf(accountChanges.codeChanges()).map(BlockAccessList.CodeChange::newCode),
61+
storageFinalChanges));
62+
}
63+
64+
return accountFinalChanges;
65+
}
66+
67+
public record AccountFinalChanges(
68+
Address address,
69+
Optional<Wei> balance,
70+
Optional<Long> nonce,
71+
Optional<Bytes> code,
72+
List<StorageFinalChange> storageChanges) {}
73+
74+
public record StorageFinalChange(StorageSlotKey slot, UInt256 value) {}
75+
76+
private static <T> Optional<T> lastOf(final List<T> list) {
77+
return list.isEmpty() ? Optional.empty() : Optional.of(list.getLast());
78+
}
79+
}

ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/pathbased/bonsai/worldview/BalStateRootCalculator.java

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@
1616

1717
import org.hyperledger.besu.datatypes.Address;
1818
import org.hyperledger.besu.datatypes.Hash;
19-
import org.hyperledger.besu.datatypes.Wei;
2019
import org.hyperledger.besu.ethereum.ProtocolContext;
2120
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList;
22-
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.AccountChanges;
23-
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessList.SlotChanges;
21+
import org.hyperledger.besu.ethereum.mainnet.block.access.list.BlockAccessListChanges;
2422
import org.hyperledger.besu.ethereum.mainnet.staterootcommitter.BalRootComputation;
2523
import org.hyperledger.besu.ethereum.trie.pathbased.common.provider.WorldStateQueryParams;
2624
import org.hyperledger.besu.ethereum.trie.pathbased.common.storage.PathBasedWorldStateKeyValueStorage;
@@ -29,12 +27,9 @@
2927
import org.hyperledger.besu.evm.account.MutableAccount;
3028
import org.hyperledger.besu.plugin.data.BlockHeader;
3129

32-
import java.util.List;
3330
import java.util.Optional;
3431
import java.util.concurrent.CompletableFuture;
3532

36-
import org.apache.tuweni.units.bigints.UInt256;
37-
3833
@SuppressWarnings("rawtypes")
3934
public class BalStateRootCalculator {
4035

@@ -79,29 +74,16 @@ private static BonsaiWorldState openParentWorldState(
7974

8075
private static void applyBalChanges(
8176
final PathBasedWorldStateUpdateAccumulator accumulator, final BlockAccessList bal) {
82-
for (final AccountChanges changes : bal.accountChanges()) {
83-
if (!changes.hasAnyChange()) {
84-
continue;
85-
}
77+
for (final var changes : BlockAccessListChanges.latestChanges(bal)) {
8678
final Address address = changes.address();
8779
final MutableAccount account = accumulator.getOrCreate(address);
8880

89-
lastOf(changes.balanceChanges())
90-
.ifPresent(c -> account.setBalance(Wei.wrap(c.postBalance())));
91-
lastOf(changes.nonceChanges()).ifPresent(c -> account.setNonce(c.newNonce()));
92-
lastOf(changes.codeChanges()).ifPresent(c -> account.setCode(c.newCode()));
81+
changes.balance().ifPresent(account::setBalance);
82+
changes.nonce().ifPresent(account::setNonce);
83+
changes.code().ifPresent(account::setCode);
9384

94-
for (final SlotChanges slot : changes.storageChanges()) {
95-
lastOf(slot.changes())
96-
.ifPresent(
97-
change ->
98-
slot.slot()
99-
.getSlotKey()
100-
.ifPresent(
101-
key -> {
102-
final UInt256 value = change.newValue();
103-
account.setStorageValue(key, value == null ? UInt256.ZERO : value);
104-
}));
85+
for (final var storage : changes.storageChanges()) {
86+
storage.slot().getSlotKey().ifPresent(key -> account.setStorageValue(key, storage.value()));
10587
}
10688
}
10789
accumulator.clearAccountsThatAreEmpty();
@@ -116,8 +98,4 @@ private static BalRootComputation computeRoot(final PathBasedWorldState worldSta
11698
updater.commit();
11799
return new BalRootComputation(root, accumulator);
118100
}
119-
120-
private static <T> Optional<T> lastOf(final List<T> list) {
121-
return list.isEmpty() ? Optional.empty() : Optional.of(list.getLast());
122-
}
123101
}

ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ public static Optional<PivotSyncDownloader> createSnapDownloader(
9292
chainSyncState != null
9393
? new PivotSyncState(chainSyncState.pivotBlockHeader(), false)
9494
: PivotSyncState.EMPTY_SYNC_STATE;
95-
final SnapSyncProcessState snapSyncState = new SnapSyncProcessState(pivotSyncState);
95+
final Optional<BlockHeader> firstPivotHeader =
96+
chainSyncState != null
97+
? Optional.of(chainSyncState.firstPivotBlockHeader())
98+
: Optional.empty();
99+
final SnapSyncProcessState snapSyncState =
100+
new SnapSyncProcessState(pivotSyncState, firstPivotHeader);
96101

97102
final InMemoryTasksPriorityQueues<SnapDataRequest> snapTaskCollection =
98103
createSnapWorldStateDownloaderTaskCollection();
@@ -101,6 +106,7 @@ public static Optional<PivotSyncDownloader> createSnapDownloader(
101106
ethContext,
102107
snapContext,
103108
protocolContext,
109+
protocolSchedule,
104110
worldStateStorageCoordinator,
105111
snapTaskCollection,
106112
syncConfig.getSnapSyncConfiguration(),

ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
package org.hyperledger.besu.ethereum.eth.sync.snapsync;
1616

17+
import org.hyperledger.besu.ethereum.core.BlockHeader;
1718
import org.hyperledger.besu.ethereum.eth.sync.common.PivotSyncActions;
1819
import org.hyperledger.besu.ethereum.eth.sync.common.PivotSyncDownloader;
1920
import org.hyperledger.besu.ethereum.eth.sync.common.PivotSyncState;
@@ -22,6 +23,7 @@
2223
import org.hyperledger.besu.metrics.SyncDurationMetrics;
2324

2425
import java.nio.file.Path;
26+
import java.util.Optional;
2527
import java.util.concurrent.CompletableFuture;
2628

2729
public class SnapSyncDownloader extends PivotSyncDownloader {
@@ -50,7 +52,11 @@ protected CompletableFuture<PivotSyncState> start(final PivotSyncState fastSyncS
5052

5153
@Override
5254
protected PivotSyncState storeState(final PivotSyncState fastSyncState) {
55+
final Optional<BlockHeader> firstPivotBlockHeader =
56+
initialPivotSyncState instanceof SnapSyncProcessState snapSyncState
57+
? snapSyncState.getFirstPivotBlockHeader().or(fastSyncState::getPivotBlockHeader)
58+
: fastSyncState.getPivotBlockHeader();
5359
initialPivotSyncState = fastSyncState;
54-
return new SnapSyncProcessState(fastSyncState);
60+
return new SnapSyncProcessState(fastSyncState, firstPivotBlockHeader);
5561
}
5662
}

ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncProcessState.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
*/
1515
package org.hyperledger.besu.ethereum.eth.sync.snapsync;
1616

17+
import org.hyperledger.besu.ethereum.core.BlockHeader;
1718
import org.hyperledger.besu.ethereum.core.SealableBlockHeader;
1819
import org.hyperledger.besu.ethereum.eth.sync.common.PivotSyncState;
1920
import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest;
2021

22+
import java.util.Optional;
23+
2124
import org.slf4j.Logger;
2225
import org.slf4j.LoggerFactory;
2326

@@ -28,16 +31,23 @@
2831
public class SnapSyncProcessState extends PivotSyncState {
2932
private static final Logger LOG = LoggerFactory.getLogger(SnapSyncProcessState.class);
3033

34+
private final Optional<BlockHeader> firstPivotBlockHeader;
3135
private boolean isHealTrieInProgress;
3236
private boolean isHealFlatDatabaseInProgress;
3337
private boolean isWaitingBlockchain;
3438

35-
public SnapSyncProcessState(final PivotSyncState fastSyncState) {
39+
public SnapSyncProcessState(
40+
final PivotSyncState fastSyncState, final Optional<BlockHeader> firstPivotBlockHeader) {
3641
super(
3742
fastSyncState.getPivotBlockNumber(),
3843
fastSyncState.getPivotBlockHash(),
3944
fastSyncState.getPivotBlockHeader(),
4045
fastSyncState.isSourceTrusted());
46+
this.firstPivotBlockHeader = firstPivotBlockHeader;
47+
}
48+
49+
public Optional<BlockHeader> getFirstPivotBlockHeader() {
50+
return firstPivotBlockHeader;
4151
}
4252

4353
public boolean isHealTrieInProgress() {

ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest.createAccountFlatHealingRangeRequest;
1818
import static org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest.createAccountTrieNodeDataRequest;
19+
import static org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest.createBlockAccessListDataRequest;
1920
import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy;
2021

2122
import org.hyperledger.besu.ethereum.chain.BlockAddedObserver;
@@ -32,6 +33,7 @@
3233
import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.AccountFlatDatabaseHealingRangeRequest;
3334
import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.StorageFlatDatabaseHealingRangeRequest;
3435
import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldDownloadState;
36+
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
3537
import org.hyperledger.besu.ethereum.trie.RangeManager;
3638
import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
3739
import org.hyperledger.besu.ethereum.worldstate.FlatDbMode;
@@ -51,6 +53,7 @@
5153
import java.util.HashSet;
5254
import java.util.List;
5355
import java.util.Map;
56+
import java.util.Optional;
5457
import java.util.Set;
5558
import java.util.concurrent.atomic.AtomicBoolean;
5659
import java.util.function.Consumer;
@@ -90,6 +93,7 @@ public class SnapWorldDownloadState extends WorldDownloadState<SnapDataRequest>
9093

9194
private final SnapSyncStatePersistenceManager snapContext;
9295
private final SnapSyncProcessState snapSyncState;
96+
private final ProtocolSchedule protocolSchedule;
9397

9498
// blockchain
9599
private final Blockchain blockchain;
@@ -101,12 +105,14 @@ public class SnapWorldDownloadState extends WorldDownloadState<SnapDataRequest>
101105

102106
private final AtomicBoolean trieHealStartedBefore = new AtomicBoolean(false);
103107
private final AtomicBoolean worldStateHealFinishedNotified = new AtomicBoolean(false);
108+
private final AtomicBoolean blockAccessListHealEnqueued = new AtomicBoolean(false);
104109

105110
public SnapWorldDownloadState(
106111
final WorldStateStorageCoordinator worldStateStorageCoordinator,
107112
final SnapSyncStatePersistenceManager snapContext,
108113
final Blockchain blockchain,
109114
final SnapSyncProcessState snapSyncState,
115+
final ProtocolSchedule protocolSchedule,
110116
final InMemoryTasksPriorityQueues<SnapDataRequest> pendingRequests,
111117
final int maxRequestsWithoutProgress,
112118
final long minMillisBeforeStalling,
@@ -124,6 +130,7 @@ public SnapWorldDownloadState(
124130
this.snapContext = snapContext;
125131
this.blockchain = blockchain;
126132
this.snapSyncState = snapSyncState;
133+
this.protocolSchedule = protocolSchedule;
127134
this.metricsManager = metricsManager;
128135
this.blockObserverId = blockchain.observeBlockAdded(createBlockchainObserver());
129136
this.ethContext = ethContext;
@@ -207,6 +214,9 @@ else if (pivotBlockSelector.isBlockchainBehind()) {
207214
if (!snapSyncState.isHealFlatDatabaseInProgress()
208215
&& (worldStateStorageCoordinator.isMatchingFlatMode(FlatDbMode.FULL)
209216
|| worldStateStorageCoordinator.isMatchingFlatMode(FlatDbMode.ARCHIVE))) {
217+
if (enqueueBlockAccessListsForPivotRangeIfRequired()) {
218+
return false;
219+
}
210220
startFlatDatabaseHeal(header);
211221
}
212222
// If the flat database healing process is in progress or the flat database mode is not FULL
@@ -305,6 +315,50 @@ public synchronized void startFlatDatabaseHeal(final BlockHeader header) {
305315
createAccountFlatHealingRangeRequest(header.getStateRoot(), key, value)));
306316
}
307317

318+
private boolean enqueueBlockAccessListsForPivotRangeIfRequired() {
319+
if (!blockAccessListHealEnqueued.compareAndSet(false, true)) {
320+
return false;
321+
}
322+
323+
final Optional<BlockHeader> maybeFirstPivotHeader = snapSyncState.getFirstPivotBlockHeader();
324+
final Optional<BlockHeader> maybeLastPivotHeader = snapSyncState.getPivotBlockHeader();
325+
326+
LOG.debug("Starting BAL apply attempt");
327+
328+
if (maybeFirstPivotHeader.isEmpty() || maybeLastPivotHeader.isEmpty()) {
329+
LOG.debug(
330+
"Skipping BAL apply - firstPivotHeader={}, lastPivotHeader={}",
331+
maybeFirstPivotHeader,
332+
maybeLastPivotHeader);
333+
return false;
334+
}
335+
336+
final BlockHeader firstPivotHeader = maybeFirstPivotHeader.get();
337+
if (!protocolSchedule.getByBlockHeader(firstPivotHeader).isBlockAccessListEnabled()) {
338+
LOG.debug("Skipping BAL apply - BALs not enabled on first pivot {}", firstPivotHeader);
339+
return false;
340+
}
341+
342+
final long fromBlock = firstPivotHeader.getNumber();
343+
final long toBlock = maybeLastPivotHeader.get().getNumber();
344+
if (toBlock < fromBlock) {
345+
LOG.error("Attempted to apply BALs with fromBlock {} > {} toBlock", fromBlock, toBlock);
346+
return false;
347+
}
348+
349+
LOG.info("Queueing block access list heal from block {} to {}", fromBlock, toBlock);
350+
for (long blockNumber = fromBlock; blockNumber <= toBlock; blockNumber++) {
351+
final Optional<BlockHeader> maybeBlockHeader = blockchain.getBlockHeader(blockNumber);
352+
if (maybeBlockHeader.isPresent()) {
353+
final BlockHeader blockHeader = maybeBlockHeader.get();
354+
enqueueRequest(createBlockAccessListDataRequest(blockHeader.getStateRoot(), blockHeader));
355+
} else {
356+
LOG.warn("Unable to queue block access list heal for missing block {}", blockNumber);
357+
}
358+
}
359+
return !pendingBlockAccessListRequests.isEmpty();
360+
}
361+
308362
@Override
309363
public synchronized void enqueueRequest(final SnapDataRequest request) {
310364
if (!internalFuture.isDone()) {

ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.AccountRangeDataRequest;
2828
import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest;
2929
import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader;
30+
import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule;
3031
import org.hyperledger.besu.ethereum.trie.RangeManager;
3132
import org.hyperledger.besu.ethereum.trie.pathbased.bonsai.storage.BonsaiWorldStateKeyValueStorage;
3233
import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator;
@@ -65,6 +66,7 @@ public class SnapWorldStateDownloader implements WorldStateDownloader {
6566
private final int maxOutstandingRequests;
6667
private final int maxNodeRequestsWithoutProgress;
6768
private final ProtocolContext protocolContext;
69+
private final ProtocolSchedule protocolSchedule;
6870
private final WorldStateStorageCoordinator worldStateStorageCoordinator;
6971

7072
private final AtomicReference<SnapWorldDownloadState> downloadState = new AtomicReference<>();
@@ -76,6 +78,7 @@ public SnapWorldStateDownloader(
7678
final EthContext ethContext,
7779
final SnapSyncStatePersistenceManager snapContext,
7880
final ProtocolContext protocolContext,
81+
final ProtocolSchedule protocolSchedule,
7982
final WorldStateStorageCoordinator worldStateStorageCoordinator,
8083
final InMemoryTasksPriorityQueues<SnapDataRequest> snapTaskCollection,
8184
final SnapSyncConfiguration snapSyncConfiguration,
@@ -87,6 +90,7 @@ public SnapWorldStateDownloader(
8790
final SyncDurationMetrics syncDurationMetrics) {
8891
this.ethContext = ethContext;
8992
this.protocolContext = protocolContext;
93+
this.protocolSchedule = protocolSchedule;
9094
this.worldStateStorageCoordinator = worldStateStorageCoordinator;
9195
this.snapContext = snapContext;
9296
this.snapTaskCollection = snapTaskCollection;
@@ -160,6 +164,7 @@ public CompletableFuture<Void> run(
160164
snapContext,
161165
protocolContext.getBlockchain(),
162166
snapSyncState,
167+
protocolSchedule,
163168
snapTaskCollection,
164169
maxNodeRequestsWithoutProgress,
165170
minMillisBeforeStalling,

0 commit comments

Comments
 (0)