Skip to content
Closed
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions applications/cli/config/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ bp.create.enabled=true
#yano.server.port=14447
#yano.http.port=6060

## Slots between empty blocks in Yano's past-time-travel backfill (catch-up from the shifted genesis to
## wall clock). auto (default): a quarter of the stability window 3k/f, the widest gap a Haskell relay can
## still validate with margin, so create time stops growing with epoch length. An integer forces it;
## 1 keeps one block per slot. Needs a Yano build with yano.block-producer.backfill-block-interval-slots.
#yano.backfill.block.interval.slots=auto


######################################################
#To configure an external database for Yaci Store (Indexer),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ public class YanoConfigBuilder {
* @param yanoConfigDir resolved path to Yano's devnet config (genesis files, keys)
* @param yanoDataDir resolved path for Yano's chainstate storage
* @param pastTimeTravelMode whether past-time-travel mode is enabled
* @param backfillBlockIntervalSlots slots between empty blocks in the past-time-travel backfill; 1 = every slot
* @return true if config was written successfully
*/
public boolean build(ClusterInfo clusterInfo, Path yanoConfigDir, Path yanoDataDir,
boolean pastTimeTravelMode) {
boolean pastTimeTravelMode, int backfillBlockIntervalSlots) {
Map<String, String> props = new LinkedHashMap<>();

// Quarkus profile
Expand Down Expand Up @@ -70,6 +71,9 @@ public boolean build(ClusterInfo clusterInfo, Path yanoConfigDir, Path yanoDataD
if (clusterInfo.isLocalMultiNodeEnabled()) {
props.put("yano.block-producer.past-time-travel-slot-leader-mode", "true");
}
if (backfillBlockIntervalSlots > 1) {
props.put("yano.block-producer.backfill-block-interval-slots", String.valueOf(backfillBlockIntervalSlots));
}
}

// Write to {yanoHome}/config/application.properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -47,6 +48,13 @@ public class YanoService {
private List<ExecutorService> executors = new ArrayList<>();
private Queue<String> logs = EvictingQueue.create(300);

// How many slots apart Yano's past-time-travel backfill places its empty blocks
// (yano.block-producer.backfill-block-interval-slots). auto: a quarter of the stability window
// (3k/f), the widest gap a Haskell relay can still validate with margin. An integer forces the
// interval; 1 (or 0) keeps one block per slot. Needs a Yano build that knows the property.
@Value("${yano.backfill.block.interval.slots:auto}")
private String backfillBlockIntervalSlots = "auto";

public boolean start(ClusterInfo clusterInfo, Path clusterFolder, boolean pastTimeTravelMode, Consumer<String> writer) {
logs.clear();

Expand Down Expand Up @@ -233,6 +241,35 @@ private void alignCostModelFromArray(ObjectMapper mapper, ObjectNode costModels,
costModels.set(language, aligned);
}

/**
* Slots between empty blocks in Yano's past-time-travel backfill, from
* {@code yano.backfill.block.interval.slots}: {@code auto} derives a quarter of the stability window
* 3k/f (at least 1); an integer forces it; anything else, or a cluster without k, means 1.
*/
int backfillBlockIntervalSlots(ClusterInfo clusterInfo) {
String setting = backfillBlockIntervalSlots == null ? "auto" : backfillBlockIntervalSlots.trim().toLowerCase();
if (setting.isEmpty() || setting.equals("auto")) {
long stabilityWindow = stabilityWindowSlots(clusterInfo);
return (int) Math.max(1, Math.min(Integer.MAX_VALUE, stabilityWindow / 4));
}
try {
return Math.max(1, Integer.parseInt(setting));
} catch (NumberFormatException e) {
log.warn("Unknown yano.backfill.block.interval.slots '{}', using one block per slot", backfillBlockIntervalSlots);
return 1;
}
}

/** Stability window 3k/f in slots, or 0 when k or f is not known. */
static long stabilityWindowSlots(ClusterInfo clusterInfo) {
long k = clusterInfo.getSecurityParam();
double f = clusterInfo.getActiveSlotsCoeff();
if (k <= 0 || f <= 0) {
return 0;
}
return (long) Math.floor(3 * k / f);
}

private Process startYanoProcess(ClusterInfo clusterInfo, Path clusterFolder, Path yanoConfigDir,
boolean pastTimeTravelMode, Consumer<String> writer)
throws IOException, InterruptedException {
Expand All @@ -244,7 +281,8 @@ private Process startYanoProcess(ClusterInfo clusterInfo, Path clusterFolder, Pa
Files.createDirectories(yanoDataDir);

// Write application.properties for Yano (persists config on disk for debugging)
yanoConfigBuilder.build(clusterInfo, yanoConfigDir, yanoDataDir, pastTimeTravelMode);
int backfillInterval = pastTimeTravelMode ? backfillBlockIntervalSlots(clusterInfo) : 1;
yanoConfigBuilder.build(clusterInfo, yanoConfigDir, yanoDataDir, pastTimeTravelMode, backfillInterval);

ProcessBuilder builder = new ProcessBuilder();
builder.directory(new File(clusterConfig.getYanoHome()));
Expand All @@ -271,6 +309,11 @@ private Process startYanoProcess(ClusterInfo clusterInfo, Path clusterFolder, Pa
if (clusterInfo.isLocalMultiNodeEnabled()) {
env.put("YANO_BLOCK_PRODUCER_PAST_TIME_TRAVEL_SLOT_LEADER_MODE", "true");
}
if (backfillInterval > 1) {
env.put("YANO_BLOCK_PRODUCER_BACKFILL_BLOCK_INTERVAL_SLOTS", String.valueOf(backfillInterval));
writer.accept(info("Yano backfill places one block every %d slots (stability window %d slots)",
backfillInterval, stabilityWindowSlots(clusterInfo)));
}
}

builder.command(yanoBin.toAbsolutePath().toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.bloxbean.cardano.yacicli.localcluster.yano;

import com.bloxbean.cardano.yacicli.localcluster.ClusterInfo;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;

class YanoServiceBackfillIntervalTest {

private static YanoService service(String setting) {
YanoService service = new YanoService(null, null, null);
ReflectionTestUtils.setField(service, "backfillBlockIntervalSlots", setting);
return service;
}

private static ClusterInfo cluster(long k, double f) {
return ClusterInfo.builder().securityParam(k).activeSlotsCoeff(f).build();
}

@Test
void autoDerivesAQuarterOfTheStabilityWindow() {
// k 300, f 1: 3k/f = 900 slots, a quarter is 225
assertThat(service("auto").backfillBlockIntervalSlots(cluster(300, 1.0))).isEqualTo(225);
// k 17280, f 1: 3k/f = 51840, a quarter is 12960
assertThat(service("auto").backfillBlockIntervalSlots(cluster(17280, 1.0))).isEqualTo(12960);
// k 20, f 0.2: 3k/f = 300, a quarter is 75
assertThat(service("auto").backfillBlockIntervalSlots(cluster(20, 0.2))).isEqualTo(75);
}

@Test
void autoFallsBackToOneBlockPerSlotWithoutK() {
assertThat(service("auto").backfillBlockIntervalSlots(cluster(0, 1.0))).isEqualTo(1);
assertThat(service(null).backfillBlockIntervalSlots(cluster(1, 1.0))).isEqualTo(1);
}

@Test
void explicitIntegerForcesTheInterval() {
assertThat(service("500").backfillBlockIntervalSlots(cluster(300, 1.0))).isEqualTo(500);
assertThat(service("1").backfillBlockIntervalSlots(cluster(300, 1.0))).isEqualTo(1);
assertThat(service("0").backfillBlockIntervalSlots(cluster(300, 1.0))).isEqualTo(1);
}

@Test
void unparseableSettingMeansOneBlockPerSlot() {
assertThat(service("dense").backfillBlockIntervalSlots(cluster(300, 1.0))).isEqualTo(1);
}

@Test
void stabilityWindowIsThreeKOverF() {
assertThat(YanoService.stabilityWindowSlots(cluster(300, 1.0))).isEqualTo(900);
assertThat(YanoService.stabilityWindowSlots(cluster(20, 0.2))).isEqualTo(300);
assertThat(YanoService.stabilityWindowSlots(cluster(0, 1.0))).isEqualTo(0);
}
}
13 changes: 13 additions & 0 deletions docs/pages/yano-node-modes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ This avoids requiring users to wait for two epoch transitions before updated pro

Use companion mode when you want normal Haskell-node behavior, including node socket access, but want DevKit to prepare the chain quickly for PV11 protocol parameters.

### Backfill block interval

Yano's catch-up from the shifted genesis to wall clock places one empty block per slot by default, so create
time grows with epoch length. A Haskell relay only needs one block per stability window (`3k/f` slots) to
validate the history. DevKit asks Yano to place a block every quarter of the stability window instead
(plus the first slot of each epoch), through `yano.block-producer.backfill-block-interval-slots`, which needs a
Yano build that knows the property. Force or disable it in `config/application.properties`:

```properties
# auto (default), or a number of slots; 1 keeps one block per slot
yano.backfill.block.interval.slots=auto
```

## Yano-only Mode

In `yano-only` mode, Yano runs as the devnet node. There is no Haskell node handover.
Expand Down