diff --git a/pom.xml b/pom.xml
index 65cc110..7c2189f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,12 @@
${junit-jupiter-engine.version}
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit-jupiter-engine.version}
+ test
+
org.slf4j
diff --git a/src/main/java/org/cardanofoundation/conversions/CardanoConverters.java b/src/main/java/org/cardanofoundation/conversions/CardanoConverters.java
index 32aaf89..1ef81bb 100644
--- a/src/main/java/org/cardanofoundation/conversions/CardanoConverters.java
+++ b/src/main/java/org/cardanofoundation/conversions/CardanoConverters.java
@@ -6,9 +6,9 @@
import org.cardanofoundation.conversions.converters.TimeConversions;
public record CardanoConverters(
- ConversionsConfig conversionsConfig,
- GenesisConfig genesisConfig,
- EpochConversions epoch,
- SlotConversions slot,
- TimeConversions time,
- EraConversions era) {}
+ ConversionsConfig conversionsConfig,
+ GenesisConfig genesisConfig,
+ EpochConversions epoch,
+ SlotConversions slot,
+ TimeConversions time,
+ EraConversions era) {}
diff --git a/src/main/java/org/cardanofoundation/conversions/ClasspathConversionsFactory.java b/src/main/java/org/cardanofoundation/conversions/ClasspathConversionsFactory.java
index fb64037..2603beb 100644
--- a/src/main/java/org/cardanofoundation/conversions/ClasspathConversionsFactory.java
+++ b/src/main/java/org/cardanofoundation/conversions/ClasspathConversionsFactory.java
@@ -50,12 +50,11 @@ public static CardanoConverters createConverters(
return new CardanoConverters(
conversionsConfig,
- genesisConfig,
- epochConversions,
- slotConversions,
- timeConversions,
- eraConversions
- );
+ genesisConfig,
+ epochConversions,
+ slotConversions,
+ timeConversions,
+ eraConversions);
}
private static String getGenesisEraClasspathLink(EraType era, NetworkType networkType) {
diff --git a/src/main/java/org/cardanofoundation/conversions/EraHistory.java b/src/main/java/org/cardanofoundation/conversions/EraHistory.java
index 9072e64..373c7d5 100644
--- a/src/main/java/org/cardanofoundation/conversions/EraHistory.java
+++ b/src/main/java/org/cardanofoundation/conversions/EraHistory.java
@@ -7,9 +7,8 @@
import org.cardanofoundation.conversions.domain.EraType;
/**
- * Data sources:
- * - https://cips.cardano.org/cips/cip59/feature-table.md.html -
- * - https://cardanosolutions.github.io/kupo/#section/Rollbacks-and-chain-forks/How-Kupo-deals-with-rollbacks
+ * Data sources: - https://cips.cardano.org/cips/cip59/feature-table.md.html - -
+ * https://cardanosolutions.github.io/kupo/#section/Rollbacks-and-chain-forks/How-Kupo-deals-with-rollbacks
*/
@RequiredArgsConstructor
public class EraHistory {
diff --git a/src/main/java/org/cardanofoundation/conversions/converters/EraConversions.java b/src/main/java/org/cardanofoundation/conversions/converters/EraConversions.java
index 9c6d4c1..6be94ae 100644
--- a/src/main/java/org/cardanofoundation/conversions/converters/EraConversions.java
+++ b/src/main/java/org/cardanofoundation/conversions/converters/EraConversions.java
@@ -1,5 +1,7 @@
package org.cardanofoundation.conversions.converters;
+import java.time.LocalDateTime;
+import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cardanofoundation.conversions.ConversionRuntimeException;
@@ -7,58 +9,54 @@
import org.cardanofoundation.conversions.domain.EraHistoryItem;
import org.cardanofoundation.conversions.domain.EraType;
-import java.time.LocalDateTime;
-import java.util.Optional;
-
@Slf4j
@RequiredArgsConstructor
public class EraConversions {
- private final GenesisConfig genesisConfig;
- private final SlotConversions slotConversions;
-
- public long firstRealSlot(EraType eraType) {
- return getEraHistoryItem(eraType).firstRealSlotNo();
- }
+ private final GenesisConfig genesisConfig;
+ private final SlotConversions slotConversions;
- public long firstTheoreticalSlot(EraType eraType) {
- return getEraHistoryItem(eraType).firstTheoreticalSlotNo();
- }
+ public long firstRealSlot(EraType eraType) {
+ return getEraHistoryItem(eraType).firstRealSlotNo();
+ }
- public Optional lastTheoreticalSlot(EraType eraType) {
- return getEraHistoryItem(eraType).lastTheoreticalSlotNo();
- }
+ public long firstTheoreticalSlot(EraType eraType) {
+ return getEraHistoryItem(eraType).firstTheoreticalSlotNo();
+ }
- public Optional lastRealSlot(EraType eraType) {
- return getEraHistoryItem(eraType).lastRealSlotNo();
- }
+ public Optional lastTheoreticalSlot(EraType eraType) {
+ return getEraHistoryItem(eraType).lastTheoreticalSlotNo();
+ }
- public LocalDateTime firstRealEraTime(EraType eraType) {
- var absoluteSlot = firstRealSlot(eraType);
+ public Optional lastRealSlot(EraType eraType) {
+ return getEraHistoryItem(eraType).lastRealSlotNo();
+ }
- return slotConversions.slotToTime(absoluteSlot);
- }
+ public LocalDateTime firstRealEraTime(EraType eraType) {
+ var absoluteSlot = firstRealSlot(eraType);
- public LocalDateTime firstTheoreticalEraTime(EraType eraType) {
- var absoluteSlot = firstTheoreticalSlot(eraType);
+ return slotConversions.slotToTime(absoluteSlot);
+ }
- return slotConversions.slotToTime(absoluteSlot);
- }
+ public LocalDateTime firstTheoreticalEraTime(EraType eraType) {
+ var absoluteSlot = firstTheoreticalSlot(eraType);
- public Optional lastRealEraTime(EraType eraType) {
- return lastRealSlot(eraType)
- .map(slotConversions::slotToTime);
- }
+ return slotConversions.slotToTime(absoluteSlot);
+ }
- public Optional lastTheoreticalEraTime(EraType eraType) {
- return lastTheoreticalSlot(eraType)
- .map(slotConversions::slotToTime);
- }
+ public Optional lastRealEraTime(EraType eraType) {
+ return lastRealSlot(eraType).map(slotConversions::slotToTime);
+ }
- private EraHistoryItem getEraHistoryItem(EraType eraType) {
- return genesisConfig.getEraHistory()
- .findFirstByEra(eraType)
- .orElseThrow(() -> new ConversionRuntimeException("Era details not found!, era: " + eraType));
- }
+ public Optional lastTheoreticalEraTime(EraType eraType) {
+ return lastTheoreticalSlot(eraType).map(slotConversions::slotToTime);
+ }
+ private EraHistoryItem getEraHistoryItem(EraType eraType) {
+ return genesisConfig
+ .getEraHistory()
+ .findFirstByEra(eraType)
+ .orElseThrow(
+ () -> new ConversionRuntimeException("Era details not found!, era: " + eraType));
+ }
}
diff --git a/src/main/java/org/cardanofoundation/conversions/converters/SlotConversions.java b/src/main/java/org/cardanofoundation/conversions/converters/SlotConversions.java
index 36d74f7..4267cbe 100644
--- a/src/main/java/org/cardanofoundation/conversions/converters/SlotConversions.java
+++ b/src/main/java/org/cardanofoundation/conversions/converters/SlotConversions.java
@@ -26,4 +26,26 @@ public LocalDateTime slotToTime(long absoluteSlot) {
// for now post byron we have 1 slot = 1 second
return genesisConfig.blockTime(EraType.Shelley, absoluteSlot);
}
+
+ /**
+ * Computes the epoch a slot falls in.
+ *
+ * @param absoluteSlot the slot number to convert
+ * @return the Era number the slot falls in
+ */
+ public Long slotToEpoch(long absoluteSlot) {
+ if (absoluteSlot < 0L) {
+ throw new IllegalArgumentException("absoluteSlot cannot be negative");
+ }
+
+ long firstShelleySlot = genesisConfig.firstShelleySlot();
+
+ if (absoluteSlot < firstShelleySlot) {
+ return absoluteSlot / genesisConfig.slotsPerEpoch(EraType.Byron);
+ } else {
+ var shelleyRelativeSlot = absoluteSlot - firstShelleySlot;
+ var numFullEpochs = shelleyRelativeSlot / genesisConfig.getShelleyEpochLength();
+ return genesisConfig.firstShelleyEpochNo() + numFullEpochs;
+ }
+ }
}
diff --git a/src/main/java/org/cardanofoundation/conversions/converters/TimeConversions.java b/src/main/java/org/cardanofoundation/conversions/converters/TimeConversions.java
index 6a08adf..7a427ac 100644
--- a/src/main/java/org/cardanofoundation/conversions/converters/TimeConversions.java
+++ b/src/main/java/org/cardanofoundation/conversions/converters/TimeConversions.java
@@ -5,6 +5,7 @@
import java.time.Duration;
import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
import lombok.RequiredArgsConstructor;
import org.cardanofoundation.conversions.GenesisConfig;
import org.cardanofoundation.conversions.domain.EraType;
@@ -55,4 +56,24 @@ int utcTimeToEpochNo(EraType era, LocalDateTime utcTime) {
return (int)
Math.ceil((double) (diffDurationSeconds / slotsPerEpoch / byronSlotsLengthSeconds));
}
+
+ /**
+ * @param utcTime the time to convert into a slot
+ * @return the slot corresponding the time passed as input (this might not coincide with a block)
+ */
+ public Long toSlot(LocalDateTime utcTime) {
+
+ if (utcTime.isBefore(genesisConfig.getStartTime())) {
+ throw new IllegalArgumentException("Required that falls before start of the blockchain");
+ } else if (utcTime.isBefore(genesisConfig.getShelleyStartTime())) {
+ var secondsSinceByronBegin =
+ ChronoUnit.SECONDS.between(genesisConfig.getStartTime(), utcTime);
+ return secondsSinceByronBegin / genesisConfig.slotDuration(Byron).getSeconds();
+ } else {
+ var secondsSinceShelleyBegin =
+ ChronoUnit.SECONDS.between(genesisConfig.getShelleyStartTime(), utcTime);
+ return genesisConfig.firstShelleySlot()
+ + secondsSinceShelleyBegin / genesisConfig.slotDuration(Shelley).getSeconds();
+ }
+ }
}
diff --git a/src/test/java/org/cardanofoundation/conversions/converters/EpochConversionsMainNetTest.java b/src/test/java/org/cardanofoundation/conversions/converters/EpochConversionsMainNetTest.java
index 8e904af..ee5e838 100644
--- a/src/test/java/org/cardanofoundation/conversions/converters/EpochConversionsMainNetTest.java
+++ b/src/test/java/org/cardanofoundation/conversions/converters/EpochConversionsMainNetTest.java
@@ -10,7 +10,6 @@
import lombok.extern.slf4j.Slf4j;
import org.cardanofoundation.conversions.ClasspathConversionsFactory;
import org.cardanofoundation.conversions.GenesisConfig;
-import org.cardanofoundation.conversions.domain.EraType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,8 +34,7 @@ public void testIntraByronFork() {
assertThat(epochConversions.epochToAbsoluteSlot(176, START)).isEqualTo(slot);
}
-
- @Test
+ @Test
public void testConvertByronEpochToSlot() {
assertThat(epochConversions.epochToAbsoluteSlot(207, START)).isEqualTo(4471200);
assertThat(epochConversions.epochToAbsoluteSlot(207, END)).isEqualTo(4492799L);
diff --git a/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsMainNetTest.java b/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsMainNetTest.java
index 4595b7f..def9d0f 100644
--- a/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsMainNetTest.java
+++ b/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsMainNetTest.java
@@ -1,81 +1,79 @@
package org.cardanofoundation.conversions.converters;
-import org.cardanofoundation.conversions.ClasspathConversionsFactory;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDateTime;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.cardanofoundation.conversions.domain.EraType.Babbage;
import static org.cardanofoundation.conversions.domain.EraType.Shelley;
import static org.cardanofoundation.conversions.domain.NetworkType.MAINNET;
-class EraConversionsMainNetTest {
- private EraConversions eraConversions;
-
- @BeforeEach
- public void setup() {
- var converters = ClasspathConversionsFactory.createConverters(MAINNET);
- eraConversions = converters.era();
- }
-
- @Test
- void firstRealSlotShelley() {
- var absoluteSlot = eraConversions.firstRealSlot(Shelley);
- assertThat(absoluteSlot).isEqualTo(4492800L);
- }
-
- @Test
- void firstRealSlotBabbage() {
- var absoluteSlot = eraConversions.firstRealSlot(Babbage);
- assertThat(absoluteSlot).isEqualTo(72316896L);
- }
-
- @Test
- void firstTheoreticalSlotShelley() {
- var absoluteSlot = eraConversions.firstTheoreticalSlot(Shelley);
- assertThat(absoluteSlot).isEqualTo(4492800L);
- }
-
- @Test
- void firstTheoreticalSlotBabbage() {
- var absoluteSlot = eraConversions.firstTheoreticalSlot(Babbage);
- assertThat(absoluteSlot).isEqualTo(72316800L);
- }
-
- @Test
- void lastRealSlotBabbage() {
- assertThat(eraConversions.lastRealEraTime(Babbage)).isEmpty();
- }
-
- @Test
- void lastTheoreticalSlotShelley() {
- var absoluteSlot = eraConversions.lastTheoreticalSlot(Shelley).orElseThrow();
- assertThat(absoluteSlot).isEqualTo(16588799L);
- }
-
- @Test
- void firstRealEraTimeShelley() {
- var time = eraConversions.firstRealEraTime(Shelley);
- assertThat(time).isEqualTo(LocalDateTime.of(2020, 7, 29, 21, 44, 51));
- }
-
- @Test
- void lastRealEraTimeShelley() {
- var time = eraConversions.lastRealEraTime(Shelley).orElseThrow();
- assertThat(time).isEqualTo(LocalDateTime.of(2020, 12, 16, 21, 43, 48));
- }
-
- @Test
- void firstRealEraTimeBabbage() {
- var time = eraConversions.firstRealEraTime(Babbage);
- assertThat(time).isEqualTo(LocalDateTime.of(2022, 9, 22, 21, 46, 27));
- }
-
- @Test
- void lastRealEraTimeBabbage() {
- assertThat(eraConversions.lastRealEraTime(Babbage)).isEmpty();
- }
+import java.time.LocalDateTime;
+import org.cardanofoundation.conversions.ClasspathConversionsFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
-}
\ No newline at end of file
+class EraConversionsMainNetTest {
+ private EraConversions eraConversions;
+
+ @BeforeEach
+ public void setup() {
+ var converters = ClasspathConversionsFactory.createConverters(MAINNET);
+ eraConversions = converters.era();
+ }
+
+ @Test
+ void firstRealSlotShelley() {
+ var absoluteSlot = eraConversions.firstRealSlot(Shelley);
+ assertThat(absoluteSlot).isEqualTo(4492800L);
+ }
+
+ @Test
+ void firstRealSlotBabbage() {
+ var absoluteSlot = eraConversions.firstRealSlot(Babbage);
+ assertThat(absoluteSlot).isEqualTo(72316896L);
+ }
+
+ @Test
+ void firstTheoreticalSlotShelley() {
+ var absoluteSlot = eraConversions.firstTheoreticalSlot(Shelley);
+ assertThat(absoluteSlot).isEqualTo(4492800L);
+ }
+
+ @Test
+ void firstTheoreticalSlotBabbage() {
+ var absoluteSlot = eraConversions.firstTheoreticalSlot(Babbage);
+ assertThat(absoluteSlot).isEqualTo(72316800L);
+ }
+
+ @Test
+ void lastRealSlotBabbage() {
+ assertThat(eraConversions.lastRealEraTime(Babbage)).isEmpty();
+ }
+
+ @Test
+ void lastTheoreticalSlotShelley() {
+ var absoluteSlot = eraConversions.lastTheoreticalSlot(Shelley).orElseThrow();
+ assertThat(absoluteSlot).isEqualTo(16588799L);
+ }
+
+ @Test
+ void firstRealEraTimeShelley() {
+ var time = eraConversions.firstRealEraTime(Shelley);
+ assertThat(time).isEqualTo(LocalDateTime.of(2020, 7, 29, 21, 44, 51));
+ }
+
+ @Test
+ void lastRealEraTimeShelley() {
+ var time = eraConversions.lastRealEraTime(Shelley).orElseThrow();
+ assertThat(time).isEqualTo(LocalDateTime.of(2020, 12, 16, 21, 43, 48));
+ }
+
+ @Test
+ void firstRealEraTimeBabbage() {
+ var time = eraConversions.firstRealEraTime(Babbage);
+ assertThat(time).isEqualTo(LocalDateTime.of(2022, 9, 22, 21, 46, 27));
+ }
+
+ @Test
+ void lastRealEraTimeBabbage() {
+ assertThat(eraConversions.lastRealEraTime(Babbage)).isEmpty();
+ }
+}
diff --git a/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsPreProdTest.java b/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsPreProdTest.java
index 4258241..89b5228 100644
--- a/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsPreProdTest.java
+++ b/src/test/java/org/cardanofoundation/conversions/converters/EraConversionsPreProdTest.java
@@ -1,35 +1,31 @@
package org.cardanofoundation.conversions.converters;
-import org.cardanofoundation.conversions.ClasspathConversionsFactory;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDateTime;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.cardanofoundation.conversions.domain.EraType.Shelley;
-import static org.cardanofoundation.conversions.domain.NetworkType.MAINNET;
import static org.cardanofoundation.conversions.domain.NetworkType.PREPROD;
-class EraConversionsPreProdTest {
- private EraConversions eraConversions;
-
- @BeforeEach
- public void setup() {
- var converters = ClasspathConversionsFactory.createConverters(PREPROD);
- eraConversions = converters.era();
- }
-
- @Test
- void firstTheoreticalSlot() {
- var shelleySlot = eraConversions.firstTheoreticalSlot(Shelley);
- assertThat(shelleySlot).isEqualTo(86400L);
- }
-
- @Test
- void lastTheoreticalSlot() {
- var shelleySlot = eraConversions.lastTheoreticalSlot(Shelley).orElseThrow();
- assertThat(shelleySlot).isEqualTo(518399L);
- }
+import org.cardanofoundation.conversions.ClasspathConversionsFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
-}
\ No newline at end of file
+class EraConversionsPreProdTest {
+ private EraConversions eraConversions;
+
+ @BeforeEach
+ public void setup() {
+ var converters = ClasspathConversionsFactory.createConverters(PREPROD);
+ eraConversions = converters.era();
+ }
+
+ @Test
+ void firstTheoreticalSlot() {
+ var shelleySlot = eraConversions.firstTheoreticalSlot(Shelley);
+ assertThat(shelleySlot).isEqualTo(86400L);
+ }
+
+ @Test
+ void lastTheoreticalSlot() {
+ var shelleySlot = eraConversions.lastTheoreticalSlot(Shelley).orElseThrow();
+ assertThat(shelleySlot).isEqualTo(518399L);
+ }
+}
diff --git a/src/test/java/org/cardanofoundation/conversions/converters/SlotConversionsMainNetTest.java b/src/test/java/org/cardanofoundation/conversions/converters/SlotConversionsMainNetTest.java
index 1fb6d36..7b32c92 100644
--- a/src/test/java/org/cardanofoundation/conversions/converters/SlotConversionsMainNetTest.java
+++ b/src/test/java/org/cardanofoundation/conversions/converters/SlotConversionsMainNetTest.java
@@ -9,6 +9,8 @@
import org.cardanofoundation.conversions.GenesisConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
@Slf4j
class SlotConversionsMainNetTest {
@@ -47,4 +49,19 @@ public void testSlot109090938() {
assertThat(slotConversions.slotToTime(slot))
.isEqualTo(LocalDateTime.of(2023, 11, 22, 12, 47, 9));
}
+
+ @ParameterizedTest
+ @CsvSource({
+ "1,0",
+ "21599,0", // last slot epoch 0
+ "21600,1", // first slot epoch 1
+ "4492799,207", // last byron slot
+ "4492800,208", // first shelley slot
+ "4492801,208",
+ "4924800,209",
+ "44237054,300",
+ })
+ public void slotToEpoch(long slot, long epoch) {
+ assertThat(slotConversions.slotToEpoch(slot)).isEqualTo(epoch);
+ }
}
diff --git a/src/test/java/org/cardanofoundation/conversions/converters/TimeConversionsMainNetTest.java b/src/test/java/org/cardanofoundation/conversions/converters/TimeConversionsMainNetTest.java
index 1a8024c..5901c4d 100644
--- a/src/test/java/org/cardanofoundation/conversions/converters/TimeConversionsMainNetTest.java
+++ b/src/test/java/org/cardanofoundation/conversions/converters/TimeConversionsMainNetTest.java
@@ -5,8 +5,11 @@
import java.time.LocalDateTime;
import org.cardanofoundation.conversions.ClasspathConversionsFactory;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
class TimeConversionsMainNetTest {
@@ -53,4 +56,22 @@ public void testBabbageEra1() {
assertThat(timeConversions.utcTimeToEpochNo(LocalDateTime.of(2023, 11, 22, 9, 48, 58)))
.isEqualTo(450);
}
+
+ @Test
+ public void dateTimeToSlotBeforeBlockchainStartThrowsError() {
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> timeConversions.toSlot(LocalDateTime.of(2015, 10, 3, 21, 44, 11)));
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "2017-10-03T21:44:11,43198", // Byron
+ "2023-11-22T12:47:09,109090938", // Shelley
+ "2020-07-29T21:44:31,4492799", // Last Byron
+ "2020-07-29T21:44:51,4492800", // First Shelley
+ })
+ public void slotToEpoch(String dateTime, long epoch) {
+ assertThat(timeConversions.toSlot(LocalDateTime.parse(dateTime))).isEqualTo(epoch);
+ }
}