mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 14:40:34 +01:00
API: fix default time boundaries (#2035)
Default upper bound was `Long.MaxValue unixsec` which overflowed when converted to `TimestampMilli`. We now enforce `min` and `max` values on timestamp types. API tests didn't catch it because eclair is mocked and the conversion happens later. Fixes #2031.
This commit is contained in:
parent
1573f7be05
commit
e54aaa84be
5 changed files with 86 additions and 3 deletions
|
@ -21,6 +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")
|
||||
// @formatter:off
|
||||
def toLong: Long = underlying
|
||||
def toTimestampMilli: TimestampMilli = TimestampMilli(underlying * 1000)
|
||||
|
@ -36,10 +37,13 @@ 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)
|
||||
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")
|
||||
// @formatter:off
|
||||
def toLong: Long = underlying
|
||||
def toSqlTimestamp: sql.Timestamp = sql.Timestamp.from(Instant.ofEpochMilli(underlying))
|
||||
|
@ -53,6 +57,8 @@ 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
|
||||
def now(): TimestampMilli = TimestampMilli(System.currentTimeMillis())
|
||||
def fromSqlTimestamp(sqlTs: sql.Timestamp): TimestampMilli = TimestampMilli(sqlTs.getTime)
|
||||
// @formatter:on
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair
|
||||
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
|
||||
class TimestampSpec extends AnyFunSuite {
|
||||
|
||||
test("timestamp boundaries") {
|
||||
assert(TimestampSecond.max.toLong == Long.MaxValue / 1000)
|
||||
assert(TimestampSecond.min.toLong == 0)
|
||||
assert(TimestampMilli.max.toLong == Long.MaxValue)
|
||||
assert(TimestampMilli.min.toLong == 0)
|
||||
|
||||
intercept[IllegalArgumentException] {
|
||||
TimestampSecond(Long.MaxValue / 1000 + 1)
|
||||
}
|
||||
|
||||
intercept[IllegalArgumentException] {
|
||||
TimestampSecond(-1)
|
||||
}
|
||||
|
||||
intercept[IllegalArgumentException] {
|
||||
TimestampMilli(-1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -266,7 +266,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
|
|||
channels.map(_ -> gossipOrigin).toMap + (channels(5) -> Set(bobOrigin)),
|
||||
updates.map(_ -> gossipOrigin).toMap + (updates(6) -> (gossipOrigin + bobOrigin)) + (updates(10) -> Set(bobOrigin)),
|
||||
nodes.map(_ -> gossipOrigin).toMap + (nodes(4) -> Set(bobOrigin)))
|
||||
val filter = protocol.GossipTimestampFilter(Alice.nodeParams.chainHash, 0 unixsec, Long.MaxValue) // no filtering on timestamps
|
||||
val filter = protocol.GossipTimestampFilter(Alice.nodeParams.chainHash, 0 unixsec, Int.MaxValue) // no filtering on timestamps
|
||||
transport.send(peerConnection, filter)
|
||||
transport.expectMsg(TransportHandler.ReadAck(filter))
|
||||
transport.send(peerConnection, rebroadcast)
|
||||
|
|
|
@ -42,8 +42,8 @@ trait ExtraDirectives extends Directives {
|
|||
val nodeIdFormParam: NameReceptacle[PublicKey] = "nodeId".as[PublicKey]
|
||||
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).?(0 unixsec)
|
||||
val toFormParam: NameDefaultUnmarshallerReceptacle[TimestampSecond] = "to".as[TimestampSecond](timestampSecondUnmarshaller).?(Long.MaxValue unixsec)
|
||||
val fromFormParam: NameDefaultUnmarshallerReceptacle[TimestampSecond] = "from".as[TimestampSecond](timestampSecondUnmarshaller).?(TimestampSecond.min)
|
||||
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)
|
||||
|
|
|
@ -1072,6 +1072,40 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
|
|||
}
|
||||
}
|
||||
|
||||
test("'audit'") {
|
||||
val eclair = mock[Eclair]
|
||||
val mockService = new MockService(eclair)
|
||||
val auditResponse = AuditResponse(Seq.empty, Seq.empty, Seq.empty)
|
||||
eclair.audit(any, any)(any[Timeout]) returns Future.successful(auditResponse)
|
||||
|
||||
Post("/audit") ~>
|
||||
addCredentials(BasicHttpCredentials("", mockApi().password)) ~>
|
||||
Route.seal(mockService.audit) ~>
|
||||
check {
|
||||
assert(handled)
|
||||
assert(status == OK)
|
||||
eclair.audit(TimestampSecond.min, TimestampSecond.max)(any[Timeout]).wasCalled(once)
|
||||
}
|
||||
|
||||
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, TimestampSecond.max)(any[Timeout]).wasCalled(twice)
|
||||
}
|
||||
|
||||
Post("/audit", FormData("from" -> 123456.toString, "to" -> 654321.toString)) ~>
|
||||
addCredentials(BasicHttpCredentials("", mockApi().password)) ~>
|
||||
Route.seal(mockService.audit) ~>
|
||||
check {
|
||||
assert(handled)
|
||||
assert(status == OK)
|
||||
eclair.audit(123456 unixsec, 654321 unixsec)(any[Timeout]).wasCalled(once)
|
||||
}
|
||||
}
|
||||
|
||||
test("the websocket should return typed objects") {
|
||||
val mockService = new MockService(mock[Eclair])
|
||||
val fixedUUID = UUID.fromString("487da196-a4dc-4b1e-92b4-3e5e905e9f3f")
|
||||
|
|
Loading…
Add table
Reference in a new issue