diff --git a/emerald-grpc b/emerald-grpc index 9a2bedc9..6d56214e 160000 --- a/emerald-grpc +++ b/emerald-grpc @@ -1 +1 @@ -Subproject commit 9a2bedc9999ccbc34c9f08351ea3f33eb48322a3 +Subproject commit 6d56214e4a171b7f28de49afd1f47dd3c45dd42f diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt index f901b9a6..f79bda82 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Selector.kt @@ -109,6 +109,7 @@ class Selector { it.lowerHeightSelector.height, it.lowerHeightSelector.lowerBoundType.fromProtoType(), it.lowerHeightSelector.timeOffset, + it.lowerHeightSelector.heightDelta, ) } else { empty @@ -571,10 +572,11 @@ class Selector { private val lowerHeight: Long, private val boundType: LowerBoundType, private val timeOffsetSeconds: Long = 0, + private val delta: Long = 0, ) : Matcher() { override fun matchesWithCause(up: Upstream): MatchesResponse { val predictedLowerBound = up.predictLowerBound(boundType, timeOffsetSeconds) - return if (lowerHeight >= predictedLowerBound && predictedLowerBound != 0L) { + return if (lowerHeight >= (predictedLowerBound - delta) && predictedLowerBound != 0L) { Success } else { LowerHeightResponse(lowerHeight, predictedLowerBound, boundType) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBounds.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBounds.kt index 723aa15c..0dfb5e27 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBounds.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBounds.kt @@ -75,6 +75,12 @@ class LowerBounds( return (lowerBoundCoeffs.k.get() * xTime + lowerBoundCoeffs.b.get()).roundToLong() } + fun predictNextBoundAtSpecificTime(type: LowerBoundType, timestamp: Long): Long { + val lowerBoundCoeffs = lowerBounds[type] ?: return 0 + + return (lowerBoundCoeffs.k.get() * timestamp + lowerBoundCoeffs.b.get()).roundToLong() + } + fun getLastBound(type: LowerBoundType): LowerBoundData? { return lowerBounds[type]?.getLastBound() } @@ -124,7 +130,8 @@ class LowerBounds( regression.addObservation(doubleArrayOf(it.timestamp.toDouble()), it.lowerBound.toDouble()) } - updateCoeffs(regression.slope, regression.intercept) + // we want our line to go through the last point in terms of the function k(y-y(t))+b + updateCoeffs(regression.slope, lowerBounds.last.lowerBound.toDouble() - regression.slope * lowerBounds.last.timestamp) } } } diff --git a/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundsPredictionTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundsPredictionTest.kt index 1bfeafb1..c5794981 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundsPredictionTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/upstream/lowerbound/LowerBoundsPredictionTest.kt @@ -4,10 +4,49 @@ import io.emeraldpay.dshackle.Chain import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit class LowerBoundsPredictionTest { + @Test + fun testPredictionAtSpecificTime() { + val lowerBounds = LowerBounds(Chain.`0G__GALILEO_TESTNET`) + val dateStr = "28.08.2025 11:00:57" + val reqStr = "28.08.2025 11:10:25" + val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss") + val localDateTime = LocalDateTime.parse(dateStr, formatter) + val reqDateTime = LocalDateTime.parse(reqStr, formatter) + + lowerBounds.updateBound( + LowerBoundData( + 3294252, + localDateTime.toEpochSecond(ZoneOffset.UTC), + LowerBoundType.RECEIPTS, + ), + ) + lowerBounds.updateBound( + LowerBoundData( + 3294552, + localDateTime.plus(3, ChronoUnit.MINUTES).toEpochSecond(ZoneOffset.UTC), + LowerBoundType.RECEIPTS, + ), + ) + lowerBounds.updateBound( + LowerBoundData( + 3294552, + localDateTime.plus(6, ChronoUnit.MINUTES).toEpochSecond(ZoneOffset.UTC), + LowerBoundType.RECEIPTS, + ), + ) + + val predicted = lowerBounds.predictNextBoundAtSpecificTime(LowerBoundType.RECEIPTS, reqDateTime.toEpochSecond(ZoneOffset.UTC)) + + assertThat(predicted).isEqualTo(3294725) + } + @Test fun `first archival lower bound data, get it and predict the next bound`() { val lowerBounds = LowerBounds(Chain.ETHEREUM__MAINNET) @@ -196,8 +235,8 @@ class LowerBoundsPredictionTest { val predicted = lowerBounds.predictNextBound(LowerBoundType.STATE, 0) assertThat(predicted) - .isLessThan(37996030) - .isGreaterThan(37996020) + .isLessThan(37996090) + .isGreaterThan(37996070) } @Test