Skip to content

Commit

Permalink
Add fast sync status to eth_syncing and EthQL query (#565)
Browse files Browse the repository at this point in the history
The EthQL API spec specifies pulledStates and knownStates in the
`syncing` query. Previously we always returned null. This plumbs through
the needed data so that the synchronization states can report the
fast sync progress via EthQL, as well as the `eth_syncing` JSON-RPC.

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
  • Loading branch information
shemnon authored Mar 25, 2020
1 parent 5e368dd commit 6d7d525
Show file tree
Hide file tree
Showing 17 changed files with 197 additions and 29 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Additions and Improvements

-
- Added `pulledStates` and `knownStates` to the EthQL `syncing` query and `eth_syncing` JSON-RPC api [\#565](https://github.com/hyperledger/besu/pull/565)

### Bug Fixes

Expand Down Expand Up @@ -1757,7 +1757,7 @@ If using the URL `http://127.0.0.1` to make JSON-RPC calls, use `--host-whitelis

If your application publishes RPC ports, specify the hostnames when starting Besu. For example:

```json
```bash
pantheon --host-whitelist=example.com
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,10 @@ public Optional<Long> getHighestBlock() {
}

public Optional<Long> getPulledStates() {
// currently synchronizer has no this information
return Optional.empty();
return syncStatus.getPulledStates();
}

public Optional<Long> getKnownStates() {
// currently synchronizer has no this information
return Optional.empty();
return syncStatus.getKnownStates();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({"startingBlock", "currentBlock", "highestBlock"})
Expand All @@ -27,12 +29,16 @@ public class SyncingResult implements JsonRpcResult {
private final String startingBlock;
private final String currentBlock;
private final String highestBlock;
private final String pullStates;
private final String knownStates;

public SyncingResult(final SyncStatus syncStatus) {

this.startingBlock = Quantity.create(syncStatus.getStartingBlock());
this.currentBlock = Quantity.create(syncStatus.getCurrentBlock());
this.highestBlock = Quantity.create(syncStatus.getHighestBlock());
this.pullStates = syncStatus.getPulledStates().map(Quantity::create).orElse(null);
this.knownStates = syncStatus.getKnownStates().map(Quantity::create).orElse(null);
}

@JsonGetter(value = "startingBlock")
Expand All @@ -50,6 +56,18 @@ public String getHighestBlock() {
return highestBlock;
}

@JsonInclude(value = Include.NON_NULL)
@JsonGetter(value = "pulledStates")
public String getPullStates() {
return pullStates;
}

@JsonInclude(value = Include.NON_NULL)
@JsonGetter(value = "knownStates")
public String getKnownStates() {
return knownStates;
}

@Override
public boolean equals(final Object other) {
if (!(other instanceof SyncingResult)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public static void setupConstants() throws Exception {
@Before
public void setupTest() throws Exception {
final Synchronizer synchronizerMock = Mockito.mock(Synchronizer.class);
final SyncStatus status = new DefaultSyncStatus(1, 2, 3);
final SyncStatus status = new DefaultSyncStatus(1, 2, 3, Optional.of(4L), Optional.of(5L));
Mockito.when(synchronizerMock.getSyncStatus()).thenReturn(Optional.of(status));

final EthHashMiningCoordinator miningCoordinatorMock =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1858,7 +1858,8 @@ public void ethSyncingFalse() throws Exception {

@Test
public void ethSyncingResultIsPresent() throws Exception {
final SyncStatus testResult = new DefaultSyncStatus(1L, 8L, 7L);
final SyncStatus testResult =
new DefaultSyncStatus(1L, 8L, 7L, Optional.empty(), Optional.empty());
when(synchronizer.getSyncStatus()).thenReturn(Optional.of(testResult));
final String id = "999";
final RequestBody body =
Expand All @@ -1879,6 +1880,34 @@ public void ethSyncingResultIsPresent() throws Exception {
}
}

@Test
public void ethFastSyncingResultIsPresent() throws Exception {
final SyncStatus testResult =
new DefaultSyncStatus(1L, 8L, 7L, Optional.of(6L), Optional.of(5L));
when(synchronizer.getSyncStatus()).thenReturn(Optional.of(testResult));
final String id = "999";
final RequestBody body =
RequestBody.create(
JSON,
"{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"eth_syncing\"}");

try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
final String respBody = resp.body().string();
final JsonObject json = new JsonObject(respBody);
final JsonObject result = json.getJsonObject("result");
final long startingBlock = Long.decode(result.getString("startingBlock"));
assertThat(startingBlock).isEqualTo(1L);
final long currentBlock = Long.decode(result.getString("currentBlock"));
assertThat(currentBlock).isEqualTo(8L);
final long highestBlock = Long.decode(result.getString("highestBlock"));
assertThat(highestBlock).isEqualTo(7L);
final long pulledStates = Long.decode(result.getString("pulledStates"));
assertThat(pulledStates).isEqualTo(6L);
final long knownStates = Long.decode(result.getString("knownStates"));
assertThat(knownStates).isEqualTo(5L);
}
}

public BlockWithMetadata<TransactionWithMetadata, Hash> blockWithMetadata(final Block block) {
final Difficulty td = block.getHeader().getDifficulty().add(10L);
final int size = block.calculateSize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public void shouldNotBeReadyWhenCustomMaxBlocksBehindIsInvalid() {
}

private Optional<SyncStatus> createSyncStatus(final int currentBlock, final int highestBlock) {
return Optional.of(new DefaultSyncStatus(0, currentBlock, highestBlock));
return Optional.of(
new DefaultSyncStatus(0, currentBlock, highestBlock, Optional.empty(), Optional.empty()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,25 @@ public void shouldReturnFalseWhenSyncStatusIsEmpty() {
@Test
public void shouldReturnExpectedValueWhenSyncStatusIsNotEmpty() {
final JsonRpcRequestContext request = requestWithParams();
final SyncStatus expectedSyncStatus = new DefaultSyncStatus(0, 1, 2);
final SyncStatus expectedSyncStatus =
new DefaultSyncStatus(0, 1, 2, Optional.empty(), Optional.empty());
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(
request.getRequest().getId(), new SyncingResult(expectedSyncStatus));
final Optional<SyncStatus> optionalSyncStatus = Optional.of(expectedSyncStatus);
when(synchronizer.getSyncStatus()).thenReturn(optionalSyncStatus);

final JsonRpcResponse actualResponse = method.response(request);
assertThat(actualResponse).isEqualToComparingFieldByField(expectedResponse);
verify(synchronizer).getSyncStatus();
verifyNoMoreInteractions(synchronizer);
}

@Test
public void shouldReturnExpectedValueWhenFastSyncStatusIsNotEmpty() {
final JsonRpcRequestContext request = requestWithParams();
final SyncStatus expectedSyncStatus =
new DefaultSyncStatus(0, 1, 2, Optional.of(3L), Optional.of(4L));
final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(
request.getRequest().getId(), new SyncingResult(expectedSyncStatus));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ public void shouldSendSyncStatusWhenReceiveSyncStatus() {
final SyncingSubscription subscription =
new SyncingSubscription(9L, "conn", SubscriptionType.SYNCING);
final List<SyncingSubscription> subscriptions = Collections.singletonList(subscription);
final Optional<SyncStatus> syncStatus = Optional.of(new DefaultSyncStatus(0L, 1L, 3L));
final Optional<SyncStatus> syncStatus =
Optional.of(new DefaultSyncStatus(0L, 1L, 3L, Optional.empty(), Optional.empty()));
final JsonRpcResult expectedSyncingResult = new SyncingResult(syncStatus.get());

doAnswer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"startingBlock" : 1,
"currentBlock" : 2,
"highestBlock" : 3,
"pulledStates" : null,
"knownStates" : null
"pulledStates" : 4,
"knownStates" : 5
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@
package org.hyperledger.besu.ethereum.core;

import java.util.Objects;
import java.util.Optional;

public final class DefaultSyncStatus implements org.hyperledger.besu.plugin.data.SyncStatus {

private final long startingBlock;
private final long currentBlock;
private final long highestBlock;
private final Optional<Long> pulledStates;
private final Optional<Long> knownStates;

public DefaultSyncStatus(
final long startingBlock, final long currentBlock, final long highestBlock) {
final long startingBlock,
final long currentBlock,
final long highestBlock,
final Optional<Long> pulledStates,
final Optional<Long> knownStates) {
this.startingBlock = startingBlock;
this.currentBlock = currentBlock;
this.highestBlock = highestBlock;
this.pulledStates = pulledStates;
this.knownStates = knownStates;
}

@Override
Expand All @@ -44,6 +53,16 @@ public long getHighestBlock() {
return highestBlock;
}

@Override
public Optional<Long> getPulledStates() {
return pulledStates;
}

@Override
public Optional<Long> getKnownStates() {
return knownStates;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static <C> Optional<FastSyncDownloader<C>> create(
new FastSyncStateStorage(fastSyncDataDirectory);
final FastSyncState fastSyncState =
fastSyncStateStorage.loadState(ScheduleBasedBlockHeaderFunctions.create(protocolSchedule));
if (!fastSyncState.getPivotBlockHeader().isPresent()
if (fastSyncState.getPivotBlockHeader().isEmpty()
&& protocolContext.getBlockchain().getChainHeadBlockNumber()
!= BlockHeader.GENESIS_BLOCK_NUMBER) {
LOG.info(
Expand Down Expand Up @@ -98,6 +98,7 @@ public static <C> Optional<FastSyncDownloader<C>> create(
taskCollection,
fastSyncDataDirectory,
fastSyncState);
syncState.setWorldStateDownloadStatus(worldStateDownloader);
return Optional.of(fastSyncDownloader);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.hyperledger.besu.ethereum.eth.manager.ChainHeadEstimate;
import org.hyperledger.besu.ethereum.eth.manager.EthPeer;
import org.hyperledger.besu.ethereum.eth.manager.EthPeers;
import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloadStatus;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import org.hyperledger.besu.plugin.data.SyncStatus;
import org.hyperledger.besu.plugin.services.BesuEvents.SyncStatusListener;
Expand All @@ -43,6 +44,7 @@ public class SyncState {
private final Subscribers<SyncStatusListener> syncStatusListeners = Subscribers.create();
private volatile long chainHeightListenerId;
private volatile Optional<SyncTarget> syncTarget = Optional.empty();
private Optional<WorldStateDownloadStatus> worldStateDownloadStatus = Optional.empty();

public SyncState(final Blockchain blockchain, final EthPeers ethPeers) {
this.blockchain = blockchain;
Expand Down Expand Up @@ -111,6 +113,10 @@ public void setSyncTarget(final EthPeer peer, final BlockHeader commonAncestor)
replaceSyncTarget(Optional.of(syncTarget));
}

public void setWorldStateDownloadStatus(final WorldStateDownloadStatus worldStateDownloadStatus) {
this.worldStateDownloadStatus = Optional.ofNullable(worldStateDownloadStatus);
}

public boolean isInSync() {
return isInSync(Synchronizer.DEFAULT_IN_SYNC_TOLERANCE);
}
Expand Down Expand Up @@ -183,7 +189,12 @@ private Optional<SyncStatus> syncStatus(final Optional<SyncTarget> maybeTarget)
final long chainHeadBlockNumber = blockchain.getChainHeadBlockNumber();
final long commonAncestor = target.commonAncestor().getNumber();
final long highestKnownBlock = bestChainHeight(chainHeadBlockNumber);
return new DefaultSyncStatus(commonAncestor, chainHeadBlockNumber, highestKnownBlock);
return new DefaultSyncStatus(
commonAncestor,
chainHeadBlockNumber,
highestKnownBlock,
worldStateDownloadStatus.flatMap(WorldStateDownloadStatus::getPulledStates),
worldStateDownloadStatus.flatMap(WorldStateDownloadStatus::getKnownStates));
});
}

Expand Down Expand Up @@ -211,9 +222,9 @@ public long bestChainHeight(final long localChainHeight) {
}

private synchronized void checkInSync() {
ChainHead localChain = getLocalChainHead();
Optional<ChainHeadEstimate> syncTargetChain = getSyncTargetChainHead();
Optional<ChainHeadEstimate> bestPeerChain = getBestPeerChainHead();
final ChainHead localChain = getLocalChainHead();
final Optional<ChainHeadEstimate> syncTargetChain = getSyncTargetChainHead();
final Optional<ChainHeadEstimate> bestPeerChain = getBestPeerChainHead();

inSyncTrackers
.values()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public class CompleteTaskStep {
private final Counter retriedRequestsCounter;
private final LongSupplier worldStatePendingRequestsCurrentSupplier;
private final DecimalFormat doubleFormatter = new DecimalFormat("#.##");
private double estimatedWorldStateCompletion;

public CompleteTaskStep(
final WorldStateStorage worldStateStorage,
Expand Down Expand Up @@ -80,16 +79,23 @@ public void markAsCompleteOrFailed(
private void displayWorldStateSyncProgress() {
LOG.info(
"Downloaded {} world state nodes. At least {} nodes remaining. Estimated World State completion: {} %.",
completedRequestsCounter.get(),
getCompletedRequests(),
worldStatePendingRequestsCurrentSupplier.getAsLong(),
doubleFormatter.format(computeWorldStateSyncProgress() * 100.0));
}

public double computeWorldStateSyncProgress() {
final double pendingRequests = worldStatePendingRequestsCurrentSupplier.getAsLong();
final double completedRequests = completedRequestsCounter.get();
estimatedWorldStateCompletion = completedRequests / (completedRequests + pendingRequests);
return this.estimatedWorldStateCompletion;
final double pendingRequests = getPendingRequests();
final double completedRequests = getCompletedRequests();
return completedRequests / (completedRequests + pendingRequests);
}

long getCompletedRequests() {
return completedRequestsCounter.get();
}

long getPendingRequests() {
return worldStatePendingRequestsCurrentSupplier.getAsLong();
}

private void enqueueChildren(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright ConsenSys AG.
*
* 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.eth.sync.worldstate;

import java.util.Optional;

public interface WorldStateDownloadStatus {

public Optional<Long> getPulledStates();

public Optional<Long> getKnownStates();
}
Loading

0 comments on commit 6d7d525

Please sign in to comment.