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); + } }