From 82813fa8ae5517256190a7500bff306434205f53 Mon Sep 17 00:00:00 2001 From: Satya Date: Wed, 16 Jul 2025 23:59:53 +0800 Subject: [PATCH 01/11] Update Yaci Store version and fix yaci-store jar download handling Upgraded Yaci Store to version 2.0.0-beta3 across CLI configs and Earthfile. Improved download process to handle jar packaged in a zip file, adding extraction, file relocation, and cleanup steps. Adjusted error handling to handle null ClusterInfo and refined logic for jar-specific downloads. --- applications/cli/Earthfile | 2 +- applications/cli/config/download.properties | 6 +-- .../commands/common/DownloadService.java | 37 +++++++++++++++++-- .../commands/general/DownloadCommand.java | 2 +- .../localcluster/peer/LocalPeerService.java | 5 +-- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/applications/cli/Earthfile b/applications/cli/Earthfile index 4fd0761..fc57a66 100644 --- a/applications/cli/Earthfile +++ b/applications/cli/Earthfile @@ -50,7 +50,7 @@ docker-build: FROM ubuntu:22.04 ENV JAVA_HOME=/opt/java/openjdk ENV STORE_VERSION=0.1.0 - ENV STORE_NATIVE_BRANCH=release/2.0.0-beta2 + ENV STORE_NATIVE_BRANCH=release/2.0.0-beta3 ARG TARGETOS ARG TARGETARCH diff --git a/applications/cli/config/download.properties b/applications/cli/config/download.properties index dcca81a..63830bd 100644 --- a/applications/cli/config/download.properties +++ b/applications/cli/config/download.properties @@ -3,9 +3,9 @@ node.version=10.5.0 ogmios.version=6.13.0 kupo.version=2.11.0 -yaci.store.tag=rel-native-2.0.0-beta2 -yaci.store.version=2.0.0-beta2 -yaci.store.jar.version=2.0.0-beta2 +yaci.store.tag=rel-native-2.0.0-beta3 +yaci.store.version=2.0.0-beta3 +yaci.store.jar.version=2.0.0-beta3 #node.url= #ogmios.url= diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java index cd72459..ef3b139 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java @@ -168,6 +168,7 @@ public boolean downloadYaciStoreNative(boolean overwrite) { return false; } + //YaciStore Jar is bundled in a zip file. public boolean downloadYaciStoreJar(boolean overwrite) { downloadJre(overwrite); //Download JRE first (if not already downloaded @@ -190,11 +191,39 @@ public boolean downloadYaciStoreJar(boolean overwrite) { } } - var downloadedFile = download("yaci-store", downloadPath, clusterConfig.getYaciStoreBinPath(), "yaci-store.jar"); + var downloadedFile = download("yaci-store", downloadPath, clusterConfig.getYaciStoreBinPath(), "yaci-store-jar.zip"); if (downloadedFile != null) { - return true; + try { + var tmpFolder = Paths.get(clusterConfig.getYaciStoreBinPath(), "tmp"); + extractZip(downloadedFile.toFile().getAbsolutePath(), tmpFolder.toFile().getAbsolutePath()); + + File[] files = tmpFolder.toFile().listFiles(); + if(files != null && files.length > 0) { + File extractedFolder = files[0]; + if (extractedFolder.getName().startsWith("yaci-store")) { + extractedFolder.renameTo(Paths.get(tmpFolder.toFile().getAbsolutePath(), "yaci-store-files").toFile()); + } + } + + //Move the file yaci-store.jar inside yaci-store folder to yaciStoreBinPath. Then remove the tmpFolder + Path downloadedYaciStoreJar = Paths.get(tmpFolder.toFile().getAbsolutePath(), "yaci-store-files", "yaci-store.jar"); + + if(downloadedYaciStoreJar.toFile().exists()) { + writeLn(info("Copying yaci-store.jar to " + yaciStoreJar.toFile().getAbsolutePath())); + Files.copy(downloadedYaciStoreJar, yaciStoreJar.toFile().toPath()); + writeLn(success("Copied")); + } else { + writeLn(error("yaci-store.jar not found in the extracted folder : " + downloadedYaciStoreJar.toFile().getAbsolutePath())); + } + + FileUtils.deleteDirectory(tmpFolder.toFile()); + return true; + } catch (IOException e) { + e.printStackTrace(); + writeLn(error("Error extracting yaci-store jar zip" + e.getMessage())); + } } else { - writeLn(error("Download failed for yaci-store")); + writeLn(error("Download failed for yaci-store jar zip")); } return false; @@ -503,7 +532,7 @@ private String resolveYaciStoreJarDownloadPath() { return null; } - String url = YACI_STORE_DOWNLOAD_URL + "/v" + yaciStoreJarVersion + "/yaci-store-all-" + yaciStoreJarVersion +".jar"; + String url = YACI_STORE_DOWNLOAD_URL + "/v" + yaciStoreJarVersion + "/yaci-store-" + yaciStoreJarVersion +".zip"; return url; } diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java index d3eb60b..337ce0e 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java @@ -48,7 +48,7 @@ public boolean download( validComponent = true; } - if (componentList.contains("all") || componentList.contains("yaci-store-jar")) { + if (componentList.contains("yaci-store-jar")) { downloadService.downloadYaciStoreJar(overwrite); validComponent = true; } diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java index 1ff5b84..b58e7b3 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java @@ -239,10 +239,9 @@ public void handleClusterStopped(ClusterStopped clusterStopped) { ClusterInfo clusterInfo = null; try { clusterInfo = clusterInfoService.getClusterInfo(clusterName); - } catch (IOException e) { - writeLn(error("Unable to get cluster info for " + clusterName + ": " + e.getMessage())); + } catch (Exception e) { } - if (!clusterInfo.isLocalMultiNodeEnabled()) + if (clusterInfo == null || !clusterInfo.isLocalMultiNodeEnabled()) return; stopLocalPeers(msg -> writeLn(msg)); From 94e33fd0a6430cae3752fe3ede28fe2a21e15be1 Mon Sep 17 00:00:00 2001 From: Satya Date: Thu, 17 Jul 2025 11:53:17 +0800 Subject: [PATCH 02/11] Update CI workflows to use Ubuntu 22.04 instead of 20.04 --- .github/workflows/dev-release-cli.yml | 2 +- .github/workflows/release-cli.yml | 6 +++--- .github/workflows/release-viewer.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dev-release-cli.yml b/.github/workflows/dev-release-cli.yml index 153a4a4..53f6a80 100644 --- a/.github/workflows/dev-release-cli.yml +++ b/.github/workflows/dev-release-cli.yml @@ -12,7 +12,7 @@ jobs: buildAndPush: strategy: matrix: - os: ["ubuntu-20.04", "macos-14"] + os: ["ubuntu-22.04", "macos-14"] runs-on: ${{ matrix.os }} env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index efe59c5..719addb 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -10,7 +10,7 @@ jobs: buildAndPush: strategy: matrix: - os: ["ubuntu-20.04-self-hosted", "macos-14"] + os: ["ubuntu-22.04", "macos-14"] runs-on: ${{ matrix.os }} env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} @@ -73,7 +73,7 @@ jobs: needs: buildAndPush strategy: matrix: - os: [ "ubuntu-20.04-self-hosted", "macos-14" ] + os: [ "ubuntu-22.04", "macos-14" ] runs-on: ${{ matrix.os }} env: FORCE_COLOR: 1 @@ -97,7 +97,7 @@ jobs: - name: Determine Platform-Specific Folder id: platform-folder run: | - if [[ "${{ matrix.os }}" == "ubuntu-20.04-self-hosted" ]]; then + if [[ "${{ matrix.os }}" == "ubuntu-22.04" ]]; then echo "npm_folder=yaci-devkit-linux-x64" >> $GITHUB_ENV elif [[ "${{ matrix.os }}" == "macos-14" ]]; then echo "npm_folder=yaci-devkit-macos-arm64" >> $GITHUB_ENV diff --git a/.github/workflows/release-viewer.yml b/.github/workflows/release-viewer.yml index 3045fd2..69e8136 100644 --- a/.github/workflows/release-viewer.yml +++ b/.github/workflows/release-viewer.yml @@ -7,7 +7,7 @@ on: jobs: npm-publish: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: FORCE_COLOR: 1 steps: From 543fe96e375cbc23bb5f27ab42ab27429d0ac325 Mon Sep 17 00:00:00 2001 From: Satya Date: Thu, 17 Jul 2025 17:49:28 +0800 Subject: [PATCH 03/11] Update configuration file extension to .json --- .../cardano/yacicli/localcluster/peer/LocalPeerService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java index b58e7b3..11e4be9 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java @@ -75,9 +75,9 @@ public void adjustAndCopyRequiredFilesForMultiNodeSetup(String clusterName, Clus //Copy configuration file try { - Path primaryConfigFile = primaryNodeFolder.resolve("configuration.yaml"); - FileUtils.copyFile(primaryConfigFile.toFile(), node2Folder.resolve("configuration.yaml").toFile()); - FileUtils.copyFile(primaryConfigFile.toFile(), node3Folder.resolve("configuration.yaml").toFile()); + Path primaryConfigFile = primaryNodeFolder.resolve("configuration.json"); + FileUtils.copyFile(primaryConfigFile.toFile(), node2Folder.resolve("configuration.json").toFile()); + FileUtils.copyFile(primaryConfigFile.toFile(), node3Folder.resolve("configuration.json").toFile()); } catch (Exception e) { writer.accept(error("Failed to copy configuration files: " + e.getMessage())); } From 622f2da6bae91ec7d81acbafa4be193c780bcccf Mon Sep 17 00:00:00 2001 From: Satya Date: Thu, 17 Jul 2025 17:50:16 +0800 Subject: [PATCH 04/11] The `conwayHardForkAtEpoch` and `shiftStartTimeBehind` properties were commented out --- config/node.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/node.properties b/config/node.properties index 6ea299f..ceb1b3c 100644 --- a/config/node.properties +++ b/config/node.properties @@ -101,5 +101,5 @@ # So, the shiftStartTimeBehind flag should be "false" for non-development / multi-node networks. # ######################################################################################################### -conwayHardForkAtEpoch=1 -shiftStartTimeBehind=true +#conwayHardForkAtEpoch=1 +#shiftStartTimeBehind=true From d7e154a99fb9cb4423cde9dd54cb0daafdd39d67 Mon Sep 17 00:00:00 2001 From: Satya Date: Thu, 17 Jul 2025 19:42:00 +0800 Subject: [PATCH 05/11] Enable new store features and update network URL handling Enabled `adapot` and `governance-aggr` features in application properties. Added `PUBLIC_INDEXER_CLIENT_BASE_URL` for client-side usage to improve environmental variable flexibility. Updated `NetworkInfo.svelte` to prioritize the client-specific base URL. --- applications/cli/docker/store-application.properties | 5 +++++ applications/store-build/store.build.application.properties | 5 +++++ applications/viewer/src/routes/NetworkInfo.svelte | 2 +- config/env | 2 ++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/applications/cli/docker/store-application.properties b/applications/cli/docker/store-application.properties index d368db5..f8a5925 100644 --- a/applications/cli/docker/store-application.properties +++ b/applications/cli/docker/store-application.properties @@ -31,6 +31,11 @@ store.account.api-enabled=true store.account.balance-aggregation-enabled=true store.account.history-cleanup-enabled=false +store.adapot.enabled=true +store.adapot.api-enabled=true +store.governance-aggr.enabled=true +store.governance-aggr.api-enabled=true + store.live.enabled=true store.epoch.endpoints.epoch.local.enabled=true diff --git a/applications/store-build/store.build.application.properties b/applications/store-build/store.build.application.properties index 4cdfefe..66ff6a3 100644 --- a/applications/store-build/store.build.application.properties +++ b/applications/store-build/store.build.application.properties @@ -8,6 +8,11 @@ store.account.api-enabled=true store.account.balance-aggregation-enabled=true store.account.history-cleanup-enabled=false +store.adapot.enabled=true +store.adapot.api-enabled=true +store.governance-aggr.enabled=true +store.governance-aggr.api-enabled=true + store.live.enabled=true store.epoch.endpoints.epoch.local.enabled=true diff --git a/applications/viewer/src/routes/NetworkInfo.svelte b/applications/viewer/src/routes/NetworkInfo.svelte index c2930cb..e6e8355 100644 --- a/applications/viewer/src/routes/NetworkInfo.svelte +++ b/applications/viewer/src/routes/NetworkInfo.svelte @@ -33,7 +33,7 @@ async function fetchEpochData() { try { console.log('Fetching network data'); - const baseUrl = env.PUBLIC_INDEXER_BASE_URL; + const baseUrl = env.PUBLIC_INDEXER_CLIENT_BASE_URL || env.PUBLIC_INDEXER_BASE_URL; const response = await fetch(`${baseUrl}/network`); if (!response.ok) { console.error('Failed to fetch network data:', response.status, response.statusText); diff --git a/config/env b/config/env index c7f2574..9325116 100644 --- a/config/env +++ b/config/env @@ -22,6 +22,8 @@ HOST_KUPO_PORT=1442 # Viewer Config - DON'T CHANGE ####################################################### PUBLIC_INDEXER_BASE_URL=http://yaci-cli:8080/api/v1 +#URL used in the client side +PUBLIC_INDEXER_CLIENT_BASE_URL=http://localhost:8080/api/v1 PUBLIC_INDEXER_WS_URL=ws://localhost:${HOST_STORE_API_PORT}/ws/liveblocks IS_DOCKER=true From cf22b6fadbf8a40820caff56c2fd4e37c3a5151b Mon Sep 17 00:00:00 2001 From: Satya Date: Fri, 18 Jul 2025 11:59:49 +0800 Subject: [PATCH 06/11] Add stake ratio factor for multi-node setups Introduced support for configuring a stake ratio factor in multi-node cluster setups, improving flexibility in testing rollback scenarios. Adjusted stake amount allocation logic, updated CLI options, and ensured proper handling in related services. --- .../commands/general/DownloadCommand.java | 5 ++-- .../yacicli/localcluster/ClusterCommands.java | 4 ++- .../yacicli/localcluster/ClusterInfo.java | 1 + .../yacicli/localcluster/ClusterService.java | 2 +- .../localcluster/ClusterStartService.java | 14 ++++++++++ .../api/ClusterAdminController.java | 4 ++- .../localcluster/peer/LocalPeerService.java | 26 +++++++++---------- .../localcluster/proxy/TcpProxyManager.java | 5 +++- 8 files changed, 42 insertions(+), 19 deletions(-) diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java index 337ce0e..71f804b 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/general/DownloadCommand.java @@ -93,7 +93,8 @@ public void downloadAndStart( @ShellOption(value = {"--enable-kupomios"}, defaultValue = "false", help= "Enable Ogmios and Kupo") boolean enableKupomios, @ShellOption(value = {"--interactive"}, defaultValue="false", help="To start in interactive mode when 'up' command is passed as an arg to yaci-cli") boolean interactive, @ShellOption(value = {"--tail"}, defaultValue="false", help="To tail the network when 'up' command is passed as an arg to yaci-cli. Only works in non-interactive mode.") boolean tail, - @ShellOption(value = {"--enable-multi-node"}, defaultValue = "false", help="Create multiple local block producing nodes") boolean enableMultiNode + @ShellOption(value = {"--enable-multi-node"}, defaultValue = "false", help="Create multiple local block producing nodes") boolean enableMultiNode, + @ShellOption(value = {"--stake-ratio-factor"}, defaultValue = "5", help="The stake ratio between the primary node and two peers is only used when multi-node is enabled for rollback testing.") int stakeRatioFactor ) { if (components == null) @@ -125,7 +126,7 @@ public void downloadAndStart( var status = download(componentList.toArray(new String[0]), overwrite); if (status) { clusterCommands.createCluster(clusterName, port, submitApiPort, slotLength, blockTime, epochLength, - true, true, null, genesisProfile, false, enableMultiNode); + true, true, null, genesisProfile, false, enableMultiNode, stakeRatioFactor); if (!interactive && tail) clusterCommands.ltail(true, true, true, true, true, true, null, null); diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterCommands.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterCommands.java index f5ff117..63ce261 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterCommands.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterCommands.java @@ -106,7 +106,8 @@ public void createCluster(@ShellOption(value = {"-n", "--name"}, defaultValue = help = "Use a pre-defined genesis profile (Options: zero_fee, zero_min_utxo_value, zero_fee_and_min_utxo_value)") GenesisProfile genesisProfile, @ShellOption(value = {"--generate-new-keys"}, defaultValue = "false", help = "Generate new genesis keys, pool keys instead of default keys") boolean generateNewKeys, - @ShellOption(value = {"--enable-multi-node"}, defaultValue = "false", help="Create multiple local block producing nodes") boolean enableMultiNode + @ShellOption(value = {"--enable-multi-node"}, defaultValue = "false", help="Create multiple local block producing nodes") boolean enableMultiNode, + @ShellOption(value = {"--stake-ratio-factor"}, defaultValue = "5", help="The stake ratio between the primary node and two peers is only used when multi-node is enabled for rollback testing") int stakeRatioFactor ) { try { @@ -168,6 +169,7 @@ else if (era.equalsIgnoreCase("conway")) .socatPort(socatPort) .prometheusPort(prometheusPort) .localMultiNodeEnabled(enableMultiNode) + .localMultiNodeStakeRatioFactor(stakeRatioFactor) .build(); boolean success = localClusterService.createNodeClusterFolder(clusterName, clusterInfo, overwrite, generateNewKeys, enableMultiNode, (msg) -> writeLn(msg)); diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterInfo.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterInfo.java index 8185860..8f1cc14 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterInfo.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterInfo.java @@ -44,4 +44,5 @@ public class ClusterInfo { private int prometheusPort=12798; private boolean localMultiNodeEnabled; + private int localMultiNodeStakeRatioFactor; } diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterService.java index 83b6bc3..5b93fcc 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterService.java @@ -361,7 +361,7 @@ private void updateGenesis(Path clusterFolder, String clusterName, ClusterInfo c //Local BP peers for rollback testing if (enableMultiNode) { - localPeerService.setupNewPoolInfos(genesisConfigCopy, writer); + localPeerService.setupNewPoolInfos(genesisConfigCopy, clusterInfo, writer); } String slotLengthStr; diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterStartService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterStartService.java index deb456b..ef9763f 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterStartService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/ClusterStartService.java @@ -1,9 +1,13 @@ package com.bloxbean.cardano.yacicli.localcluster; import com.bloxbean.cardano.yaci.core.util.OSUtil; +import com.bloxbean.cardano.yacicli.common.CommandContext; import com.bloxbean.cardano.yacicli.localcluster.config.CustomGenesisConfig; import com.bloxbean.cardano.yacicli.localcluster.config.GenesisConfig; +import com.bloxbean.cardano.yacicli.localcluster.events.ClusterStarted; +import com.bloxbean.cardano.yacicli.localcluster.events.FirstRunDone; import com.bloxbean.cardano.yacicli.localcluster.model.RunStatus; +import com.bloxbean.cardano.yacicli.localcluster.peer.LocalPeerService; import com.bloxbean.cardano.yacicli.util.PortUtil; import com.bloxbean.cardano.yacicli.util.ProcessUtil; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; @@ -42,6 +46,7 @@ public class ClusterStartService { private final ProcessUtil processUtil; private final GenesisConfig genesisConfig; private final CustomGenesisConfig customGenesisConfig; + private final LocalPeerService localPeerService; private ObjectMapper objectMapper = new ObjectMapper(); private List processes = new ArrayList<>(); @@ -67,6 +72,15 @@ public RunStatus startCluster(ClusterInfo clusterInfo, Path clusterFolder, Consu if (clusterInfo.isMasterNode() && firstRun) setupFirstRun(clusterInfo, clusterFolder, writer); + if (clusterInfo.isLocalMultiNodeEnabled()) { + String clusterName = CommandContext.INSTANCE.getProperty(ClusterConfig.CLUSTER_NAME); + if (firstRun) { + localPeerService.handleFirstRun(new FirstRunDone(clusterName)); + } + + localPeerService.handleClusterStarted(new ClusterStarted(clusterName)); + } + Process nodeProcess = startNode(clusterFolder, clusterInfo, writer); if (nodeProcess == null) { writer.accept(error("Node process could not be started.")); diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java index 69ebf35..cad570b 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java @@ -246,7 +246,7 @@ public boolean create(@RequestBody DevNetCreateRequest request) { try { clusterCommands.createCluster(DEFAULT_CLUSTER_NAME, 3001, 8090, slotLength, blockTime, epochLength, true, - true, "conway", null, false, request.enableMultiNode()); + true, "conway", null, false, request.enableMultiNode(), request.multiNodeStakeRatioFactor); return true; } catch (Exception e) { return false; @@ -261,6 +261,8 @@ public boolean create(@RequestBody DevNetCreateRequest request) { record DevNetCreateRequest(Map genesisProperties, @Schema(description = "Create multiple local block producing nodes", defaultValue = "false") boolean enableMultiNode, + @Schema(description = "The stake ratio between the primary node and two peers is only used when multi-node is enabled for rollback testing", defaultValue = "5") + int multiNodeStakeRatioFactor, @Schema(description = "Enable Yaci Store", defaultValue = "false") boolean enableYaciStore, @Schema(description = "Enable Ogmios", defaultValue = "false") diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java index 11e4be9..628b407 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/peer/LocalPeerService.java @@ -47,7 +47,9 @@ public class LocalPeerService { private List processes = new ArrayList<>(); - public void setupNewPoolInfos(GenesisConfig genesisConfigCopy, Consumer writer) { + public void setupNewPoolInfos(GenesisConfig genesisConfigCopy, ClusterInfo clusterInfo, Consumer writer) { + int stakeRatioFactor = clusterInfo != null? clusterInfo.getLocalMultiNodeStakeRatioFactor(): 5; + genesisConfigCopy.setPools(genesisConfigCopy.getMultiNodePools()); //Bootstrap loaders (Byron) @@ -63,8 +65,8 @@ public void setupNewPoolInfos(GenesisConfig genesisConfigCopy, Consumer var lastItemInFundList = initialFundList.get(initialFundList.size() - 1); initialFundList.set(initialFundList.size() - 1, new GenesisConfig.MapItem<>(lastItemInFundList.key(), lastItemInFundList.value(), false)); - initialFundList.add(new GenesisConfig.MapItem<>("0013b147b6cc8e23615254c31598bc43159d01b6ceb5984e25771677043a82eb2c3f2729b35c504d305e2f582f8d335d033b4109cccac3c74b", new BigInteger("300000000000"), false)); - initialFundList.add(new GenesisConfig.MapItem<>("00cb2015b6312bbbc11e8c912b54e6187d4d4f196fa5536ea6c64c80f6e95458639f01cbf21e75bc2083d245e55b0773bddf0b06c8b4aff6f0", new BigInteger("300000000000"), true)); + initialFundList.add(new GenesisConfig.MapItem<>("0013b147b6cc8e23615254c31598bc43159d01b6ceb5984e25771677043a82eb2c3f2729b35c504d305e2f582f8d335d033b4109cccac3c74b", new BigInteger("300000000000").multiply(BigInteger.valueOf(stakeRatioFactor)), false)); + initialFundList.add(new GenesisConfig.MapItem<>("00cb2015b6312bbbc11e8c912b54e6187d4d4f196fa5536ea6c64c80f6e95458639f01cbf21e75bc2083d245e55b0773bddf0b06c8b4aff6f0", new BigInteger("300000000000").multiply(BigInteger.valueOf(stakeRatioFactor)), true)); } public void adjustAndCopyRequiredFilesForMultiNodeSetup(String clusterName, ClusterInfo clusterInfo, Consumer writer) { @@ -115,7 +117,6 @@ public void adjustAndCopyRequiredFilesForMultiNodeSetup(String clusterName, Clus } } - @EventListener public void handleFirstRun(FirstRunDone firstRunDone) { var clusterName = firstRunDone.getCluster(); Path clusterFolder = clusterConfig.getClusterFolder(clusterName); @@ -134,7 +135,6 @@ public void handleFirstRun(FirstRunDone firstRunDone) { } } - @EventListener public void handleClusterStarted(ClusterStarted clusterStarted) { var clusterName = clusterStarted.getClusterName(); ClusterInfo clusterInfo = null; @@ -166,14 +166,6 @@ public void handleClusterStarted(ClusterStarted clusterStarted) { return; } - //Start tcp proxy - try { - tcpProxyManager.startProxy(4001, "127.0.0.1", 3001); - } catch (IOException e) { - writeLn(error("Failed to start proxy for main node: " + e.getMessage())); - return; - } - try { var node2Process = startNode("node-2", node2Folder, node2Logs, msg -> writeLn(msg)); if (node2Process == null) { @@ -198,6 +190,14 @@ public void handleClusterStarted(ClusterStarted clusterStarted) { } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) { writeLn(error("Error starting node-3: " + e.getMessage())); } + + //Node 1 is last to start + try { + tcpProxyManager.startProxy(4001, "127.0.0.1", 3001); + } catch (IOException e) { + writeLn(error("Failed to start proxy for main node: " + e.getMessage())); + return; + } } private Process startNode(String nodeName, Path nodeFolder, Queue logs, Consumer writer) throws IOException, InterruptedException, ExecutionException, TimeoutException { diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/proxy/TcpProxyManager.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/proxy/TcpProxyManager.java index 3dea635..777241f 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/proxy/TcpProxyManager.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/proxy/TcpProxyManager.java @@ -7,6 +7,9 @@ import java.util.*; import java.util.concurrent.*; +import static com.bloxbean.cardano.yacicli.util.ConsoleWriter.warn; +import static com.bloxbean.cardano.yacicli.util.ConsoleWriter.writeLn; + @Component public class TcpProxyManager { private final Map proxies = new ConcurrentHashMap<>(); @@ -59,7 +62,7 @@ public void start() throws IOException { pipe(server.getInputStream(), client.getOutputStream()); } catch (IOException e) { if (running) { - System.err.println("Proxy error on port " + localPort + ": " + e.getMessage()); + writeLn(warn("Proxy error on port " + localPort + ": " + e.getMessage())); } } } From a3ecb05e614a444c12b084d9149f3f87b782bd12 Mon Sep 17 00:00:00 2001 From: Satya Date: Fri, 18 Jul 2025 12:42:37 +0800 Subject: [PATCH 07/11] Add endpoint to retrieve tips for all nodes in the cluster Introduce a new `/devnet/tip` API to fetch the current tips for all nodes, supporting multi-node setups. The response includes block number, slot, and hash for each node and indicates if multi-node mode is enabled. This enhances visibility into the cluster's state. --- .../api/ClusterAdminController.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java index cad570b..f11ee3b 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/localcluster/api/ClusterAdminController.java @@ -1,6 +1,7 @@ package com.bloxbean.cardano.yacicli.localcluster.api; import com.bloxbean.cardano.yacicli.localcluster.ClusterCommands; +import com.bloxbean.cardano.yacicli.localcluster.ClusterConfig; import com.bloxbean.cardano.yacicli.localcluster.ClusterInfo; import com.bloxbean.cardano.yacicli.localcluster.ClusterService; import com.bloxbean.cardano.yacicli.localcluster.config.ApplicationConfig; @@ -29,6 +30,10 @@ import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import com.bloxbean.cardano.yaci.core.protocol.chainsync.messages.Point; +import com.bloxbean.cardano.yacicli.common.Tuple; +import java.util.ArrayList; +import java.util.List; @RestController @RequestMapping(path = "/local-cluster/api/admin") @@ -258,6 +263,43 @@ public boolean create(@RequestBody DevNetCreateRequest request) { } } + @Operation(summary = "Retrieve the current tip for all nodes (multi-node aware)") + @GetMapping("/devnet/tip") + public MultiNodeTipResponse getTip() { + CommandContext.INSTANCE.setProperty(ClusterConfig.CLUSTER_NAME, DEFAULT_CLUSTER_NAME); + + boolean isMultiNodeEnabled = false; + try { + var clusterInfo = clusterService.getClusterInfo(DEFAULT_CLUSTER_NAME); + isMultiNodeEnabled = clusterInfo.isLocalMultiNodeEnabled(); + } catch (Exception e) { + log.error("Error getting cluster info", e); + } + + List tips = new ArrayList<>(); + + // Get tip for primary node + Tuple tip1 = clusterUtilService.getTip(msg -> log.debug(msg)); + if (tip1 != null) { + tips.add(new NodeTip("node-1", tip1._1, tip1._2.getSlot(), tip1._2.getHash())); + } + + // Get tips for additional nodes if multi-node is enabled + if (isMultiNodeEnabled) { + Tuple tip2 = clusterUtilService.getTip(msg -> log.debug(msg), "node-2"); + if (tip2 != null) { + tips.add(new NodeTip("node-2", tip2._1, tip2._2.getSlot(), tip2._2.getHash())); + } + + Tuple tip3 = clusterUtilService.getTip(msg -> log.debug(msg), "node-3"); + if (tip3 != null) { + tips.add(new NodeTip("node-3", tip3._1, tip3._2.getSlot(), tip3._2.getHash())); + } + } + + return new MultiNodeTipResponse(isMultiNodeEnabled, tips); + } + record DevNetCreateRequest(Map genesisProperties, @Schema(description = "Create multiple local block producing nodes", defaultValue = "false") boolean enableMultiNode, @@ -270,4 +312,22 @@ record DevNetCreateRequest(Map genesisProperties, @Schema(description = "Enable Ogmios and Kupo", defaultValue = "false") boolean enableKupomios) { } + + record NodeTip( + @Schema(description = "Node name") + String nodeName, + @Schema(description = "Current block number") + long blockNumber, + @Schema(description = "Current slot number") + long slot, + @Schema(description = "Current block hash") + String blockHash) { + } + + record MultiNodeTipResponse( + @Schema(description = "Whether multi-node mode is enabled") + boolean multiNodeEnabled, + @Schema(description = "List of tips for all nodes") + List tips) { + } } From 3b7eb4a26b1c2c6fba229348be2d2f42ca1f102c Mon Sep 17 00:00:00 2001 From: Satya <35016438+satran004@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:44:57 +0800 Subject: [PATCH 08/11] Update applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../cardano/yacicli/commands/common/DownloadService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java index ef3b139..ca9aedf 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java @@ -210,7 +210,7 @@ public boolean downloadYaciStoreJar(boolean overwrite) { if(downloadedYaciStoreJar.toFile().exists()) { writeLn(info("Copying yaci-store.jar to " + yaciStoreJar.toFile().getAbsolutePath())); - Files.copy(downloadedYaciStoreJar, yaciStoreJar.toFile().toPath()); + Files.copy(downloadedYaciStoreJar, yaciStoreJar.toFile().toPath(), StandardCopyOption.REPLACE_EXISTING); writeLn(success("Copied")); } else { writeLn(error("yaci-store.jar not found in the extracted folder : " + downloadedYaciStoreJar.toFile().getAbsolutePath())); From e62a25e5df47770474078e13f285557f9b80659c Mon Sep 17 00:00:00 2001 From: Satya <35016438+satran004@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:46:31 +0800 Subject: [PATCH 09/11] Update applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../cardano/yacicli/commands/common/DownloadService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java index ca9aedf..17285b6 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java @@ -201,7 +201,11 @@ public boolean downloadYaciStoreJar(boolean overwrite) { if(files != null && files.length > 0) { File extractedFolder = files[0]; if (extractedFolder.getName().startsWith("yaci-store")) { - extractedFolder.renameTo(Paths.get(tmpFolder.toFile().getAbsolutePath(), "yaci-store-files").toFile()); + File targetFolder = Paths.get(tmpFolder.toFile().getAbsolutePath(), "yaci-store-files").toFile(); + if (!extractedFolder.renameTo(targetFolder)) { + writeLn(error("Failed to rename folder " + extractedFolder.getAbsolutePath() + " to " + targetFolder.getAbsolutePath())); + return false; // Abort further operations + } } } From c492f15af801abc8c7f8693cf0afa09c627e8736 Mon Sep 17 00:00:00 2001 From: Satya <35016438+satran004@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:46:45 +0800 Subject: [PATCH 10/11] Update applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../cardano/yacicli/commands/common/DownloadService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java index 17285b6..30b0a68 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java @@ -224,7 +224,7 @@ public boolean downloadYaciStoreJar(boolean overwrite) { return true; } catch (IOException e) { e.printStackTrace(); - writeLn(error("Error extracting yaci-store jar zip" + e.getMessage())); + writeLn(error("Error extracting yaci-store jar zip: " + e.getMessage())); } } else { writeLn(error("Download failed for yaci-store jar zip")); From 85d993c95ce461dc349a99db4cfa0adb9217677c Mon Sep 17 00:00:00 2001 From: Satya Date: Fri, 18 Jul 2025 12:55:38 +0800 Subject: [PATCH 11/11] Add StandardCopyOption import to DownloadService --- .../cardano/yacicli/commands/common/DownloadService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java index 30b0a68..1c13f7a 100644 --- a/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java +++ b/applications/cli/src/main/java/com/bloxbean/cardano/yacicli/commands/common/DownloadService.java @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.Objects; import static com.bloxbean.cardano.yacicli.util.ConsoleWriter.*;