From 1fd6344a5d2a89bf7993193678520f2dc27a60bd Mon Sep 17 00:00:00 2001 From: Pierre-Marie Padiou Date: Mon, 3 Jan 2022 16:00:38 +0100 Subject: [PATCH] Define 9999-12-31 as max value for timestamps (#2118) This effectively reverts #2112 and implements it differently. Having a single very conservative max value is simpler and the risk of regression is very low. Also fixed a wrong comment, previous max wasn't `11/04/2262` but a huge value. --- .../src/main/scala/fr/acinq/eclair/Timestamp.scala | 8 ++++---- .../src/test/scala/fr/acinq/eclair/TimestampSpec.scala | 6 +++--- .../fr/acinq/eclair/api/directives/ExtraDirectives.scala | 2 +- .../test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala | 7 +++---- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Timestamp.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Timestamp.scala index 56683dd48..1d6a1b447 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Timestamp.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Timestamp.scala @@ -21,7 +21,7 @@ import java.time.Instant import scala.concurrent.duration.{DurationLong, FiniteDuration} case class TimestampSecond(private val underlying: Long) extends Ordered[TimestampSecond] { - require(underlying >= 0 && underlying <= Long.MaxValue / 1000, "invalid timestamp value") + require(underlying >= 0 && underlying <= 253402300799L, "invalid timestamp value") // @formatter:off def toLong: Long = underlying def toTimestampMilli: TimestampMilli = TimestampMilli(underlying * 1000) @@ -38,12 +38,12 @@ case class TimestampSecond(private val underlying: Long) extends Ordered[Timesta object TimestampSecond { val min: TimestampSecond = TimestampSecond(0) // 1/1/1970 - val max: TimestampSecond = TimestampSecond(Long.MaxValue / 1000) // 11/04/2262 (upper limit prevents overflow when converting to milli precision) + val max: TimestampSecond = TimestampSecond(253402300799L) // 31/12/9999 (prevents overflow when converting to milli precision or sql timestamps) def now(): TimestampSecond = TimestampSecond(System.currentTimeMillis() / 1000) } case class TimestampMilli(private val underlying: Long) extends Ordered[TimestampMilli] { - require(underlying >= 0 && underlying <= Long.MaxValue, "invalid timestamp value") + require(underlying >= 0 && underlying <= 253402300799L * 1000, "invalid timestamp value") // @formatter:off def toLong: Long = underlying def toSqlTimestamp: sql.Timestamp = sql.Timestamp.from(Instant.ofEpochMilli(underlying)) @@ -58,7 +58,7 @@ case class TimestampMilli(private val underlying: Long) extends Ordered[Timestam object TimestampMilli { // @formatter:off val min: TimestampMilli = TimestampMilli(0) // 1/1/1970 - val max: TimestampMilli = TimestampMilli(Long.MaxValue) // 11/04/2262 + val max: TimestampMilli = TimestampMilli(253402300799L * 1000) // 31/12/9999 (prevents overflow when converting to sql timestamps) def now(): TimestampMilli = TimestampMilli(System.currentTimeMillis()) def fromSqlTimestamp(sqlTs: sql.Timestamp): TimestampMilli = TimestampMilli(sqlTs.getTime) // @formatter:on diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TimestampSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TimestampSpec.scala index 4c0bfa091..dd540549d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TimestampSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TimestampSpec.scala @@ -22,13 +22,13 @@ import org.scalatest.funsuite.AnyFunSuite class TimestampSpec extends AnyFunSuite { test("timestamp boundaries") { - assert(TimestampSecond.max.toLong == Long.MaxValue / 1000) + assert(TimestampSecond.max.toLong == 253402300799L) assert(TimestampSecond.min.toLong == 0) - assert(TimestampMilli.max.toLong == Long.MaxValue) + assert(TimestampMilli.max.toLong == 253402300799L * 1000) assert(TimestampMilli.min.toLong == 0) intercept[IllegalArgumentException] { - TimestampSecond(Long.MaxValue / 1000 + 1) + TimestampSecond(253402300799L + 1) } intercept[IllegalArgumentException] { diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala index 57eda709b..4af2e3c6f 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/directives/ExtraDirectives.scala @@ -43,7 +43,7 @@ trait ExtraDirectives extends Directives { val nodeIdsFormParam: NameUnmarshallerReceptacle[List[PublicKey]] = "nodeIds".as[List[PublicKey]](pubkeyListUnmarshaller) val paymentHashFormParam: NameUnmarshallerReceptacle[ByteVector32] = "paymentHash".as[ByteVector32](sha256HashUnmarshaller) val fromFormParam: NameDefaultUnmarshallerReceptacle[TimestampSecond] = "from".as[TimestampSecond](timestampSecondUnmarshaller).?(TimestampSecond.min) - val toFormParam: NameDefaultUnmarshallerReceptacle[TimestampSecond] = "to".as[TimestampSecond](timestampSecondUnmarshaller).?(TimestampSecond(253402300799L)) // 31/12/9999 + val toFormParam: NameDefaultUnmarshallerReceptacle[TimestampSecond] = "to".as[TimestampSecond](timestampSecondUnmarshaller).?(TimestampSecond.max) val amountMsatFormParam: NameReceptacle[MilliSatoshi] = "amountMsat".as[MilliSatoshi] val invoiceFormParam: NameReceptacle[PaymentRequest] = "invoice".as[PaymentRequest] val routeFormatFormParam: NameUnmarshallerReceptacle[RouteFormat] = "format".as[RouteFormat](routeFormatUnmarshaller) diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 749b5b735..8a791601b 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -1056,7 +1056,6 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM test("'audit'") { val eclair = mock[Eclair] val mockService = new MockService(eclair) - val year9999 = TimestampSecond(253402300799L) val auditResponse = AuditResponse(Seq.empty, Seq.empty, Seq.empty) eclair.audit(any, any)(any[Timeout]) returns Future.successful(auditResponse) @@ -1066,16 +1065,16 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM check { assert(handled) assert(status == OK) - eclair.audit(TimestampSecond.min, year9999)(any[Timeout]).wasCalled(once) + eclair.audit(TimestampSecond.min, TimestampSecond.max)(any[Timeout]).wasCalled(once) } - Post("/audit", FormData("from" -> TimestampSecond.min.toLong.toString, "to" -> year9999.toLong.toString)) ~> + Post("/audit", FormData("from" -> TimestampSecond.min.toLong.toString, "to" -> TimestampSecond.max.toLong.toString)) ~> addCredentials(BasicHttpCredentials("", mockApi().password)) ~> Route.seal(mockService.audit) ~> check { assert(handled) assert(status == OK) - eclair.audit(TimestampSecond.min, year9999)(any[Timeout]).wasCalled(twice) + eclair.audit(TimestampSecond.min, TimestampSecond.max)(any[Timeout]).wasCalled(twice) } Post("/audit", FormData("from" -> 123456.toString, "to" -> 654321.toString)) ~>