diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index b01d29ae5d..c707536bd6 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -54,6 +54,26 @@ jobs: --network-id devkit \ --openapi ../../api/src/main/resources/rosetta-specifications-1.4.15/api.yaml + - name: "Wait for indexer data to be ready (stage: LIVE)" + run: | + echo "Waiting for Rosetta API network status stage to be LIVE..." + for i in {1..60}; do + STAGE=$(curl -s -X POST http://localhost:8082/network/status \ + -H 'Content-Type: application/json' \ + -d '{"network_identifier": {"blockchain": "cardano", "network": "devkit"}}' | \ + jq -r '.sync_status.stage' 2>/dev/null || echo "UNKNOWN") + + if [ "$STAGE" = "LIVE" ]; then + echo "✓ Indexer is ready and stage is LIVE!" + exit 0 + fi + + echo "Waiting for stage to be LIVE... Current stage: $STAGE ($i/60)" + sleep 5 + done + echo "❌ Indexer failed to reach LIVE stage within 5 minutes" + exit 1 + - name: "Run rosetta check:data tests" run: ./bin/rosetta-cli check:data --configuration-file ./rosetta-cli-tests/data/byron_sample.json diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusService.java b/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusService.java index d8f74e5cd9..4caba57593 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusService.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusService.java @@ -54,7 +54,26 @@ public void init() { */ @Nullable public Optional calculateSyncStatus(BlockIdentifierExtended latestBlock) { - return offlineSlotService.getCurrentSlotBasedOnTime().map(slotBasedOnTime -> { + Optional currentSlotBasedOnTimeOpt = offlineSlotService.getCurrentSlotBasedOnTime(); + + if (currentSlotBasedOnTimeOpt.isEmpty()) { + // When current slot cannot be determined based on time (e.g. on DevKit where slot converters are null), + // we assume the tip is reached and we check if the required database indexes are ready. + boolean indexesNotReady = indexCreationMonitor.isCreatingIndexes(); + SyncStage stage = indexesNotReady ? SyncStage.APPLYING_INDEXES : SyncStage.LIVE; + boolean isSynced = !indexesNotReady; + + log.info("[SyncStatus] Converters unavailable (devkit). Returning status based on index readiness. Stage: {}, Synced: {}", stage, isSynced); + + return Optional.of(SyncStatus.builder() + .targetIndex(latestBlock.getSlot()) + .currentIndex(latestBlock.getSlot()) + .synced(isSynced) + .stage(stage.getValue()) + .build()); + } + + return currentSlotBasedOnTimeOpt.map(slotBasedOnTime -> { long slotBasedOnLatestBlock = latestBlock.getSlot(); // Check if node has reached the tip diff --git a/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusServiceTest.java b/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusServiceTest.java index b707b9e676..332cc02671 100644 --- a/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusServiceTest.java +++ b/api/src/test/java/org/cardanofoundation/rosetta/api/network/service/SyncStatusServiceTest.java @@ -128,20 +128,36 @@ void shouldReturnNotSyncedWhenNotReachedTip() { } @Test - @DisplayName("Should return empty when current slot cannot be determined") - void shouldReturnEmptyWhenCurrentSlotUnavailable() { + @DisplayName("Should return status based on index readiness when current slot cannot be determined (e.g. devkit)") + void shouldReturnStatusBasedOnIndexesWhenCurrentSlotUnavailable() { // Given BlockIdentifierExtended latestBlock = BlockIdentifierExtended.builder() .slot(1000L) .build(); when(offlineSlotService.getCurrentSlotBasedOnTime()).thenReturn(Optional.empty()); + when(indexCreationMonitor.isCreatingIndexes()).thenReturn(false); // When Optional result = syncStatusService.calculateSyncStatus(latestBlock); // Then - assertThat(result).isEmpty(); + assertThat(result).isPresent(); + assertThat(result.get().getSynced()).isTrue(); + assertThat(result.get().getStage()).isEqualTo(SyncStage.LIVE.getValue()); + assertThat(result.get().getTargetIndex()).isEqualTo(1000L); + assertThat(result.get().getCurrentIndex()).isEqualTo(1000L); + + // And given indexes are not ready + when(indexCreationMonitor.isCreatingIndexes()).thenReturn(true); + + // When + result = syncStatusService.calculateSyncStatus(latestBlock); + + // Then + assertThat(result).isPresent(); + assertThat(result.get().getSynced()).isFalse(); + assertThat(result.get().getStage()).isEqualTo(SyncStage.APPLYING_INDEXES.getValue()); } @Test