diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 09d91f5494..3b911b04e3 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -8,6 +8,46 @@ ### API changes +#### Timestamps + +All timestamps are now returned as an object with two attributes: +- `iso`: ISO-8601 format with GMT time zone. Precision may be second or millisecond depending on the timestamp. +- `unix`: seconds since epoch formats (seconds since epoch). Precision is always second. + +Examples: +- second-precision timestamp: + - before: + ```json + { + "timestamp": 1633357961 + } + ``` + - after + ```json + { + "timestamp": { + "iso": "2021-10-04T14:32:41Z", + "unix": 1633357961 + } + } + ``` +- milli-second precision timestamp: + - before: + ```json + { + "timestamp": 1633357961456 + } + ``` + - after (note how the unix format is in second precision): + ```json + { + "timestamp": { + "iso": "2021-10-04T14:32:41.456Z", + "unix": 1633357961 + } + } + ``` + ### Miscellaneous improvements and bug fixes diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 734c88a595..a4438c8b6d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -24,7 +24,6 @@ import akka.util.Timeout import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi} -import fr.acinq.eclair.TimestampQueryFilters._ import fr.acinq.eclair.balance.CheckBalance.GlobalBalance import fr.acinq.eclair.balance.{BalanceActor, ChannelsListener} import fr.acinq.eclair.blockchain.OnChainWallet.OnChainBalance @@ -57,25 +56,10 @@ case class GetInfoResponse(version: String, nodeId: PublicKey, alias: String, co case class AuditResponse(sent: Seq[PaymentSent], received: Seq[PaymentReceived], relayed: Seq[PaymentRelayed]) -case class TimestampQueryFilters(from: Long, to: Long) - case class SignedMessage(nodeId: PublicKey, message: String, signature: ByteVector) case class VerifiedMessage(valid: Boolean, publicKey: PublicKey) -object TimestampQueryFilters { - /** We use this in the context of timestamp filtering, when we don't need an upper bound. */ - val MaxEpochMilliseconds: Long = Duration.fromNanos(Long.MaxValue).toMillis - - def getDefaultTimestampFilters(from_opt: Option[Long], to_opt: Option[Long]): TimestampQueryFilters = { - // NB: we expect callers to use seconds, but internally we use milli-seconds everywhere. - val from = from_opt.getOrElse(0L).seconds.toMillis - val to = to_opt.map(_.seconds.toMillis).getOrElse(MaxEpochMilliseconds) - - TimestampQueryFilters(from, to) - } -} - object SignedMessage { def signedBytes(message: ByteVector): ByteVector32 = Crypto.hash256(ByteVector("Lightning Signed Message:".getBytes(StandardCharsets.UTF_8)) ++ message) @@ -129,19 +113,19 @@ trait Eclair { def sendToRoute(amount: MilliSatoshi, recipientAmount_opt: Option[MilliSatoshi], externalId_opt: Option[String], parentId_opt: Option[UUID], invoice: PaymentRequest, finalCltvExpiryDelta: CltvExpiryDelta, route: PredefinedRoute, trampolineSecret_opt: Option[ByteVector32] = None, trampolineFees_opt: Option[MilliSatoshi] = None, trampolineExpiryDelta_opt: Option[CltvExpiryDelta] = None, trampolineNodes_opt: Seq[PublicKey] = Nil)(implicit timeout: Timeout): Future[SendPaymentToRouteResponse] - def audit(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[AuditResponse] + def audit(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[AuditResponse] - def networkFees(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[NetworkFee]] + def networkFees(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[NetworkFee]] - def channelStats(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[Stats]] + def channelStats(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[Stats]] def networkStats()(implicit timeout: Timeout): Future[Option[NetworkStats]] def getInvoice(paymentHash: ByteVector32)(implicit timeout: Timeout): Future[Option[PaymentRequest]] - def pendingInvoices(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[PaymentRequest]] + def pendingInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[PaymentRequest]] - def allInvoices(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[PaymentRequest]] + def allInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[PaymentRequest]] def allChannels()(implicit timeout: Timeout): Future[Iterable[ChannelDesc]] @@ -380,35 +364,30 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging { appKit.nodeParams.db.payments.getIncomingPayment(paymentHash) } - override def audit(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[AuditResponse] = { - val filter = getDefaultTimestampFilters(from_opt, to_opt) + override def audit(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[AuditResponse] = { Future(AuditResponse( - sent = appKit.nodeParams.db.audit.listSent(filter.from, filter.to), - received = appKit.nodeParams.db.audit.listReceived(filter.from, filter.to), - relayed = appKit.nodeParams.db.audit.listRelayed(filter.from, filter.to) + sent = appKit.nodeParams.db.audit.listSent(from.toTimestampMilli, to.toTimestampMilli), + received = appKit.nodeParams.db.audit.listReceived(from.toTimestampMilli, to.toTimestampMilli), + relayed = appKit.nodeParams.db.audit.listRelayed(from.toTimestampMilli, to.toTimestampMilli) )) } - override def networkFees(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[NetworkFee]] = { - val filter = getDefaultTimestampFilters(from_opt, to_opt) - Future(appKit.nodeParams.db.audit.listNetworkFees(filter.from, filter.to)) + override def networkFees(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[NetworkFee]] = { + Future(appKit.nodeParams.db.audit.listNetworkFees(from.toTimestampMilli, to.toTimestampMilli)) } - override def channelStats(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[Stats]] = { - val filter = getDefaultTimestampFilters(from_opt, to_opt) - Future(appKit.nodeParams.db.audit.stats(filter.from, filter.to)) + override def channelStats(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[Stats]] = { + Future(appKit.nodeParams.db.audit.stats(from.toTimestampMilli, to.toTimestampMilli)) } override def networkStats()(implicit timeout: Timeout): Future[Option[NetworkStats]] = (appKit.router ? GetNetworkStats).mapTo[GetNetworkStatsResponse].map(_.stats) - override def allInvoices(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[PaymentRequest]] = Future { - val filter = getDefaultTimestampFilters(from_opt, to_opt) - appKit.nodeParams.db.payments.listIncomingPayments(filter.from, filter.to).map(_.paymentRequest) + override def allInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[PaymentRequest]] = Future { + appKit.nodeParams.db.payments.listIncomingPayments(from.toTimestampMilli, to.toTimestampMilli).map(_.paymentRequest) } - override def pendingInvoices(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[Seq[PaymentRequest]] = Future { - val filter = getDefaultTimestampFilters(from_opt, to_opt) - appKit.nodeParams.db.payments.listPendingIncomingPayments(filter.from, filter.to).map(_.paymentRequest) + override def pendingInvoices(from: TimestampSecond, to: TimestampSecond)(implicit timeout: Timeout): Future[Seq[PaymentRequest]] = Future { + appKit.nodeParams.db.payments.listPendingIncomingPayments(from.toTimestampMilli, to.toTimestampMilli).map(_.paymentRequest) } override def getInvoice(paymentHash: ByteVector32)(implicit timeout: Timeout): Future[Option[PaymentRequest]] = Future { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Timestamp.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Timestamp.scala new file mode 100644 index 0000000000..9b2af2b0cb --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Timestamp.scala @@ -0,0 +1,59 @@ +/* + * 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 java.sql +import java.time.Instant +import scala.concurrent.duration.{DurationLong, FiniteDuration} + +case class TimestampSecond(private val underlying: Long) extends Ordered[TimestampSecond] { + // @formatter:off + def toLong: Long = underlying + def toTimestampMilli: TimestampMilli = TimestampMilli(underlying * 1000) + def toSqlTimestamp: sql.Timestamp = sql.Timestamp.from(Instant.ofEpochSecond(underlying)) + override def toString: String = s"$underlying unixsec" + override def compare(that: TimestampSecond): Int = underlying.compareTo(that.underlying) + def +(x: Long): TimestampSecond = TimestampSecond(underlying + x) + def -(x: Long): TimestampSecond = TimestampSecond(underlying - x) + def +(x: FiniteDuration): TimestampSecond = TimestampSecond(underlying + x.toSeconds) + def -(x: FiniteDuration): TimestampSecond = TimestampSecond(underlying - x.toSeconds) + def -(x: TimestampSecond): FiniteDuration = (underlying - x.underlying).seconds + // @formatter:on +} + +object TimestampSecond { + def now(): TimestampSecond = TimestampSecond(System.currentTimeMillis() / 1000) +} + +case class TimestampMilli(private val underlying: Long) extends Ordered[TimestampMilli] { + // @formatter:off + def toLong: Long = underlying + def toSqlTimestamp: sql.Timestamp = sql.Timestamp.from(Instant.ofEpochMilli(underlying)) + override def toString: String = s"$underlying unixms" + override def compare(that: TimestampMilli): Int = underlying.compareTo(that.underlying) + def +(x: FiniteDuration): TimestampMilli = TimestampMilli(underlying + x.toMillis) + def -(x: FiniteDuration): TimestampMilli = TimestampMilli(underlying - x.toMillis) + def -(x: TimestampMilli): FiniteDuration = (underlying - x.underlying).millis + // @formatter:on +} + +object TimestampMilli { + // @formatter:off + def now(): TimestampMilli = TimestampMilli(System.currentTimeMillis()) + def fromSqlTimestamp(sqlTs: sql.Timestamp): TimestampMilli = TimestampMilli(sqlTs.getTime) + // @formatter:on +} \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala index e42d2e6408..829984f391 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient import fr.acinq.eclair.blockchain.watchdogs.BlockchainWatchdog import fr.acinq.eclair.wire.protocol.ChannelAnnouncement -import fr.acinq.eclair.{KamonExt, NodeParams, ShortChannelId} +import fr.acinq.eclair.{KamonExt, NodeParams, ShortChannelId, TimestampSecond} import java.util.concurrent.atomic.AtomicLong import scala.concurrent.duration._ @@ -68,7 +68,7 @@ object ZmqWatcher { final case class ValidateResult(c: ChannelAnnouncement, fundingTx: Either[Throwable, (Transaction, UtxoStatus)]) final case class GetTxWithMeta(replyTo: ActorRef[GetTxWithMetaResponse], txid: ByteVector32) extends Command - final case class GetTxWithMetaResponse(txid: ByteVector32, tx_opt: Option[Transaction], lastBlockTimestamp: Long) + final case class GetTxWithMetaResponse(txid: ByteVector32, tx_opt: Option[Transaction], lastBlockTimestamp: TimestampSecond) sealed trait UtxoStatus object UtxoStatus { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala index fc97fea8ab..18f2686d5a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.blockchain.bitcoind.rpc import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin._ import fr.acinq.eclair.ShortChannelId.coordinates -import fr.acinq.eclair.TxCoordinates +import fr.acinq.eclair.{TimestampSecond, TxCoordinates} import fr.acinq.eclair.blockchain.OnChainWallet import fr.acinq.eclair.blockchain.OnChainWallet.{MakeFundingTxResponse, OnChainBalance} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{GetTxWithMetaResponse, UtxoStatus, ValidateResult} @@ -62,7 +62,7 @@ class BitcoinCoreClient(val rpcClient: BitcoinJsonRPCClient) extends OnChainWall tx_opt <- getTransaction(txid).map(Some(_)).recover { case _ => None } blockchainInfo <- rpcClient.invoke("getblockchaininfo") JInt(timestamp) = blockchainInfo \ "mediantime" - } yield GetTxWithMetaResponse(txid, tx_opt, timestamp.toLong) + } yield GetTxWithMetaResponse(txid, tx_opt, TimestampSecond(timestamp.toLong)) /** Get the number of confirmations of a given transaction. */ def getTxConfirmations(txid: ByteVector32)(implicit ec: ExecutionContext): Future[Option[Int]] = diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 69fe30f4a6..60882860df 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -544,7 +544,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo originChannels = Map.empty, remoteNextCommitInfo = Right(randomKey().publicKey), // we will receive their next per-commitment point in the next message, so we temporarily put a random byte array commitInput, ShaChain.init) - val now = System.currentTimeMillis.milliseconds.toSeconds + val blockHeight = nodeParams.currentBlockHeight context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) log.info(s"publishing funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}") watchFundingTx(commitments) @@ -567,7 +567,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo } } - goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, Some(fundingTx), now, None, Left(fundingCreated)) storing() calling publishFundingTx() + goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, Some(fundingTx), blockHeight, None, Left(fundingCreated)) storing() calling publishFundingTx() } case Event(c: CloseCommand, d: DATA_WAIT_FOR_FUNDING_SIGNED) => @@ -1019,7 +1019,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo goto(NORMAL) using d.copy(channelUpdate = channelUpdate1) storing() case Event(BroadcastChannelUpdate(reason), d: DATA_NORMAL) => - val age = System.currentTimeMillis.milliseconds - d.channelUpdate.timestamp.seconds + val age = TimestampSecond.now() - d.channelUpdate.timestamp val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) reason match { case Reconnected if d.commitments.announceChannel && Announcements.areSame(channelUpdate1, d.channelUpdate) && age < REFRESH_CHANNEL_UPDATE_INTERVAL => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index c94071e220..a223c19fec 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -204,8 +204,8 @@ object Helpers { * * @return the delay until the next update */ - def nextChannelUpdateRefresh(currentUpdateTimestamp: Long)(implicit log: DiagnosticLoggingAdapter): FiniteDuration = { - val age = System.currentTimeMillis.milliseconds - currentUpdateTimestamp.seconds + def nextChannelUpdateRefresh(currentUpdateTimestamp: TimestampSecond)(implicit log: DiagnosticLoggingAdapter): FiniteDuration = { + val age = TimestampSecond.now() - currentUpdateTimestamp val delay = 0.days.max(REFRESH_CHANNEL_UPDATE_INTERVAL - age) Logs.withMdc(log)(Logs.mdc(category_opt = Some(Logs.LogCategory.CONNECTION))) { log.debug("current channel_update was created {} days ago, will refresh it in {} days", age.toDays, delay.toDays) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala index 8a9dae4e47..69a686ea57 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/WeakEntropyPool.scala @@ -21,6 +21,7 @@ import akka.actor.typed.eventstream.EventStream import akka.actor.typed.scaladsl.Behaviors import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto} +import fr.acinq.eclair.TimestampMilli import fr.acinq.eclair.blockchain.NewBlock import fr.acinq.eclair.channel.ChannelSignatureReceived import fr.acinq.eclair.io.PeerConnected @@ -47,7 +48,7 @@ object WeakEntropyPool { sealed trait Command private case object FlushEntropy extends Command private case class WrappedNewBlock(blockHash: ByteVector32) extends Command - private case class WrappedPaymentRelayed(paymentHash: ByteVector32, relayedAt: Long) extends Command + private case class WrappedPaymentRelayed(paymentHash: ByteVector32, relayedAt: TimestampMilli) extends Command private case class WrappedPeerConnected(nodeId: PublicKey) extends Command private case class WrappedChannelSignature(wtxid: ByteVector32) extends Command private case class WrappedNodeUpdated(sig: ByteVector64) extends Command @@ -80,7 +81,7 @@ object WeakEntropyPool { case WrappedNewBlock(blockHash) => collecting(collector, collect(entropy_opt, blockHash ++ ByteVector.fromLong(System.currentTimeMillis()))) - case WrappedPaymentRelayed(paymentHash, relayedAt) => collecting(collector, collect(entropy_opt, paymentHash ++ ByteVector.fromLong(relayedAt))) + case WrappedPaymentRelayed(paymentHash, relayedAt) => collecting(collector, collect(entropy_opt, paymentHash ++ ByteVector.fromLong(relayedAt.toLong))) case WrappedPeerConnected(nodeId) => collecting(collector, collect(entropy_opt, nodeId.value ++ ByteVector.fromLong(System.currentTimeMillis()))) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/AuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/AuditDb.scala index a668155809..6645430e6c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/AuditDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/AuditDb.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.db import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, Satoshi} +import fr.acinq.eclair.{TimestampMilli, TimestampSecond} import fr.acinq.eclair.channel._ import fr.acinq.eclair.db.AuditDb.{NetworkFee, Stats} import fr.acinq.eclair.db.DbEventHandler.ChannelEvent @@ -45,21 +46,21 @@ trait AuditDb extends Closeable { def addPathFindingExperimentMetrics(metrics: PathFindingExperimentMetrics): Unit - def listSent(from: Long, to: Long): Seq[PaymentSent] + def listSent(from: TimestampMilli, to: TimestampMilli): Seq[PaymentSent] - def listReceived(from: Long, to: Long): Seq[PaymentReceived] + def listReceived(from: TimestampMilli, to: TimestampMilli): Seq[PaymentReceived] - def listRelayed(from: Long, to: Long): Seq[PaymentRelayed] + def listRelayed(from: TimestampMilli, to: TimestampMilli): Seq[PaymentRelayed] - def listNetworkFees(from: Long, to: Long): Seq[NetworkFee] + def listNetworkFees(from: TimestampMilli, to: TimestampMilli): Seq[NetworkFee] - def stats(from: Long, to: Long): Seq[Stats] + def stats(from: TimestampMilli, to: TimestampMilli): Seq[Stats] } object AuditDb { - case class NetworkFee(remoteNodeId: PublicKey, channelId: ByteVector32, txId: ByteVector32, fee: Satoshi, txType: String, timestamp: Long) + case class NetworkFee(remoteNodeId: PublicKey, channelId: ByteVector32, txId: ByteVector32, fee: Satoshi, txType: String, timestamp: TimestampMilli) case class Stats(channelId: ByteVector32, direction: String, avgPaymentAmount: Satoshi, paymentCount: Int, relayFee: Satoshi, networkFee: Satoshi) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala index cf656a1717..c72af3402f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala @@ -12,7 +12,7 @@ import fr.acinq.eclair.payment._ import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, NodeAddress, NodeAnnouncement} -import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, ShortChannelId, TimestampMilli} import grizzled.slf4j.Logging import java.io.File @@ -176,27 +176,27 @@ case class DualAuditDb(sqlite: SqliteAuditDb, postgres: PgAuditDb) extends Audit sqlite.addPathFindingExperimentMetrics(metrics) } - override def listSent(from: Long, to: Long): Seq[PaymentSent] = { + override def listSent(from: TimestampMilli, to: TimestampMilli): Seq[PaymentSent] = { runAsync(postgres.listSent(from, to)) sqlite.listSent(from, to) } - override def listReceived(from: Long, to: Long): Seq[PaymentReceived] = { + override def listReceived(from: TimestampMilli, to: TimestampMilli): Seq[PaymentReceived] = { runAsync(postgres.listReceived(from, to)) sqlite.listReceived(from, to) } - override def listRelayed(from: Long, to: Long): Seq[PaymentRelayed] = { + override def listRelayed(from: TimestampMilli, to: TimestampMilli): Seq[PaymentRelayed] = { runAsync(postgres.listRelayed(from, to)) sqlite.listRelayed(from, to) } - override def listNetworkFees(from: Long, to: Long): Seq[AuditDb.NetworkFee] = { + override def listNetworkFees(from: TimestampMilli, to: TimestampMilli): Seq[AuditDb.NetworkFee] = { runAsync(postgres.listNetworkFees(from, to)) sqlite.listNetworkFees(from, to) } - override def stats(from: Long, to: Long): Seq[AuditDb.Stats] = { + override def stats(from: TimestampMilli, to: TimestampMilli): Seq[AuditDb.Stats] = { runAsync(postgres.stats(from, to)) sqlite.stats(from, to) } @@ -306,7 +306,7 @@ case class DualPaymentsDb(sqlite: SqlitePaymentsDb, postgres: PgPaymentsDb) exte sqlite.addIncomingPayment(pr, preimage, paymentType) } - override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: Long): Unit = { + override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli): Unit = { runAsync(postgres.receiveIncomingPayment(paymentHash, amount, receivedAt)) sqlite.receiveIncomingPayment(paymentHash, amount, receivedAt) } @@ -316,22 +316,22 @@ case class DualPaymentsDb(sqlite: SqlitePaymentsDb, postgres: PgPaymentsDb) exte sqlite.getIncomingPayment(paymentHash) } - override def listIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = { + override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = { runAsync(postgres.listIncomingPayments(from, to)) sqlite.listIncomingPayments(from, to) } - override def listPendingIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = { + override def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = { runAsync(postgres.listPendingIncomingPayments(from, to)) sqlite.listPendingIncomingPayments(from, to) } - override def listExpiredIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = { + override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = { runAsync(postgres.listExpiredIncomingPayments(from, to)) sqlite.listExpiredIncomingPayments(from, to) } - override def listReceivedIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = { + override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = { runAsync(postgres.listReceivedIncomingPayments(from, to)) sqlite.listReceivedIncomingPayments(from, to) } @@ -366,7 +366,7 @@ case class DualPaymentsDb(sqlite: SqlitePaymentsDb, postgres: PgPaymentsDb) exte sqlite.listOutgoingPayments(paymentHash) } - override def listOutgoingPayments(from: Long, to: Long): Seq[OutgoingPayment] = { + override def listOutgoingPayments(from: TimestampMilli, to: TimestampMilli): Seq[OutgoingPayment] = { runAsync(postgres.listOutgoingPayments(from, to)) sqlite.listOutgoingPayments(from, to) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala index 6dc271fc21..430a7dea50 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/PaymentsDb.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Router.{ChannelHop, Hop, NodeHop} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TimestampMilli} import java.io.Closeable import java.util.UUID @@ -35,22 +35,22 @@ trait IncomingPaymentsDb { * Mark an incoming payment as received (paid). The received amount may exceed the payment request amount. * Note that this function assumes that there is a matching payment request in the DB. */ - def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: Long = System.currentTimeMillis): Unit + def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli = TimestampMilli.now()): Unit /** Get information about the incoming payment (paid or not) for the given payment hash, if any. */ def getIncomingPayment(paymentHash: ByteVector32): Option[IncomingPayment] /** List all incoming payments (pending, expired and succeeded) in the given time range (milli-seconds). */ - def listIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] + def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] /** List all pending (not paid, not expired) incoming payments in the given time range (milli-seconds). */ - def listPendingIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] + def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] /** List all expired (not paid) incoming payments in the given time range (milli-seconds). */ - def listExpiredIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] + def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] /** List all received (paid) incoming payments in the given time range (milli-seconds). */ - def listReceivedIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] + def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] } trait OutgoingPaymentsDb { @@ -74,7 +74,7 @@ trait OutgoingPaymentsDb { def listOutgoingPayments(paymentHash: ByteVector32): Seq[OutgoingPayment] /** List all the outgoing payment attempts in the given time range (milli-seconds). */ - def listOutgoingPayments(from: Long, to: Long): Seq[OutgoingPayment] + def listOutgoingPayments(from: TimestampMilli, to: TimestampMilli): Seq[OutgoingPayment] } @@ -99,7 +99,7 @@ case object PaymentType { case class IncomingPayment(paymentRequest: PaymentRequest, paymentPreimage: ByteVector32, paymentType: String, - createdAt: Long, + createdAt: TimestampMilli, status: IncomingPaymentStatus) sealed trait IncomingPaymentStatus @@ -118,7 +118,7 @@ object IncomingPaymentStatus { * @param amount amount of the payment received, in milli-satoshis (may exceed the payment request amount). * @param receivedAt absolute time in milli-seconds since UNIX epoch when the payment was received. */ - case class Received(amount: MilliSatoshi, receivedAt: Long) extends IncomingPaymentStatus + case class Received(amount: MilliSatoshi, receivedAt: TimestampMilli) extends IncomingPaymentStatus } @@ -146,7 +146,7 @@ case class OutgoingPayment(id: UUID, amount: MilliSatoshi, recipientAmount: MilliSatoshi, recipientNodeId: PublicKey, - createdAt: Long, + createdAt: TimestampMilli, paymentRequest: Option[PaymentRequest], status: OutgoingPaymentStatus) @@ -167,7 +167,7 @@ object OutgoingPaymentStatus { * @param route payment route used. * @param completedAt absolute time in milli-seconds since UNIX epoch when the payment was completed. */ - case class Succeeded(paymentPreimage: ByteVector32, feesPaid: MilliSatoshi, route: Seq[HopSummary], completedAt: Long) extends OutgoingPaymentStatus + case class Succeeded(paymentPreimage: ByteVector32, feesPaid: MilliSatoshi, route: Seq[HopSummary], completedAt: TimestampMilli) extends OutgoingPaymentStatus /** * Payment has failed and may be retried. @@ -175,7 +175,7 @@ object OutgoingPaymentStatus { * @param failures failed payment attempts. * @param completedAt absolute time in milli-seconds since UNIX epoch when the payment was completed. */ - case class Failed(failures: Seq[FailureSummary], completedAt: Long) extends OutgoingPaymentStatus + case class Failed(failures: Seq[FailureSummary], completedAt: TimestampMilli) extends OutgoingPaymentStatus } @@ -232,8 +232,8 @@ sealed trait PlainPayment { val paymentType: String val paymentRequest: Option[String] val finalAmount: Option[MilliSatoshi] - val createdAt: Long - val completedAt: Option[Long] + val createdAt: TimestampMilli + val completedAt: Option[TimestampMilli] } case class PlainIncomingPayment(paymentHash: ByteVector32, @@ -241,9 +241,9 @@ case class PlainIncomingPayment(paymentHash: ByteVector32, finalAmount: Option[MilliSatoshi], paymentRequest: Option[String], status: IncomingPaymentStatus, - createdAt: Long, - completedAt: Option[Long], - expireAt: Option[Long]) extends PlainPayment + createdAt: TimestampMilli, + completedAt: Option[TimestampMilli], + expireAt: Option[TimestampMilli]) extends PlainPayment case class PlainOutgoingPayment(parentId: Option[UUID], externalId: Option[String], @@ -252,5 +252,5 @@ case class PlainOutgoingPayment(parentId: Option[UUID], finalAmount: Option[MilliSatoshi], paymentRequest: Option[String], status: OutgoingPaymentStatus, - createdAt: Long, - completedAt: Option[Long]) extends PlainPayment \ No newline at end of file + createdAt: TimestampMilli, + completedAt: Option[TimestampMilli]) extends PlainPayment \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala index cba7e0bee8..0230714b1d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgAuditDb.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.db.Monitoring.Tags.DbBackends import fr.acinq.eclair.db._ import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.Transactions.PlaceHolderPubKey -import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong} +import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong, TimestampMilli} import grizzled.slf4j.Logging import java.sql.{Statement, Timestamp} @@ -45,7 +45,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { import ExtendedResultSet._ import PgAuditDb._ - case class RelayedPart(channelId: ByteVector32, amount: MilliSatoshi, direction: String, relayType: String, timestamp: Long) + case class RelayedPart(channelId: ByteVector32, amount: MilliSatoshi, direction: String, relayType: String, timestamp: TimestampMilli) inTransaction { pg => using(pg.createStatement()) { statement => @@ -189,7 +189,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { statement.setString(7, e.paymentPreimage.toHex) statement.setString(8, e.recipientNodeId.value.toHex) statement.setString(9, p.toChannelId.toHex) - statement.setTimestamp(10, Timestamp.from(Instant.ofEpochMilli(p.timestamp))) + statement.setTimestamp(10, p.timestamp.toSqlTimestamp) statement.addBatch() }) statement.executeBatch() @@ -204,7 +204,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { statement.setLong(1, p.amount.toLong) statement.setString(2, e.paymentHash.toHex) statement.setString(3, p.fromChannelId.toHex) - statement.setTimestamp(4, Timestamp.from(Instant.ofEpochMilli(p.timestamp))) + statement.setTimestamp(4, p.timestamp.toSqlTimestamp) statement.addBatch() }) statement.executeBatch() @@ -223,7 +223,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { statement.setString(1, e.paymentHash.toHex) statement.setLong(2, nextTrampolineAmount.toLong) statement.setString(3, nextTrampolineNodeId.value.toHex) - statement.setTimestamp(4, Timestamp.from(Instant.ofEpochMilli(e.timestamp))) + statement.setTimestamp(4, e.timestamp.toSqlTimestamp) statement.executeUpdate() } // trampoline relayed payments do MPP aggregation and may have M inputs and N outputs @@ -236,7 +236,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { statement.setString(3, p.channelId.toHex) statement.setString(4, p.direction) statement.setString(5, p.relayType) - statement.setTimestamp(6, Timestamp.from(Instant.ofEpochMilli(e.timestamp))) + statement.setTimestamp(6, e.timestamp.toSqlTimestamp) statement.executeUpdate() } } @@ -309,8 +309,8 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { statement.setLong(1, m.amount.toLong) statement.setLong(2, m.fees.toLong) statement.setString(3, m.status) - statement.setLong(4, m.duration) - statement.setTimestamp(5, new Timestamp(m.timestamp)) + statement.setLong(4, m.duration.toMillis) + statement.setTimestamp(5, m.timestamp.toSqlTimestamp) statement.setBoolean(6, m.isMultiPart) statement.setString(7, m.experimentName) statement.setString(8, m.recipientNodeId.value.toHex) @@ -319,11 +319,11 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { } } - override def listSent(from: Long, to: Long): Seq[PaymentSent] = + override def listSent(from: TimestampMilli, to: TimestampMilli): Seq[PaymentSent] = inTransaction { pg => using(pg.prepareStatement("SELECT * FROM audit.sent WHERE timestamp BETWEEN ? AND ?")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery() .foldLeft(Map.empty[UUID, PaymentSent]) { (sentByParentId, rs) => val parentId = UUID.fromString(rs.getString("parent_payment_id")) @@ -333,7 +333,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { MilliSatoshi(rs.getLong("fees_msat")), rs.getByteVector32FromHex("to_channel_id"), None, // we don't store the route in the audit DB - rs.getTimestamp("timestamp").getTime) + TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp"))) val sent = sentByParentId.get(parentId) match { case Some(s) => s.copy(parts = s.parts :+ part) case None => PaymentSent( @@ -349,18 +349,18 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { } } - override def listReceived(from: Long, to: Long): Seq[PaymentReceived] = + override def listReceived(from: TimestampMilli, to: TimestampMilli): Seq[PaymentReceived] = inTransaction { pg => using(pg.prepareStatement("SELECT * FROM audit.received WHERE timestamp BETWEEN ? AND ?")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery() .foldLeft(Map.empty[ByteVector32, PaymentReceived]) { (receivedByHash, rs) => val paymentHash = rs.getByteVector32FromHex("payment_hash") val part = PaymentReceived.PartialPayment( MilliSatoshi(rs.getLong("amount_msat")), rs.getByteVector32FromHex("from_channel_id"), - rs.getTimestamp("timestamp").getTime) + TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp"))) val received = receivedByHash.get(paymentHash) match { case Some(r) => r.copy(parts = r.parts :+ part) case None => PaymentReceived(paymentHash, Seq(part)) @@ -370,11 +370,11 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { } } - override def listRelayed(from: Long, to: Long): Seq[PaymentRelayed] = + override def listRelayed(from: TimestampMilli, to: TimestampMilli): Seq[PaymentRelayed] = inTransaction { pg => val trampolineByHash = using(pg.prepareStatement("SELECT * FROM audit.relayed_trampoline WHERE timestamp BETWEEN ? and ?")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery() .foldLeft(Map.empty[ByteVector32, (MilliSatoshi, PublicKey)]) { (trampolineByHash, rs) => val paymentHash = rs.getByteVector32FromHex("payment_hash") @@ -384,8 +384,8 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { } } val relayedByHash = using(pg.prepareStatement("SELECT * FROM audit.relayed WHERE timestamp BETWEEN ? and ?")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery() .foldLeft(Map.empty[ByteVector32, Seq[RelayedPart]]) { (relayedByHash, rs) => val paymentHash = rs.getByteVector32FromHex("payment_hash") @@ -394,7 +394,7 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { MilliSatoshi(rs.getLong("amount_msat")), rs.getString("direction"), rs.getString("relay_type"), - rs.getTimestamp("timestamp").getTime) + TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp"))) relayedByHash + (paymentHash -> (relayedByHash.getOrElse(paymentHash, Nil) :+ part)) } } @@ -416,11 +416,11 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { }.toSeq.sortBy(_.timestamp) } - override def listNetworkFees(from: Long, to: Long): Seq[NetworkFee] = + override def listNetworkFees(from: TimestampMilli, to: TimestampMilli): Seq[NetworkFee] = inTransaction { pg => using(pg.prepareStatement("SELECT * FROM audit.transactions_confirmed INNER JOIN audit.transactions_published ON audit.transactions_published.tx_id = audit.transactions_confirmed.tx_id WHERE audit.transactions_confirmed.timestamp BETWEEN ? and ? ORDER BY audit.transactions_confirmed.timestamp")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery().map { rs => NetworkFee( remoteNodeId = PublicKey(rs.getByteVectorFromHex("node_id")), @@ -428,12 +428,12 @@ class PgAuditDb(implicit ds: DataSource) extends AuditDb with Logging { txId = rs.getByteVector32FromHex("tx_id"), fee = Satoshi(rs.getLong("mining_fee_sat")), txType = rs.getString("tx_type"), - timestamp = rs.getTimestamp("timestamp").getTime) + timestamp = TimestampMilli.fromSqlTimestamp(rs.getTimestamp("timestamp"))) }.toSeq } } - override def stats(from: Long, to: Long): Seq[Stats] = { + override def stats(from: TimestampMilli, to: TimestampMilli): Seq[Stats] = { val networkFees = listNetworkFees(from, to).foldLeft(Map.empty[ByteVector32, Satoshi]) { (feeByChannelId, f) => feeByChannelId + (f.channelId -> (feeByChannelId.getOrElse(f.channelId, 0 sat) + f.fee)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala index ae0d3ec9ae..9e2bce977e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgPaymentsDb.scala @@ -18,13 +18,13 @@ package fr.acinq.eclair.db.pg import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.Crypto.PublicKey -import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.db.Monitoring.Metrics.withMetrics import fr.acinq.eclair.db.Monitoring.Tags.DbBackends import fr.acinq.eclair.db._ import fr.acinq.eclair.db.pg.PgUtils.PgLock import fr.acinq.eclair.payment.{PaymentFailed, PaymentRequest, PaymentSent} import fr.acinq.eclair.wire.protocol.CommonCodecs +import fr.acinq.eclair.{MilliSatoshi, TimestampMilli, TimestampMilliLong} import grizzled.slf4j.Logging import scodec.Attempt import scodec.bits.BitVector @@ -34,6 +34,7 @@ import java.sql.{ResultSet, Statement, Timestamp} import java.time.Instant import java.util.UUID import javax.sql.DataSource +import scala.concurrent.duration.DurationLong object PgPaymentsDb { val DB_NAME = "payments" @@ -112,7 +113,7 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit statement.setLong(6, sent.amount.toLong) statement.setLong(7, sent.recipientAmount.toLong) statement.setString(8, sent.recipientNodeId.value.toHex) - statement.setTimestamp(9, Timestamp.from(Instant.ofEpochMilli(sent.createdAt))) + statement.setTimestamp(9, sent.createdAt.toSqlTimestamp) statement.setString(10, sent.paymentRequest.map(PaymentRequest.write).orNull) statement.executeUpdate() } @@ -123,7 +124,7 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit withLock { pg => using(pg.prepareStatement("UPDATE payments.sent SET (completed_at, payment_preimage, fees_msat, payment_route) = (?, ?, ?, ?) WHERE id = ? AND completed_at IS NULL")) { statement => paymentResult.parts.foreach(p => { - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(p.timestamp))) + statement.setTimestamp(1, p.timestamp.toSqlTimestamp) statement.setString(2, paymentResult.paymentPreimage.toHex) statement.setLong(3, p.feesPaid.toLong) statement.setBytes(4, paymentRouteCodec.encode(p.route.getOrElse(Nil).map(h => HopSummary(h)).toList).require.toByteArray) @@ -138,7 +139,7 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit override def updateOutgoingPayment(paymentResult: PaymentFailed): Unit = withMetrics("payments/update-outgoing-failed", DbBackends.Postgres) { withLock { pg => using(pg.prepareStatement("UPDATE payments.sent SET (completed_at, failures) = (?, ?) WHERE id = ? AND completed_at IS NULL")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(paymentResult.timestamp))) + statement.setTimestamp(1, paymentResult.timestamp.toSqlTimestamp) statement.setBytes(2, paymentFailuresCodec.encode(paymentResult.failures.map(f => FailureSummary(f)).toList).require.toByteArray) statement.setString(3, paymentResult.id.toString) if (statement.executeUpdate() == 0) throw new IllegalArgumentException(s"Tried to mark an outgoing payment as failed but already in final status (id=${paymentResult.id})") @@ -151,7 +152,7 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit rs.getByteVector32FromHexNullable("payment_preimage"), rs.getMilliSatoshiNullable("fees_msat"), rs.getBitVectorOpt("payment_route"), - rs.getTimestampNullable("completed_at").map(_.getTime), + rs.getTimestampNullable("completed_at").map(TimestampMilli.fromSqlTimestamp), rs.getBitVectorOpt("failures")) OutgoingPayment( @@ -163,13 +164,13 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit MilliSatoshi(rs.getLong("amount_msat")), MilliSatoshi(rs.getLong("recipient_amount_msat")), PublicKey(rs.getByteVectorFromHex("recipient_node_id")), - rs.getTimestamp("created_at").getTime, + TimestampMilli(rs.getTimestamp("created_at").getTime), rs.getStringNullable("payment_request").map(PaymentRequest.read), status ) } - private def buildOutgoingPaymentStatus(preimage_opt: Option[ByteVector32], fees_opt: Option[MilliSatoshi], paymentRoute_opt: Option[BitVector], completedAt_opt: Option[Long], failures: Option[BitVector]): OutgoingPaymentStatus = { + private def buildOutgoingPaymentStatus(preimage_opt: Option[ByteVector32], fees_opt: Option[MilliSatoshi], paymentRoute_opt: Option[BitVector], completedAt_opt: Option[TimestampMilli], failures: Option[BitVector]): OutgoingPaymentStatus = { preimage_opt match { // If we have a pre-image, the payment succeeded. case Some(preimage) => OutgoingPaymentStatus.Succeeded( @@ -177,7 +178,7 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit case Attempt.Successful(route) => route.value case Attempt.Failure(_) => Nil }).getOrElse(Nil), - completedAt_opt.getOrElse(0) + completedAt_opt.getOrElse(0 unixms) ) case None => completedAt_opt match { // Otherwise if the payment was marked completed, it's a failure. @@ -221,11 +222,11 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit } } - override def listOutgoingPayments(from: Long, to: Long): Seq[OutgoingPayment] = withMetrics("payments/list-outgoing-by-timestamp", DbBackends.Postgres) { + override def listOutgoingPayments(from: TimestampMilli, to: TimestampMilli): Seq[OutgoingPayment] = withMetrics("payments/list-outgoing-by-timestamp", DbBackends.Postgres) { withLock { pg => using(pg.prepareStatement("SELECT * FROM payments.sent WHERE created_at >= ? AND created_at < ? ORDER BY created_at")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery().map { rs => parseOutgoingPayment(rs) }.toSeq @@ -240,18 +241,18 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit statement.setString(2, preimage.toHex) statement.setString(3, paymentType) statement.setString(4, PaymentRequest.write(pr)) - statement.setTimestamp(5, Timestamp.from(Instant.ofEpochSecond(pr.timestamp))) // BOLT11 timestamp is in seconds - statement.setTimestamp(6, Timestamp.from(Instant.ofEpochSecond(pr.timestamp + pr.expiry.getOrElse(PaymentRequest.DEFAULT_EXPIRY_SECONDS.toLong)))) + statement.setTimestamp(5, pr.timestamp.toSqlTimestamp) + statement.setTimestamp(6, (pr.timestamp + pr.expiry.getOrElse(PaymentRequest.DEFAULT_EXPIRY_SECONDS).seconds).toSqlTimestamp) statement.executeUpdate() } } } - override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: Long): Unit = withMetrics("payments/receive-incoming", DbBackends.Postgres) { + override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli): Unit = withMetrics("payments/receive-incoming", DbBackends.Postgres) { withLock { pg => using(pg.prepareStatement("UPDATE payments.received SET (received_msat, received_at) = (? + COALESCE(received_msat, 0), ?) WHERE payment_hash = ?")) { update => update.setLong(1, amount.toLong) - update.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(receivedAt))) + update.setTimestamp(2, receivedAt.toSqlTimestamp) update.setString(3, paymentHash.toHex) val updated = update.executeUpdate() if (updated == 0) { @@ -267,13 +268,13 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit PaymentRequest.read(paymentRequest), rs.getByteVector32FromHex("payment_preimage"), rs.getString("payment_type"), - rs.getTimestamp("created_at").getTime, - buildIncomingPaymentStatus(rs.getMilliSatoshiNullable("received_msat"), Some(paymentRequest), rs.getTimestampNullable("received_at").map(_.getTime))) + TimestampMilli.fromSqlTimestamp(rs.getTimestamp("created_at")), + buildIncomingPaymentStatus(rs.getMilliSatoshiNullable("received_msat"), Some(paymentRequest), rs.getTimestampNullable("received_at").map(TimestampMilli.fromSqlTimestamp))) } - private def buildIncomingPaymentStatus(amount_opt: Option[MilliSatoshi], serializedPaymentRequest_opt: Option[String], receivedAt_opt: Option[Long]): IncomingPaymentStatus = { + private def buildIncomingPaymentStatus(amount_opt: Option[MilliSatoshi], serializedPaymentRequest_opt: Option[String], receivedAt_opt: Option[TimestampMilli]): IncomingPaymentStatus = { amount_opt match { - case Some(amount) => IncomingPaymentStatus.Received(amount, receivedAt_opt.getOrElse(0)) + case Some(amount) => IncomingPaymentStatus.Received(amount, receivedAt_opt.getOrElse(0 unixms)) case None if serializedPaymentRequest_opt.exists(PaymentRequest.fastHasExpired) => IncomingPaymentStatus.Expired case None => IncomingPaymentStatus.Pending } @@ -288,42 +289,42 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit } } - override def listIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Postgres) { + override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Postgres) { withLock { pg => using(pg.prepareStatement("SELECT * FROM payments.received WHERE created_at > ? AND created_at < ? ORDER BY created_at")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery().map(parseIncomingPayment).toSeq } } } - override def listReceivedIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Postgres) { + override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Postgres) { withLock { pg => using(pg.prepareStatement("SELECT * FROM payments.received WHERE received_msat > 0 AND created_at > ? AND created_at < ? ORDER BY created_at")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.executeQuery().map(parseIncomingPayment).toSeq } } } - override def listPendingIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming-pending", DbBackends.Postgres) { + override def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-pending", DbBackends.Postgres) { withLock { pg => using(pg.prepareStatement("SELECT * FROM payments.received WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at > ? ORDER BY created_at")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.setTimestamp(3, Timestamp.from(Instant.now())) statement.executeQuery().map(parseIncomingPayment).toSeq } } } - override def listExpiredIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Postgres) { + override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Postgres) { withLock { pg => using(pg.prepareStatement("SELECT * FROM payments.received WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at < ? ORDER BY created_at")) { statement => - statement.setTimestamp(1, Timestamp.from(Instant.ofEpochMilli(from))) - statement.setTimestamp(2, Timestamp.from(Instant.ofEpochMilli(to))) + statement.setTimestamp(1, from.toSqlTimestamp) + statement.setTimestamp(2, to.toSqlTimestamp) statement.setTimestamp(3, Timestamp.from(Instant.now())) statement.executeQuery().map(parseIncomingPayment).toSeq } @@ -383,9 +384,9 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit val paymentType = rs.getString("payment_type") val paymentRequest_opt = rs.getStringNullable("payment_request") val amount_opt = rs.getMilliSatoshiNullable("final_amount") - val createdAt = rs.getTimestamp("created_at").getTime - val completedAt_opt = rs.getTimestampNullable("completed_at").map(_.getTime) - val expireAt_opt = rs.getTimestampNullable("expire_at").map(_.getTime) + val createdAt = TimestampMilli.fromSqlTimestamp(rs.getTimestamp("created_at")) + val completedAt_opt = rs.getTimestampNullable("completed_at").map(TimestampMilli.fromSqlTimestamp) + val expireAt_opt = rs.getTimestampNullable("expire_at").map(TimestampMilli.fromSqlTimestamp) if (rs.getString("type") == "received") { val status: IncomingPaymentStatus = buildIncomingPaymentStatus(amount_opt, paymentRequest_opt, completedAt_opt) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala index 01b7a746e5..74c01a145e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteAuditDb.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.db.Monitoring.Tags.DbBackends import fr.acinq.eclair.db._ import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.Transactions.PlaceHolderPubKey -import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong} +import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong, TimestampMilli} import grizzled.slf4j.Logging import java.sql.{Connection, Statement} @@ -43,7 +43,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { import ExtendedResultSet._ import SqliteAuditDb._ - case class RelayedPart(channelId: ByteVector32, amount: MilliSatoshi, direction: String, relayType: String, timestamp: Long) + case class RelayedPart(channelId: ByteVector32, amount: MilliSatoshi, direction: String, relayType: String, timestamp: TimestampMilli) using(sqlite.createStatement(), inTransaction = true) { statement => @@ -177,7 +177,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setBoolean(4, e.isFunder) statement.setBoolean(5, e.isPrivate) statement.setString(6, e.event.label) - statement.setLong(7, System.currentTimeMillis) + statement.setLong(7, TimestampMilli.now().toLong) statement.executeUpdate() } } @@ -194,7 +194,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setBytes(7, e.paymentPreimage.toArray) statement.setBytes(8, e.recipientNodeId.value.toArray) statement.setBytes(9, p.toChannelId.toArray) - statement.setLong(10, p.timestamp) + statement.setLong(10, p.timestamp.toLong) statement.addBatch() }) statement.executeBatch() @@ -207,7 +207,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setLong(1, p.amount.toLong) statement.setBytes(2, e.paymentHash.toArray) statement.setBytes(3, p.fromChannelId.toArray) - statement.setLong(4, p.timestamp) + statement.setLong(4, p.timestamp.toLong) statement.addBatch() }) statement.executeBatch() @@ -224,7 +224,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setBytes(1, e.paymentHash.toArray) statement.setLong(2, nextTrampolineAmount.toLong) statement.setBytes(3, nextTrampolineNodeId.value.toArray) - statement.setLong(4, e.timestamp) + statement.setLong(4, e.timestamp.toLong) statement.executeUpdate() } // trampoline relayed payments do MPP aggregation and may have M inputs and N outputs @@ -238,7 +238,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setBytes(3, p.channelId.toArray) statement.setString(4, p.direction) statement.setString(5, p.relayType) - statement.setLong(6, e.timestamp) + statement.setLong(6, e.timestamp.toLong) statement.executeUpdate() } } @@ -251,7 +251,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setBytes(3, e.remoteNodeId.value.toArray) statement.setLong(4, e.miningFee.toLong) statement.setString(5, e.desc) - statement.setLong(6, System.currentTimeMillis) + statement.setLong(6, TimestampMilli.now().toLong) statement.executeUpdate() } } @@ -261,7 +261,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setBytes(1, e.tx.txid.toArray) statement.setBytes(2, e.channelId.toArray) statement.setBytes(3, e.remoteNodeId.value.toArray) - statement.setLong(4, System.currentTimeMillis) + statement.setLong(4, TimestampMilli.now().toLong) statement.executeUpdate() } } @@ -277,7 +277,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setString(3, errorName) statement.setString(4, errorMessage) statement.setBoolean(5, e.isFatal) - statement.setLong(6, System.currentTimeMillis) + statement.setLong(6, TimestampMilli.now().toLong) statement.executeUpdate() } } @@ -291,7 +291,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setLong(5, u.channelUpdate.cltvExpiryDelta.toInt) statement.setLong(6, u.channelUpdate.htlcMinimumMsat.toLong) statement.setLong(7, u.channelUpdate.htlcMaximumMsat.map(_.toLong).getOrElse(-1)) - statement.setLong(8, System.currentTimeMillis) + statement.setLong(8, TimestampMilli.now().toLong) statement.executeUpdate() } } @@ -301,8 +301,8 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { statement.setLong(1, m.amount.toLong) statement.setLong(2, m.fees.toLong) statement.setString(3, m.status) - statement.setLong(4, m.duration) - statement.setLong(5, m.timestamp) + statement.setLong(4, m.duration.toMillis) + statement.setLong(5, m.timestamp.toLong) statement.setBoolean(6, m.isMultiPart) statement.setString(7, m.experimentName) statement.setBytes(8, m.recipientNodeId.value.toArray) @@ -310,10 +310,10 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { } } - override def listSent(from: Long, to: Long): Seq[PaymentSent] = + override def listSent(from: TimestampMilli, to: TimestampMilli): Seq[PaymentSent] = using(sqlite.prepareStatement("SELECT * FROM sent WHERE timestamp >= ? AND timestamp < ?")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery() .foldLeft(Map.empty[UUID, PaymentSent]) { (sentByParentId, rs) => val parentId = UUID.fromString(rs.getString("parent_payment_id")) @@ -323,7 +323,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { MilliSatoshi(rs.getLong("fees_msat")), rs.getByteVector32("to_channel_id"), None, // we don't store the route in the audit DB - rs.getLong("timestamp")) + TimestampMilli(rs.getLong("timestamp"))) val sent = sentByParentId.get(parentId) match { case Some(s) => s.copy(parts = s.parts :+ part) case None => PaymentSent( @@ -338,17 +338,17 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { }.values.toSeq.sortBy(_.timestamp) } - override def listReceived(from: Long, to: Long): Seq[PaymentReceived] = + override def listReceived(from: TimestampMilli, to: TimestampMilli): Seq[PaymentReceived] = using(sqlite.prepareStatement("SELECT * FROM received WHERE timestamp >= ? AND timestamp < ?")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery() .foldLeft(Map.empty[ByteVector32, PaymentReceived]) { (receivedByHash, rs) => val paymentHash = rs.getByteVector32("payment_hash") val part = PaymentReceived.PartialPayment( MilliSatoshi(rs.getLong("amount_msat")), rs.getByteVector32("from_channel_id"), - rs.getLong("timestamp")) + TimestampMilli(rs.getLong("timestamp"))) val received = receivedByHash.get(paymentHash) match { case Some(r) => r.copy(parts = r.parts :+ part) case None => PaymentReceived(paymentHash, Seq(part)) @@ -357,10 +357,10 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { }.values.toSeq.sortBy(_.timestamp) } - override def listRelayed(from: Long, to: Long): Seq[PaymentRelayed] = { + override def listRelayed(from: TimestampMilli, to: TimestampMilli): Seq[PaymentRelayed] = { val trampolineByHash = using(sqlite.prepareStatement("SELECT * FROM relayed_trampoline WHERE timestamp >= ? AND timestamp < ?")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery() .map { rs => val paymentHash = rs.getByteVector32("payment_hash") @@ -371,8 +371,8 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { .toMap } val relayedByHash = using(sqlite.prepareStatement("SELECT * FROM relayed WHERE timestamp >= ? AND timestamp < ?")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery() .foldLeft(Map.empty[ByteVector32, Seq[RelayedPart]]) { (relayedByHash, rs) => val paymentHash = rs.getByteVector32("payment_hash") @@ -381,7 +381,7 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { MilliSatoshi(rs.getLong("amount_msat")), rs.getString("direction"), rs.getString("relay_type"), - rs.getLong("timestamp")) + TimestampMilli(rs.getLong("timestamp"))) relayedByHash + (paymentHash -> (relayedByHash.getOrElse(paymentHash, Nil) :+ part)) } } @@ -403,10 +403,10 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { }.toSeq.sortBy(_.timestamp) } - override def listNetworkFees(from: Long, to: Long): Seq[NetworkFee] = + override def listNetworkFees(from: TimestampMilli, to: TimestampMilli): Seq[NetworkFee] = using(sqlite.prepareStatement("SELECT * FROM transactions_confirmed INNER JOIN transactions_published ON transactions_published.tx_id = transactions_confirmed.tx_id WHERE transactions_confirmed.timestamp >= ? AND transactions_confirmed.timestamp < ? ORDER BY transactions_confirmed.timestamp")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery() .map { rs => NetworkFee( @@ -415,11 +415,11 @@ class SqliteAuditDb(sqlite: Connection) extends AuditDb with Logging { txId = rs.getByteVector32("tx_id"), fee = Satoshi(rs.getLong("mining_fee_sat")), txType = rs.getString("tx_type"), - timestamp = rs.getLong("timestamp")) + timestamp = TimestampMilli(rs.getLong("timestamp"))) }.toSeq } - override def stats(from: Long, to: Long): Seq[Stats] = { + override def stats(from: TimestampMilli, to: TimestampMilli): Seq[Stats] = { val networkFees = listNetworkFees(from, to).foldLeft(Map.empty[ByteVector32, Satoshi]) { (feeByChannelId, f) => feeByChannelId + (f.channelId -> (feeByChannelId.getOrElse(f.channelId, 0 sat) + f.fee)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala index 7fa7690c86..ccee72c049 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.db.sqlite import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.CltvExpiry +import fr.acinq.eclair.{CltvExpiry, TimestampMilli} import fr.acinq.eclair.channel.HasCommitments import fr.acinq.eclair.db.ChannelsDb import fr.acinq.eclair.db.DbEventHandler.ChannelEvent @@ -120,7 +120,7 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb with Logging { */ private def updateChannelMetaTimestampColumn(channelId: ByteVector32, columnName: String): Unit = { using(sqlite.prepareStatement(s"UPDATE local_channels SET $columnName=? WHERE channel_id=?")) { statement => - statement.setLong(1, System.currentTimeMillis) + statement.setLong(1, TimestampMilli.now().toLong) statement.setBytes(2, channelId.toArray) statement.executeUpdate() } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteFeeratesDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteFeeratesDb.scala index b303acfa97..3717fa5bea 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteFeeratesDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteFeeratesDb.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair.db.sqlite import fr.acinq.bitcoin.Satoshi +import fr.acinq.eclair.TimestampMilli import fr.acinq.eclair.blockchain.fee.{FeeratePerKB, FeeratesPerKB} import fr.acinq.eclair.db.FeeratesDb import grizzled.slf4j.Logging @@ -74,7 +75,7 @@ class SqliteFeeratesDb(sqlite: Connection) extends FeeratesDb with Logging { update.setLong(6, feeratesPerKB.blocks_72.toLong) update.setLong(7, feeratesPerKB.blocks_144.toLong) update.setLong(8, feeratesPerKB.blocks_1008.toLong) - update.setLong(9, System.currentTimeMillis()) + update.setLong(9, TimestampMilli.now().toLong) if (update.executeUpdate() == 0) { using(sqlite.prepareStatement("INSERT INTO feerates_per_kb VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")) { insert => insert.setLong(1, feeratesPerKB.block_1.toLong) @@ -85,7 +86,7 @@ class SqliteFeeratesDb(sqlite: Connection) extends FeeratesDb with Logging { insert.setLong(6, feeratesPerKB.blocks_72.toLong) insert.setLong(7, feeratesPerKB.blocks_144.toLong) insert.setLong(8, feeratesPerKB.blocks_1008.toLong) - insert.setLong(9, System.currentTimeMillis()) + insert.setLong(9, TimestampMilli.now().toLong) insert.executeUpdate() } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala index 199470d5c2..4d2f5ac0b9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqlitePaymentsDb.scala @@ -18,13 +18,13 @@ package fr.acinq.eclair.db.sqlite import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.db.Monitoring.Metrics.withMetrics import fr.acinq.eclair.db.Monitoring.Tags.DbBackends import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite.SqliteUtils._ import fr.acinq.eclair.payment.{PaymentFailed, PaymentRequest, PaymentSent} import fr.acinq.eclair.wire.protocol.CommonCodecs +import fr.acinq.eclair.{MilliSatoshi, TimestampMilli, TimestampMilliLong} import grizzled.slf4j.Logging import scodec.Attempt import scodec.bits.BitVector @@ -132,7 +132,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setLong(6, sent.amount.toLong) statement.setLong(7, sent.recipientAmount.toLong) statement.setBytes(8, sent.recipientNodeId.value.toArray) - statement.setLong(9, sent.createdAt) + statement.setLong(9, sent.createdAt.toLong) statement.setString(10, sent.paymentRequest.map(PaymentRequest.write).orNull) statement.executeUpdate() } @@ -141,7 +141,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { override def updateOutgoingPayment(paymentResult: PaymentSent): Unit = withMetrics("payments/update-outgoing-sent", DbBackends.Sqlite) { using(sqlite.prepareStatement("UPDATE sent_payments SET (completed_at, payment_preimage, fees_msat, payment_route) = (?, ?, ?, ?) WHERE id = ? AND completed_at IS NULL")) { statement => paymentResult.parts.foreach(p => { - statement.setLong(1, p.timestamp) + statement.setLong(1, p.timestamp.toLong) statement.setBytes(2, paymentResult.paymentPreimage.toArray) statement.setLong(3, p.feesPaid.toLong) statement.setBytes(4, paymentRouteCodec.encode(p.route.getOrElse(Nil).map(h => HopSummary(h)).toList).require.toByteArray) @@ -154,7 +154,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { override def updateOutgoingPayment(paymentResult: PaymentFailed): Unit = withMetrics("payments/update-outgoing-failed", DbBackends.Sqlite) { using(sqlite.prepareStatement("UPDATE sent_payments SET (completed_at, failures) = (?, ?) WHERE id = ? AND completed_at IS NULL")) { statement => - statement.setLong(1, paymentResult.timestamp) + statement.setLong(1, paymentResult.timestamp.toLong) statement.setBytes(2, paymentFailuresCodec.encode(paymentResult.failures.map(f => FailureSummary(f)).toList).require.toByteArray) statement.setString(3, paymentResult.id.toString) if (statement.executeUpdate() == 0) throw new IllegalArgumentException(s"Tried to mark an outgoing payment as failed but already in final status (id=${paymentResult.id})") @@ -166,7 +166,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { rs.getByteVector32Nullable("payment_preimage"), rs.getMilliSatoshiNullable("fees_msat"), rs.getBitVectorOpt("payment_route"), - rs.getLongNullable("completed_at"), + rs.getLongNullable("completed_at").map(TimestampMilli(_)), rs.getBitVectorOpt("failures")) OutgoingPayment( @@ -178,13 +178,13 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { MilliSatoshi(rs.getLong("amount_msat")), MilliSatoshi(rs.getLong("recipient_amount_msat")), PublicKey(rs.getByteVector("recipient_node_id")), - rs.getLong("created_at"), + TimestampMilli(rs.getLong("created_at")), rs.getStringNullable("payment_request").map(PaymentRequest.read), status ) } - private def buildOutgoingPaymentStatus(preimage_opt: Option[ByteVector32], fees_opt: Option[MilliSatoshi], paymentRoute_opt: Option[BitVector], completedAt_opt: Option[Long], failures: Option[BitVector]): OutgoingPaymentStatus = { + private def buildOutgoingPaymentStatus(preimage_opt: Option[ByteVector32], fees_opt: Option[MilliSatoshi], paymentRoute_opt: Option[BitVector], completedAt_opt: Option[TimestampMilli], failures: Option[BitVector]): OutgoingPaymentStatus = { preimage_opt match { // If we have a pre-image, the payment succeeded. case Some(preimage) => OutgoingPaymentStatus.Succeeded( @@ -192,7 +192,7 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { case Attempt.Successful(route) => route.value case Attempt.Failure(_) => Nil }).getOrElse(Nil), - completedAt_opt.getOrElse(0) + completedAt_opt.getOrElse(0 unixms) ) case None => completedAt_opt match { // Otherwise if the payment was marked completed, it's a failure. @@ -230,10 +230,10 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { } } - override def listOutgoingPayments(from: Long, to: Long): Seq[OutgoingPayment] = withMetrics("payments/list-outgoing-by-timestamp", DbBackends.Sqlite) { + override def listOutgoingPayments(from: TimestampMilli, to: TimestampMilli): Seq[OutgoingPayment] = withMetrics("payments/list-outgoing-by-timestamp", DbBackends.Sqlite) { using(sqlite.prepareStatement("SELECT * FROM sent_payments WHERE created_at >= ? AND created_at < ? ORDER BY created_at")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery().map(parseOutgoingPayment).toSeq } } @@ -244,16 +244,16 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { statement.setBytes(2, preimage.toArray) statement.setString(3, paymentType) statement.setString(4, PaymentRequest.write(pr)) - statement.setLong(5, pr.timestamp.seconds.toMillis) // BOLT11 timestamp is in seconds - statement.setLong(6, (pr.timestamp + pr.expiry.getOrElse(PaymentRequest.DEFAULT_EXPIRY_SECONDS.toLong)).seconds.toMillis) + statement.setLong(5, pr.timestamp.toTimestampMilli.toLong) // BOLT11 timestamp is in seconds + statement.setLong(6, (pr.timestamp + pr.expiry.getOrElse(PaymentRequest.DEFAULT_EXPIRY_SECONDS)).toLong.seconds.toMillis) statement.executeUpdate() } } - override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: Long): Unit = withMetrics("payments/receive-incoming", DbBackends.Sqlite) { + override def receiveIncomingPayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedAt: TimestampMilli): Unit = withMetrics("payments/receive-incoming", DbBackends.Sqlite) { using(sqlite.prepareStatement("UPDATE received_payments SET (received_msat, received_at) = (? + COALESCE(received_msat, 0), ?) WHERE payment_hash = ?")) { update => update.setLong(1, amount.toLong) - update.setLong(2, receivedAt) + update.setLong(2, receivedAt.toLong) update.setBytes(3, paymentHash.toArray) val updated = update.executeUpdate() if (updated == 0) { @@ -268,13 +268,13 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { PaymentRequest.read(paymentRequest), rs.getByteVector32("payment_preimage"), rs.getString("payment_type"), - rs.getLong("created_at"), - buildIncomingPaymentStatus(rs.getMilliSatoshiNullable("received_msat"), Some(paymentRequest), rs.getLongNullable("received_at"))) + TimestampMilli(rs.getLong("created_at")), + buildIncomingPaymentStatus(rs.getMilliSatoshiNullable("received_msat"), Some(paymentRequest), rs.getLongNullable("received_at").map(TimestampMilli(_)))) } - private def buildIncomingPaymentStatus(amount_opt: Option[MilliSatoshi], serializedPaymentRequest_opt: Option[String], receivedAt_opt: Option[Long]): IncomingPaymentStatus = { + private def buildIncomingPaymentStatus(amount_opt: Option[MilliSatoshi], serializedPaymentRequest_opt: Option[String], receivedAt_opt: Option[TimestampMilli]): IncomingPaymentStatus = { amount_opt match { - case Some(amount) => IncomingPaymentStatus.Received(amount, receivedAt_opt.getOrElse(0)) + case Some(amount) => IncomingPaymentStatus.Received(amount, receivedAt_opt.getOrElse(0 unixms)) case None if serializedPaymentRequest_opt.exists(PaymentRequest.fastHasExpired) => IncomingPaymentStatus.Expired case None => IncomingPaymentStatus.Pending } @@ -287,36 +287,36 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { } } - override def listIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Sqlite) { + override def listIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming", DbBackends.Sqlite) { using(sqlite.prepareStatement("SELECT * FROM received_payments WHERE created_at > ? AND created_at < ? ORDER BY created_at")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery().map(parseIncomingPayment).toSeq } } - override def listReceivedIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Sqlite) { + override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Sqlite) { using(sqlite.prepareStatement("SELECT * FROM received_payments WHERE received_msat > 0 AND created_at > ? AND created_at < ? ORDER BY created_at")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) statement.executeQuery().map(parseIncomingPayment).toSeq } } - override def listPendingIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming-pending", DbBackends.Sqlite) { + override def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-pending", DbBackends.Sqlite) { using(sqlite.prepareStatement("SELECT * FROM received_payments WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at > ? ORDER BY created_at")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) - statement.setLong(3, System.currentTimeMillis) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) + statement.setLong(3, TimestampMilli.now().toLong) statement.executeQuery().map(parseIncomingPayment).toSeq } } - override def listExpiredIncomingPayments(from: Long, to: Long): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Sqlite) { + override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Sqlite) { using(sqlite.prepareStatement("SELECT * FROM received_payments WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at < ? ORDER BY created_at")) { statement => - statement.setLong(1, from) - statement.setLong(2, to) - statement.setLong(3, System.currentTimeMillis) + statement.setLong(1, from.toLong) + statement.setLong(2, to.toLong) + statement.setLong(3, TimestampMilli.now().toLong) statement.executeQuery().map(parseIncomingPayment).toSeq } } @@ -373,9 +373,9 @@ class SqlitePaymentsDb(sqlite: Connection) extends PaymentsDb with Logging { val paymentType = rs.getString("payment_type") val paymentRequest_opt = rs.getStringNullable("payment_request") val amount_opt = rs.getMilliSatoshiNullable("final_amount") - val createdAt = rs.getLong("created_at") - val completedAt_opt = rs.getLongNullable("completed_at") - val expireAt_opt = rs.getLongNullable("expire_at") + val createdAt = TimestampMilli(rs.getLong("created_at")) + val completedAt_opt = rs.getLongNullable("completed_at").map(TimestampMilli(_)) + val expireAt_opt = rs.getLongNullable("expire_at").map(TimestampMilli(_)) if (rs.getString("type") == "received") { val status: IncomingPaymentStatus = buildIncomingPaymentStatus(amount_opt, paymentRequest_opt, completedAt_opt) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala index 8fe812ffe7..9d8790deb2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/PeerConnection.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{FSMDiagnosticActorLogging, Features, Logs} +import fr.acinq.eclair.{FSMDiagnosticActorLogging, Features, Logs, TimestampMilli, TimestampSecond} import scodec.Attempt import scodec.bits.ByteVector @@ -203,7 +203,7 @@ class PeerConnection(keyPair: KeyPair, conf: PeerConnection.Conf, switchboard: A d.expectedPong_opt match { case Some(ExpectedPong(ping, timestamp)) if ping.pongLength == data.length => // we use the pong size to correlate between pings and pongs - val latency = System.currentTimeMillis - timestamp + val latency = TimestampMilli.now() - timestamp log.debug(s"received pong with latency=$latency") cancelTimer(PingTimeout.toString()) // we don't need to call scheduleNextPing here, the next ping was already scheduled when we received that pong @@ -496,7 +496,7 @@ object PeerConnection { case class InitializingData(chainHash: ByteVector32, pendingAuth: PendingAuth, remoteNodeId: PublicKey, transport: ActorRef, peer: ActorRef, localInit: protocol.Init, doSync: Boolean) extends Data with HasTransport case class ConnectedData(chainHash: ByteVector32, remoteNodeId: PublicKey, transport: ActorRef, peer: ActorRef, localInit: protocol.Init, remoteInit: protocol.Init, rebroadcastDelay: FiniteDuration, gossipTimestampFilter: Option[GossipTimestampFilter] = None, behavior: Behavior = Behavior(), expectedPong_opt: Option[ExpectedPong] = None) extends Data with HasTransport - case class ExpectedPong(ping: Ping, timestamp: Long = System.currentTimeMillis) + case class ExpectedPong(ping: Ping, timestamp: TimestampMilli = TimestampMilli.now()) case class PingTimeout(ping: Ping) sealed trait State @@ -563,7 +563,7 @@ object PeerConnection { // Otherwise we check if this message has a timestamp that matches the timestamp filter. val matchesFilter = (msg, gossipTimestampFilter_opt) match { case (_, None) => false // BOLT 7: A node which wants any gossip messages would have to send this, otherwise [...] no gossip messages would be received. - case (hasTs: HasTimestamp, Some(GossipTimestampFilter(_, firstTimestamp, timestampRange, _))) => hasTs.timestamp >= firstTimestamp && hasTs.timestamp <= firstTimestamp + timestampRange + case (hasTs: HasTimestamp, Some(GossipTimestampFilter(_, firstTimestamp, timestampRange, _))) => hasTs.timestamp >= firstTimestamp && hasTs.timestamp <= TimestampSecond(firstTimestamp.toLong + timestampRange) case _ => true // if there is a filter and message doesn't have a timestamp (e.g. channel_announcement), then we send it } isOurGossip || matchesFilter diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala index 27f4cac905..3d40af4b1b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala @@ -17,7 +17,6 @@ package fr.acinq.eclair.io import java.net.InetSocketAddress - import akka.actor.{ActorRef, Props} import akka.cluster.Cluster import akka.cluster.pubsub.DistributedPubSub @@ -28,7 +27,7 @@ import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.eclair.Logs.LogCategory import fr.acinq.eclair.db.{NetworkDb, PeersDb} import fr.acinq.eclair.io.Monitoring.Metrics -import fr.acinq.eclair.{FSMDiagnosticActorLogging, Logs, NodeParams} +import fr.acinq.eclair.{FSMDiagnosticActorLogging, Logs, NodeParams, TimestampMilli, TimestampSecond} import scala.concurrent.duration.{FiniteDuration, _} import scala.util.Random @@ -97,7 +96,7 @@ class ReconnectionTask(nodeParams: NodeParams, remoteNodeId: PublicKey) extends val firstNextReconnectionDelay = nodeParams.maxReconnectInterval.minus(Random.nextInt(nodeParams.maxReconnectInterval.toSeconds.toInt / 2).seconds) log.debug("first connection attempt in {}", initialDelay) (initialDelay, firstNextReconnectionDelay) - case (_, cd: ConnectingData) if System.currentTimeMillis.milliseconds - d.since < 30.seconds => + case (_, cd: ConnectingData) if TimestampMilli.now() - d.since < 30.seconds => // If our latest successful connection attempt was less than 30 seconds ago, we pick up the exponential // back-off retry delay where we left it. The goal is to address cases where the reconnection is successful, // but we are disconnected right away. @@ -185,7 +184,7 @@ object ReconnectionTask { // @formatter:off sealed trait Data case object Nothing extends Data - case class IdleData(previousData: Data, since: FiniteDuration = System.currentTimeMillis.milliseconds) extends Data + case class IdleData(previousData: Data, since: TimestampMilli = TimestampMilli.now()) extends Data case class ConnectingData(to: InetSocketAddress, nextReconnectionDelay: FiniteDuration) extends Data case class WaitingData(nextReconnectionDelay: FiniteDuration) extends Data // @formatter:on diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 536d0df688..91a316a9b4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.router.Router.RouteResponse import fr.acinq.eclair.transactions.DirectedHtlc import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, MilliSatoshi, ShortChannelId, UInt64, UnknownFeature} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64, UnknownFeature} import org.json4s import org.json4s.JsonAST._ import org.json4s.jackson.Serialization @@ -39,6 +39,8 @@ import org.json4s.{DefaultFormats, Extraction, Formats, JDecimal, JValue, KeySer import scodec.bits.ByteVector import java.net.InetSocketAddress +import java.time.Instant +import java.time.format.DateTimeFormatter import java.util.UUID /** @@ -108,6 +110,18 @@ object UInt64Serializer extends MinimalSerializer({ case x: UInt64 => JInt(x.toBigInt) }) +// @formatter:off +private case class TimestampJson(iso: String, unix: Long) +object TimestampSecondSerializer extends ConvertClassSerializer[TimestampSecond](ts => TimestampJson( + iso = DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochSecond(ts.toLong)), + unix = ts.toLong +)) +object TimestampMilliSerializer extends ConvertClassSerializer[TimestampMilli](ts => TimestampJson( + iso = DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(ts.toLong)), + unix = ts.toLong / 1000 // we convert to standard unix timestamp with second precision +)) +// @formatter:on + object BtcSerializer extends MinimalSerializer({ case x: Btc => JDecimal(x.toDouble) }) @@ -318,7 +332,7 @@ object PaymentRequestSerializer extends MinimalSerializer({ CltvExpiryDeltaSerializer )) val fieldList = List(JField("prefix", JString(p.prefix)), - JField("timestamp", JLong(p.timestamp)), + JField("timestamp", JLong(p.timestamp.toLong)), JField("nodeId", JString(p.nodeId.toString())), JField("serialized", JString(PaymentRequest.write(p))), p.description.fold(string => JField("description", JString(string)), hash => JField("descriptionHash", JString(hash.toHex))), @@ -445,6 +459,8 @@ object JsonSerializers { ByteVector64Serializer + ChannelEventSerializer + UInt64Serializer + + TimestampSecondSerializer + + TimestampMilliSerializer + BtcSerializer + SatoshiSerializer + MilliSatoshiSerializer + diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala index 310ac0c1c8..0ff85d1276 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala @@ -99,6 +99,14 @@ package object eclair { def msat = MilliSatoshi(n) } + implicit class TimestampSecondLong(private val n: Long) extends AnyVal { + def unixsec = TimestampSecond(n) + } + + implicit class TimestampMilliLong(private val n: Long) extends AnyVal { + def unixms = TimestampMilli(n) + } + // We implement Numeric to take advantage of operations such as sum, sort or min/max on iterables. implicit object NumericMilliSatoshi extends Numeric[MilliSatoshi] { // @formatter:off diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala index 4b7ad673e9..e8e4651a33 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala @@ -24,9 +24,10 @@ import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentConfig import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Router.{ChannelDesc, ChannelHop, Hop, Ignore} import fr.acinq.eclair.wire.protocol.{ChannelDisabled, ChannelUpdate, Node, TemporaryChannelFailure} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TimestampMilli} import java.util.UUID +import scala.concurrent.duration.FiniteDuration import scala.util.{Failure, Success, Try} /** @@ -35,7 +36,7 @@ import scala.util.{Failure, Success, Try} sealed trait PaymentEvent { val paymentHash: ByteVector32 - val timestamp: Long + val timestamp: TimestampMilli } /** @@ -55,7 +56,7 @@ case class PaymentSent(id: UUID, paymentHash: ByteVector32, paymentPreimage: Byt val feesPaid: MilliSatoshi = amountWithFees - recipientAmount // overall fees for this payment (routing + trampoline) val trampolineFees: MilliSatoshi = parts.map(_.amount).sum - recipientAmount val nonTrampolineFees: MilliSatoshi = feesPaid - trampolineFees // routing fees to reach the first trampoline node, or the recipient if not using trampoline - val timestamp: Long = parts.map(_.timestamp).min // we use min here because we receive the proof of payment as soon as the first partial payment is fulfilled + val timestamp: TimestampMilli = parts.map(_.timestamp).min // we use min here because we receive the proof of payment as soon as the first partial payment is fulfilled } object PaymentSent { @@ -70,24 +71,24 @@ object PaymentSent { * @param route payment route used. * @param timestamp absolute time in milli-seconds since UNIX epoch when the payment was fulfilled. */ - case class PartialPayment(id: UUID, amount: MilliSatoshi, feesPaid: MilliSatoshi, toChannelId: ByteVector32, route: Option[Seq[Hop]], timestamp: Long = System.currentTimeMillis) { + case class PartialPayment(id: UUID, amount: MilliSatoshi, feesPaid: MilliSatoshi, toChannelId: ByteVector32, route: Option[Seq[Hop]], timestamp: TimestampMilli = TimestampMilli.now()) { require(route.isEmpty || route.get.nonEmpty, "route must be None or contain at least one hop") val amountWithFees: MilliSatoshi = amount + feesPaid } } -case class PaymentFailed(id: UUID, paymentHash: ByteVector32, failures: Seq[PaymentFailure], timestamp: Long = System.currentTimeMillis) extends PaymentEvent +case class PaymentFailed(id: UUID, paymentHash: ByteVector32, failures: Seq[PaymentFailure], timestamp: TimestampMilli = TimestampMilli.now()) extends PaymentEvent sealed trait PaymentRelayed extends PaymentEvent { val amountIn: MilliSatoshi val amountOut: MilliSatoshi - val timestamp: Long + val timestamp: TimestampMilli } -case class ChannelPaymentRelayed(amountIn: MilliSatoshi, amountOut: MilliSatoshi, paymentHash: ByteVector32, fromChannelId: ByteVector32, toChannelId: ByteVector32, timestamp: Long = System.currentTimeMillis) extends PaymentRelayed +case class ChannelPaymentRelayed(amountIn: MilliSatoshi, amountOut: MilliSatoshi, paymentHash: ByteVector32, fromChannelId: ByteVector32, toChannelId: ByteVector32, timestamp: TimestampMilli = TimestampMilli.now()) extends PaymentRelayed -case class TrampolinePaymentRelayed(paymentHash: ByteVector32, incoming: PaymentRelayed.Incoming, outgoing: PaymentRelayed.Outgoing, nextTrampolineNodeId: PublicKey, nextTrampolineAmount: MilliSatoshi, timestamp: Long = System.currentTimeMillis) extends PaymentRelayed { +case class TrampolinePaymentRelayed(paymentHash: ByteVector32, incoming: PaymentRelayed.Incoming, outgoing: PaymentRelayed.Outgoing, nextTrampolineNodeId: PublicKey, nextTrampolineAmount: MilliSatoshi, timestamp: TimestampMilli = TimestampMilli.now()) extends PaymentRelayed { override val amountIn: MilliSatoshi = incoming.map(_.amount).sum override val amountOut: MilliSatoshi = outgoing.map(_.amount).sum } @@ -104,16 +105,16 @@ object PaymentRelayed { case class PaymentReceived(paymentHash: ByteVector32, parts: Seq[PaymentReceived.PartialPayment]) extends PaymentEvent { require(parts.nonEmpty, "must have at least one payment part") val amount: MilliSatoshi = parts.map(_.amount).sum - val timestamp: Long = parts.map(_.timestamp).max // we use max here because we fulfill the payment only once we received all the parts + val timestamp: TimestampMilli = parts.map(_.timestamp).max // we use max here because we fulfill the payment only once we received all the parts } object PaymentReceived { - case class PartialPayment(amount: MilliSatoshi, fromChannelId: ByteVector32, timestamp: Long = System.currentTimeMillis) + case class PartialPayment(amount: MilliSatoshi, fromChannelId: ByteVector32, timestamp: TimestampMilli = TimestampMilli.now()) } -case class PaymentSettlingOnChain(id: UUID, amount: MilliSatoshi, paymentHash: ByteVector32, timestamp: Long = System.currentTimeMillis) extends PaymentEvent +case class PaymentSettlingOnChain(id: UUID, amount: MilliSatoshi, paymentHash: ByteVector32, timestamp: TimestampMilli = TimestampMilli.now()) extends PaymentEvent sealed trait PaymentFailure { // @formatter:off @@ -250,8 +251,8 @@ object PaymentFailure { case class PathFindingExperimentMetrics(amount: MilliSatoshi, fees: MilliSatoshi, status: String, - duration: Long, - timestamp: Long, + duration: FiniteDuration, + timestamp: TimestampMilli, isMultiPart: Boolean, experimentName: String, recipientNodeId: PublicKey) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala index 2b8dd2ac14..beb32089bd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentRequest.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.payment import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, Block, ByteVector32, ByteVector64, Crypto} import fr.acinq.eclair.payment.PaymentRequest._ -import fr.acinq.eclair.{CltvExpiryDelta, FeatureSupport, Features, MilliSatoshi, MilliSatoshiLong, NodeParams, ShortChannelId, randomBytes32} +import fr.acinq.eclair.{CltvExpiryDelta, FeatureSupport, Features, MilliSatoshi, MilliSatoshiLong, NodeParams, ShortChannelId, TimestampSecond, randomBytes32} import scodec.bits.{BitVector, ByteOrdering, ByteVector} import scodec.codecs.{list, ubyte} import scodec.{Codec, Err} @@ -38,7 +38,7 @@ import scala.util.{Failure, Success, Try} * @param tags payment tags; must include a single PaymentHash tag and a single PaymentSecret tag. * @param signature request signature that will be checked against node id */ -case class PaymentRequest(prefix: String, amount: Option[MilliSatoshi], timestamp: Long, nodeId: PublicKey, tags: List[PaymentRequest.TaggedField], signature: ByteVector) { +case class PaymentRequest(prefix: String, amount: Option[MilliSatoshi], timestamp: TimestampSecond, nodeId: PublicKey, tags: List[PaymentRequest.TaggedField], signature: ByteVector) { amount.foreach(a => require(a > 0.msat, s"amount is not valid")) require(tags.collect { case _: PaymentRequest.PaymentHash => }.size == 1, "there must be exactly one payment hash tag") @@ -87,8 +87,8 @@ case class PaymentRequest(prefix: String, amount: Option[MilliSatoshi], timestam lazy val features: PaymentRequestFeatures = tags.collectFirst { case f: PaymentRequestFeatures => f }.getOrElse(PaymentRequestFeatures(BitVector.empty)) def isExpired: Boolean = expiry match { - case Some(expiryTime) => timestamp + expiryTime <= System.currentTimeMillis.milliseconds.toSeconds - case None => timestamp + DEFAULT_EXPIRY_SECONDS <= System.currentTimeMillis.milliseconds.toSeconds + case Some(expiryTime) => timestamp + expiryTime <= TimestampSecond.now() + case None => timestamp + DEFAULT_EXPIRY_SECONDS <= TimestampSecond.now() } /** @@ -118,7 +118,7 @@ case class PaymentRequest(prefix: String, amount: Option[MilliSatoshi], timestam object PaymentRequest { - val DEFAULT_EXPIRY_SECONDS = 3600 + val DEFAULT_EXPIRY_SECONDS: Long = 3600 val prefixes = Map( Block.RegtestGenesisBlock.hash -> "lnbcrt", @@ -135,7 +135,7 @@ object PaymentRequest { fallbackAddress: Option[String] = None, expirySeconds: Option[Long] = None, extraHops: List[List[ExtraHop]] = Nil, - timestamp: Long = System.currentTimeMillis() / 1000L, + timestamp: TimestampSecond = TimestampSecond.now(), paymentSecret: ByteVector32 = randomBytes32(), features: PaymentRequestFeatures = PaymentRequestFeatures(Features.VariableLengthOnion.mandatory, Features.PaymentSecret.mandatory)): PaymentRequest = { require(features.requirePaymentSecret, "invoices must require a payment secret") @@ -163,7 +163,7 @@ object PaymentRequest { ).sign(privateKey) } - case class Bolt11Data(timestamp: Long, taggedFields: List[TaggedField], signature: ByteVector) + case class Bolt11Data(timestamp: TimestampSecond, taggedFields: List[TaggedField], signature: ByteVector) sealed trait TaggedField @@ -444,7 +444,7 @@ object PaymentRequest { ) val bolt11DataCodec: Codec[Bolt11Data] = ( - ("timestamp" | ulong(35)) :: + ("timestamp" | ulong(35).xmapc(TimestampSecond(_))(_.toLong)) :: ("taggedFields" | fixedSizeTrailingCodec(list(taggedFieldCodec), 520)) :: ("signature" | bytes(65)) ).as[Bolt11Data] @@ -564,8 +564,8 @@ object PaymentRequest { } val timestamp = bolt11Data.timestamp expiry_opt match { - case Some(expiry) => timestamp + expiry.toLong <= System.currentTimeMillis.milliseconds.toSeconds - case None => timestamp + DEFAULT_EXPIRY_SECONDS <= System.currentTimeMillis.milliseconds.toSeconds + case Some(expiry) => timestamp + expiry.toLong <= TimestampSecond.now() + case None => timestamp + DEFAULT_EXPIRY_SECONDS <= TimestampSecond.now() } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala index 6779a0e416..e6babcabfa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.ByteVector32 import fr.acinq.eclair.payment.Monitoring.{Metrics, Tags} import fr.acinq.eclair.wire.protocol import fr.acinq.eclair.wire.protocol.{FailureMessage, IncorrectOrUnknownPaymentDetails, UpdateAddHtlc} -import fr.acinq.eclair.{FSMDiagnosticActorLogging, Logs, MilliSatoshi, NodeParams} +import fr.acinq.eclair.{FSMDiagnosticActorLogging, Logs, MilliSatoshi, NodeParams, TimestampMilli} import java.util.concurrent.TimeUnit import scala.collection.immutable.Queue @@ -41,7 +41,7 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot import MultiPartPaymentFSM._ - val start = System.currentTimeMillis + val start = TimestampMilli.now() startSingleTimer(PaymentTimeout.toString, PaymentTimeout, nodeParams.multiPartPaymentExpiry) @@ -97,7 +97,7 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot case PaymentSucceeded(parts) => // We expect the parent actor to send us a PoisonPill after receiving this message. replyTo ! MultiPartPaymentSucceeded(paymentHash, parts) - Metrics.ReceivedPaymentDuration.withTag(Tags.Success, value = true).record(System.currentTimeMillis - start, TimeUnit.MILLISECONDS) + Metrics.ReceivedPaymentDuration.withTag(Tags.Success, value = true).record((TimestampMilli.now() - start).toMillis, TimeUnit.MILLISECONDS) case d => log.error("unexpected payment success data {}", d.getClass.getSimpleName) } @@ -106,7 +106,7 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot case PaymentFailed(failure, parts) => // We expect the parent actor to send us a PoisonPill after receiving this message. replyTo ! MultiPartPaymentFailed(paymentHash, failure, parts) - Metrics.ReceivedPaymentDuration.withTag(Tags.Success, value = false).record(System.currentTimeMillis - start, TimeUnit.MILLISECONDS) + Metrics.ReceivedPaymentDuration.withTag(Tags.Success, value = false).record((TimestampMilli.now() - start).toMillis, TimeUnit.MILLISECONDS) case d => log.error("unexpected payment failure data {}", d.getClass.getSimpleName) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala index db8faf94b0..732e732621 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.payment.Monitoring.Tags import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPacket, PaymentFailed, PaymentSent} import fr.acinq.eclair.transactions.DirectedHtlc.outgoing import fr.acinq.eclair.wire.protocol.{FailureMessage, TemporaryNodeFailure, UpdateAddHtlc} -import fr.acinq.eclair.{CustomCommitmentsPlugin, MilliSatoshiLong, NodeParams} +import fr.acinq.eclair.{CustomCommitmentsPlugin, MilliSatoshiLong, NodeParams, TimestampMilli} import scala.concurrent.Promise import scala.util.Try @@ -167,7 +167,7 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial // dummy values in the DB (to make sure we store the preimage) but we don't emit an event. val dummyFinalAmount = fulfilledHtlc.amountMsat val dummyNodeId = nodeParams.nodeId - nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id, id, None, fulfilledHtlc.paymentHash, PaymentType.Standard, fulfilledHtlc.amountMsat, dummyFinalAmount, dummyNodeId, System.currentTimeMillis, None, OutgoingPaymentStatus.Pending)) + nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id, id, None, fulfilledHtlc.paymentHash, PaymentType.Standard, fulfilledHtlc.amountMsat, dummyFinalAmount, dummyNodeId, TimestampMilli.now(), None, OutgoingPaymentStatus.Pending)) nodeParams.db.payments.updateOutgoingPayment(PaymentSent(id, fulfilledHtlc.paymentHash, paymentPreimage, dummyFinalAmount, dummyNodeId, PaymentSent.PartialPayment(id, fulfilledHtlc.amountMsat, feesPaid, fulfilledHtlc.channelId, None) :: Nil)) } // There can never be more than one pending downstream HTLC for a given local origin (a multi-part payment is diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala index 0ea44f14c3..ebcdae283f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala @@ -22,7 +22,7 @@ import fr.acinq.eclair.crypto.Sphinx.DecryptedFailurePacket import fr.acinq.eclair.payment.{PaymentEvent, PaymentFailed, PaymentRequest, RemoteFailure} import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.IncorrectOrUnknownPaymentDetails -import fr.acinq.eclair.{MilliSatoshiLong, NodeParams, randomBytes32, randomLong} +import fr.acinq.eclair.{MilliSatoshiLong, NodeParams, TimestampSecond, randomBytes32, randomLong} import scodec.bits.ByteVector import scala.concurrent.duration._ @@ -56,7 +56,7 @@ class Autoprobe(nodeParams: NodeParams, router: ActorRef, paymentInitiator: Acto val fakeInvoice = PaymentRequest( PaymentRequest.prefixes(nodeParams.chainHash), Some(PAYMENT_AMOUNT_MSAT), - System.currentTimeMillis(), + TimestampSecond.now(), targetNodeId, List( PaymentRequest.PaymentHash(randomBytes32()), // we don't even know the preimage (this needs to be a secure random!) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala index 76bf555e1c..e8015f2442 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentConfig import fr.acinq.eclair.payment.send.PaymentLifecycle.SendPaymentToRoute import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, FSMDiagnosticActorLogging, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams} +import fr.acinq.eclair.{CltvExpiry, FSMDiagnosticActorLogging, Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli} import java.util.UUID import java.util.concurrent.TimeUnit @@ -52,7 +52,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, val id = cfg.id val paymentHash = cfg.paymentHash - val start = System.currentTimeMillis + val start = TimestampMilli.now() private var retriedFailedChannels = false startWith(WAIT_FOR_PAYMENT_REQUEST, WaitingForRequest) @@ -104,7 +104,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, if (cfg.storeInDb && d.pending.isEmpty && d.failures.isEmpty) { // In cases where we fail early (router error during the first attempt), the DB won't have an entry for that // payment, which may be confusing for users. - val dummyPayment = OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, cfg.recipientAmount, cfg.recipientAmount, cfg.recipientNodeId, System.currentTimeMillis, cfg.paymentRequest, OutgoingPaymentStatus.Pending) + val dummyPayment = OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, cfg.recipientAmount, cfg.recipientAmount, cfg.recipientNodeId, TimestampMilli.now(), cfg.paymentRequest, OutgoingPaymentStatus.Pending) nodeParams.db.payments.addOutgoingPayment(dummyPayment) nodeParams.db.payments.updateOutgoingPayment(PaymentFailed(id, paymentHash, failure :: Nil)) } @@ -244,7 +244,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, "FAILURE" } } - val now = System.currentTimeMillis + val now = TimestampMilli.now() val duration = now - start if (cfg.recordPathFindingMetrics) { val fees = event match { @@ -268,7 +268,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, Metrics.SentPaymentDuration .withTag(Tags.MultiPart, Tags.MultiPartType.Parent) .withTag(Tags.Success, value = status == "SUCCESS") - .record(duration, TimeUnit.MILLISECONDS) + .record(duration.toMillis, TimeUnit.MILLISECONDS) if (retriedFailedChannels) { Metrics.RetryFailedChannelsResult.withTag(Tags.Success, event.isRight).increment() } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala index f25f201ded..29da2b82b3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala @@ -48,7 +48,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A private val id = cfg.id private val paymentHash = cfg.paymentHash private val paymentsDb = nodeParams.db.payments - private val start = System.currentTimeMillis + private val start = TimestampMilli.now() startWith(WAITING_FOR_REQUEST, WaitingForRequest) @@ -60,7 +60,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A route => self ! RouteResponse(route :: Nil) ) if (cfg.storeInDb) { - paymentsDb.addOutgoingPayment(OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, c.finalPayload.amount, cfg.recipientAmount, cfg.recipientNodeId, System.currentTimeMillis, cfg.paymentRequest, OutgoingPaymentStatus.Pending)) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, c.finalPayload.amount, cfg.recipientAmount, cfg.recipientNodeId, TimestampMilli.now(), cfg.paymentRequest, OutgoingPaymentStatus.Pending)) } goto(WAITING_FOR_ROUTE) using WaitingForRoute(c, Nil, Ignore.empty) @@ -68,7 +68,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A log.debug("sending {} to {}", c.finalPayload.amount, c.targetNodeId) router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.maxFee, c.assistedRoutes, routeParams = c.routeParams, paymentContext = Some(cfg.paymentContext)) if (cfg.storeInDb) { - paymentsDb.addOutgoingPayment(OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, c.finalPayload.amount, cfg.recipientAmount, cfg.recipientNodeId, System.currentTimeMillis, cfg.paymentRequest, OutgoingPaymentStatus.Pending)) + paymentsDb.addOutgoingPayment(OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, c.finalPayload.amount, cfg.recipientAmount, cfg.recipientNodeId, TimestampMilli.now(), cfg.paymentRequest, OutgoingPaymentStatus.Pending)) } goto(WAITING_FOR_ROUTE) using WaitingForRoute(c, Nil, Ignore.empty) } @@ -302,7 +302,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A "FAILURE" } } - val now = System.currentTimeMillis + val now = TimestampMilli.now() val duration = now - start if (cfg.recordPathFindingMetrics) { val fees = result match { @@ -334,7 +334,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A Metrics.SentPaymentDuration .withTag(Tags.MultiPart, if (cfg.id != cfg.parentId) Tags.MultiPartType.Child else Tags.MultiPartType.Disabled) .withTag(Tags.Success, value = status == "SUCCESS") - .record(duration, TimeUnit.MILLISECONDS) + .record(duration.toMillis, TimeUnit.MILLISECONDS) stop(FSM.Normal) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index 0fc4df8a5a..2241756093 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -18,14 +18,11 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, sha256, verifySignature} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, LexicographicalOrdering} -import fr.acinq.eclair.router.Announcements.isNode1 import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, serializationResult} -import scodec.bits.{BitVector, ByteVector} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, TimestampSecond, TimestampSecondLong, serializationResult} +import scodec.bits.ByteVector import shapeless.HNil -import scala.concurrent.duration._ - /** * Created by PM on 03/02/2017. */ @@ -34,10 +31,10 @@ object Announcements { def channelAnnouncementWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, bitcoinKey2: PublicKey, features: Features, tlvStream: TlvStream[ChannelAnnouncementTlv]): ByteVector = sha256(sha256(serializationResult(LightningMessageCodecs.channelAnnouncementWitnessCodec.encode(features :: chainHash :: shortChannelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: tlvStream :: HNil)))) - def nodeAnnouncementWitnessEncode(timestamp: Long, nodeId: PublicKey, rgbColor: Color, alias: String, features: Features, addresses: List[NodeAddress], tlvStream: TlvStream[NodeAnnouncementTlv]): ByteVector = + def nodeAnnouncementWitnessEncode(timestamp: TimestampSecond, nodeId: PublicKey, rgbColor: Color, alias: String, features: Features, addresses: List[NodeAddress], tlvStream: TlvStream[NodeAnnouncementTlv]): ByteVector = sha256(sha256(serializationResult(LightningMessageCodecs.nodeAnnouncementWitnessCodec.encode(features :: timestamp :: nodeId :: rgbColor :: alias :: addresses :: tlvStream :: HNil)))) - def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: Long, channelFlags: ChannelUpdate.ChannelFlags, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: Option[MilliSatoshi], tlvStream: TlvStream[ChannelUpdateTlv]): ByteVector = + def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: TimestampSecond, channelFlags: ChannelUpdate.ChannelFlags, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: Option[MilliSatoshi], tlvStream: TlvStream[ChannelUpdateTlv]): ByteVector = sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: channelFlags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: htlcMaximumMsat :: tlvStream :: HNil)))) def generateChannelAnnouncementWitness(chainHash: ByteVector32, shortChannelId: ShortChannelId, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, features: Features): ByteVector = @@ -71,7 +68,7 @@ object Announcements { ) } - def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], features: Features, timestamp: Long = System.currentTimeMillis.milliseconds.toSeconds): NodeAnnouncement = { + def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], features: Features, timestamp: TimestampSecond = TimestampSecond.now()): NodeAnnouncement = { require(alias.length <= 32) val sortedAddresses = nodeAddresses.map { case address@(_: IPv4) => (1, address) @@ -108,7 +105,7 @@ object Announcements { * @return true if channel updates are "equal" */ def areSame(u1: ChannelUpdate, u2: ChannelUpdate): Boolean = - u1.copy(signature = ByteVector64.Zeroes, timestamp = 0) == u2.copy(signature = ByteVector64.Zeroes, timestamp = 0) + u1.copy(signature = ByteVector64.Zeroes, timestamp = 0 unixsec) == u2.copy(signature = ByteVector64.Zeroes, timestamp = 0 unixsec) def areSameIgnoreFlags(u1: ChannelUpdate, u2: ChannelUpdate): Boolean = u1.feeBaseMsat == u2.feeBaseMsat && @@ -117,7 +114,7 @@ object Announcements { u1.htlcMinimumMsat == u2.htlcMinimumMsat && u1.htlcMaximumMsat == u2.htlcMaximumMsat - def makeChannelUpdate(chainHash: ByteVector32, nodeSecret: PrivateKey, remoteNodeId: PublicKey, shortChannelId: ShortChannelId, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: MilliSatoshi, enable: Boolean = true, timestamp: Long = System.currentTimeMillis.milliseconds.toSeconds): ChannelUpdate = { + def makeChannelUpdate(chainHash: ByteVector32, nodeSecret: PrivateKey, remoteNodeId: PublicKey, shortChannelId: ShortChannelId, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: MilliSatoshi, enable: Boolean = true, timestamp: TimestampSecond = TimestampSecond.now()): ChannelUpdate = { val channelFlags = ChannelUpdate.ChannelFlags(isNode1 = isNode1(nodeSecret.publicKey, remoteNodeId), isEnabled = enable) val htlcMaximumMsatOpt = Some(htlcMaximumMsat) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Monitoring.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Monitoring.scala index f3b7c97c38..e4c2d2f740 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Monitoring.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Monitoring.scala @@ -56,8 +56,8 @@ object Monitoring { private val ChannelUpdateRefreshRate = Kamon.histogram("router.gossip.channel-update-refresh-rate", "Rate at which channels update their fee policy (minutes)") def channelUpdateRefreshed(update: ChannelUpdate, previous: ChannelUpdate, public: Boolean): Unit = { - val elapsed = (update.timestamp - previous.timestamp) / 60 - ChannelUpdateRefreshRate.withTag(Tags.Announced, public).record(elapsed) + val elapsed = update.timestamp - previous.timestamp + ChannelUpdateRefreshRate.withTag(Tags.Announced, public).record(elapsed.toMinutes) } private val GossipResult = Kamon.counter("router.gossip.result") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala index 448ae4d6b5..ed80b789a9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala @@ -136,7 +136,7 @@ object RouteCalculation { private def toFakeUpdate(extraHop: ExtraHop, htlcMaximum: MilliSatoshi): ChannelUpdate = { // the `direction` bit in flags will not be accurate but it doesn't matter because it is not used // what matters is that the `disable` bit is 0 so that this update doesn't get filtered out - ChannelUpdate(signature = ByteVector64.Zeroes, chainHash = ByteVector32.Zeroes, extraHop.shortChannelId, System.currentTimeMillis.milliseconds.toSeconds, channelFlags = ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = true), extraHop.cltvExpiryDelta, htlcMinimumMsat = 0 msat, extraHop.feeBase, extraHop.feeProportionalMillionths, Some(htlcMaximum)) + ChannelUpdate(signature = ByteVector64.Zeroes, chainHash = ByteVector32.Zeroes, extraHop.shortChannelId, TimestampSecond.now(), channelFlags = ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = true), extraHop.cltvExpiryDelta, htlcMinimumMsat = 0 msat, extraHop.feeBase, extraHop.feeProportionalMillionths, Some(htlcMaximum)) } def toAssistedChannels(extraRoute: Seq[ExtraHop], targetNodeId: PublicKey, amount: MilliSatoshi): Map[ShortChannelId, AssistedChannel] = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala index 99d0cc3f15..d6115f4ffa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala @@ -21,7 +21,7 @@ import akka.event.LoggingAdapter import fr.acinq.eclair.db.NetworkDb import fr.acinq.eclair.router.Router.{ChannelDesc, Data, PublicChannel, hasChannels} import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate} -import fr.acinq.eclair.{ShortChannelId, TxCoordinates} +import fr.acinq.eclair.{ShortChannelId, TimestampSecond, TxCoordinates} import scala.collection.mutable import scala.concurrent.duration._ @@ -65,17 +65,17 @@ object StaleChannels { def isStale(u: ChannelUpdate): Boolean = isStale(u.timestamp) - def isStale(timestamp: Long): Boolean = { + def isStale(timestamp: TimestampSecond): Boolean = { // BOLT 7: "nodes MAY prune channels should the timestamp of the latest channel_update be older than 2 weeks" // but we don't want to prune brand new channels for which we didn't yet receive a channel update - val staleThresholdSeconds = (System.currentTimeMillis.milliseconds - 14.days).toSeconds - timestamp < staleThresholdSeconds + val staleThreshold = TimestampSecond.now() - 14.days + timestamp < staleThreshold } - def isAlmostStale(timestamp: Long): Boolean = { + def isAlmostStale(timestamp: TimestampSecond): Boolean = { // we define almost stale as 2 weeks minus 4 days - val staleThresholdSeconds = (System.currentTimeMillis.milliseconds - 10.days).toSeconds - timestamp < staleThresholdSeconds + val almostStaleThreshold = TimestampSecond.now() - 10.days + timestamp < almostStaleThreshold } /** diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala index 98b60bbde1..15a0f25b42 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala @@ -24,14 +24,13 @@ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.router.Monitoring.{Metrics, Tags} import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{ShortChannelId, serializationResult} +import fr.acinq.eclair.{ShortChannelId, TimestampSecond, TimestampSecondLong, serializationResult} import scodec.bits.ByteVector import shapeless.HNil import scala.annotation.tailrec import scala.collection.SortedSet import scala.collection.immutable.SortedMap -import scala.concurrent.duration._ import scala.util.Random object Sync { @@ -59,7 +58,7 @@ object Sync { // the first_timestamp field to the current date/time and timestamp_range to the maximum value // NB: we can't just set firstTimestamp to 0, because in that case peer would send us all past messages matching // that (i.e. the whole routing table) - val filter = GossipTimestampFilter(s.chainHash, firstTimestamp = System.currentTimeMillis.milliseconds.toSeconds, timestampRange = Int.MaxValue) + val filter = GossipTimestampFilter(s.chainHash, firstTimestamp = TimestampSecond.now(), timestampRange = Int.MaxValue) s.to ! filter // reset our sync state for this peer: we create an entry to ensure we reject duplicate queries and unsolicited reply_channel_range @@ -224,7 +223,7 @@ object Sync { height >= firstBlockNum && height < (firstBlockNum + numberOfBlocks) } - def shouldRequestUpdate(ourTimestamp: Long, ourChecksum: Long, theirTimestamp_opt: Option[Long], theirChecksum_opt: Option[Long]): Boolean = { + def shouldRequestUpdate(ourTimestamp: TimestampSecond, ourChecksum: Long, theirTimestamp_opt: Option[TimestampSecond], theirChecksum_opt: Option[Long]): Boolean = { (theirTimestamp_opt, theirChecksum_opt) match { case (Some(theirTimestamp), Some(theirChecksum)) => // we request their channel_update if all those conditions are met: @@ -372,8 +371,8 @@ object Sync { def getChannelDigestInfo(channels: SortedMap[ShortChannelId, PublicChannel])(shortChannelId: ShortChannelId): (ReplyChannelRangeTlv.Timestamps, ReplyChannelRangeTlv.Checksums) = { val c = channels(shortChannelId) - val timestamp1 = c.update_1_opt.map(_.timestamp).getOrElse(0L) - val timestamp2 = c.update_2_opt.map(_.timestamp).getOrElse(0L) + val timestamp1 = c.update_1_opt.map(_.timestamp).getOrElse(0L unixsec) + val timestamp2 = c.update_2_opt.map(_.timestamp).getOrElse(0L unixsec) val checksum1 = c.update_1_opt.map(getChecksum).getOrElse(0L) val checksum2 = c.update_2_opt.map(getChecksum).getOrElse(0L) (ReplyChannelRangeTlv.Timestamps(timestamp1 = timestamp1, timestamp2 = timestamp2), ReplyChannelRangeTlv.Checksums(checksum1 = checksum1, checksum2 = checksum2)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index c03e14fa3b..8d6a943ba9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.wire.internal.channel.version0 import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} +import fr.acinq.eclair.TimestampSecond import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.Transactions._ @@ -352,7 +353,7 @@ private[channel] object ChannelCodecs0 { val DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentsCodec) :: ("fundingTx" | provide[Option[Transaction]](None)) :: - ("waitingSince" | provide(System.currentTimeMillis.milliseconds.toSeconds)) :: + ("waitingSince" | provide(TimestampSecond.now().toLong)) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly @@ -411,7 +412,7 @@ private[channel] object ChannelCodecs0 { val DATA_CLOSING_COMPAT_06_Codec: Codec[DATA_CLOSING] = ( ("commitments" | commitmentsCodec) :: ("fundingTx" | provide[Option[Transaction]](None)) :: - ("waitingSince" | provide(System.currentTimeMillis.milliseconds.toSeconds)) :: + ("waitingSince" | provide(TimestampSecond.now().toLong)) :: ("mutualCloseProposed" | listOfN(uint16, closingTxCodec)) :: ("mutualClosePublished" | listOfN(uint16, closingTxCodec)) :: ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index e3a070a369..973367bb92 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.crypto.Mac32 -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, TimestampSecond, UInt64} import org.apache.commons.codec.binary.Base32 import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -69,6 +69,8 @@ object CommonCodecs { // this codec will fail if the amount does not fit on 32 bits val millisatoshi32: Codec[MilliSatoshi] = uint32.xmapc(l => MilliSatoshi(l))(_.toLong) + val timestampSecond: Codec[TimestampSecond] = uint32.xmapc(TimestampSecond(_))(_.toLong) + /** * We impose a minimal encoding on some values (such as varint and truncated int) to ensure that signed hashes can be * re-computed correctly. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index 4c74cf1b63..36b9287b99 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -210,7 +210,7 @@ object LightningMessageCodecs { val nodeAnnouncementWitnessCodec = ("features" | featuresCodec) :: - ("timestamp" | uint32) :: + ("timestamp" | timestampSecond) :: ("nodeId" | publicKey) :: ("rgbColor" | rgb) :: ("alias" | zeropaddedstring(32)) :: @@ -250,7 +250,7 @@ object LightningMessageCodecs { val channelUpdateWitnessCodec = (("chainHash" | bytes32) :: ("shortChannelId" | shortchannelid) :: - ("timestamp" | uint32) :: + ("timestamp" | timestampSecond) :: (messageFlagsCodec >>:~ { messageFlags => channelFlagsCodec :: ("cltvExpiryDelta" | cltvExpiryDelta) :: @@ -304,7 +304,7 @@ object LightningMessageCodecs { val gossipTimestampFilterCodec: Codec[GossipTimestampFilter] = ( ("chainHash" | bytes32) :: - ("firstTimestamp" | uint32) :: + ("firstTimestamp" | timestampSecond) :: ("timestampRange" | uint32) :: ("tlvStream" | GossipTimestampFilterTlv.gossipTimestampFilterTlvCodec)).as[GossipTimestampFilter] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index f5a1775e4f..c7773fc716 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelType -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64} import scodec.bits.ByteVector import java.net.{Inet4Address, Inet6Address, InetAddress, InetSocketAddress} @@ -39,7 +39,7 @@ sealed trait ChannelMessage extends LightningMessage sealed trait HtlcMessage extends LightningMessage sealed trait RoutingMessage extends LightningMessage sealed trait AnnouncementMessage extends RoutingMessage // <- not in the spec -sealed trait HasTimestamp extends LightningMessage { def timestamp: Long } +sealed trait HasTimestamp extends LightningMessage { def timestamp: TimestampSecond } sealed trait HasTemporaryChannelId extends LightningMessage { def temporaryChannelId: ByteVector32 } // <- not in the spec sealed trait HasChannelId extends LightningMessage { def channelId: ByteVector32 } // <- not in the spec sealed trait HasChainHash extends LightningMessage { def chainHash: ByteVector32 } // <- not in the spec @@ -243,7 +243,7 @@ case class Tor3(tor3: String, port: Int) extends OnionAddress { override def soc case class NodeAnnouncement(signature: ByteVector64, features: Features, - timestamp: Long, + timestamp: TimestampSecond, nodeId: PublicKey, rgbColor: Color, alias: String, @@ -253,7 +253,7 @@ case class NodeAnnouncement(signature: ByteVector64, case class ChannelUpdate(signature: ByteVector64, chainHash: ByteVector32, shortChannelId: ShortChannelId, - timestamp: Long, + timestamp: TimestampSecond, channelFlags: ChannelUpdate.ChannelFlags, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, @@ -318,7 +318,7 @@ object ReplyChannelRange { } } -case class GossipTimestampFilter(chainHash: ByteVector32, firstTimestamp: Long, timestampRange: Long, tlvStream: TlvStream[GossipTimestampFilterTlv] = TlvStream.empty) extends RoutingMessage with HasChainHash +case class GossipTimestampFilter(chainHash: ByteVector32, firstTimestamp: TimestampSecond, timestampRange: Long, tlvStream: TlvStream[GossipTimestampFilterTlv] = TlvStream.empty) extends RoutingMessage with HasChainHash // NB: blank lines to minimize merge conflicts diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RoutingTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RoutingTlv.scala index 3b486699a3..c567aa86d6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RoutingTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RoutingTlv.scala @@ -16,9 +16,9 @@ package fr.acinq.eclair.wire.protocol -import fr.acinq.eclair.UInt64 -import fr.acinq.eclair.wire.protocol.CommonCodecs.{varint, varintoverflow} +import fr.acinq.eclair.wire.protocol.CommonCodecs.{timestampSecond, varint, varintoverflow} import fr.acinq.eclair.wire.protocol.TlvCodecs.tlvStream +import fr.acinq.eclair.{TimestampSecond, UInt64} import scodec.Codec import scodec.codecs._ @@ -97,7 +97,7 @@ object ReplyChannelRangeTlv { * @param timestamp1 timestamp for node 1, or 0 * @param timestamp2 timestamp for node 2, or 0 */ - case class Timestamps(timestamp1: Long, timestamp2: Long) + case class Timestamps(timestamp1: TimestampSecond, timestamp2: TimestampSecond) /** * Optional timestamps TLV that can be appended to ReplyChannelRange @@ -124,8 +124,8 @@ object ReplyChannelRangeTlv { } val timestampsCodec: Codec[Timestamps] = ( - ("timestamp1" | uint32) :: - ("timestamp2" | uint32) + ("timestamp1" | timestampSecond) :: + ("timestamp2" | timestampSecond) ).as[Timestamps] val encodedTimestampsCodec: Codec[EncodedTimestamps] = variableSizeBytesLong(varintoverflow, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index f7c8b5f1a1..61eda8374f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -25,7 +25,7 @@ import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64, Crypto, SatoshiLong} import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair.blockchain.DummyOnChainWallet import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} -import fr.acinq.eclair.channel.{CMD_FORCECLOSE, Register, _} +import fr.acinq.eclair.channel._ import fr.acinq.eclair.db._ import fr.acinq.eclair.io.Peer.OpenChannel import fr.acinq.eclair.payment.PaymentRequest @@ -132,7 +132,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I // with finalCltvExpiry val externalId2 = "487da196-a4dc-4b1e-92b4-3e5e905e9f3f" - val invoice2 = PaymentRequest("lntb", Some(123 msat), System.currentTimeMillis() / 1000L, nodePrivKey.publicKey, List(PaymentRequest.MinFinalCltvExpiry(96), PaymentRequest.PaymentHash(ByteVector32.Zeroes), PaymentRequest.Description("description")), ByteVector.empty) + val invoice2 = PaymentRequest("lntb", Some(123 msat), TimestampSecond.now(), nodePrivKey.publicKey, List(PaymentRequest.MinFinalCltvExpiry(96), PaymentRequest.PaymentHash(ByteVector32.Zeroes), PaymentRequest.Description("description")), ByteVector.empty) eclair.send(Some(externalId2), 123 msat, invoice2) val send2 = paymentInitiator.expectMsgType[SendPaymentToNode] assert(send2.externalId === Some(externalId2)) @@ -155,7 +155,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I val invalidExternalId = "Robert'); DROP TABLE received_payments; DROP TABLE sent_payments; DROP TABLE payments;" assertThrows[IllegalArgumentException](Await.result(eclair.send(Some(invalidExternalId), 123 msat, invoice0), 50 millis)) - val expiredInvoice = invoice2.copy(timestamp = 0L) + val expiredInvoice = invoice2.copy(timestamp = 0.unixsec) assertThrows[IllegalArgumentException](Await.result(eclair.send(None, 123 msat, expiredInvoice), 50 millis)) } @@ -163,13 +163,13 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I import f._ val eclair = new EclairImpl(kit) - val remoteNodeAnn1 = NodeAnnouncement(randomBytes64(), Features.empty, 42L, randomKey().publicKey, Color(42, 42, 42), "LN-rocks", Nil) - val remoteNodeAnn2 = NodeAnnouncement(randomBytes64(), Features.empty, 43L, randomKey().publicKey, Color(43, 43, 43), "LN-papers", Nil) + val remoteNodeAnn1 = NodeAnnouncement(randomBytes64(), Features.empty, TimestampSecond(42L), randomKey().publicKey, Color(42, 42, 42), "LN-rocks", Nil) + val remoteNodeAnn2 = NodeAnnouncement(randomBytes64(), Features.empty, TimestampSecond(43L), randomKey().publicKey, Color(43, 43, 43), "LN-papers", Nil) val allNodes = Seq( - NodeAnnouncement(randomBytes64(), Features.empty, 561L, randomKey().publicKey, Color(0, 0, 0), "some-node", Nil), + NodeAnnouncement(randomBytes64(), Features.empty, TimestampSecond(561L), randomKey().publicKey, Color(0, 0, 0), "some-node", Nil), remoteNodeAnn1, remoteNodeAnn2, - NodeAnnouncement(randomBytes64(), Features.empty, 1105L, randomKey().publicKey, Color(0, 0, 0), "some-other-node", Nil), + NodeAnnouncement(randomBytes64(), Features.empty, TimestampSecond(1105L), randomKey().publicKey, Color(0, 0, 0), "some-other-node", Nil), ) { @@ -354,37 +354,6 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I }) } - test("networkFees/audit/allinvoices should use a default to/from filter expressed in seconds") { f => - import f._ - - val auditDb = mock[AuditDb] - val paymentDb = mock[PaymentsDb] - - auditDb.listNetworkFees(anyLong, anyLong) returns Seq.empty - auditDb.listSent(anyLong, anyLong) returns Seq.empty - auditDb.listReceived(anyLong, anyLong) returns Seq.empty - auditDb.listRelayed(anyLong, anyLong) returns Seq.empty - paymentDb.listIncomingPayments(anyLong, anyLong) returns Seq.empty - - val databases = mock[Databases] - databases.audit returns auditDb - databases.payments returns paymentDb - - val kitWithMockAudit = kit.copy(nodeParams = kit.nodeParams.copy(db = databases)) - val eclair = new EclairImpl(kitWithMockAudit) - - Await.result(eclair.networkFees(None, None), 10 seconds) - auditDb.listNetworkFees(0, TimestampQueryFilters.MaxEpochMilliseconds).wasCalled(once) // assert the call was made only once and with the specified params - - Await.result(eclair.audit(None, None), 10 seconds) - auditDb.listRelayed(0, TimestampQueryFilters.MaxEpochMilliseconds).wasCalled(once) - auditDb.listReceived(0, TimestampQueryFilters.MaxEpochMilliseconds).wasCalled(once) - auditDb.listSent(0, TimestampQueryFilters.MaxEpochMilliseconds).wasCalled(once) - - Await.result(eclair.allInvoices(None, None), 10 seconds) - paymentDb.listIncomingPayments(0, TimestampQueryFilters.MaxEpochMilliseconds).wasCalled(once) // assert the call was made only once and with the specified params - } - test("sendtoroute should pass the parameters correctly") { f => import f._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index 35a3ef817a..6e9edb059b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel.states.{ChannelStateTestsHelperMethods, ChannelStateTestsTags} import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc -import fr.acinq.eclair.{MilliSatoshiLong, TestKitBaseClass} +import fr.acinq.eclair.{MilliSatoshiLong, TestKitBaseClass, TimestampSecond, TimestampSecondLong} import org.scalatest.Tag import org.scalatest.funsuite.AnyFunSuiteLike import scodec.bits.HexStringSyntax @@ -50,10 +50,10 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat test("compute refresh delay") { import org.scalatest.matchers.should.Matchers._ implicit val log: akka.event.DiagnosticLoggingAdapter = NoLoggingDiagnostics - Helpers.nextChannelUpdateRefresh(1544400000).toSeconds should equal(0) - Helpers.nextChannelUpdateRefresh((System.currentTimeMillis.milliseconds - 9.days).toSeconds).toSeconds should equal(24 * 3600L +- 100) - Helpers.nextChannelUpdateRefresh((System.currentTimeMillis.milliseconds - 3.days).toSeconds).toSeconds should equal(7 * 24 * 3600L +- 100) - Helpers.nextChannelUpdateRefresh(System.currentTimeMillis.milliseconds.toSeconds).toSeconds should equal(10 * 24 * 3600L +- 100) + Helpers.nextChannelUpdateRefresh(1544400000 unixsec).toSeconds should equal(0) + Helpers.nextChannelUpdateRefresh(TimestampSecond.now() - 9.days).toSeconds should equal(24 * 3600L +- 100) + Helpers.nextChannelUpdateRefresh(TimestampSecond.now() - 3.days).toSeconds should equal(7 * 24 * 3600L +- 100) + Helpers.nextChannelUpdateRefresh(TimestampSecond.now()).toSeconds should equal(10 * 24 * 3600L +- 100) } case class Fixture(alice: TestFSMRef[ChannelState, ChannelData, Channel], aliceCommitPublished: LocalCommitPublished, aliceHtlcs: Set[UpdateAddHtlc], bob: TestFSMRef[ChannelState, ChannelData, Channel], bobCommitPublished: RemoteCommitPublished, bobHtlcs: Set[UpdateAddHtlc], probe: TestProbe) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 3f50da615d..1e90bd5283 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.ChannelStateTestsBase import fr.acinq.eclair.transactions.Scripts.multiSig2of2 import fr.acinq.eclair.wire.protocol.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel} -import fr.acinq.eclair.{TestConstants, TestKitBaseClass, randomKey} +import fr.acinq.eclair.{TestConstants, TestKitBaseClass, TimestampSecond, randomKey} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -165,7 +165,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF test("migrate waitingSince to waitingSinceBlocks") { f => import f._ // Before version 0.5.1, eclair used an absolute timestamp instead of a block height for funding timeouts. - val beforeMigration = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].copy(waitingSinceBlock = System.currentTimeMillis().milliseconds.toSeconds) + val beforeMigration = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].copy(waitingSinceBlock = TimestampSecond.now().toLong) bob.setState(WAIT_FOR_INIT_INTERNAL, Nothing) bob ! INPUT_RESTORED(beforeMigration) awaitCond(bob.stateName == OFFLINE) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 4035b968f1..5ad850e77f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -30,7 +30,7 @@ import fr.acinq.eclair.payment.relay.Relayer._ import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, DefaultCommitmentFormat, HtlcSuccessTx, HtlcTimeoutTx, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} import fr.acinq.eclair.transactions.{Scripts, Transactions} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiry, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomBytes32, randomKey} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -159,7 +159,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectMsgType[WatchTxConfirmed] // claim-main-delayed // test starts here - alice ! GetTxWithMetaResponse(fundingTx.txid, Some(fundingTx), System.currentTimeMillis.milliseconds.toSeconds) + alice ! GetTxWithMetaResponse(fundingTx.txid, Some(fundingTx), TimestampSecond.now()) alice2bob.expectNoMessage(200 millis) alice2blockchain.expectNoMessage(200 millis) assert(alice.stateName == CLOSING) // the above expectNoMsg will make us wait, so this checks that we are still in CLOSING @@ -178,7 +178,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2blockchain.expectMsgType[WatchTxConfirmed] // claim-main-delayed // test starts here - alice ! GetTxWithMetaResponse(fundingTx.txid, None, System.currentTimeMillis.milliseconds.toSeconds) + alice ! GetTxWithMetaResponse(fundingTx.txid, None, TimestampSecond.now()) alice2bob.expectNoMessage(200 millis) assert(alice2blockchain.expectMsgType[PublishRawTx].tx === fundingTx) // we republish the funding tx assert(alice.stateName == CLOSING) // the above expectNoMsg will make us wait, so this checks that we are still in CLOSING @@ -197,7 +197,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2blockchain.expectMsgType[WatchTxConfirmed] // claim-main-delayed // test starts here - bob ! GetTxWithMetaResponse(fundingTx.txid, Some(fundingTx), System.currentTimeMillis.milliseconds.toSeconds) + bob ! GetTxWithMetaResponse(fundingTx.txid, Some(fundingTx), TimestampSecond.now()) bob2alice.expectNoMessage(200 millis) bob2blockchain.expectNoMessage(200 millis) assert(bob.stateName == CLOSING) // the above expectNoMsg will make us wait, so this checks that we are still in CLOSING @@ -216,7 +216,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2blockchain.expectMsgType[WatchTxConfirmed] // claim-main-delayed // test starts here - bob ! GetTxWithMetaResponse(fundingTx.txid, None, System.currentTimeMillis.milliseconds.toSeconds) + bob ! GetTxWithMetaResponse(fundingTx.txid, None, TimestampSecond.now()) bob2alice.expectNoMessage(200 millis) bob2blockchain.expectNoMessage(200 millis) assert(bob.stateName == CLOSING) // the above expectNoMsg will make us wait, so this checks that we are still in CLOSING @@ -236,7 +236,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // test starts here bob.setState(stateData = bob.stateData.asInstanceOf[DATA_CLOSING].copy(waitingSinceBlock = bob.underlyingActor.nodeParams.currentBlockHeight - Channel.FUNDING_TIMEOUT_FUNDEE - 1)) - bob ! GetTxWithMetaResponse(fundingTx.txid, None, System.currentTimeMillis.milliseconds.toSeconds) + bob ! GetTxWithMetaResponse(fundingTx.txid, None, TimestampSecond.now()) bob2alice.expectMsgType[Error] bob2blockchain.expectNoMessage(200 millis) assert(bob.stateName == CLOSED) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala index 0cb3c3b240..d61f98d641 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala @@ -71,17 +71,17 @@ class AuditDbSpec extends AnyFunSuite { val e4a = TransactionPublished(randomBytes32(), randomKey().publicKey, Transaction(0, Seq.empty, Seq.empty, 0), 42 sat, "mutual") val e4b = TransactionConfirmed(e4a.channelId, e4a.remoteNodeId, e4a.tx) val e4c = TransactionConfirmed(randomBytes32(), randomKey().publicKey, Transaction(2, Nil, TxOut(500 sat, hex"1234") :: Nil, 0)) - val pp5a = PaymentSent.PartialPayment(UUID.randomUUID(), 42000 msat, 1000 msat, randomBytes32(), None, timestamp = 0) - val pp5b = PaymentSent.PartialPayment(UUID.randomUUID(), 42100 msat, 900 msat, randomBytes32(), None, timestamp = 1) + val pp5a = PaymentSent.PartialPayment(UUID.randomUUID(), 42000 msat, 1000 msat, randomBytes32(), None, timestamp = 0 unixms) + val pp5b = PaymentSent.PartialPayment(UUID.randomUUID(), 42100 msat, 900 msat, randomBytes32(), None, timestamp = 1 unixms) val e5 = PaymentSent(UUID.randomUUID(), randomBytes32(), randomBytes32(), 84100 msat, randomKey().publicKey, pp5a :: pp5b :: Nil) - val pp6 = PaymentSent.PartialPayment(UUID.randomUUID(), 42000 msat, 1000 msat, randomBytes32(), None, timestamp = (System.currentTimeMillis.milliseconds + 10.minutes).toMillis) + val pp6 = PaymentSent.PartialPayment(UUID.randomUUID(), 42000 msat, 1000 msat, randomBytes32(), None, timestamp = TimestampMilli.now() + 10.minutes) val e6 = PaymentSent(UUID.randomUUID(), randomBytes32(), randomBytes32(), 42000 msat, randomKey().publicKey, pp6 :: Nil) val e7 = ChannelEvent(randomBytes32(), randomKey().publicKey, 456123000 sat, isFunder = true, isPrivate = false, ChannelEvent.EventType.Closed(MutualClose(null))) val e8 = ChannelErrorOccurred(null, randomBytes32(), randomKey().publicKey, null, LocalError(new RuntimeException("oops")), isFatal = true) val e9 = ChannelErrorOccurred(null, randomBytes32(), randomKey().publicKey, null, RemoteError(Error(randomBytes32(), "remote oops")), isFatal = true) val e10 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(20000 msat, randomBytes32()), PaymentRelayed.Part(22000 msat, randomBytes32())), Seq(PaymentRelayed.Part(10000 msat, randomBytes32()), PaymentRelayed.Part(12000 msat, randomBytes32()), PaymentRelayed.Part(15000 msat, randomBytes32())), randomKey().publicKey, 30000 msat) val multiPartPaymentHash = randomBytes32() - val now = System.currentTimeMillis + val now = TimestampMilli.now() val e11 = ChannelPaymentRelayed(13000 msat, 11000 msat, multiPartPaymentHash, randomBytes32(), randomBytes32(), now) val e12 = ChannelPaymentRelayed(15000 msat, 12500 msat, multiPartPaymentHash, randomBytes32(), randomBytes32(), now) @@ -100,12 +100,12 @@ class AuditDbSpec extends AnyFunSuite { db.add(e11) db.add(e12) - assert(db.listSent(from = 0L, to = (System.currentTimeMillis.milliseconds + 15.minute).toMillis).toSet === Set(e1, e5, e6)) - assert(db.listSent(from = 100000L, to = (System.currentTimeMillis.milliseconds + 1.minute).toMillis).toList === List(e1)) - assert(db.listReceived(from = 0L, to = (System.currentTimeMillis.milliseconds + 1.minute).toMillis).toList === List(e2)) - assert(db.listRelayed(from = 0L, to = (System.currentTimeMillis.milliseconds + 1.minute).toMillis).toList === List(e3, e10, e11, e12)) - assert(db.listNetworkFees(from = 0L, to = (System.currentTimeMillis.milliseconds + 1.minute).toMillis).size === 1) - assert(db.listNetworkFees(from = 0L, to = (System.currentTimeMillis.milliseconds + 1.minute).toMillis).head.txType === "mutual") + assert(db.listSent(from = TimestampMilli(0L), to = TimestampMilli.now() + 15.minute).toSet === Set(e1, e5, e6)) + assert(db.listSent(from = TimestampMilli(100000L), to = TimestampMilli.now() + 1.minute).toList === List(e1)) + assert(db.listReceived(from = TimestampMilli(0L), to = TimestampMilli.now() + 1.minute).toList === List(e2)) + assert(db.listRelayed(from = TimestampMilli(0L), to = TimestampMilli.now() + 1.minute).toList === List(e3, e10, e11, e12)) + assert(db.listNetworkFees(from = TimestampMilli(0L), to = TimestampMilli.now() + 1.minute).size === 1) + assert(db.listNetworkFees(from = TimestampMilli(0L), to = TimestampMilli.now() + 1.minute).head.txType === "mutual") } } @@ -147,7 +147,7 @@ class AuditDbSpec extends AnyFunSuite { db.add(TransactionConfirmed(c4, n4, Transaction(0, Seq.empty, Seq(TxOut(2500 sat, hex"ffffff")), 0))) // doesn't match a published tx // NB: we only count a relay fee for the outgoing channel, no the incoming one. - assert(db.stats(0, System.currentTimeMillis + 1).toSet === Set( + assert(db.stats(0 unixms, TimestampMilli.now() + 1.milli).toSet === Set( Stats(channelId = c1, direction = "IN", avgPaymentAmount = 0 sat, paymentCount = 0, relayFee = 0 sat, networkFee = 0 sat), Stats(channelId = c1, direction = "OUT", avgPaymentAmount = 42 sat, paymentCount = 3, relayFee = 4 sat, networkFee = 0 sat), Stats(channelId = c2, direction = "IN", avgPaymentAmount = 0 sat, paymentCount = 0, relayFee = 0 sat, networkFee = 500 sat), @@ -193,9 +193,9 @@ class AuditDbSpec extends AnyFunSuite { } }) // Test starts here. - val start = System.currentTimeMillis - assert(db.stats(0, start + 1).nonEmpty) - val end = System.currentTimeMillis + val start = TimestampMilli.now() + assert(db.stats(0 unixms, start + 1.milli).nonEmpty) + val end = TimestampMilli.now() fail(s"took ${end - start}ms") } } @@ -240,7 +240,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, ps.paymentHash.toArray) statement.setBytes(4, ps.paymentPreimage.toArray) statement.setBytes(5, ps.parts.head.toChannelId.toArray) - statement.setLong(6, ps.timestamp) + statement.setLong(6, ps.timestamp.toLong) statement.executeUpdate() } }, @@ -248,7 +248,7 @@ class AuditDbSpec extends AnyFunSuite { targetVersion = SqliteAuditDb.CURRENT_VERSION, postCheck = connection => { // existing rows in the 'sent' table will use id=00000000-0000-0000-0000-000000000000 as default - assert(dbs.audit.listSent(0, (System.currentTimeMillis.milliseconds + 1.minute).toMillis) === Seq(ps.copy(id = ZERO_UUID, parts = Seq(ps.parts.head.copy(id = ZERO_UUID))))) + assert(dbs.audit.listSent(0 unixms, TimestampMilli.now() + 1.minute) === Seq(ps.copy(id = ZERO_UUID, parts = Seq(ps.parts.head.copy(id = ZERO_UUID))))) val postMigrationDb = new SqliteAuditDb(connection) @@ -262,7 +262,7 @@ class AuditDbSpec extends AnyFunSuite { // the old record will have the UNKNOWN_UUID but the new ones will have their actual id val expected = Seq(ps.copy(id = ZERO_UUID, parts = Seq(ps.parts.head.copy(id = ZERO_UUID))), ps1) - assert(postMigrationDb.listSent(0, (System.currentTimeMillis.milliseconds + 1.minute).toMillis) === expected) + assert(postMigrationDb.listSent(0 unixms, TimestampMilli.now() + 1.minute) === expected) } ) } @@ -317,12 +317,12 @@ class AuditDbSpec extends AnyFunSuite { val dbs = TestSqliteDatabases() - val pp1 = PaymentSent.PartialPayment(UUID.randomUUID(), 500 msat, 10 msat, randomBytes32(), None, 100) - val pp2 = PaymentSent.PartialPayment(UUID.randomUUID(), 600 msat, 5 msat, randomBytes32(), None, 110) + val pp1 = PaymentSent.PartialPayment(UUID.randomUUID(), 500 msat, 10 msat, randomBytes32(), None, 100 unixms) + val pp2 = PaymentSent.PartialPayment(UUID.randomUUID(), 600 msat, 5 msat, randomBytes32(), None, 110 unixms) val ps1 = PaymentSent(UUID.randomUUID(), randomBytes32(), randomBytes32(), 1100 msat, PrivateKey(ByteVector32.One).publicKey, pp1 :: pp2 :: Nil) - val relayed1 = ChannelPaymentRelayed(600 msat, 500 msat, randomBytes32(), randomBytes32(), randomBytes32(), 105) - val relayed2 = ChannelPaymentRelayed(650 msat, 500 msat, randomBytes32(), randomBytes32(), randomBytes32(), 115) + val relayed1 = ChannelPaymentRelayed(600 msat, 500 msat, randomBytes32(), randomBytes32(), randomBytes32(), 105 unixms) + val relayed2 = ChannelPaymentRelayed(650 msat, 500 msat, randomBytes32(), randomBytes32(), randomBytes32(), 115 unixms) migrationCheck( dbs = dbs, @@ -355,7 +355,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, ps1.paymentHash.toArray) statement.setBytes(4, ps1.paymentPreimage.toArray) statement.setBytes(5, pp.toChannelId.toArray) - statement.setLong(6, pp.timestamp) + statement.setLong(6, pp.timestamp.toLong) statement.setBytes(7, pp.id.toString.getBytes) statement.executeUpdate() } @@ -368,7 +368,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, relayed.paymentHash.toArray) statement.setBytes(4, relayed.fromChannelId.toArray) statement.setBytes(5, relayed.toChannelId.toArray) - statement.setLong(6, relayed.timestamp) + statement.setLong(6, relayed.timestamp.toLong) statement.executeUpdate() } } @@ -380,33 +380,33 @@ class AuditDbSpec extends AnyFunSuite { using(connection.createStatement()) { statement => assert(getVersion(statement, "audit").contains(SqliteAuditDb.CURRENT_VERSION)) } - assert(migratedDb.listSent(50, 150).toSet === Set( + assert(migratedDb.listSent(50 unixms, 150 unixms).toSet === Set( ps1.copy(id = pp1.id, recipientAmount = pp1.amount, parts = pp1 :: Nil), ps1.copy(id = pp2.id, recipientAmount = pp2.amount, parts = pp2 :: Nil) )) - assert(migratedDb.listRelayed(100, 120) === Seq(relayed1, relayed2)) + assert(migratedDb.listRelayed(100 unixms, 120 unixms) === Seq(relayed1, relayed2)) val postMigrationDb = new SqliteAuditDb(connection) using(connection.createStatement()) { statement => assert(getVersion(statement, "audit").contains(SqliteAuditDb.CURRENT_VERSION)) } val ps2 = PaymentSent(UUID.randomUUID(), randomBytes32(), randomBytes32(), 1100 msat, randomKey().publicKey, Seq( - PaymentSent.PartialPayment(UUID.randomUUID(), 500 msat, 10 msat, randomBytes32(), None, 160), - PaymentSent.PartialPayment(UUID.randomUUID(), 600 msat, 5 msat, randomBytes32(), None, 165) + PaymentSent.PartialPayment(UUID.randomUUID(), 500 msat, 10 msat, randomBytes32(), None, 160 unixms), + PaymentSent.PartialPayment(UUID.randomUUID(), 600 msat, 5 msat, randomBytes32(), None, 165 unixms) )) - val relayed3 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(450 msat, randomBytes32()), PaymentRelayed.Part(500 msat, randomBytes32())), Seq(PaymentRelayed.Part(800 msat, randomBytes32())), randomKey().publicKey, 700 msat, 150) + val relayed3 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(450 msat, randomBytes32()), PaymentRelayed.Part(500 msat, randomBytes32())), Seq(PaymentRelayed.Part(800 msat, randomBytes32())), randomKey().publicKey, 700 msat, 150 unixms) postMigrationDb.add(ps2) - assert(postMigrationDb.listSent(155, 200) === Seq(ps2)) + assert(postMigrationDb.listSent(155 unixms, 200 unixms) === Seq(ps2)) postMigrationDb.add(relayed3) - assert(postMigrationDb.listRelayed(100, 160) === Seq(relayed1, relayed2, relayed3)) + assert(postMigrationDb.listRelayed(100 unixms, 160 unixms) === Seq(relayed1, relayed2, relayed3)) } ) } test("migrate audit database v4 -> current") { - val relayed1 = ChannelPaymentRelayed(600 msat, 500 msat, randomBytes32(), randomBytes32(), randomBytes32(), 105) - val relayed2 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(300 msat, randomBytes32()), PaymentRelayed.Part(350 msat, randomBytes32())), Seq(PaymentRelayed.Part(600 msat, randomBytes32())), PlaceHolderPubKey, 0 msat, 110) + val relayed1 = ChannelPaymentRelayed(600 msat, 500 msat, randomBytes32(), randomBytes32(), randomBytes32(), 105 unixms) + val relayed2 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(300 msat, randomBytes32()), PaymentRelayed.Part(350 msat, randomBytes32())), Seq(PaymentRelayed.Part(600 msat, randomBytes32())), PlaceHolderPubKey, 0 msat, 110 unixms) forAllDbs { case dbs: TestPgDatabases => @@ -439,7 +439,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setString(3, relayed1.fromChannelId.toHex) statement.setString(4, "IN") statement.setString(5, "channel") - statement.setLong(6, relayed1.timestamp) + statement.setLong(6, relayed1.timestamp.toLong) statement.executeUpdate() } using(connection.prepareStatement("INSERT INTO relayed VALUES (?, ?, ?, ?, ?, ?)")) { statement => @@ -448,7 +448,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setString(3, relayed1.toChannelId.toHex) statement.setString(4, "OUT") statement.setString(5, "channel") - statement.setLong(6, relayed1.timestamp) + statement.setLong(6, relayed1.timestamp.toLong) statement.executeUpdate() } for (incoming <- relayed2.incoming) { @@ -458,7 +458,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setString(3, incoming.channelId.toHex) statement.setString(4, "IN") statement.setString(5, "trampoline") - statement.setLong(6, relayed2.timestamp) + statement.setLong(6, relayed2.timestamp.toLong) statement.executeUpdate() } } @@ -469,7 +469,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setString(3, outgoing.channelId.toHex) statement.setString(4, "OUT") statement.setString(5, "trampoline") - statement.setLong(6, relayed2.timestamp) + statement.setLong(6, relayed2.timestamp.toLong) statement.executeUpdate() } } @@ -479,15 +479,15 @@ class AuditDbSpec extends AnyFunSuite { postCheck = connection => { val migratedDb = dbs.audit - assert(migratedDb.listRelayed(100, 120) === Seq(relayed1, relayed2)) + assert(migratedDb.listRelayed(100 unixms, 120 unixms) === Seq(relayed1, relayed2)) val postMigrationDb = new PgAuditDb()(dbs.datasource) using(connection.createStatement()) { statement => assert(getVersion(statement, "audit").contains(PgAuditDb.CURRENT_VERSION)) } - val relayed3 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(450 msat, randomBytes32()), PaymentRelayed.Part(500 msat, randomBytes32())), Seq(PaymentRelayed.Part(800 msat, randomBytes32())), randomKey().publicKey, 700 msat, 150) + val relayed3 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(450 msat, randomBytes32()), PaymentRelayed.Part(500 msat, randomBytes32())), Seq(PaymentRelayed.Part(800 msat, randomBytes32())), randomKey().publicKey, 700 msat, 150 unixms) postMigrationDb.add(relayed3) - assert(postMigrationDb.listRelayed(100, 160) === Seq(relayed1, relayed2, relayed3)) + assert(postMigrationDb.listRelayed(100 unixms, 160 unixms) === Seq(relayed1, relayed2, relayed3)) } ) case dbs: TestSqliteDatabases => @@ -520,7 +520,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, relayed1.fromChannelId.toArray) statement.setString(4, "IN") statement.setString(5, "channel") - statement.setLong(6, relayed1.timestamp) + statement.setLong(6, relayed1.timestamp.toLong) statement.executeUpdate() } using(connection.prepareStatement("INSERT INTO relayed VALUES (?, ?, ?, ?, ?, ?)")) { statement => @@ -529,7 +529,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, relayed1.toChannelId.toArray) statement.setString(4, "OUT") statement.setString(5, "channel") - statement.setLong(6, relayed1.timestamp) + statement.setLong(6, relayed1.timestamp.toLong) statement.executeUpdate() } for (incoming <- relayed2.incoming) { @@ -539,7 +539,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, incoming.channelId.toArray) statement.setString(4, "IN") statement.setString(5, "trampoline") - statement.setLong(6, relayed2.timestamp) + statement.setLong(6, relayed2.timestamp.toLong) statement.executeUpdate() } } @@ -550,7 +550,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, outgoing.channelId.toArray) statement.setString(4, "OUT") statement.setString(5, "trampoline") - statement.setLong(6, relayed2.timestamp) + statement.setLong(6, relayed2.timestamp.toLong) statement.executeUpdate() } } @@ -562,15 +562,15 @@ class AuditDbSpec extends AnyFunSuite { using(connection.createStatement()) { statement => assert(getVersion(statement, "audit").contains(SqliteAuditDb.CURRENT_VERSION)) } - assert(migratedDb.listRelayed(100, 120) === Seq(relayed1, relayed2)) + assert(migratedDb.listRelayed(100 unixms, 120 unixms) === Seq(relayed1, relayed2)) val postMigrationDb = new SqliteAuditDb(connection) using(connection.createStatement()) { statement => assert(getVersion(statement, "audit").contains(SqliteAuditDb.CURRENT_VERSION)) } - val relayed3 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(450 msat, randomBytes32()), PaymentRelayed.Part(500 msat, randomBytes32())), Seq(PaymentRelayed.Part(800 msat, randomBytes32())), randomKey().publicKey, 700 msat, 150) + val relayed3 = TrampolinePaymentRelayed(randomBytes32(), Seq(PaymentRelayed.Part(450 msat, randomBytes32()), PaymentRelayed.Part(500 msat, randomBytes32())), Seq(PaymentRelayed.Part(800 msat, randomBytes32())), randomKey().publicKey, 700 msat, 150 unixms) postMigrationDb.add(relayed3) - assert(postMigrationDb.listRelayed(100, 160) === Seq(relayed1, relayed2, relayed3)) + assert(postMigrationDb.listRelayed(100 unixms, 160 unixms) === Seq(relayed1, relayed2, relayed3)) } ) } @@ -578,8 +578,8 @@ class AuditDbSpec extends AnyFunSuite { test("migrate audit database v7 -> current") { val networkFees = Seq( - NetworkFee(randomKey().publicKey, randomBytes32(), randomBytes32(), 50 sat, "test-tx-1", 500), - NetworkFee(randomKey().publicKey, randomBytes32(), randomBytes32(), 0 sat, "test-tx-2", 600), + NetworkFee(randomKey().publicKey, randomBytes32(), randomBytes32(), 50 sat, "test-tx-1", 500 unixms), + NetworkFee(randomKey().publicKey, randomBytes32(), randomBytes32(), 0 sat, "test-tx-2", 600 unixms), ) forAllDbs { @@ -630,7 +630,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setString(3, tx.txId.toHex) statement.setLong(4, tx.fee.toLong) statement.setString(5, tx.txType) - statement.setTimestamp(6, Timestamp.from(Instant.ofEpochMilli(tx.timestamp))) + statement.setTimestamp(6, tx.timestamp.toSqlTimestamp) statement.executeUpdate() } } @@ -640,7 +640,7 @@ class AuditDbSpec extends AnyFunSuite { postCheck = connection => { val migratedDb = dbs.audit using(connection.createStatement()) { statement => assert(getVersion(statement, "audit").contains(PgAuditDb.CURRENT_VERSION)) } - assert(migratedDb.listNetworkFees(0, 700) === networkFees) + assert(migratedDb.listNetworkFees(0 unixms, 700 unixms) === networkFees) } ) case dbs: TestSqliteDatabases => @@ -688,7 +688,7 @@ class AuditDbSpec extends AnyFunSuite { statement.setBytes(3, tx.txId.toArray) statement.setLong(4, tx.fee.toLong) statement.setString(5, tx.txType) - statement.setLong(6, tx.timestamp) + statement.setLong(6, tx.timestamp.toLong) statement.executeUpdate() } } @@ -698,7 +698,7 @@ class AuditDbSpec extends AnyFunSuite { postCheck = connection => { val migratedDb = dbs.audit using(connection.createStatement()) { statement => assert(getVersion(statement, "audit").contains(SqliteAuditDb.CURRENT_VERSION)) } - assert(migratedDb.listNetworkFees(0, 700) === networkFees) + assert(migratedDb.listNetworkFees(0 unixms, 700 unixms) === networkFees) } ) } @@ -744,7 +744,7 @@ class AuditDbSpec extends AnyFunSuite { statement.executeUpdate() } - assert(db.listRelayed(0, 40) === Nil) + assert(db.listRelayed(0 unixms, 40 unixms) === Nil) } } @@ -760,7 +760,7 @@ class AuditDbSpec extends AnyFunSuite { test("add experiment metrics") { forAllDbs { dbs => - dbs.audit.addPathFindingExperimentMetrics(PathFindingExperimentMetrics(100000000 msat, 3000 msat, status = "SUCCESS", 37, System.currentTimeMillis, isMultiPart = false, "my-test-experiment", randomKey().publicKey)) + dbs.audit.addPathFindingExperimentMetrics(PathFindingExperimentMetrics(100000000 msat, 3000 msat, status = "SUCCESS", 37 millis, TimestampMilli.now(), isMultiPart = false, "my-test-experiment", randomKey().publicKey)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala index 57384a87c9..40a86a8ef9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.db.sqlite.SqlitePaymentsDb import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Router.{ChannelHop, NodeHop} import fr.acinq.eclair.wire.protocol.{ChannelUpdate, UnknownNextPeer} -import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, TestDatabases, randomBytes32, randomBytes64, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, TestDatabases, TimestampMilli, TimestampMilliLong, TimestampSecond, TimestampSecondLong, randomBytes32, randomBytes64, randomKey} import org.scalatest.funsuite.AnyFunSuite import java.time.Instant @@ -77,16 +77,16 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.getIncomingPayment(paymentHash1).isEmpty) // add a few rows - val ps1 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), None, paymentHash1, PaymentType.Standard, 12345 msat, 12345 msat, alice, 1000, None, OutgoingPaymentStatus.Pending) - val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(500 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 1) - val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(550 msat, 1100)) + val ps1 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), None, paymentHash1, PaymentType.Standard, 12345 msat, 12345 msat, alice, 1000 unixms, None, OutgoingPaymentStatus.Pending) + val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(500 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 1 unixsec) + val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.toTimestampMilli, IncomingPaymentStatus.Received(550 msat, 1100 unixms)) db.addOutgoingPayment(ps1) db.addIncomingPayment(i1, preimage1) - db.receiveIncomingPayment(i1.paymentHash, 550 msat, 1100) + db.receiveIncomingPayment(i1.paymentHash, 550 msat, 1100 unixms) - assert(db.listIncomingPayments(1, 1500) === Seq(pr1)) - assert(db.listOutgoingPayments(1, 1500) === Seq(ps1)) + assert(db.listIncomingPayments(1 unixms, 1500 unixms) === Seq(pr1)) + assert(db.listOutgoingPayments(1 unixms, 1500 unixms) === Seq(ps1)) } ) @@ -99,13 +99,13 @@ class PaymentsDbSpec extends AnyFunSuite { val id1 = UUID.randomUUID() val id2 = UUID.randomUUID() val id3 = UUID.randomUUID() - val ps1 = OutgoingPayment(id1, id1, None, randomBytes32(), PaymentType.Standard, 561 msat, 561 msat, PrivateKey(ByteVector32.One).publicKey, 1000, None, OutgoingPaymentStatus.Pending) - val ps2 = OutgoingPayment(id2, id2, None, randomBytes32(), PaymentType.Standard, 1105 msat, 1105 msat, PrivateKey(ByteVector32.One).publicKey, 1010, None, OutgoingPaymentStatus.Failed(Nil, 1050)) - val ps3 = OutgoingPayment(id3, id3, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, PrivateKey(ByteVector32.One).publicKey, 1040, None, OutgoingPaymentStatus.Succeeded(preimage1, 0 msat, Nil, 1060)) - val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 1) - val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(12345678 msat, 1090)) - val i2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash2, carolPriv, Left("Another invoice"), CltvExpiryDelta(18), expirySeconds = Some(30), timestamp = 1) - val pr2 = IncomingPayment(i2, preimage2, PaymentType.Standard, i2.timestamp.seconds.toMillis, IncomingPaymentStatus.Expired) + val ps1 = OutgoingPayment(id1, id1, None, randomBytes32(), PaymentType.Standard, 561 msat, 561 msat, PrivateKey(ByteVector32.One).publicKey, 1000 unixms, None, OutgoingPaymentStatus.Pending) + val ps2 = OutgoingPayment(id2, id2, None, randomBytes32(), PaymentType.Standard, 1105 msat, 1105 msat, PrivateKey(ByteVector32.One).publicKey, 1010 unixms, None, OutgoingPaymentStatus.Failed(Nil, 1050 unixms)) + val ps3 = OutgoingPayment(id3, id3, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, PrivateKey(ByteVector32.One).publicKey, 1040 unixms, None, OutgoingPaymentStatus.Succeeded(preimage1, 0 msat, Nil, 1060 unixms)) + val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 1 unixsec) + val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.toTimestampMilli, IncomingPaymentStatus.Received(12345678 msat, 1090 unixms)) + val i2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash2, carolPriv, Left("Another invoice"), CltvExpiryDelta(18), expirySeconds = Some(30), timestamp = 1 unixsec) + val pr2 = IncomingPayment(i2, preimage2, PaymentType.Standard, i2.timestamp.toTimestampMilli, IncomingPaymentStatus.Expired) migrationCheck( dbs = dbs, @@ -130,7 +130,7 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setString(1, ps1.id.toString) statement.setBytes(2, ps1.paymentHash.toArray) statement.setLong(3, ps1.amount.toLong) - statement.setLong(4, ps1.createdAt) + statement.setLong(4, ps1.createdAt.toLong) statement.setString(5, "PENDING") statement.executeUpdate() } @@ -139,8 +139,8 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setString(1, ps2.id.toString) statement.setBytes(2, ps2.paymentHash.toArray) statement.setLong(3, ps2.amount.toLong) - statement.setLong(4, ps2.createdAt) - statement.setLong(5, ps2.status.asInstanceOf[OutgoingPaymentStatus.Failed].completedAt) + statement.setLong(4, ps2.createdAt.toLong) + statement.setLong(5, ps2.status.asInstanceOf[OutgoingPaymentStatus.Failed].completedAt.toLong) statement.setString(6, "FAILED") statement.executeUpdate() } @@ -150,8 +150,8 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setBytes(2, ps3.paymentHash.toArray) statement.setBytes(3, ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].paymentPreimage.toArray) statement.setLong(4, ps3.amount.toLong) - statement.setLong(5, ps3.createdAt) - statement.setLong(6, ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].completedAt) + statement.setLong(5, ps3.createdAt.toLong) + statement.setLong(6, ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].completedAt.toLong) statement.setString(7, "SUCCEEDED") statement.executeUpdate() } @@ -165,8 +165,8 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setBytes(2, pr1.paymentPreimage.toArray) statement.setString(3, PaymentRequest.write(i1)) statement.setLong(4, pr1.status.asInstanceOf[IncomingPaymentStatus.Received].amount.toLong) - statement.setLong(5, pr1.createdAt) - statement.setLong(6, pr1.status.asInstanceOf[IncomingPaymentStatus.Received].receivedAt) + statement.setLong(5, pr1.createdAt.toLong) + statement.setLong(6, pr1.status.asInstanceOf[IncomingPaymentStatus.Received].receivedAt.toLong) statement.executeUpdate() } @@ -174,8 +174,8 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setBytes(1, i2.paymentHash.toArray) statement.setBytes(2, pr2.paymentPreimage.toArray) statement.setString(3, PaymentRequest.write(i2)) - statement.setLong(4, pr2.createdAt) - statement.setLong(5, (i2.timestamp + i2.expiry.get).seconds.toMillis) + statement.setLong(4, pr2.createdAt.toLong) + statement.setLong(5, (i2.timestamp + i2.expiry.get).toLong) statement.executeUpdate() } }, @@ -186,24 +186,24 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.getIncomingPayment(i1.paymentHash) === Some(pr1)) assert(db.getIncomingPayment(i2.paymentHash) === Some(pr2)) - assert(db.listOutgoingPayments(1, 2000) === Seq(ps1, ps2, ps3)) + assert(db.listOutgoingPayments(1 unixms, 2000 unixms) === Seq(ps1, ps2, ps3)) val i3 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), paymentHash3, alicePriv, Left("invoice #3"), CltvExpiryDelta(18), expirySeconds = Some(30)) - val pr3 = IncomingPayment(i3, preimage3, PaymentType.Standard, i3.timestamp.seconds.toMillis, IncomingPaymentStatus.Pending) + val pr3 = IncomingPayment(i3, preimage3, PaymentType.Standard, i3.timestamp.toTimestampMilli, IncomingPaymentStatus.Pending) db.addIncomingPayment(i3, pr3.paymentPreimage) - val ps4 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), Some("1"), randomBytes32(), PaymentType.Standard, 123 msat, 123 msat, alice, 1100, Some(i3), OutgoingPaymentStatus.Pending) - val ps5 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), Some("2"), randomBytes32(), PaymentType.Standard, 456 msat, 456 msat, bob, 1150, Some(i2), OutgoingPaymentStatus.Succeeded(preimage1, 42 msat, Nil, 1180)) - val ps6 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), Some("3"), randomBytes32(), PaymentType.Standard, 789 msat, 789 msat, bob, 1250, None, OutgoingPaymentStatus.Failed(Nil, 1300)) + val ps4 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), Some("1"), randomBytes32(), PaymentType.Standard, 123 msat, 123 msat, alice, 1100 unixms, Some(i3), OutgoingPaymentStatus.Pending) + val ps5 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), Some("2"), randomBytes32(), PaymentType.Standard, 456 msat, 456 msat, bob, 1150 unixms, Some(i2), OutgoingPaymentStatus.Succeeded(preimage1, 42 msat, Nil, 1180 unixms)) + val ps6 = OutgoingPayment(UUID.randomUUID(), UUID.randomUUID(), Some("3"), randomBytes32(), PaymentType.Standard, 789 msat, 789 msat, bob, 1250 unixms, None, OutgoingPaymentStatus.Failed(Nil, 1300 unixms)) db.addOutgoingPayment(ps4) db.addOutgoingPayment(ps5.copy(status = OutgoingPaymentStatus.Pending)) - db.updateOutgoingPayment(PaymentSent(ps5.parentId, ps5.paymentHash, preimage1, ps5.amount, ps5.recipientNodeId, Seq(PaymentSent.PartialPayment(ps5.id, ps5.amount, 42 msat, randomBytes32(), None, 1180)))) + db.updateOutgoingPayment(PaymentSent(ps5.parentId, ps5.paymentHash, preimage1, ps5.amount, ps5.recipientNodeId, Seq(PaymentSent.PartialPayment(ps5.id, ps5.amount, 42 msat, randomBytes32(), None, 1180 unixms)))) db.addOutgoingPayment(ps6.copy(status = OutgoingPaymentStatus.Pending)) - db.updateOutgoingPayment(PaymentFailed(ps6.id, ps6.paymentHash, Nil, 1300)) + db.updateOutgoingPayment(PaymentFailed(ps6.id, ps6.paymentHash, Nil, 1300 unixms)) - assert(db.listOutgoingPayments(1, 2000) === Seq(ps1, ps2, ps3, ps4, ps5, ps6)) - assert(db.listIncomingPayments(1, System.currentTimeMillis) === Seq(pr1, pr2, pr3)) - assert(db.listExpiredIncomingPayments(1, 2000) === Seq(pr2)) + assert(db.listOutgoingPayments(1 unixms, 2000 unixms) === Seq(ps1, ps2, ps3, ps4, ps5, ps6)) + assert(db.listIncomingPayments(1 unixms, TimestampMilli.now()) === Seq(pr1, pr2, pr3)) + assert(db.listExpiredIncomingPayments(1 unixms, 2000 unixms) === Seq(pr2)) }) } @@ -214,9 +214,9 @@ class PaymentsDbSpec extends AnyFunSuite { val (id1, id2, id3) = (UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()) val parentId = UUID.randomUUID() val invoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(2834 msat), paymentHash1, bobPriv, Left("invoice #1"), CltvExpiryDelta(18), expirySeconds = Some(30)) - val ps1 = OutgoingPayment(id1, id1, Some("42"), randomBytes32(), PaymentType.Standard, 561 msat, 561 msat, alice, 1000, None, OutgoingPaymentStatus.Failed(Seq(FailureSummary(FailureType.REMOTE, "no candy for you", List(HopSummary(hop_ab), HopSummary(hop_bc)))), 1020)) - val ps2 = OutgoingPayment(id2, parentId, Some("42"), paymentHash1, PaymentType.Standard, 1105 msat, 1105 msat, bob, 1010, Some(invoice1), OutgoingPaymentStatus.Pending) - val ps3 = OutgoingPayment(id3, parentId, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, bob, 1040, None, OutgoingPaymentStatus.Succeeded(preimage1, 10 msat, Seq(HopSummary(hop_ab), HopSummary(hop_bc)), 1060)) + val ps1 = OutgoingPayment(id1, id1, Some("42"), randomBytes32(), PaymentType.Standard, 561 msat, 561 msat, alice, 1000 unixms, None, OutgoingPaymentStatus.Failed(Seq(FailureSummary(FailureType.REMOTE, "no candy for you", List(HopSummary(hop_ab), HopSummary(hop_bc)))), 1020 unixms)) + val ps2 = OutgoingPayment(id2, parentId, Some("42"), paymentHash1, PaymentType.Standard, 1105 msat, 1105 msat, bob, 1010 unixms, Some(invoice1), OutgoingPaymentStatus.Pending) + val ps3 = OutgoingPayment(id3, parentId, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, bob, 1040 unixms, None, OutgoingPaymentStatus.Succeeded(preimage1, 10 msat, Seq(HopSummary(hop_ab), HopSummary(hop_bc)), 1060 unixms)) migrationCheck( dbs = dbs, @@ -242,8 +242,8 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setBytes(4, ps1.paymentHash.toArray) statement.setLong(5, ps1.amount.toLong) statement.setBytes(6, ps1.recipientNodeId.value.toArray) - statement.setLong(7, ps1.createdAt) - statement.setLong(8, ps1.status.asInstanceOf[OutgoingPaymentStatus.Failed].completedAt) + statement.setLong(7, ps1.createdAt.toLong) + statement.setLong(8, ps1.status.asInstanceOf[OutgoingPaymentStatus.Failed].completedAt.toLong) statement.setBytes(9, SqlitePaymentsDb.paymentFailuresCodec.encode(ps1.status.asInstanceOf[OutgoingPaymentStatus.Failed].failures.toList).require.toByteArray) statement.executeUpdate() } @@ -255,7 +255,7 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setBytes(4, ps2.paymentHash.toArray) statement.setLong(5, ps2.amount.toLong) statement.setBytes(6, ps2.recipientNodeId.value.toArray) - statement.setLong(7, ps2.createdAt) + statement.setLong(7, ps2.createdAt.toLong) statement.setString(8, PaymentRequest.write(invoice1)) statement.executeUpdate() } @@ -266,8 +266,8 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setBytes(3, ps3.paymentHash.toArray) statement.setLong(4, ps3.amount.toLong) statement.setBytes(5, ps3.recipientNodeId.value.toArray) - statement.setLong(6, ps3.createdAt) - statement.setLong(7, ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].completedAt) + statement.setLong(6, ps3.createdAt.toLong) + statement.setLong(7, ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].completedAt.toLong) statement.setBytes(8, ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].paymentPreimage.toArray) statement.setLong(9, ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].feesPaid.toLong) statement.setBytes(10, SqlitePaymentsDb.paymentRouteCodec.encode(ps3.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].route.toList).require.toByteArray) @@ -297,13 +297,13 @@ class PaymentsDbSpec extends AnyFunSuite { val id1 = UUID.randomUUID() val id2 = UUID.randomUUID() val id3 = UUID.randomUUID() - val ps1 = OutgoingPayment(id1, id1, None, randomBytes32(), PaymentType.Standard, 561 msat, 561 msat, PrivateKey(ByteVector32.One).publicKey, Instant.parse("2021-01-01T10:15:30.00Z").toEpochMilli, None, OutgoingPaymentStatus.Pending) - val ps2 = OutgoingPayment(id2, id2, None, randomBytes32(), PaymentType.Standard, 1105 msat, 1105 msat, PrivateKey(ByteVector32.One).publicKey, Instant.parse("2020-05-14T13:47:21.00Z").toEpochMilli, None, OutgoingPaymentStatus.Failed(Nil, Instant.parse("2021-05-15T04:12:40.00Z").toEpochMilli)) - val ps3 = OutgoingPayment(id3, id3, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, PrivateKey(ByteVector32.One).publicKey, Instant.parse("2021-01-28T09:12:05.00Z").toEpochMilli, None, OutgoingPaymentStatus.Succeeded(preimage1, 0 msat, Nil, Instant.now().toEpochMilli)) - val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = Instant.now().getEpochSecond) - val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(12345678 msat, Instant.now().toEpochMilli)) - val i2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash2, carolPriv, Left("Another invoice"), CltvExpiryDelta(18), expirySeconds = Some(24 * 3600), timestamp = Instant.parse("2020-12-30T10:00:55.00Z").getEpochSecond) - val pr2 = IncomingPayment(i2, preimage2, PaymentType.Standard, i2.timestamp.seconds.toMillis, IncomingPaymentStatus.Expired) + val ps1 = OutgoingPayment(id1, id1, None, randomBytes32(), PaymentType.Standard, 561 msat, 561 msat, PrivateKey(ByteVector32.One).publicKey, TimestampMilli(Instant.parse("2021-01-01T10:15:30.00Z").toEpochMilli), None, OutgoingPaymentStatus.Pending) + val ps2 = OutgoingPayment(id2, id2, None, randomBytes32(), PaymentType.Standard, 1105 msat, 1105 msat, PrivateKey(ByteVector32.One).publicKey, TimestampMilli(Instant.parse("2020-05-14T13:47:21.00Z").toEpochMilli), None, OutgoingPaymentStatus.Failed(Nil, TimestampMilli(Instant.parse("2021-05-15T04:12:40.00Z").toEpochMilli))) + val ps3 = OutgoingPayment(id3, id3, None, paymentHash1, PaymentType.Standard, 1729 msat, 1729 msat, PrivateKey(ByteVector32.One).publicKey, TimestampMilli(Instant.parse("2021-01-28T09:12:05.00Z").toEpochMilli), None, OutgoingPaymentStatus.Succeeded(preimage1, 0 msat, Nil, TimestampMilli.now())) + val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = TimestampSecond.now()) + val pr1 = IncomingPayment(i1, preimage1, PaymentType.Standard, i1.timestamp.toTimestampMilli, IncomingPaymentStatus.Received(12345678 msat, TimestampMilli.now())) + val i2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(12345678 msat), paymentHash2, carolPriv, Left("Another invoice"), CltvExpiryDelta(18), expirySeconds = Some(24 * 3600), timestamp = TimestampSecond(Instant.parse("2020-12-30T10:00:55.00Z").getEpochSecond)) + val pr2 = IncomingPayment(i2, preimage2, PaymentType.Standard, i2.timestamp.toTimestampMilli, IncomingPaymentStatus.Expired) migrationCheck( dbs = dbs, @@ -330,14 +330,14 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setLong(6, sent.amount.toLong) statement.setLong(7, sent.recipientAmount.toLong) statement.setString(8, sent.recipientNodeId.value.toHex) - statement.setLong(9, sent.createdAt) + statement.setLong(9, sent.createdAt.toLong) statement.setString(10, sent.paymentRequest.map(PaymentRequest.write).orNull) sent.status match { case s: OutgoingPaymentStatus.Succeeded => - statement.setLong(11, s.completedAt) + statement.setLong(11, s.completedAt.toLong) statement.setString(12, s.paymentPreimage.toHex) case s: OutgoingPaymentStatus.Failed => - statement.setLong(11, s.completedAt) + statement.setLong(11, s.completedAt.toLong) statement.setObject(12, null) case _ => statement.setObject(11, null) @@ -353,15 +353,15 @@ class PaymentsDbSpec extends AnyFunSuite { statement.setString(2, preimage.toHex) statement.setString(3, PaymentType.Standard) statement.setString(4, PaymentRequest.write(pr)) - statement.setLong(5, pr.timestamp.seconds.toMillis) // BOLT11 timestamp is in seconds - statement.setLong(6, (pr.timestamp + pr.expiry.getOrElse(PaymentRequest.DEFAULT_EXPIRY_SECONDS.toLong)).seconds.toMillis) + statement.setLong(5, pr.timestamp.toTimestampMilli.toLong) // BOLT11 timestamp is in seconds + statement.setLong(6, (pr.timestamp + pr.expiry.getOrElse(PaymentRequest.DEFAULT_EXPIRY_SECONDS)).toTimestampMilli.toLong) statement.executeUpdate() } } using(connection.prepareStatement("UPDATE received_payments SET (received_msat, received_at) = (? + COALESCE(received_msat, 0), ?) WHERE payment_hash = ?")) { update => update.setLong(1, pr1.status.asInstanceOf[IncomingPaymentStatus.Received].amount.toLong) - update.setLong(2, pr1.status.asInstanceOf[IncomingPaymentStatus.Received].receivedAt) + update.setLong(2, pr1.status.asInstanceOf[IncomingPaymentStatus.Received].receivedAt.toLong) update.setString(3, pr1.paymentRequest.paymentHash.toHex) val updated = update.executeUpdate() if (updated == 0) { @@ -380,16 +380,16 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.getIncomingPayment(i1.paymentHash) === Some(pr1)) assert(db.getIncomingPayment(i2.paymentHash) === Some(pr2)) - assert(db.listIncomingPayments(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli) === Seq(pr2, pr1)) - assert(db.listIncomingPayments(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli) === Seq(pr2)) - assert(db.listIncomingPayments(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli) === Seq.empty) - assert(db.listExpiredIncomingPayments(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli) === Seq(pr2)) - assert(db.listExpiredIncomingPayments(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli) === Seq(pr2)) - assert(db.listExpiredIncomingPayments(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli) === Seq.empty) - - assert(db.listOutgoingPayments(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2021-12-31T23:59:59.00Z").toEpochMilli) === Seq(ps2, ps1, ps3)) - assert(db.listOutgoingPayments(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2021-01-15T23:59:59.00Z").toEpochMilli) === Seq(ps2, ps1)) - assert(db.listOutgoingPayments(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli, Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli) === Seq.empty) + assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli)) === Seq(pr2, pr1)) + assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli)) === Seq(pr2)) + assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli)) === Seq.empty) + assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli)) === Seq(pr2)) + assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli)) === Seq(pr2)) + assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli)) === Seq.empty) + + assert(db.listOutgoingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2021-12-31T23:59:59.00Z").toEpochMilli)) === Seq(ps2, ps1, ps3)) + assert(db.listOutgoingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2021-01-15T23:59:59.00Z").toEpochMilli)) === Seq(ps2, ps1)) + assert(db.listOutgoingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli)) === Seq.empty) } ) } @@ -401,22 +401,22 @@ class PaymentsDbSpec extends AnyFunSuite { // can't receive a payment without an invoice associated with it assertThrows[IllegalArgumentException](db.receiveIncomingPayment(randomBytes32(), 12345678 msat)) - val expiredInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32(), alicePriv, Left("invoice #1"), CltvExpiryDelta(18), timestamp = 1) - val expiredInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32(), bobPriv, Left("invoice #2"), CltvExpiryDelta(18), timestamp = 2, expirySeconds = Some(30)) - val expiredPayment1 = IncomingPayment(expiredInvoice1, randomBytes32(), PaymentType.Standard, expiredInvoice1.timestamp.seconds.toMillis, IncomingPaymentStatus.Expired) - val expiredPayment2 = IncomingPayment(expiredInvoice2, randomBytes32(), PaymentType.Standard, expiredInvoice2.timestamp.seconds.toMillis, IncomingPaymentStatus.Expired) + val expiredInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32(), alicePriv, Left("invoice #1"), CltvExpiryDelta(18), timestamp = 1 unixsec) + val expiredInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32(), bobPriv, Left("invoice #2"), CltvExpiryDelta(18), timestamp = 2 unixsec, expirySeconds = Some(30)) + val expiredPayment1 = IncomingPayment(expiredInvoice1, randomBytes32(), PaymentType.Standard, expiredInvoice1.timestamp.toTimestampMilli, IncomingPaymentStatus.Expired) + val expiredPayment2 = IncomingPayment(expiredInvoice2, randomBytes32(), PaymentType.Standard, expiredInvoice2.timestamp.toTimestampMilli, IncomingPaymentStatus.Expired) val pendingInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32(), alicePriv, Left("invoice #3"), CltvExpiryDelta(18)) val pendingInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32(), bobPriv, Left("invoice #4"), CltvExpiryDelta(18), expirySeconds = Some(30)) - val pendingPayment1 = IncomingPayment(pendingInvoice1, randomBytes32(), PaymentType.Standard, pendingInvoice1.timestamp.seconds.toMillis, IncomingPaymentStatus.Pending) - val pendingPayment2 = IncomingPayment(pendingInvoice2, randomBytes32(), PaymentType.SwapIn, pendingInvoice2.timestamp.seconds.toMillis, IncomingPaymentStatus.Pending) + val pendingPayment1 = IncomingPayment(pendingInvoice1, randomBytes32(), PaymentType.Standard, pendingInvoice1.timestamp.toTimestampMilli, IncomingPaymentStatus.Pending) + val pendingPayment2 = IncomingPayment(pendingInvoice2, randomBytes32(), PaymentType.SwapIn, pendingInvoice2.timestamp.toTimestampMilli, IncomingPaymentStatus.Pending) val paidInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(561 msat), randomBytes32(), alicePriv, Left("invoice #5"), CltvExpiryDelta(18)) val paidInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1105 msat), randomBytes32(), bobPriv, Left("invoice #6"), CltvExpiryDelta(18), expirySeconds = Some(60)) - val receivedAt1 = System.currentTimeMillis + 1 - val receivedAt2 = System.currentTimeMillis + 2 - val payment1 = IncomingPayment(paidInvoice1, randomBytes32(), PaymentType.Standard, paidInvoice1.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(561 msat, receivedAt2)) - val payment2 = IncomingPayment(paidInvoice2, randomBytes32(), PaymentType.Standard, paidInvoice2.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(1111 msat, receivedAt2)) + val receivedAt1 = TimestampMilli.now() + 1.milli + val receivedAt2 = TimestampMilli.now() + 2.milli + val payment1 = IncomingPayment(paidInvoice1, randomBytes32(), PaymentType.Standard, paidInvoice1.timestamp.toTimestampMilli, IncomingPaymentStatus.Received(561 msat, receivedAt2)) + val payment2 = IncomingPayment(paidInvoice2, randomBytes32(), PaymentType.Standard, paidInvoice2.timestamp.toTimestampMilli, IncomingPaymentStatus.Received(1111 msat, receivedAt2)) db.addIncomingPayment(pendingInvoice1, pendingPayment1.paymentPreimage) db.addIncomingPayment(pendingInvoice2, pendingPayment2.paymentPreimage, PaymentType.SwapIn) @@ -429,21 +429,21 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.getIncomingPayment(expiredInvoice2.paymentHash) === Some(expiredPayment2)) assert(db.getIncomingPayment(paidInvoice1.paymentHash) === Some(payment1.copy(status = IncomingPaymentStatus.Pending))) - val now = System.currentTimeMillis - assert(db.listIncomingPayments(0, now) === Seq(expiredPayment1, expiredPayment2, pendingPayment1, pendingPayment2, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending))) - assert(db.listExpiredIncomingPayments(0, now) === Seq(expiredPayment1, expiredPayment2)) - assert(db.listReceivedIncomingPayments(0, now) === Nil) - assert(db.listPendingIncomingPayments(0, now) === Seq(pendingPayment1, pendingPayment2, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending))) + val now = TimestampMilli.now() + assert(db.listIncomingPayments(0 unixms, now) === Seq(expiredPayment1, expiredPayment2, pendingPayment1, pendingPayment2, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending))) + assert(db.listExpiredIncomingPayments(0 unixms, now) === Seq(expiredPayment1, expiredPayment2)) + assert(db.listReceivedIncomingPayments(0 unixms, now) === Nil) + assert(db.listPendingIncomingPayments(0 unixms, now) === Seq(pendingPayment1, pendingPayment2, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending))) db.receiveIncomingPayment(paidInvoice1.paymentHash, 461 msat, receivedAt1) db.receiveIncomingPayment(paidInvoice1.paymentHash, 100 msat, receivedAt2) // adding another payment to this invoice should sum db.receiveIncomingPayment(paidInvoice2.paymentHash, 1111 msat, receivedAt2) assert(db.getIncomingPayment(paidInvoice1.paymentHash) === Some(payment1)) - assert(db.listIncomingPayments(0, now) === Seq(expiredPayment1, expiredPayment2, pendingPayment1, pendingPayment2, payment1, payment2)) - assert(db.listIncomingPayments(now - 60.seconds.toMillis, now) === Seq(pendingPayment1, pendingPayment2, payment1, payment2)) - assert(db.listPendingIncomingPayments(0, now) === Seq(pendingPayment1, pendingPayment2)) - assert(db.listReceivedIncomingPayments(0, now) === Seq(payment1, payment2)) + assert(db.listIncomingPayments(0 unixms, now) === Seq(expiredPayment1, expiredPayment2, pendingPayment1, pendingPayment2, payment1, payment2)) + assert(db.listIncomingPayments(now - 60.seconds, now) === Seq(pendingPayment1, pendingPayment2, payment1, payment2)) + assert(db.listPendingIncomingPayments(0 unixms, now) === Seq(pendingPayment1, pendingPayment2)) + assert(db.listReceivedIncomingPayments(0 unixms, now) === Seq(payment1, payment2)) } } @@ -452,20 +452,20 @@ class PaymentsDbSpec extends AnyFunSuite { val db = dbs.payments val parentId = UUID.randomUUID() - val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 0) - val s1 = OutgoingPayment(UUID.randomUUID(), parentId, None, paymentHash1, PaymentType.Standard, 123 msat, 600 msat, dave, 100, Some(i1), OutgoingPaymentStatus.Pending) - val s2 = OutgoingPayment(UUID.randomUUID(), parentId, Some("1"), paymentHash1, PaymentType.SwapOut, 456 msat, 600 msat, dave, 200, None, OutgoingPaymentStatus.Pending) + val i1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), paymentHash1, davePriv, Left("Some invoice"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 0 unixsec) + val s1 = OutgoingPayment(UUID.randomUUID(), parentId, None, paymentHash1, PaymentType.Standard, 123 msat, 600 msat, dave, 100 unixms, Some(i1), OutgoingPaymentStatus.Pending) + val s2 = OutgoingPayment(UUID.randomUUID(), parentId, Some("1"), paymentHash1, PaymentType.SwapOut, 456 msat, 600 msat, dave, 200 unixms, None, OutgoingPaymentStatus.Pending) - assert(db.listOutgoingPayments(0, System.currentTimeMillis).isEmpty) + assert(db.listOutgoingPayments(0 unixms, TimestampMilli.now()).isEmpty) db.addOutgoingPayment(s1) db.addOutgoingPayment(s2) // can't add an outgoing payment in non-pending state - assertThrows[IllegalArgumentException](db.addOutgoingPayment(s1.copy(status = OutgoingPaymentStatus.Succeeded(randomBytes32(), 0 msat, Nil, 110)))) + assertThrows[IllegalArgumentException](db.addOutgoingPayment(s1.copy(status = OutgoingPaymentStatus.Succeeded(randomBytes32(), 0 msat, Nil, 110 unixms)))) - assert(db.listOutgoingPayments(1, 300).toList == Seq(s1, s2)) - assert(db.listOutgoingPayments(1, 150).toList == Seq(s1)) - assert(db.listOutgoingPayments(150, 250).toList == Seq(s2)) + assert(db.listOutgoingPayments(1 unixms, 300 unixms).toList == Seq(s1, s2)) + assert(db.listOutgoingPayments(1 unixms, 150 unixms).toList == Seq(s1)) + assert(db.listOutgoingPayments(150 unixms, 250 unixms).toList == Seq(s2)) assert(db.getOutgoingPayment(s1.id) === Some(s1)) assert(db.getOutgoingPayment(UUID.randomUUID()) === None) assert(db.listOutgoingPayments(s2.paymentHash) === Seq(s1, s2)) @@ -473,27 +473,27 @@ class PaymentsDbSpec extends AnyFunSuite { assert(db.listOutgoingPayments(parentId) === Seq(s1, s2)) assert(db.listOutgoingPayments(ByteVector32.Zeroes) === Nil) - val s3 = s2.copy(id = UUID.randomUUID(), amount = 789 msat, createdAt = 300) - val s4 = s2.copy(id = UUID.randomUUID(), paymentType = PaymentType.Standard, createdAt = 301) + val s3 = s2.copy(id = UUID.randomUUID(), amount = 789 msat, createdAt = 300 unixms) + val s4 = s2.copy(id = UUID.randomUUID(), paymentType = PaymentType.Standard, createdAt = 301 unixms) db.addOutgoingPayment(s3) db.addOutgoingPayment(s4) - db.updateOutgoingPayment(PaymentFailed(s3.id, s3.paymentHash, Nil, 310)) - val ss3 = s3.copy(status = OutgoingPaymentStatus.Failed(Nil, 310)) + db.updateOutgoingPayment(PaymentFailed(s3.id, s3.paymentHash, Nil, 310 unixms)) + val ss3 = s3.copy(status = OutgoingPaymentStatus.Failed(Nil, 310 unixms)) assert(db.getOutgoingPayment(s3.id) === Some(ss3)) - db.updateOutgoingPayment(PaymentFailed(s4.id, s4.paymentHash, Seq(LocalFailure(s4.amount, Seq(hop_ab), new RuntimeException("woops")), RemoteFailure(s4.amount, Seq(hop_ab, hop_bc), Sphinx.DecryptedFailurePacket(carol, UnknownNextPeer))), 320)) - val ss4 = s4.copy(status = OutgoingPaymentStatus.Failed(Seq(FailureSummary(FailureType.LOCAL, "woops", List(HopSummary(alice, bob, Some(ShortChannelId(42))))), FailureSummary(FailureType.REMOTE, "processing node does not know the next peer in the route", List(HopSummary(alice, bob, Some(ShortChannelId(42))), HopSummary(bob, carol, None)))), 320)) + db.updateOutgoingPayment(PaymentFailed(s4.id, s4.paymentHash, Seq(LocalFailure(s4.amount, Seq(hop_ab), new RuntimeException("woops")), RemoteFailure(s4.amount, Seq(hop_ab, hop_bc), Sphinx.DecryptedFailurePacket(carol, UnknownNextPeer))), 320 unixms)) + val ss4 = s4.copy(status = OutgoingPaymentStatus.Failed(Seq(FailureSummary(FailureType.LOCAL, "woops", List(HopSummary(alice, bob, Some(ShortChannelId(42))))), FailureSummary(FailureType.REMOTE, "processing node does not know the next peer in the route", List(HopSummary(alice, bob, Some(ShortChannelId(42))), HopSummary(bob, carol, None)))), 320 unixms)) assert(db.getOutgoingPayment(s4.id) === Some(ss4)) // can't update again once it's in a final state assertThrows[IllegalArgumentException](db.updateOutgoingPayment(PaymentSent(parentId, s3.paymentHash, preimage1, s3.recipientAmount, s3.recipientNodeId, Seq(PaymentSent.PartialPayment(s3.id, s3.amount, 42 msat, randomBytes32(), None))))) val paymentSent = PaymentSent(parentId, paymentHash1, preimage1, 600 msat, carol, Seq( - PaymentSent.PartialPayment(s1.id, s1.amount, 15 msat, randomBytes32(), None, 400), - PaymentSent.PartialPayment(s2.id, s2.amount, 20 msat, randomBytes32(), Some(Seq(hop_ab, hop_bc)), 410) + PaymentSent.PartialPayment(s1.id, s1.amount, 15 msat, randomBytes32(), None, 400 unixms), + PaymentSent.PartialPayment(s2.id, s2.amount, 20 msat, randomBytes32(), Some(Seq(hop_ab, hop_bc)), 410 unixms) )) - val ss1 = s1.copy(status = OutgoingPaymentStatus.Succeeded(preimage1, 15 msat, Nil, 400)) - val ss2 = s2.copy(status = OutgoingPaymentStatus.Succeeded(preimage1, 20 msat, Seq(HopSummary(alice, bob, Some(ShortChannelId(42))), HopSummary(bob, carol, None)), 410)) + val ss1 = s1.copy(status = OutgoingPaymentStatus.Succeeded(preimage1, 15 msat, Nil, 400 unixms)) + val ss2 = s2.copy(status = OutgoingPaymentStatus.Succeeded(preimage1, 20 msat, Seq(HopSummary(alice, bob, Some(ShortChannelId(42))), HopSummary(bob, carol, None)), 410 unixms)) db.updateOutgoingPayment(paymentSent) assert(db.getOutgoingPayment(s1.id) === Some(ss1)) assert(db.getOutgoingPayment(s2.id) === Some(ss2)) @@ -508,16 +508,16 @@ class PaymentsDbSpec extends AnyFunSuite { val db = new SqlitePaymentsDb(TestDatabases.sqliteInMemory()) // -- feed db with incoming payments - val expiredInvoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), randomBytes32(), alicePriv, Left("incoming #1"), CltvExpiryDelta(18), timestamp = 1) - val expiredPayment = IncomingPayment(expiredInvoice, randomBytes32(), PaymentType.Standard, 100, IncomingPaymentStatus.Expired) + val expiredInvoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(123 msat), randomBytes32(), alicePriv, Left("incoming #1"), CltvExpiryDelta(18), timestamp = 1 unixsec) + val expiredPayment = IncomingPayment(expiredInvoice, randomBytes32(), PaymentType.Standard, 100 unixms, IncomingPaymentStatus.Expired) val pendingInvoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(456 msat), randomBytes32(), alicePriv, Left("incoming #2"), CltvExpiryDelta(18)) - val pendingPayment = IncomingPayment(pendingInvoice, randomBytes32(), PaymentType.Standard, 120, IncomingPaymentStatus.Pending) + val pendingPayment = IncomingPayment(pendingInvoice, randomBytes32(), PaymentType.Standard, 120 unixms, IncomingPaymentStatus.Pending) val paidInvoice1 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(789 msat), randomBytes32(), alicePriv, Left("incoming #3"), CltvExpiryDelta(18)) - val receivedAt1 = 150 - val receivedPayment1 = IncomingPayment(paidInvoice1, randomBytes32(), PaymentType.Standard, 130, IncomingPaymentStatus.Received(561 msat, receivedAt1)) + val receivedAt1 = 150 unixms + val receivedPayment1 = IncomingPayment(paidInvoice1, randomBytes32(), PaymentType.Standard, 130 unixms, IncomingPaymentStatus.Received(561 msat, receivedAt1)) val paidInvoice2 = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(888 msat), randomBytes32(), alicePriv, Left("incoming #4"), CltvExpiryDelta(18)) - val receivedAt2 = 160 - val receivedPayment2 = IncomingPayment(paidInvoice2, randomBytes32(), PaymentType.Standard, paidInvoice2.timestamp.seconds.toMillis, IncomingPaymentStatus.Received(889 msat, receivedAt2)) + val receivedAt2 = 160 unixms + val receivedPayment2 = IncomingPayment(paidInvoice2, randomBytes32(), PaymentType.Standard, paidInvoice2.timestamp.toTimestampMilli, IncomingPaymentStatus.Received(889 msat, receivedAt2)) db.addIncomingPayment(pendingInvoice, pendingPayment.paymentPreimage) db.addIncomingPayment(expiredInvoice, expiredPayment.paymentPreimage) db.addIncomingPayment(paidInvoice1, receivedPayment1.paymentPreimage) @@ -528,14 +528,14 @@ class PaymentsDbSpec extends AnyFunSuite { // -- feed db with outgoing payments val parentId1 = UUID.randomUUID() val parentId2 = UUID.randomUUID() - val invoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1337 msat), paymentHash1, davePriv, Left("outgoing #1"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 0) + val invoice = PaymentRequest(Block.TestnetGenesisBlock.hash, Some(1337 msat), paymentHash1, davePriv, Left("outgoing #1"), CltvExpiryDelta(18), expirySeconds = None, timestamp = 0 unixsec) // 1st attempt, pending -> failed - val outgoing1 = OutgoingPayment(UUID.randomUUID(), parentId1, None, paymentHash1, PaymentType.Standard, 123 msat, 123 msat, alice, 200, Some(invoice), OutgoingPaymentStatus.Pending) + val outgoing1 = OutgoingPayment(UUID.randomUUID(), parentId1, None, paymentHash1, PaymentType.Standard, 123 msat, 123 msat, alice, 200 unixms, Some(invoice), OutgoingPaymentStatus.Pending) db.addOutgoingPayment(outgoing1) - db.updateOutgoingPayment(PaymentFailed(outgoing1.id, outgoing1.paymentHash, Nil, 210)) + db.updateOutgoingPayment(PaymentFailed(outgoing1.id, outgoing1.paymentHash, Nil, 210 unixms)) // 2nd attempt: pending - val outgoing2 = OutgoingPayment(UUID.randomUUID(), parentId1, None, paymentHash1, PaymentType.Standard, 123 msat, 123 msat, alice, 211, Some(invoice), OutgoingPaymentStatus.Pending) + val outgoing2 = OutgoingPayment(UUID.randomUUID(), parentId1, None, paymentHash1, PaymentType.Standard, 123 msat, 123 msat, alice, 211 unixms, Some(invoice), OutgoingPaymentStatus.Pending) db.addOutgoingPayment(outgoing2) // -- 1st check: result contains 2 incoming PAID, 1 outgoing PENDING. Outgoing1 must not be overridden by Outgoing2 @@ -546,15 +546,15 @@ class PaymentsDbSpec extends AnyFunSuite { assert(check1.head.asInstanceOf[PlainOutgoingPayment].status == OutgoingPaymentStatus.Pending) // failed #2 and add a successful payment (made of 2 partial payments) - db.updateOutgoingPayment(PaymentFailed(outgoing2.id, outgoing2.paymentHash, Nil, 250)) - val outgoing3 = OutgoingPayment(UUID.randomUUID(), parentId2, None, paymentHash1, PaymentType.Standard, 200 msat, 500 msat, bob, 300, Some(invoice), OutgoingPaymentStatus.Pending) - val outgoing4 = OutgoingPayment(UUID.randomUUID(), parentId2, None, paymentHash1, PaymentType.Standard, 300 msat, 500 msat, bob, 310, Some(invoice), OutgoingPaymentStatus.Pending) + db.updateOutgoingPayment(PaymentFailed(outgoing2.id, outgoing2.paymentHash, Nil, 250 unixms)) + val outgoing3 = OutgoingPayment(UUID.randomUUID(), parentId2, None, paymentHash1, PaymentType.Standard, 200 msat, 500 msat, bob, 300 unixms, Some(invoice), OutgoingPaymentStatus.Pending) + val outgoing4 = OutgoingPayment(UUID.randomUUID(), parentId2, None, paymentHash1, PaymentType.Standard, 300 msat, 500 msat, bob, 310 unixms, Some(invoice), OutgoingPaymentStatus.Pending) db.addOutgoingPayment(outgoing3) db.addOutgoingPayment(outgoing4) // complete #2 and #3 partial payments val sent = PaymentSent(parentId2, paymentHash1, preimage1, outgoing3.recipientAmount, outgoing3.recipientNodeId, Seq( - PaymentSent.PartialPayment(outgoing3.id, outgoing3.amount, 15 msat, randomBytes32(), None, 400), - PaymentSent.PartialPayment(outgoing4.id, outgoing4.amount, 20 msat, randomBytes32(), None, 410) + PaymentSent.PartialPayment(outgoing3.id, outgoing3.amount, 15 msat, randomBytes32(), None, 400 unixms), + PaymentSent.PartialPayment(outgoing4.id, outgoing4.amount, 20 msat, randomBytes32(), None, 410 unixms) )) db.updateOutgoingPayment(sent) @@ -583,7 +583,7 @@ class PaymentsDbSpec extends AnyFunSuite { object PaymentsDbSpec { val (alicePriv, bobPriv, carolPriv, davePriv) = (randomKey(), randomKey(), randomKey(), randomKey()) val (alice, bob, carol, dave) = (alicePriv.publicKey, bobPriv.publicKey, carolPriv.publicKey, davePriv.publicKey) - val hop_ab = ChannelHop(alice, bob, ChannelUpdate(randomBytes64(), randomBytes32(), ShortChannelId(42), 1, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(12), 1 msat, 1 msat, 1, None)) + val hop_ab = ChannelHop(alice, bob, ChannelUpdate(randomBytes64(), randomBytes32(), ShortChannelId(42), 1 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(12), 1 msat, 1 msat, 1, None)) val hop_bc = NodeHop(bob, carol, CltvExpiryDelta(14), 1 msat) val (preimage1, preimage2, preimage3, preimage4) = (randomBytes32(), randomBytes32(), randomBytes32(), randomBytes32()) val (paymentHash1, paymentHash2, paymentHash3, paymentHash4) = (Crypto.sha256(preimage1), Crypto.sha256(preimage2), Crypto.sha256(preimage3), Crypto.sha256(preimage4)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteFeeratesDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteFeeratesDbSpec.scala index 536064ee11..e280b622c4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteFeeratesDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteFeeratesDbSpec.scala @@ -76,7 +76,7 @@ class SqliteFeeratesDbSpec extends AnyFunSuite { statement.setLong(5, feerate.blocks_36.toLong) statement.setLong(6, feerate.blocks_72.toLong) statement.setLong(7, feerate.blocks_144.toLong) - statement.setLong(8, System.currentTimeMillis()) + statement.setLong(8, TimestampMilli.now().toLong) statement.executeUpdate() } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala index 1349d7b9d9..fca9b79aba 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala @@ -43,7 +43,7 @@ import fr.acinq.eclair.router.Graph.WeightRatios import fr.acinq.eclair.router.Router.{GossipDecision, PublicChannel} import fr.acinq.eclair.router.{Announcements, AnnouncementsBatchValidationSpec, Router} import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, IncorrectOrUnknownPaymentDetails, NodeAnnouncement} -import fr.acinq.eclair.{CltvExpiryDelta, Kit, MilliSatoshiLong, ShortChannelId, randomBytes32} +import fr.acinq.eclair.{CltvExpiryDelta, Kit, MilliSatoshiLong, ShortChannelId, TimestampMilli, randomBytes32} import org.json4s.JsonAST.{JString, JValue} import scodec.bits.ByteVector @@ -337,7 +337,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { } test("send a multi-part payment B->D") { - val start = System.currentTimeMillis + val start = TimestampMilli.now() val sender = TestProbe() val amount = 1000000000L.msat sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amount), Left("split the restaurant bill"))) @@ -364,8 +364,8 @@ class PaymentIntegrationSpec extends IntegrationSpec { assert(paymentParts.forall(p => p.parentId != p.id), paymentParts) assert(paymentParts.forall(p => p.status.asInstanceOf[OutgoingPaymentStatus.Succeeded].feesPaid > 0.msat), paymentParts) - awaitCond(nodes("B").nodeParams.db.audit.listSent(start, System.currentTimeMillis).nonEmpty) - val sent = nodes("B").nodeParams.db.audit.listSent(start, System.currentTimeMillis) + awaitCond(nodes("B").nodeParams.db.audit.listSent(start, TimestampMilli.now()).nonEmpty) + val sent = nodes("B").nodeParams.db.audit.listSent(start, TimestampMilli.now()) assert(sent.length === 1, sent) assert(sent.head.copy(parts = sent.head.parts.sortBy(_.timestamp)) === paymentSent.copy(parts = paymentSent.parts.map(_.copy(route = None)).sortBy(_.timestamp)), sent) @@ -460,7 +460,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { } test("send a trampoline payment B->F1 with retry (via trampoline G)") { - val start = System.currentTimeMillis + val start = TimestampMilli.now() val sender = TestProbe() val amount = 4000000000L.msat sender.send(nodes("F").paymentHandler, ReceivePayment(Some(amount), Left("like trampoline much?"))) @@ -486,10 +486,10 @@ class PaymentIntegrationSpec extends IntegrationSpec { assert(receivedAmount === amount) awaitCond({ - val relayed = nodes("G").nodeParams.db.audit.listRelayed(start, System.currentTimeMillis).filter(_.paymentHash == pr.paymentHash) + val relayed = nodes("G").nodeParams.db.audit.listRelayed(start, TimestampMilli.now()).filter(_.paymentHash == pr.paymentHash) relayed.nonEmpty && relayed.head.amountOut >= amount }) - val relayed = nodes("G").nodeParams.db.audit.listRelayed(start, System.currentTimeMillis).filter(_.paymentHash == pr.paymentHash).head + val relayed = nodes("G").nodeParams.db.audit.listRelayed(start, TimestampMilli.now()).filter(_.paymentHash == pr.paymentHash).head assert(relayed.amountIn - relayed.amountOut > 0.msat, relayed) assert(relayed.amountIn - relayed.amountOut < 1000000.msat, relayed) @@ -502,7 +502,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { } test("send a trampoline payment D->B (via trampoline C)") { - val start = System.currentTimeMillis + val start = TimestampMilli.now() val sender = TestProbe() val amount = 2500000000L.msat sender.send(nodes("B").paymentHandler, ReceivePayment(Some(amount), Left("trampoline-MPP is so #reckless"))) @@ -525,10 +525,10 @@ class PaymentIntegrationSpec extends IntegrationSpec { assert(receivedAmount === amount) awaitCond({ - val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, System.currentTimeMillis).filter(_.paymentHash == pr.paymentHash) + val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, TimestampMilli.now()).filter(_.paymentHash == pr.paymentHash) relayed.nonEmpty && relayed.head.amountOut >= amount }) - val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, System.currentTimeMillis).filter(_.paymentHash == pr.paymentHash).head + val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, TimestampMilli.now()).filter(_.paymentHash == pr.paymentHash).head assert(relayed.amountIn - relayed.amountOut > 0.msat, relayed) assert(relayed.amountIn - relayed.amountOut < 350000.msat, relayed) @@ -539,15 +539,15 @@ class PaymentIntegrationSpec extends IntegrationSpec { } assert(outgoingSuccess.map(_.amount).sum === amount + 350000.msat, outgoingSuccess) - awaitCond(nodes("D").nodeParams.db.audit.listSent(start, System.currentTimeMillis).nonEmpty) - val sent = nodes("D").nodeParams.db.audit.listSent(start, System.currentTimeMillis) + awaitCond(nodes("D").nodeParams.db.audit.listSent(start, TimestampMilli.now()).nonEmpty) + val sent = nodes("D").nodeParams.db.audit.listSent(start, TimestampMilli.now()) assert(sent.length === 1, sent) assert(sent.head.copy(parts = sent.head.parts.sortBy(_.timestamp)) === paymentSent.copy(parts = paymentSent.parts.map(_.copy(route = None)).sortBy(_.timestamp)), sent) } test("send a trampoline payment F1->A (via trampoline C, non-trampoline recipient)") { // The A -> B channel is not announced. - val start = System.currentTimeMillis + val start = TimestampMilli.now() val sender = TestProbe() sender.send(nodes("B").relayer, Relayer.GetOutgoingChannels()) val channelUpdate_ba = sender.expectMsgType[Relayer.OutgoingChannels].channels.filter(c => c.nextNodeId == nodes("A").nodeParams.nodeId).head.channelUpdate @@ -573,10 +573,10 @@ class PaymentIntegrationSpec extends IntegrationSpec { assert(receivedAmount === amount) awaitCond({ - val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, System.currentTimeMillis).filter(_.paymentHash == pr.paymentHash) + val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, TimestampMilli.now()).filter(_.paymentHash == pr.paymentHash) relayed.nonEmpty && relayed.head.amountOut >= amount }) - val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, System.currentTimeMillis).filter(_.paymentHash == pr.paymentHash).head + val relayed = nodes("C").nodeParams.db.audit.listRelayed(start, TimestampMilli.now()).filter(_.paymentHash == pr.paymentHash).head assert(relayed.amountIn - relayed.amountOut > 0.msat, relayed) assert(relayed.amountIn - relayed.amountOut < 1000000.msat, relayed) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala index 7270d38934..9097df797c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PerformanceIntegrationSpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.integration import akka.testkit.TestProbe import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.SatoshiLong -import fr.acinq.eclair.MilliSatoshiLong +import fr.acinq.eclair.{MilliSatoshiLong, TimestampMilli} import fr.acinq.eclair.channel._ import fr.acinq.eclair.payment._ import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceivePayment @@ -97,14 +97,14 @@ class PerformanceIntegrationSpec extends IntegrationSpec { val SENDERS_COUNT = 16 val PAYMENTS_COUNT = 3_000 val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(SENDERS_COUNT)) - val start = System.currentTimeMillis() + val start = TimestampMilli.now() val futures = (0 until PAYMENTS_COUNT).map(_ => sendPayment()(ec)) implicit val dummyEc: ExecutionContext = ExecutionContext.Implicits.global val f = Future.sequence(futures) Await.result(f, 1 hour) - val end = System.currentTimeMillis() + val end = TimestampMilli.now() val duration = end - start - println(s"$PAYMENTS_COUNT payments in ${duration}ms ${PAYMENTS_COUNT * 1000 / duration}htlc/s (senders=$SENDERS_COUNT)") + println(s"$PAYMENTS_COUNT payments in ${duration.toMillis}ms ${PAYMENTS_COUNT * 1000 / duration.toMillis}htlc/s (senders=$SENDERS_COUNT)") } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala index f85d1d9b7e..4abce782e7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala @@ -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, Long.MaxValue) // no filtering on timestamps + val filter = protocol.GossipTimestampFilter(Alice.nodeParams.chainHash, 0 unixsec, Long.MaxValue) // no filtering on timestamps transport.send(peerConnection, filter) transport.expectMsg(TransportHandler.ReadAck(filter)) transport.send(peerConnection, rebroadcast) @@ -282,7 +282,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi val gossipOrigin = Set[GossipOrigin](RemoteGossip(TestProbe().ref, randomKey().publicKey)) val rebroadcast = Rebroadcast(channels.map(_ -> gossipOrigin).toMap, updates.map(_ -> gossipOrigin).toMap, nodes.map(_ -> gossipOrigin).toMap) val timestamps = updates.map(_.timestamp).sorted.slice(10, 30) - val filter = protocol.GossipTimestampFilter(Alice.nodeParams.chainHash, timestamps.head, timestamps.last - timestamps.head) + val filter = protocol.GossipTimestampFilter(Alice.nodeParams.chainHash, timestamps.head, (timestamps.last - timestamps.head).toSeconds) transport.send(peerConnection, filter) transport.expectMsg(TransportHandler.ReadAck(filter)) transport.send(peerConnection, rebroadcast) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 2d69084bb5..e518717025 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -73,7 +73,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle .modify(_.autoReconnect).setToIf(test.tags.contains("auto_reconnect"))(true) if (test.tags.contains("with_node_announcement")) { - val bobAnnouncement = NodeAnnouncement(randomBytes64(), Features.empty, 1, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil) + val bobAnnouncement = NodeAnnouncement(randomBytes64(), Features.empty, 1 unixsec, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil) aliceParams.db.network.addNode(bobAnnouncement) } @@ -140,7 +140,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle val mockAddress = NodeAddress.fromParts(serverAddress.getHostName, serverAddress.getPort).get // we put the server address in the node db - val ann = NodeAnnouncement(randomBytes64(), Features.empty, 1, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", mockAddress :: Nil) + val ann = NodeAnnouncement(randomBytes64(), Features.empty, 1 unixsec, Bob.nodeParams.nodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", mockAddress :: Nil) nodeParams.db.network.addNode(ann) val probe = TestProbe() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/ReconnectionTaskSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/ReconnectionTaskSpec.scala index ee8e32c96b..d179e37eaf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/ReconnectionTaskSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/ReconnectionTaskSpec.scala @@ -48,7 +48,7 @@ class ReconnectionTaskSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike .modify(_.autoReconnect).setToIf(test.tags.contains("auto_reconnect"))(true) if (test.tags.contains("with_node_announcements")) { - val bobAnnouncement = NodeAnnouncement(randomBytes64(), Features.empty, 1, remoteNodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil) + val bobAnnouncement = NodeAnnouncement(randomBytes64(), Features.empty, 1 unixsec, remoteNodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", fakeIPAddress :: Nil) aliceParams.db.network.addNode(bobAnnouncement) } @@ -116,7 +116,7 @@ class ReconnectionTaskSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val TransitionWithData(ReconnectionTask.CONNECTING, ReconnectionTask.IDLE, _, _) = monitor.expectMsgType[TransitionWithData] // NB: we change the data to make it appear like we have been connected for a long time - reconnectionTask.setState(stateData = reconnectionTask.stateData.asInstanceOf[ReconnectionTask.IdleData].copy(since = 0.seconds)) + reconnectionTask.setState(stateData = reconnectionTask.stateData.asInstanceOf[ReconnectionTask.IdleData].copy(since = 0 unixms)) val TransitionWithData(ReconnectionTask.IDLE, ReconnectionTask.IDLE, _, _) = monitor.expectMsgType[TransitionWithData] // disconnection @@ -208,7 +208,7 @@ class ReconnectionTaskSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike // we create a dummy tcp server and update bob's announcement to point to it val (mockServer, serverAddress) = PeerSpec.createMockServer() val mockAddress = NodeAddress.fromParts(serverAddress.getHostName, serverAddress.getPort).get - val bobAnnouncement = NodeAnnouncement(randomBytes64(), Features.empty, 1, remoteNodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", mockAddress :: Nil) + val bobAnnouncement = NodeAnnouncement(randomBytes64(), Features.empty, 1 unixsec, remoteNodeId, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", mockAddress :: Nil) nodeParams.db.network.addNode(bobAnnouncement) val peer = TestProbe() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala index bd1d24d2f1..ac4c331e27 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/SwitchboardSpec.scala @@ -9,7 +9,7 @@ import fr.acinq.eclair.channel.ChannelIdAssigned import fr.acinq.eclair.io.Switchboard.PeerFactory import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Features, NodeParams, TestKitBaseClass, randomBytes32, randomKey} +import fr.acinq.eclair.{Features, NodeParams, TestKitBaseClass, TimestampSecondLong, randomBytes32, randomKey} import org.scalatest.funsuite.AnyFunSuiteLike import scodec.bits._ @@ -38,7 +38,7 @@ class SwitchboardSpec extends TestKitBaseClass with AnyFunSuiteLike { val (probe, peer) = (TestProbe(), TestProbe()) val remoteNodeId = PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") val remoteNodeAddress = NodeAddress.fromParts("127.0.0.1", 9735).get - nodeParams.db.network.addNode(NodeAnnouncement(ByteVector64.Zeroes, Features.empty, 0, remoteNodeId, Color(0, 0, 0), "alias", remoteNodeAddress :: Nil)) + nodeParams.db.network.addNode(NodeAnnouncement(ByteVector64.Zeroes, Features.empty, 0 unixsec, remoteNodeId, Color(0, 0, 0), "alias", remoteNodeAddress :: Nil)) val switchboard = TestActorRef(new Switchboard(nodeParams, FakePeerFactory(remoteNodeId, peer))) probe.send(switchboard, Peer.Connect(remoteNodeId, None)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 96c0fbb8a0..508f99bc59 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -266,6 +266,13 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { } + test("serialize timestamps") { + val ts = TimestampSecond(1633357961) + JsonSerializers.serialization.write(ts)(JsonSerializers.formats) shouldBe """{"iso":"2021-10-04T14:32:41Z","unix":1633357961}""" + val tsms = TimestampMilli(1633357961456L) + JsonSerializers.serialization.write(tsms)(JsonSerializers.formats) shouldBe """{"iso":"2021-10-04T14:32:41.456Z","unix":1633357961}""" + } + /** utility method that strips line breaks in the expected json */ def assertJsonEquals(actual: String, expected: String) = { val cleanedExpected = expected diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala index 18353137ac..7dab48ae41 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.payment.receive.MultiPartPaymentFSM.HtlcPart import fr.acinq.eclair.payment.receive.{MultiPartPaymentFSM, PaymentHandler} import fr.acinq.eclair.wire.protocol.Onion.FinalTlvPayload import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshiLong, NodeParams, ShortChannelId, TestConstants, TestKitBaseClass, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshiLong, NodeParams, ShortChannelId, TestConstants, TestKitBaseClass, TimestampMilliLong, randomBytes32, randomKey} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -96,10 +96,10 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]] val paymentReceived = eventListener.expectMsgType[PaymentReceived] - assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0))) === PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, timestamp = 0) :: Nil)) + assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0 unixms))) === PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, timestamp = 0 unixms) :: Nil)) val received = nodeParams.db.payments.getIncomingPayment(pr.paymentHash) assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received]) - assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].copy(receivedAt = 0) === IncomingPaymentStatus.Received(amountMsat, 0)) + assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].copy(receivedAt = 0 unixms) === IncomingPaymentStatus.Received(amountMsat, 0 unixms)) sender.expectNoMessage(50 millis) } @@ -115,10 +115,10 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]] val paymentReceived = eventListener.expectMsgType[PaymentReceived] - assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0))) === PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, timestamp = 0) :: Nil)) + assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0 unixms))) === PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, timestamp = 0 unixms) :: Nil)) val received = nodeParams.db.payments.getIncomingPayment(pr.paymentHash) assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received]) - assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].copy(receivedAt = 0) === IncomingPaymentStatus.Received(amountMsat, 0)) + assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].copy(receivedAt = 0 unixms) === IncomingPaymentStatus.Received(amountMsat, 0 unixms)) sender.expectNoMessage(50 millis) } @@ -414,7 +414,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike ) val paymentReceived = f.eventListener.expectMsgType[PaymentReceived] - assert(paymentReceived.parts.map(_.copy(timestamp = 0)).toSet === Set(PartialPayment(800 msat, ByteVector32.One, 0), PartialPayment(200 msat, ByteVector32.Zeroes, 0))) + assert(paymentReceived.parts.map(_.copy(timestamp = 0 unixms)).toSet === Set(PartialPayment(800 msat, ByteVector32.One, 0 unixms), PartialPayment(200 msat, ByteVector32.Zeroes, 0 unixms))) val received = nodeParams.db.payments.getIncomingPayment(pr.paymentHash) assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received]) assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].amount === 1000.msat) @@ -465,7 +465,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val paymentReceived = f.eventListener.expectMsgType[PaymentReceived] assert(paymentReceived.paymentHash === pr.paymentHash) - assert(paymentReceived.parts.map(_.copy(timestamp = 0)).toSet === Set(PartialPayment(300 msat, ByteVector32.One, 0), PartialPayment(700 msat, ByteVector32.Zeroes, 0))) + assert(paymentReceived.parts.map(_.copy(timestamp = 0 unixms)).toSet === Set(PartialPayment(300 msat, ByteVector32.One, 0 unixms), PartialPayment(700 msat, ByteVector32.Zeroes, 0 unixms))) val received = nodeParams.db.payments.getIncomingPayment(pr.paymentHash) assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received]) assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].amount === 1000.msat) @@ -491,10 +491,10 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]] val paymentReceived = eventListener.expectMsgType[PaymentReceived] - assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0))) === PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, timestamp = 0) :: Nil)) + assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0 unixms))) === PaymentReceived(add.paymentHash, PartialPayment(amountMsat, add.channelId, timestamp = 0 unixms) :: Nil)) val received = nodeParams.db.payments.getIncomingPayment(paymentHash) assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received]) - assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].copy(receivedAt = 0) === IncomingPaymentStatus.Received(amountMsat, 0)) + assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].copy(receivedAt = 0 unixms) === IncomingPaymentStatus.Received(amountMsat, 0 unixms)) } test("PaymentHandler should reject KeySend payment when feature is disabled") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala index 0c39c73fc8..e2b12fc7c4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala @@ -687,7 +687,7 @@ object MultiPartPaymentLifecycleSpec { val channelId_ce = ShortChannelId(13) val channelId_ad = ShortChannelId(21) val channelId_de = ShortChannelId(22) - val defaultChannelUpdate = ChannelUpdate(randomBytes64(), Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(12), 1 msat, 100 msat, 0, Some(2000000 msat)) + val defaultChannelUpdate = ChannelUpdate(randomBytes64(), Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(12), 1 msat, 100 msat, 0, Some(2000000 msat)) val channelUpdate_ab_1 = defaultChannelUpdate.copy(shortChannelId = channelId_ab_1) val channelUpdate_ab_2 = defaultChannelUpdate.copy(shortChannelId = channelId_ab_2) val channelUpdate_be = defaultChannelUpdate.copy(shortChannelId = channelId_be) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index 2228c3764c..691a9f0610 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -110,7 +110,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) awaitCond(nodeParams.db.payments.getOutgoingPayment(id).exists(_.status == OutgoingPaymentStatus.Pending)) val Some(outgoing) = nodeParams.db.payments.getOutgoingPayment(id) - assert(outgoing.copy(createdAt = 0) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) + assert(outgoing.copy(createdAt = 0 unixms) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0 unixms, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFulfill(UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentPreimage)))) val ps = sender.expectMsgType[PaymentSent] @@ -138,7 +138,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) awaitCond(nodeParams.db.payments.getOutgoingPayment(id).exists(_.status == OutgoingPaymentStatus.Pending)) val Some(outgoing) = nodeParams.db.payments.getOutgoingPayment(id) - assert(outgoing.copy(createdAt = 0) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) + assert(outgoing.copy(createdAt = 0 unixms) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0 unixms, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFulfill(UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentPreimage)))) val ps = sender.expectMsgType[PaymentSent] @@ -640,7 +640,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]]) awaitCond(nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending)) val Some(outgoing) = nodeParams.db.payments.getOutgoingPayment(id) - assert(outgoing.copy(createdAt = 0) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) + assert(outgoing.copy(createdAt = 0 unixms) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0 unixms, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFulfill(UpdateFulfillHtlc(ByteVector32.Zeroes, 0, defaultPaymentPreimage)))) val ps = eventListener.expectMsgType[PaymentSent] @@ -800,7 +800,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val WaitingForComplete(_, _, Nil, sharedSecrets1, _, _) = paymentFSM.stateData awaitCond(nodeParams.db.payments.getOutgoingPayment(id).exists(_.status == OutgoingPaymentStatus.Pending)) val Some(outgoing) = nodeParams.db.payments.getOutgoingPayment(id) - assert(outgoing.copy(createdAt = 0) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) + assert(outgoing.copy(createdAt = 0 unixms) === OutgoingPayment(id, parentId, Some(defaultExternalId), defaultPaymentHash, PaymentType.Standard, defaultAmountMsat, defaultAmountMsat, d, 0 unixms, Some(defaultInvoice), OutgoingPaymentStatus.Pending)) // we change the cltv expiry val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, CltvExpiryDelta(42), htlcMinimumMsat = update_bc.htlcMinimumMsat, feeBaseMsat = update_bc.feeBaseMsat, feeProportionalMillionths = update_bc.feeProportionalMillionths, htlcMaximumMsat = update_bc.htlcMaximumMsat.get) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index 5298a87115..e9148931d4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.transactions.Transactions.InputInfo import fr.acinq.eclair.wire.protocol.Onion.{ChannelRelayTlvPayload, FinalTlvPayload} import fr.acinq.eclair.wire.protocol.OnionTlv.{AmountToForward, OutgoingCltv, PaymentData} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, nodeFee, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, TimestampSecond, TimestampSecondLong, nodeFee, randomBytes32, randomKey} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scodec.Attempt @@ -378,7 +378,7 @@ object PaymentPacketSpec { val (priv_a, priv_b, priv_c, priv_d, priv_e) = (TestConstants.Alice.nodeKeyManager.nodeKey, TestConstants.Bob.nodeKeyManager.nodeKey, randomExtendedPrivateKey, randomExtendedPrivateKey, randomExtendedPrivateKey) val (a, b, c, d, e) = (priv_a.publicKey, priv_b.publicKey, priv_c.publicKey, priv_d.publicKey, priv_e.publicKey) val sig = Crypto.sign(Crypto.sha256(ByteVector.empty), priv_a.privateKey) - val defaultChannelUpdate = ChannelUpdate(sig, Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(0), 42000 msat, 0 msat, 0, Some(500000000 msat)) + val defaultChannelUpdate = ChannelUpdate(sig, Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(0), 42000 msat, 0 msat, 0, Some(500000000 msat)) val channelUpdate_ab = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(1), cltvExpiryDelta = CltvExpiryDelta(4), feeBaseMsat = 642000 msat, feeProportionalMillionths = 7) val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(2), cltvExpiryDelta = CltvExpiryDelta(5), feeBaseMsat = 153000 msat, feeProportionalMillionths = 4) val channelUpdate_cd = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(3), cltvExpiryDelta = CltvExpiryDelta(10), feeBaseMsat = 60000 msat, feeProportionalMillionths = 1) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala index 8d8fda17af..a35656e7b8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala @@ -17,17 +17,16 @@ package fr.acinq.eclair.payment import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.{Block, BtcDouble, ByteVector32, Crypto, MilliBtcDouble, Protocol, SatoshiLong} +import fr.acinq.bitcoin.{Block, BtcDouble, ByteVector32, Crypto, MilliBtcDouble, SatoshiLong} import fr.acinq.eclair.FeatureSupport.Mandatory import fr.acinq.eclair.Features.{PaymentSecret, _} import fr.acinq.eclair.payment.PaymentRequest._ -import fr.acinq.eclair.{CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, ShortChannelId, TestConstants, ToMilliSatoshiConversion} +import fr.acinq.eclair.{CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, ShortChannelId, TestConstants, TimestampSecond, TimestampSecondLong, ToMilliSatoshiConversion} import org.scalatest.funsuite.AnyFunSuite import scodec.DecodeResult import scodec.bits._ import scodec.codecs.bits -import java.nio.ByteOrder import scala.util.Success /** @@ -98,7 +97,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.paymentSecret.map(_.bytes) === Some(hex"1111111111111111111111111111111111111111111111111111111111111111")) assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Left("Please consider supporting this project")) assert(pr.fallbackAddress() === None) @@ -113,7 +112,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(250000000 msat)) assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Left("1 cup coffee")) assert(pr.fallbackAddress() === None) @@ -128,7 +127,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(250000000 msat)) assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Left("ナンセンス 1杯")) assert(pr.fallbackAddress() === None) @@ -143,7 +142,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(2000000000 msat)) assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Right(Crypto.sha256(ByteVector("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon".getBytes)))) assert(pr.fallbackAddress() === None) @@ -158,7 +157,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(2000000000 msat)) assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Right(Crypto.sha256(ByteVector.view("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon".getBytes)))) assert(pr.fallbackAddress() === Some("mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP")) @@ -173,7 +172,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(2000000000 msat)) assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Right(Crypto.sha256(ByteVector.view("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon".getBytes)))) assert(pr.fallbackAddress() === Some("1RustyRX2oai4EYYDpQGWvEL62BBGqN9T")) @@ -192,7 +191,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(2000000000 msat)) assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Right(Crypto.sha256(ByteVector.view("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon".getBytes)))) assert(pr.fallbackAddress() === Some("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX")) @@ -207,7 +206,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(2000000000 msat)) assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Right(Crypto.sha256(ByteVector.view("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon".getBytes)))) assert(pr.fallbackAddress() === Some("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")) @@ -223,7 +222,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) assert(!pr.features.allowMultiPart) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Right(Crypto.sha256(ByteVector.view("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon".getBytes)))) assert(pr.fallbackAddress() === Some("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3")) @@ -239,7 +238,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.paymentHash.bytes == hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) assert(!pr.features.allowMultiPart) - assert(pr.timestamp == 1496314658L) + assert(pr.timestamp == TimestampSecond(1496314658L)) assert(pr.nodeId == PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description == Right(Crypto.sha256(ByteVector.view("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon".getBytes)))) assert(pr.fallbackAddress() === Some("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3")) @@ -263,7 +262,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(2500000000L msat)) assert(pr.paymentHash.bytes === hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.paymentSecret === Some(ByteVector32(hex"1111111111111111111111111111111111111111111111111111111111111111"))) - assert(pr.timestamp === 1496314658L) + assert(pr.timestamp === TimestampSecond(1496314658L)) assert(pr.nodeId === PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description === Left("coffee beans")) assert(pr.features.bitmask === bin"1000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000100000000") @@ -282,7 +281,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.amount === Some(2500000000L msat)) assert(pr.paymentHash.bytes === hex"0001020304050607080900010203040506070809000102030405060708090102") assert(pr.paymentSecret === Some(ByteVector32(hex"1111111111111111111111111111111111111111111111111111111111111111"))) - assert(pr.timestamp === 1496314658L) + assert(pr.timestamp === TimestampSecond(1496314658L)) assert(pr.nodeId === PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description === Left("coffee beans")) assert(pr.fallbackAddress().isEmpty) @@ -302,7 +301,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(pr.paymentHash.bytes === hex"462264ede7e14047e9b249da94fefc47f41f7d02ee9b091815a5506bc8abf75f") assert(pr.features.features === Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory)) assert(pr.features.areSupported(TestConstants.Alice.nodeParams)) - assert(pr.timestamp === 1572468703L) + assert(pr.timestamp === TimestampSecond(1572468703L)) assert(pr.nodeId === PublicKey(hex"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad")) assert(pr.description === Left("Blockstream Store: 88.85 USD for Blockstream Ledger Nano S x 1, \"Back In My Day\" Sticker x 2, \"I Got Lightning Working\" Sticker x 2 and 1 more items")) assert(pr.fallbackAddress().isEmpty) @@ -345,7 +344,7 @@ class PaymentRequestSpec extends AnyFunSuite { assert(field1 == field) // Now with a payment request - val pr = PaymentRequest(chainHash = Block.LivenetGenesisBlock.hash, amount = Some(123 msat), paymentHash = ByteVector32(ByteVector.fill(32)(1)), privateKey = priv, description = Left("Some invoice"), minFinalCltvExpiryDelta = CltvExpiryDelta(18), expirySeconds = Some(123456), timestamp = 12345) + val pr = PaymentRequest(chainHash = Block.LivenetGenesisBlock.hash, amount = Some(123 msat), paymentHash = ByteVector32(ByteVector.fill(32)(1)), privateKey = priv, description = Left("Some invoice"), minFinalCltvExpiryDelta = CltvExpiryDelta(18), expirySeconds = Some(123456), timestamp = 12345 unixsec) assert(pr.minFinalCltvExpiryDelta === Some(CltvExpiryDelta(18))) val serialized = PaymentRequest.write(pr) val pr1 = PaymentRequest.read(serialized) @@ -356,7 +355,7 @@ class PaymentRequestSpec extends AnyFunSuite { val pr = PaymentRequest( prefix = "lntb", amount = Some(100000 msat), - timestamp = System.currentTimeMillis() / 1000L, + timestamp = TimestampSecond.now(), nodeId = nodeId, tags = List( PaymentHash(ByteVector32(ByteVector.fill(32)(1))), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala index 431654bd8f..f298f14728 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala @@ -33,7 +33,7 @@ import fr.acinq.eclair.router.Router.ChannelHop import fr.acinq.eclair.transactions.{DirectedHtlc, IncomingHtlc, OutgoingHtlc} import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, CustomCommitmentsPlugin, MilliSatoshi, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, randomBytes32} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, CustomCommitmentsPlugin, MilliSatoshi, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, TimestampMilli, TimestampMilliLong, randomBytes32} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, ParallelTestExecution} import scodec.bits.ByteVector @@ -714,9 +714,9 @@ object PostRestartHtlcCleanerSpec { val origin3 = Origin.LocalCold(id3) // Prepare channels and payment state before restart. - nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id1, id1, None, paymentHash1, PaymentType.Standard, add1.amountMsat, add1.amountMsat, c, 0, None, OutgoingPaymentStatus.Pending)) - nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id2, parentId, None, paymentHash2, PaymentType.Standard, add2.amountMsat, 2500 msat, c, 0, None, OutgoingPaymentStatus.Pending)) - nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id3, parentId, None, paymentHash2, PaymentType.Standard, add3.amountMsat, 2500 msat, c, 0, None, OutgoingPaymentStatus.Pending)) + nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id1, id1, None, paymentHash1, PaymentType.Standard, add1.amountMsat, add1.amountMsat, c, 0 unixms, None, OutgoingPaymentStatus.Pending)) + nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id2, parentId, None, paymentHash2, PaymentType.Standard, add2.amountMsat, 2500 msat, c, 0 unixms, None, OutgoingPaymentStatus.Pending)) + nodeParams.db.payments.addOutgoingPayment(OutgoingPayment(id3, parentId, None, paymentHash2, PaymentType.Standard, add3.amountMsat, 2500 msat, c, 0 unixms, None, OutgoingPaymentStatus.Pending)) nodeParams.db.channels.addOrUpdateChannel(ChannelCodecsSpec.makeChannelDataNormal( Seq(add1, add2, add3).map(add => OutgoingHtlc(add)), Map(add1.id -> origin1, add2.id -> origin2, add3.id -> origin3)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala index 35d40c7d19..25ea87e986 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala @@ -407,7 +407,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a assert(fwd2.message.r === paymentPreimage) val paymentRelayed = eventListener.expectMessageType[ChannelPaymentRelayed] - assert(paymentRelayed.copy(timestamp = 0) === ChannelPaymentRelayed(r.add.amountMsat, r.payload.amountToForward, r.add.paymentHash, r.add.channelId, channelId1, timestamp = 0)) + assert(paymentRelayed.copy(timestamp = 0 unixms) === ChannelPaymentRelayed(r.add.amountMsat, r.payload.amountToForward, r.add.paymentHash, r.add.channelId, channelId1, timestamp = 0 unixms)) } } @@ -492,7 +492,7 @@ object ChannelRelayerSpec { def createLocalUpdate(shortChannelId: ShortChannelId, balance: MilliSatoshi = 10000000 msat, capacity: Satoshi = 500000 sat, enabled: Boolean = true, htlcMinimum: MilliSatoshi = 0 msat): LocalChannelUpdate = { val channelId = channelIds(shortChannelId) - val update = ChannelUpdate(ByteVector64(randomBytes(64)), Block.RegtestGenesisBlock.hash, shortChannelId, 0, ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = enabled), CltvExpiryDelta(100), htlcMinimum, 1000 msat, 100, Some(capacity.toMilliSatoshi)) + val update = ChannelUpdate(ByteVector64(randomBytes(64)), Block.RegtestGenesisBlock.hash, shortChannelId, 0 unixsec, ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = enabled), CltvExpiryDelta(100), htlcMinimum, 1000 msat, 100, Some(capacity.toMilliSatoshi)) val commitments = PaymentPacketSpec.makeCommitments(channelId, testAvailableBalanceForSend = balance, testCapacity = capacity) LocalChannelUpdate(null, channelId, shortChannelId, outgoingNodeId, None, update, commitments) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala index 32c6283bd0..d7fd68cdbb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala @@ -56,7 +56,7 @@ class AnnouncementsSpec extends AnyFunSuite { val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, Alice.nodeParams.publicAddresses, Alice.nodeParams.features) assert(ann.features.hasFeature(Features.VariableLengthOnion, Some(FeatureSupport.Mandatory))) assert(checkSig(ann)) - assert(checkSig(ann.copy(timestamp = 153)) === false) + assert(checkSig(ann.copy(timestamp = 153 unixsec)) === false) } test("sort node announcement addresses") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala index 7a7cd4876e..9bd6348636 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair.wire.protocol.QueryChannelRangeTlv.QueryFlags import fr.acinq.eclair.wire.protocol.QueryShortChannelIdsTlv.QueryFlagType._ import fr.acinq.eclair.wire.protocol.ReplyChannelRangeTlv._ import fr.acinq.eclair.wire.protocol.{EncodedShortChannelIds, EncodingType, ReplyChannelRange} -import fr.acinq.eclair.{MilliSatoshiLong, ShortChannelId, randomKey} +import fr.acinq.eclair.{MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits.ByteVector @@ -36,11 +36,11 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { test("ask for update test") { // they don't provide anything => we always ask for the update - assert(shouldRequestUpdate(0, 0, None, None)) - assert(shouldRequestUpdate(Int.MaxValue, 12345, None, None)) + assert(shouldRequestUpdate(0 unixsec, 0, None, None)) + assert(shouldRequestUpdate(TimestampSecond(Int.MaxValue), 12345, None, None)) // their update is older => don't ask - val now = System.currentTimeMillis / 1000 + val now = TimestampSecond.now() assert(!shouldRequestUpdate(now, 0, Some(now - 1), None)) assert(!shouldRequestUpdate(now, 0, Some(now - 1), Some(12345))) assert(!shouldRequestUpdate(now, 12344, Some(now - 1), None)) @@ -66,7 +66,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { assert(shouldRequestUpdate(now - 1, 12344, Some(now), Some(12345))) // they just provided a 0 checksum => don't ask - assert(!shouldRequestUpdate(0, 0, None, Some(0))) + assert(!shouldRequestUpdate(0 unixsec, 0, None, Some(0))) assert(!shouldRequestUpdate(now, 1234, None, Some(0))) // they just provided a checksum that is the same as us => don't ask @@ -84,7 +84,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { } test("compute flag tests") { - val now = System.currentTimeMillis / 1000 + val now = TimestampSecond.now() val a = randomKey().publicKey val b = randomKey().publicKey diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/NetworkStatsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/NetworkStatsSpec.scala index 95c4cb2a2d..10540278d9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/NetworkStatsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/NetworkStatsSpec.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{Satoshi, SatoshiLong} import fr.acinq.eclair.router.Router.{ChannelMeta, PublicChannel} import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate} -import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, ShortChannelId, randomBytes32, randomBytes64, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, randomBytes32, randomBytes64, randomKey} import org.scalatest.funsuite.AnyFunSuite import scala.util.Random @@ -87,11 +87,11 @@ object NetworkStatsSpec { } def fakeChannelUpdate1(cltv: CltvExpiryDelta, feeBase: MilliSatoshi, feeProportional: Long): ChannelUpdate = { - ChannelUpdate(randomBytes64(), randomBytes32(), ShortChannelId(42), 0, ChannelUpdate.ChannelFlags.DUMMY, cltv, 1 msat, feeBase, feeProportional, None) + ChannelUpdate(randomBytes64(), randomBytes32(), ShortChannelId(42), 0 unixsec, ChannelUpdate.ChannelFlags.DUMMY, cltv, 1 msat, feeBase, feeProportional, None) } def fakeChannelUpdate2(cltv: CltvExpiryDelta, feeBase: MilliSatoshi, feeProportional: Long): ChannelUpdate = { - ChannelUpdate(randomBytes64(), randomBytes32(), ShortChannelId(42), 0, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), cltv, 1 msat, feeBase, feeProportional, None) + ChannelUpdate(randomBytes64(), randomBytes32(), ShortChannelId(42), 0 unixsec, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), cltv, 1 msat, feeBase, feeProportional, None) } } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala index bbf1d06a33..089856e7be 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.router.RouteCalculation._ import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, ToMilliSatoshiConversion, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, ToMilliSatoshiConversion, randomKey} import org.scalatest.funsuite.AnyFunSuite import org.scalatest.{ParallelTestExecution, Tag} import scodec.bits._ @@ -417,14 +417,14 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { test("calculate route and return metadata") { val DUMMY_SIG = Transactions.PlaceHolderSig - val uab = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 0L, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 42 msat, 2500 msat, 140, None) - val uba = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 1L, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 43 msat, 2501 msat, 141, None) - val ubc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 44 msat, 2502 msat, 142, None) - val ucb = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 45 msat, 2503 msat, 143, None) - val ucd = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 46 msat, 2504 msat, 144, Some(500000000 msat)) - val udc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 47 msat, 2505 msat, 145, None) - val ude = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 48 msat, 2506 msat, 146, None) - val ued = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 49 msat, 2507 msat, 147, None) + val uab = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 0 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 42 msat, 2500 msat, 140, None) + val uba = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 1 unixsec, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 43 msat, 2501 msat, 141, None) + val ubc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 44 msat, 2502 msat, 142, None) + val ucb = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1 unixsec, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 45 msat, 2503 msat, 143, None) + val ucd = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 46 msat, 2504 msat, 144, Some(500000000 msat)) + val udc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1 unixsec, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 47 msat, 2505 msat, 145, None) + val ude = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(1), 48 msat, 2506 msat, 146, None) + val ued = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1 unixsec, ChannelUpdate.ChannelFlags(isNode1 = false, isEnabled = false), CltvExpiryDelta(1), 49 msat, 2507 msat, 147, None) val edges = Seq( GraphEdge(ChannelDesc(ShortChannelId(1L), a, b), uab, DEFAULT_CAPACITY, None), @@ -918,24 +918,24 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { ann = makeChannel(ShortChannelId("565643x1216x0").toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, - update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(14), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 10, Some(4294967295L msat))), - update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 0 msat, feeBaseMsat = 1000 msat, 100, Some(15000000000L msat))), + update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(14), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 10, Some(4294967295L msat))), + update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0 unixsec, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 0 msat, feeBaseMsat = 1000 msat, 100, Some(15000000000L msat))), meta_opt = None ), ShortChannelId("542280x2156x0") -> PublicChannel( ann = makeChannel(ShortChannelId("542280x2156x0").toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, - update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(144), htlcMinimumMsat = 1000 msat, feeBaseMsat = 1000 msat, 100, Some(16777000000L msat))), - update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 667 msat, 1, Some(16777000000L msat))), + update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(144), htlcMinimumMsat = 1000 msat, feeBaseMsat = 1000 msat, 100, Some(16777000000L msat))), + update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0 unixsec, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 667 msat, 1, Some(16777000000L msat))), meta_opt = None ), ShortChannelId("565779x2711x0") -> PublicChannel( ann = makeChannel(ShortChannelId("565779x2711x0").toLong, PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, - update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 100, Some(230000000L msat))), - update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 100, Some(230000000L msat))), + update_1_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 100, Some(230000000L msat))), + update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0 unixsec, ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 1000 msat, 100, Some(230000000L msat))), meta_opt = None ) ) @@ -1868,7 +1868,7 @@ object RouteCalculationSpec { GraphEdge(ChannelDesc(ShortChannelId(shortChannelId), nodeId1, nodeId2), update, capacity, balance_opt) } - def makeUpdateShort(shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, feeBase: MilliSatoshi, feeProportionalMillionth: Int, minHtlc: MilliSatoshi = DEFAULT_AMOUNT_MSAT, maxHtlc: Option[MilliSatoshi] = None, cltvDelta: CltvExpiryDelta = CltvExpiryDelta(0), timestamp: Long = 0): ChannelUpdate = + def makeUpdateShort(shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, feeBase: MilliSatoshi, feeProportionalMillionth: Int, minHtlc: MilliSatoshi = DEFAULT_AMOUNT_MSAT, maxHtlc: Option[MilliSatoshi] = None, cltvDelta: CltvExpiryDelta = CltvExpiryDelta(0), timestamp: TimestampSecond = 0 unixsec): ChannelUpdate = ChannelUpdate( signature = DUMMY_SIG, chainHash = Block.RegtestGenesisBlock.hash, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 13a2c4c2aa..5c4b52b496 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -33,7 +33,7 @@ import fr.acinq.eclair.router.RouteCalculationSpec.{DEFAULT_AMOUNT_MSAT, DEFAULT import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, TimestampSecond, randomKey} import scodec.bits._ import scala.concurrent.duration._ @@ -55,7 +55,7 @@ class RouterSpec extends BaseRouterSpec { // valid channel announcement, no stashing val chan_ac = channelAnnouncement(ShortChannelId(420000, 5, 0), priv_a, priv_c, priv_funding_a, priv_funding_c) val update_ac = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, chan_ac.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) - val node_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features, timestamp = System.currentTimeMillis.milliseconds.toSeconds + 1) + val node_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features, timestamp = TimestampSecond.now() + 1) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ac)) peerConnection.expectNoMessage(100 millis) // we don't immediately acknowledge the announcement (back pressure) assert(watcher.expectMsgType[ValidateRequest].ann === chan_ac) @@ -160,7 +160,7 @@ class RouterSpec extends BaseRouterSpec { { // stale channel update - val update_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_b.publicKey, chan_ab.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum, timestamp = (System.currentTimeMillis.milliseconds - 15.days).toSeconds) + val update_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_b.publicKey, chan_ab.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum, timestamp = TimestampSecond.now() - 15.days) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_ab)) peerConnection.expectMsg(TransportHandler.ReadAck(update_ab)) peerConnection.expectMsg(GossipDecision.Stale(update_ab)) @@ -550,7 +550,7 @@ class RouterSpec extends BaseRouterSpec { val blockHeight = 400000 - 2020 val channelId = ShortChannelId(blockHeight, 5, 0) val announcement = channelAnnouncement(channelId, priv_a, priv_c, priv_funding_a, priv_funding_c) - val oldTimestamp = (System.currentTimeMillis.milliseconds - 14.days - 1.day).toSeconds + val oldTimestamp = TimestampSecond.now() - 14.days - 1.day val staleUpdate = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, 5 msat, timestamp = oldTimestamp) val peerConnection = TestProbe() peerConnection.ignoreMsg { case _: TransportHandler.ReadAck => true } @@ -567,7 +567,7 @@ class RouterSpec extends BaseRouterSpec { sender.send(router, GetRoutingState) sender.expectMsgType[RoutingState] - val recentUpdate = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum, timestamp = System.currentTimeMillis.millisecond.toSeconds) + val recentUpdate = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum, timestamp = TimestampSecond.now()) // we want to make sure that transport receives the query peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, recentUpdate)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala index 32d73abd44..f3fccd4a46 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala @@ -340,7 +340,7 @@ object RoutingSyncSpec { val unused: PrivateKey = randomKey() def makeFakeRoutingInfo(pub2priv: mutable.Map[PublicKey, PrivateKey])(shortChannelId: ShortChannelId): (PublicChannel, NodeAnnouncement, NodeAnnouncement) = { - val timestamp = System.currentTimeMillis / 1000 + val timestamp = TimestampSecond.now() val (priv1, priv2) = { val (priv_a, priv_b) = (randomKey(), randomKey()) if (Announcements.isNode1(priv_a.publicKey, priv_b.publicKey)) (priv_a, priv_b) else (priv_b, priv_a) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index cf8fa14aa6..fdd1b7953b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -80,7 +80,7 @@ class ChannelCodecsSpec extends AnyFunSuite { // let's decode the old data (this will use the old codec that provides default values for new fields) val data_new = stateDataCodec.decode(bin_old.toBitVector).require.value assert(data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx === None) - assert(System.currentTimeMillis.milliseconds.toSeconds - data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].waitingSinceBlock < 3600) // we just set this timestamp to current time + assert(TimestampSecond.now().toLong - data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].waitingSinceBlock < 3600) // we just set this timestamp to current time // and re-encode it with the new codec val bin_new = ByteVector(stateDataCodec.encode(data_new).require.toByteVector.toArray) // data should now be encoded under the new format @@ -127,11 +127,11 @@ class ChannelCodecsSpec extends AnyFunSuite { // this test makes sure that we actually produce the same objects than previous versions of eclair val refs = Map( hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B134000456E4167E3C0EB8C856C79CA31C97C0AA0000000000000222000000012A05F2000000000000028F5C000000000000000102D0001E000BD48A2402E80B723C42EE3E42938866EC6686ABB7ABF64380000000C501A7F2974C5074E9E10DBB3F0D9B8C40932EC63ABC610FAD7EB6B21C6D081A459B000000000000011E80000001EEFFFE5C00000000000147AE00000000000001F403F000F18146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB20131AD64F76FAF90CD7DE26892F1BDAB82FB9E02EF6538D82FF4204B5348F02AE081A5388E9474769D69C4F60A763AE0CCDB5228A06281DE64408871A927297FDFD8818B6383985ABD4F0AC22E73791CF3A4D63C592FA2648242D34B8334B1539E823381BB1F1404C37D9C2318F5FC6B1BF7ECF5E6835B779E3BE09BADCF6DF1F51DCFBC80000000C0808000000000000EFD80000000007F00000000061A0A4880000001EDE5F3C3801203B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E808000000015FFFFFF800000000011001029DFB814F6502A68D6F83B6049E3D2948A2080084083750626532FDB437169C20023A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A95700AD0100000000008083B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E80800000001961B4C001618F8180000000001100102E648BA30998A28C02C2DFD9DDCD0E0BA064DA199C55186485AFAB296B94E704426FFE00000000000B000A67D9B9FAADB91650E0146B1F742E5C16006708890200239822011026A6925C659D006FEB42D639F1E42DD13224EE49AA34E71B612CF96DB66A8CD4011032C22F653C54CC5E41098227427650644266D80DED45B7387AE0FFC10E529C4680A418228110807CB47D9C1A14CB832FB361C398EA672C9542F34A90BAD4288FA6AC5FC9E9845C01101CF71CAE9252D389135D8C606225DCF1E0333CCDF1FAE84B74FC5D3D440C25F880A3A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A9573D7C531000000000000000000F3180000000007F00000001EDE5F3C380000000061A0A48D64CA627B243AD5915A2E5D0BAD026762028DDF3304992B83A26D6C11735FC5F01ED56D769BDE7F6A068AF1A4BCFDF950321F3A4744B01B1DDC7498677F112AE1A80000000000000000000000000000000000000658000000000000819800040D37301C10C9419287E9A3B704EB6D7F45CC145DD77DCE8A63B0A47C8AB67467D800901DCE3C8B05A891E56F2BAF1B82405ABD8640B759AEEBD939B976D42C311758F40400000000AFFFFFFC00000000008800814EFDC0A7B2815346B7C1DB024F1E94A451040042041BA83132997EDA1B8B4E10011D48840A33BCFBC0833F6825A4ABF0A78E2B11D5B2981CD958EA4C881204247273416D90840D9834A03892A6C59DCA9B990600A5C65882972A8A7AF7E0CE7975C031846AE78D4AB8002000EC0003FFFFFFFF86801076D98A575A4CDFD0E3F44D1BB3CD3BBAF3BD04C38FED439ED90D88DF932A9296801A80007FFFFFFFF4008136A9D5896669E8724C5120FB6B36C241EF3CEF68AE0316161F04A9EE3EAFF36000FC0003FFFFFFFF86780106E4B5CC4155733A2427082907338051A5DA1E7CA6432840A5528ECAFFA3FB628801B80007FFFFFFFF10020CA4E125E9126107745D4354D4187ABCDE323117857A1DCEB7CCF60B2AAFA80C6003A0000FFFFFFFFE1C0080981575FD981A73A848CC0243CB467BF451F6811DAF4D71CAD8CE8B1E96DB190C01000003FFFFFFFF867400814C747E0FD8290BE8A3B8B3F73015A261479A71780CD3A0A9270234E4B394409C00D80003FFFFFFFF90020E1B9C9B10A97F15F5E1BB27FC8AC670DF8DADEAE4EDFAFB23BDD0AC705FDF51600340000FFFFFFFFF0020AD2581F3494A17B0BE3F63516D53F028A204FD3156D8B21AA4E57A8738D2062080007FFFFFFFF0CE83B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E0B8C1E00000B8000FA46CC2C7E9AB4A37C64216CD65C944E6D73998419D1A1AD2827AB6BC85B32280230764E374064EC82A3751E789607E23BEAE93FB0EDDD5E7FA803767079662E80EAEF384E2AFCB68049D9DC246119E77BD2ED4112330760CAB6CD3671CFCE006C584B9C95E0B554261E00154D40806EA694F44751B328A9291BAD124EFD5664280936EC92D27B242737E7E3E83B4704BA367B7DA5108F2F6EDFB1C38EE721A369E77EED71B12090BAEAAAC322C1457E31AB0C4DE5D9351943F10FD747742616A1AABD09F680B37D4105A8872695EE9B97FAB8985FAA9D747D45046229BF265CEEB300A40FE23040C5F335E0515496C58EE47418B72331FCC6F47A31A9B33B8E000008692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002069FCA5D3141D3A78436ECFC366E31024CBB18EAF1843EB5FADAC871B42069166C0726710955E3AD621072FCBDFCB90D79E5B1951A5EE01DB533B72429F84E2562680519DE7DE0419FB412D255F853C71588EAD94C0E6CAC7526440902123939A0B6C806CC1A501C495362CEE54DCC830052E32C414B95453D7BF0673CBAE018C23573C69C694A8F88483050257A7366B838489731E5776B6FA0F02573401176D3E7FAEEF11E95A671420586631255F51A0EC2CF4D4D9F69D587712070FE1FB9316B71868692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002BA11BBBA0202012000000000000007D0000007D0000000C800000007CFFFF83000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"channelReserve":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isFunder":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"channelReserve":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":1,"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"shortChannelId":"1513532x23x1","buried":true,"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":1560862173,"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}}}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"channelReserve":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isFunder":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"channelReserve":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":1,"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"shortChannelId":"1513532x23x1","buried":true,"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":{"iso":"2019-06-18T12:49:33Z","unix":1560862173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}}}""", hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B1340004D443ECE9D9C43A11A19B554BAAA6AD150000000000000222000000003B9ACA0000000000000249F000000000000000010090001E800BD48A22F4C80A42CC8BB29A764DBAEFC95674931FBE9A4380000000C50134D4A745996002F219B5FDBA1E045374DF589ECA06ABE23CECAE47343E65EDCF800000000000011E80000001BA90824000000000000124F800000000000001F4038500F1810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E2266201E8BFEEEEED725775B8116F6F82CF8E87835A5B45B184E56F272AD70D6078118601E06212B8C8F2E25B73EE7974FDCDF007E389B437BBFE238CCC3F3BF7121B6C5E81AA8589D21E9584B24A11F3ABBA5DAD48D121DD63C57A69CD767119C05DA159CB81A649D8CC0E136EB8DFBD2268B69DCA86F8CE4A604235A03D9D37AE7B07FC563F80000000C080800000000000271C000000000177000000002808B14600000001970039BA00123767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB08800000000015E070F20000000000110010584241B5FB364208F6E64A80D1166DAD866186B10C015ED0283FF1C308C2105A0023A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA95700AD81000000000080B767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB0880000000003E7AEDC0011ABE8A00000000001100101A9CE4B6AEF469590BC7BCC51DCEEAE9C86084055A63CC01E443C733FBE400B9B5B16800000000000B000A5E5700106D1A7097E4DE87EBAF1F8F2773842FA482002418228110805E84989A81F51ABD9D11889AE43E68FAD93659DEC019F1B8C0ADBF15A57B118B81101DCC1256F9306439AD3962C043FC47A5179CAAA001CCB23342BE0E8D92E4022780A4182281108074F306DA3751B84EC5FFB155BDCA7B8E02208BBDBC8D4F3327ABA557BF27CD1701102EF4AC8CC92F469DA9642D4D4162BC545F8B34ADE15B7D6F99808AA22B086B0180A3A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA9576F8099900000000000000000271C00000000017700000001970039BA000000002808B14648CE00AE97051EE10A3C361263F81A98165CE4AA7BA076933D4266E533585F24815C15DEACF0691332B38ECF23EC39982C5C978C748374A01BA9B30D501EE4F26E8000000000000000000000000000000000001224000000000000004B800040A911C460F1467952E3B99BED072F81BFB4454FF389636DCB399FE6A78113C28580091BB3F87A7806AF4FEF920BBF794391A1ECFC7D7632E98245D2BAF3870050558440000000000AF0387900000000000880082C2120DAFD9B21047B732540688B36D6C330C3588600AF68141FF8E18461082D0011D488408570D7C50EB7AB7C042AF13382F8C8DD83E6A7121A5E2DD8B4C73F2C407113310840EF456FD0886E454A6C5CF4F7B0B5D742CC143E47C157EF87E03434BEAB81337ED4AB8001C00F40003FFFFFFFEC7200403248A1D44DFA3AC9EC237D452C936400CAA86E9517CCCF2A8F77B7493CD70B6A00780001FFFFFFFF63A0041826829646B907A97FBD1455EA8673A12B8E7AA6EA790F7802E955CE3B69DE57E006E0001FFFFFFFF640081E51EB1F91218821E680B50E4B22DF8B094385BD33ACAE36BFC9E8C2F5AD2DA5400EC0003FFFFFFFEC7801047C26AD5435658D063EBCF73A5D0EEFE73ED6B73426246E8DFB3A21D1C4C7465001900007FFFFFFFE0040B115AC58BAAA900195893EA3B2AB408D2AD348AD047E3B6CB15E599625E38608006A0001FFFFFFFF7002033C39A21A38BB61F6FB33623771A9356D8885B7C12C939C770C939EF826286C200360000FFFFFFFFB4008104EF4271064A0973B053727C3E67352D00E25CAEED944F50782449CEAE8F50960001FFFFFFFF6390DD9FC3D3C0357A7F7C905DFBCA1C8D0F67E3EBB1974C122E95D79C380282AC222B21FA0007920001295AA1FB77029F7620A90EF7AE6A6CD31E4588B93264A7ADB76152D535C52E90B9E1B7C2376DABA316A6290F1A9730D4E5E44D0B1CB0EE6A795702E6A6BCDFCDA1A4BFEBFC134AB8847A5187ECE761D75D3CCB904274875680F51984800000000AC87E8001E480002E884D2A8080804800000000000001F4000001F40000003200000001BF08EB000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isFunder":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"channelReserve":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":1,"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"1413373x969x0","buried":true,"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":1561369173,"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isFunder":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"channelReserve":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":1,"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"1413373x969x0","buried":true,"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":{"iso":"2019-06-24T09:39:33Z","unix":1561369173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""", hex"0200020000000303933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400098c4b989bbdced820a77a7186c2320e7d176a5c8b5c16d6ac2af3889d6bc8bf8080000001000000000000022200000004a817c80000000000000249f0000000000000000102d0001eff1600148061b7fbd2d84ed1884177ea785faecb2080b10302e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b300000004080aa982027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8000000000000023d000000037521048000000000000249f00000000000000001070a01e302eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b7503c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a5700000004808a52a1010000000000000004000000001046000000037e11d6000000000000000000245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aefd013b020000000001015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61040047304402207f8c1936d0a50671c993890f887c78c6019abc2a2e8018899dcdc0e891fd2b090220046b56afa2cb7e9470073c238654ecf584bcf5c00b96b91e38335a70e2739ec901483045022100871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c0220119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b01475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aed7782c20000000000000000000040000000010460000000000000000000000037e11d600b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d802e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a000000000000000000000000000000000000000000000000000000000000ff03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52ae0001003e0000fffffffffffc0080474b8cf7bb98217dd8dc475cb7c057a3465d466728978bbb909d0a05d4ae7bbe0001fffffffffff85986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b1eedce0000010000fffffd01ae98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be54920134196992f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef09bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce0000010000027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b803933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13402eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d88710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce000001000060e6eb14010100900000000000000001000003e800000064000000037e11d6000000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isFunder":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":1,"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"2026958x1x0","buried":true,"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":1625746196,"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isFunder":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"channelReserve":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":1,"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"2026958x1x0","buried":true,"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":{"iso":"2021-07-08T12:09:56Z","unix":1625746196},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" ) refs.foreach { case (oldbin, refjson) => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala index f21bd976bc..bec1abf9bd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64} import fr.acinq.eclair.router.Sync import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.ReplyChannelRangeTlv._ -import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, UInt64} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, UInt64} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -118,7 +118,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { 1, 100, 1.toByte, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), - Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1, 1), Timestamps(2, 2), Timestamps(3, 3)))), + Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec)))), None) val encoded = replyChannelRangeCodec.encode(replyChannelRange).require @@ -134,7 +134,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream( List( - EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1, 1), Timestamps(2, 2), Timestamps(3, 3))), + EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec))), EncodedChecksums(List(Checksums(1, 1), Checksums(2, 2), Checksums(3, 3))) ), GenericTlv(UInt64(7), ByteVector.fromValidHex("deadbeef")) :: Nil @@ -151,7 +151,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { chainHash = ByteVector32.fromValidHex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), signature = ByteVector64.fromValidHex("76df7e70c63cc2b63ef1c062b99c6d934a80ef2fd4dae9e1d86d277f47674af3255a97fa52ade7f129263f591ed784996eba6383135896cc117a438c80293282"), shortChannelId = ShortChannelId("103x1x0"), - timestamp = 1565587763L, + timestamp = TimestampSecond(1565587763L), channelFlags = ChannelUpdate.ChannelFlags.DUMMY, cltvExpiryDelta = CltvExpiryDelta(144), htlcMinimumMsat = 0 msat, @@ -171,7 +171,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { chainHash = ByteVector32.fromValidHex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), signature = ByteVector64.fromValidHex("06737e9e18d3e4d0ab4066ccaecdcc10e648c5f1c5413f1610747e0d463fa7fa39c1b02ea2fd694275ecfefe4fe9631f24afd182ab75b805e16cd550941f858c"), shortChannelId = ShortChannelId("109x1x0"), - timestamp = 1565587765L, + timestamp = TimestampSecond(1565587765L), channelFlags = ChannelUpdate.ChannelFlags.DUMMY, cltvExpiryDelta = CltvExpiryDelta(48), htlcMinimumMsat = 0 msat, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala index c0fc9bc79f..2d7acc1dbb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64} import fr.acinq.eclair.crypto.Hmac256 import fr.acinq.eclair.wire.protocol.FailureMessageCodecs._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, ShortChannelId, UInt64, randomBytes32, randomBytes64} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, UInt64, randomBytes32, randomBytes64} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -32,7 +32,7 @@ class FailureMessageCodecsSpec extends AnyFunSuite { signature = randomBytes64(), chainHash = Block.RegtestGenesisBlock.hash, shortChannelId = ShortChannelId(12345), - timestamp = 1234567L, + timestamp = TimestampSecond(1234567L), cltvExpiryDelta = CltvExpiryDelta(100), channelFlags = ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), htlcMinimumMsat = 1000 msat, @@ -148,7 +148,7 @@ class FailureMessageCodecsSpec extends AnyFunSuite { test("support encoding of channel_update with/without type in failure messages") { val tmp_channel_failure_notype = hex"10070080cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f45782196fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008260500041300005b91b52f0003000e00000000000003e80000000100000001" val tmp_channel_failure_withtype = hex"100700820102cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f45782196fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008260500041300005b91b52f0003000e00000000000003e80000000100000001" - val ref = TemporaryChannelFailure(ChannelUpdate(ByteVector64(hex"cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f4578219"), Block.LivenetGenesisBlock.hash, ShortChannelId(0x826050004130000L), 1536275759, ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = false), CltvExpiryDelta(14), 1000 msat, 1 msat, 1, None)) + val ref = TemporaryChannelFailure(ChannelUpdate(ByteVector64(hex"cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f4578219"), Block.LivenetGenesisBlock.hash, ShortChannelId(0x826050004130000L), 1536275759 unixsec, ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = false), CltvExpiryDelta(14), 1000 msat, 1 msat, 1, None)) val u = failureMessageCodec.decode(tmp_channel_failure_notype.toBitVector).require.value assert(u === ref) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 2c6a57432a..8fcbca9e59 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -264,10 +264,10 @@ class LightningMessageCodecsSpec extends AnyFunSuite { val commit_sig = CommitSig(randomBytes32(), randomBytes64(), randomBytes64() :: randomBytes64() :: randomBytes64() :: Nil) val revoke_and_ack = RevokeAndAck(randomBytes32(), scalar(0), point(1)) val channel_announcement = ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(bin(7, 9)), Block.RegtestGenesisBlock.hash, ShortChannelId(1), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey) - val node_announcement = NodeAnnouncement(randomBytes64(), Features(bin(1, 2)), 1, randomKey().publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil) - val channel_update = ChannelUpdate(randomBytes64(), Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(3), 4 msat, 5 msat, 6, None) + val node_announcement = NodeAnnouncement(randomBytes64(), Features(bin(1, 2)), 1 unixsec, randomKey().publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil) + val channel_update = ChannelUpdate(randomBytes64(), Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(3), 4 msat, 5 msat, 6, None) val announcement_signatures = AnnouncementSignatures(randomBytes32(), ShortChannelId(42), randomBytes64(), randomBytes64()) - val gossip_timestamp_filter = GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, 100000, 1500) + val gossip_timestamp_filter = GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, 100000 unixsec, 1500) val query_short_channel_id = QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream.empty) val unknownTlv = GenericTlv(UInt64(5), ByteVector.fromValidHex("deadbeef")) val query_channel_range = QueryChannelRange(Block.RegtestGenesisBlock.blockId, @@ -277,7 +277,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { val reply_channel_range = ReplyChannelRange(Block.RegtestGenesisBlock.blockId, 100000, 1500, 1, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream( - EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(1, 1), Timestamps(2, 2), Timestamps(3, 3))) :: EncodedChecksums(List(Checksums(1, 1), Checksums(2, 2), Checksums(3, 3))) :: Nil, + EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec))) :: EncodedChecksums(List(Checksums(1, 1), Checksums(2, 2), Checksums(3, 3))) :: Nil, unknownTlv :: Nil) ) val ping = Ping(100, bin(10, 1)) @@ -343,11 +343,11 @@ class LightningMessageCodecsSpec extends AnyFunSuite { EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(265462))), None, None) val reply_channel_range_timestamps_checksums = ReplyChannelRange(Block.RegtestGenesisBlock.blockId, 122334, 1500, 1, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(12355), ShortChannelId(489686), ShortChannelId(4645313))), - Some(EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(164545, 948165), Timestamps(489645, 4786864), Timestamps(46456, 9788415)))), + Some(EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(164545 unixsec, 948165 unixsec), Timestamps(489645 unixsec, 4786864 unixsec), Timestamps(46456 unixsec, 9788415 unixsec)))), Some(EncodedChecksums(List(Checksums(1111, 2222), Checksums(3333, 4444), Checksums(5555, 6666))))) val reply_channel_range_timestamps_checksums_zlib = ReplyChannelRange(Block.RegtestGenesisBlock.blockId, 122334, 1500, 1, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(12355), ShortChannelId(489686), ShortChannelId(4645313))), - Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(164545, 948165), Timestamps(489645, 4786864), Timestamps(46456, 9788415)))), + Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(164545 unixsec, 948165 unixsec), Timestamps(489645 unixsec, 4786864 unixsec), Timestamps(46456 unixsec, 9788415 unixsec)))), Some(EncodedChecksums(List(Checksums(1111, 2222), Checksums(3333, 4444), Checksums(5555, 6666))))) val query_short_channel_id = QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream.empty) val query_short_channel_id_zlib = QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(4564), ShortChannelId(178622), ShortChannelId(4564676))), TlvStream.empty) @@ -427,7 +427,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { // this was generated by c-lightning val bin = hex"010258fff7d0e987e2cdd560e3bb5a046b4efe7b26c969c2f51da1dceec7bcb8ae1b634790503d5290c1a6c51d681cf8f4211d27ed33a257dcc1102862571bf1792306226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0005a100000200005bc75919010100060000000000000001000000010000000a000000003a699d00" val update = lightningMessageCodec.decode(bin.bits).require.value.asInstanceOf[ChannelUpdate] - assert(update === ChannelUpdate(ByteVector64(hex"58fff7d0e987e2cdd560e3bb5a046b4efe7b26c969c2f51da1dceec7bcb8ae1b634790503d5290c1a6c51d681cf8f4211d27ed33a257dcc1102862571bf17923"), ByteVector32(hex"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), ShortChannelId(0x5a10000020000L), 1539791129, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(6), 1 msat, 1 msat, 10, Some(980000000 msat))) + assert(update === ChannelUpdate(ByteVector64(hex"58fff7d0e987e2cdd560e3bb5a046b4efe7b26c969c2f51da1dceec7bcb8ae1b634790503d5290c1a6c51d681cf8f4211d27ed33a257dcc1102862571bf17923"), ByteVector32(hex"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), ShortChannelId(0x5a10000020000L), 1539791129 unixsec, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(6), 1 msat, 1 msat, 10, Some(980000000 msat))) val nodeId = PublicKey(hex"03370c9bac836e557eb4f017fe8f9cc047f44db39c1c4e410ff0f7be142b817ae4") assert(Announcements.checkSig(update, nodeId)) val bin2 = ByteVector(lightningMessageCodec.encode(update).require.toByteArray) @@ -437,21 +437,21 @@ class LightningMessageCodecsSpec extends AnyFunSuite { test("non-regression on channel_update") { val bins = Map( hex"3b6bb4872825450ff29d0b46f5835751329b0394a10ac792e4ba2a23b4f17bcc4e5834d1424787830be0ee3d22ac99e674d121f25d19ed931aaabb8ed0eec0fb6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000086a4e000a9700016137e9e9010200900000000000000001000003e800000001000000001dcd6500" -> - """{"signature":"3b6bb4872825450ff29d0b46f5835751329b0394a10ac792e4ba2a23b4f17bcc4e5834d1424787830be0ee3d22ac99e674d121f25d19ed931aaabb8ed0eec0fb","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"551502x2711x1","timestamp":1631054313,"channelFlags":{"isEnabled":false,"isNode1":true},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":500000000,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"3b6bb4872825450ff29d0b46f5835751329b0394a10ac792e4ba2a23b4f17bcc4e5834d1424787830be0ee3d22ac99e674d121f25d19ed931aaabb8ed0eec0fb","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"551502x2711x1","timestamp":{"iso":"2021-09-07T22:38:33Z","unix":1631054313},"channelFlags":{"isEnabled":false,"isNode1":true},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":500000000,"tlvStream":{"records":[],"unknown":[]}}""", hex"12540b6a236e21932622d61432f52913d9442cc09a1057c386119a286153f8681c66d2a0f17d32505ba71bb37c8edcfa9c11e151b2b38dae98b825eff1c040b36fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008850f00058e00015e6a782e0000009000000000000003e8000003e800000002" -> - """{"signature":"12540b6a236e21932622d61432f52913d9442cc09a1057c386119a286153f8681c66d2a0f17d32505ba71bb37c8edcfa9c11e151b2b38dae98b825eff1c040b3","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"558351x1422x1","timestamp":1584035886,"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":2,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"12540b6a236e21932622d61432f52913d9442cc09a1057c386119a286153f8681c66d2a0f17d32505ba71bb37c8edcfa9c11e151b2b38dae98b825eff1c040b3","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"558351x1422x1","timestamp":{"iso":"2020-03-12T17:58:06Z","unix":1584035886},"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":2,"tlvStream":{"records":[],"unknown":[]}}""", hex"8efb98c939aba422a1f2ccd3e05e5471be41c54ac5d7cb27b9aaaecea45f3abb363907644c44b385d83ef6b577061847396d6d3464e4f1fa9e779395e36703ef6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000a79dd00098800006137f9ba0100002800000000000003e800000000000003e800000000938580c0" -> - """{"signature":"8efb98c939aba422a1f2ccd3e05e5471be41c54ac5d7cb27b9aaaecea45f3abb363907644c44b385d83ef6b577061847396d6d3464e4f1fa9e779395e36703ef","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"686557x2440x0","timestamp":1631058362,"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":0,"feeProportionalMillionths":1000,"htlcMaximumMsat":2475000000,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"8efb98c939aba422a1f2ccd3e05e5471be41c54ac5d7cb27b9aaaecea45f3abb363907644c44b385d83ef6b577061847396d6d3464e4f1fa9e779395e36703ef","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"686557x2440x0","timestamp":{"iso":"2021-09-07T23:46:02Z","unix":1631058362},"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":0,"feeProportionalMillionths":1000,"htlcMaximumMsat":2475000000,"tlvStream":{"records":[],"unknown":[]}}""", hex"4d77b955573527208ac391cf0aeffdfd5efbd99f365ef59f050e373dfbbec69337023a2ce66439cf9c56255486785af0760a08fec51cefce57e0b85ba3c594f46fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008409700047800005df69f480000009000000000000003e8000003e800000001" -> - """{"signature":"4d77b955573527208ac391cf0aeffdfd5efbd99f365ef59f050e373dfbbec69337023a2ce66439cf9c56255486785af0760a08fec51cefce57e0b85ba3c594f4","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"540823x1144x0","timestamp":1576443720,"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"4d77b955573527208ac391cf0aeffdfd5efbd99f365ef59f050e373dfbbec69337023a2ce66439cf9c56255486785af0760a08fec51cefce57e0b85ba3c594f4","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"540823x1144x0","timestamp":{"iso":"2019-12-15T21:02:00Z","unix":1576443720},"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"tlvStream":{"records":[],"unknown":[]}}""", hex"b212e4d88a5ce3201ec34160d90a07eeb0601207d7d53bcf2b8f99b21146d7eb00d6a5b4b80b878eac0d25c2209eda05c913851730a65260c943fec8956cb22e6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000a48ce0006900000613792e40100002800000000000003e8000003e8000000010000000056d35b20" -> - """{"signature":"b212e4d88a5ce3201ec34160d90a07eeb0601207d7d53bcf2b8f99b21146d7eb00d6a5b4b80b878eac0d25c2209eda05c913851730a65260c943fec8956cb22e","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"673998x1680x0","timestamp":1631032036,"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":1456692000,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"b212e4d88a5ce3201ec34160d90a07eeb0601207d7d53bcf2b8f99b21146d7eb00d6a5b4b80b878eac0d25c2209eda05c913851730a65260c943fec8956cb22e","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"673998x1680x0","timestamp":{"iso":"2021-09-07T16:27:16Z","unix":1631032036},"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":1456692000,"tlvStream":{"records":[],"unknown":[]}}""", hex"29396591aee1bfd292193b4329d24eb9f57ddb143f303d029ae004113a7402af015c721ddc3e5d2e36cc67c92af3bdcd22d55eaf1e532503f9972207b226984f6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000096f010006ea000061375a440102002800000000000003e8000003e800000001000000024e160300" -> - """{"signature":"29396591aee1bfd292193b4329d24eb9f57ddb143f303d029ae004113a7402af015c721ddc3e5d2e36cc67c92af3bdcd22d55eaf1e532503f9972207b226984f","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"618241x1770x0","timestamp":1631017540,"channelFlags":{"isEnabled":false,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":9900000000,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"29396591aee1bfd292193b4329d24eb9f57ddb143f303d029ae004113a7402af015c721ddc3e5d2e36cc67c92af3bdcd22d55eaf1e532503f9972207b226984f","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"618241x1770x0","timestamp":{"iso":"2021-09-07T12:25:40Z","unix":1631017540},"channelFlags":{"isEnabled":false,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":9900000000,"tlvStream":{"records":[],"unknown":[]}}""", hex"3c6de66a61f2b8803537a2d92e7b82db1b44eac664ed6b7f5c7b5360b21d7ce32e5238e98d54701fe6d5b9109b2a2d875878a12d254eb6d651843b787f1ba5de6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000a921f0003fc000461386d9e0100002800000000000003e8000003e80000000100000000ec08ce00" -> - """{"signature":"3c6de66a61f2b8803537a2d92e7b82db1b44eac664ed6b7f5c7b5360b21d7ce32e5238e98d54701fe6d5b9109b2a2d875878a12d254eb6d651843b787f1ba5de","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"692767x1020x4","timestamp":1631088030,"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":3960000000,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"3c6de66a61f2b8803537a2d92e7b82db1b44eac664ed6b7f5c7b5360b21d7ce32e5238e98d54701fe6d5b9109b2a2d875878a12d254eb6d651843b787f1ba5de","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"692767x1020x4","timestamp":{"iso":"2021-09-08T08:00:30Z","unix":1631088030},"channelFlags":{"isEnabled":true,"isNode1":true},"cltvExpiryDelta":40,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":1,"htlcMaximumMsat":3960000000,"tlvStream":{"records":[],"unknown":[]}}""", hex"180de159377d68ecc3b327594bfb7408374811f3c98b5982af1520802796025a1430a6049294ebc0030518cc9b56a574c38c316122cb674f972734d7054d0b546fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000868f200029a00006137935701030028000000000000000000000001000002bc0000000001c9c380" -> - """{"signature":"180de159377d68ecc3b327594bfb7408374811f3c98b5982af1520802796025a1430a6049294ebc0030518cc9b56a574c38c316122cb674f972734d7054d0b54","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"551154x666x0","timestamp":1631032151,"channelFlags":{"isEnabled":false,"isNode1":false},"cltvExpiryDelta":40,"htlcMinimumMsat":0,"feeBaseMsat":1,"feeProportionalMillionths":700,"htlcMaximumMsat":30000000,"tlvStream":{"records":[],"unknown":[]}}""", + """{"signature":"180de159377d68ecc3b327594bfb7408374811f3c98b5982af1520802796025a1430a6049294ebc0030518cc9b56a574c38c316122cb674f972734d7054d0b54","chainHash":"6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000","shortChannelId":"551154x666x0","timestamp":{"iso":"2021-09-07T16:29:11Z","unix":1631032151},"channelFlags":{"isEnabled":false,"isNode1":false},"cltvExpiryDelta":40,"htlcMinimumMsat":0,"feeBaseMsat":1,"feeProportionalMillionths":700,"htlcMaximumMsat":30000000,"tlvStream":{"records":[],"unknown":[]}}""", ) for ((bin, ref) <- bins) { val decoded = channelUpdateCodec.decode(bin.bits).require 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 bab2342a01..b92db29a4f 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 @@ -16,7 +16,7 @@ package fr.acinq.eclair.api.directives -import akka.http.scaladsl.common.{NameReceptacle, NameUnmarshallerReceptacle} +import akka.http.scaladsl.common.{NameDefaultUnmarshallerReceptacle, NameReceptacle, NameUnmarshallerReceptacle} import akka.http.scaladsl.marshalling.ToResponseMarshaller import akka.http.scaladsl.model.StatusCodes.NotFound import akka.http.scaladsl.model.{ContentTypes, HttpResponse} @@ -27,7 +27,7 @@ import fr.acinq.eclair.ApiTypes.ChannelIdentifier import fr.acinq.eclair.api.serde.FormParamExtractors._ import fr.acinq.eclair.api.serde.JsonSupport._ import fr.acinq.eclair.payment.PaymentRequest -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TimestampSecond, TimestampSecondLong} import scala.concurrent.Future import scala.util.{Failure, Success} @@ -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: NameReceptacle[Long] = "from".as[Long] - val toFormParam: NameReceptacle[Long] = "to".as[Long] + val fromFormParam: NameDefaultUnmarshallerReceptacle[TimestampSecond] = "from".as[TimestampSecond](timestampSecondUnmarshaller).?(0 unixsec) + val toFormParam: NameDefaultUnmarshallerReceptacle[TimestampSecond] = "to".as[TimestampSecond](timestampSecondUnmarshaller).?(Long.MaxValue unixsec) val amountMsatFormParam: NameReceptacle[MilliSatoshi] = "amountMsat".as[MilliSatoshi] val invoiceFormParam: NameReceptacle[PaymentRequest] = "invoice".as[PaymentRequest] val routeFormat: NameUnmarshallerReceptacle[RouteFormat] = "format".as[RouteFormat](routeFormatUnmarshaller) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala index 2ee9f2feae..4ac5155d04 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala @@ -101,8 +101,8 @@ trait Channel { } val channelStats: Route = postRequest("channelstats") { implicit t => - formFields(fromFormParam.?, toFormParam.?) { (from_opt, to_opt) => - complete(eclairApi.channelStats(from_opt, to_opt)) + formFields(fromFormParam, toFormParam) { (from, to) => + complete(eclairApi.channelStats(from, to)) } } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Fees.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Fees.scala index 40de4c5dbb..74f5c96f1f 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Fees.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Fees.scala @@ -28,8 +28,8 @@ trait Fees { import fr.acinq.eclair.api.serde.JsonSupport.{formats, marshaller, serialization} val networkFees: Route = postRequest("networkfees") { implicit t => - formFields(fromFormParam.?, toFormParam.?) { (from_opt, to_opt) => - complete(eclairApi.networkFees(from_opt, to_opt)) + formFields(fromFormParam, toFormParam) { (from, to) => + complete(eclairApi.networkFees(from, to)) } } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala index fcfc3de8e4..ef921bb76c 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Invoice.scala @@ -42,14 +42,14 @@ trait Invoice { } val listInvoices: Route = postRequest("listinvoices") { implicit t => - formFields(fromFormParam.?, toFormParam.?) { (from_opt, to_opt) => - complete(eclairApi.allInvoices(from_opt, to_opt)) + formFields(fromFormParam, toFormParam) { (from, to) => + complete(eclairApi.allInvoices(from, to)) } } val listPendingInvoices: Route = postRequest("listpendinginvoices") { implicit t => - formFields(fromFormParam.?, toFormParam.?) { (from_opt, to_opt) => - complete(eclairApi.pendingInvoices(from_opt, to_opt)) + formFields(fromFormParam, toFormParam) { (from, to) => + complete(eclairApi.pendingInvoices(from, to)) } } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Node.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Node.scala index 4f6419f56d..da5cf1a23c 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Node.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Node.scala @@ -57,8 +57,8 @@ trait Node { } val audit: Route = postRequest("audit") { implicit t => - formFields(fromFormParam.?, toFormParam.?) { (from_opt, to_opt) => - complete(eclairApi.audit(from_opt, to_opt)) + formFields(fromFormParam, toFormParam) { (from, to) => + complete(eclairApi.audit(from, to)) } } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala index e2f064fcd9..9502b3fb69 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.api.serde.JsonSupport._ import fr.acinq.eclair.blockchain.fee.FeeratePerByte import fr.acinq.eclair.io.NodeURI import fr.acinq.eclair.payment.PaymentRequest -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TimestampSecond} import scodec.bits.ByteVector import java.util.UUID @@ -66,6 +66,8 @@ object FormParamExtractors { implicit val routeFormatUnmarshaller: Unmarshaller[String, RouteFormat] = Unmarshaller.strict { str => RouteFormat.fromString(str) } + implicit val timestampSecondUnmarshaller: Unmarshaller[String, TimestampSecond] = Unmarshaller.strict { str => TimestampSecond(str.toLong) } + private def listUnmarshaller[T](unmarshal: String => T): Unmarshaller[String, List[T]] = Unmarshaller.strict { str => Try(serialization.read[List[String]](str).map(unmarshal)) .recoverWith(_ => Try(str.split(",").toList.map(unmarshal))) diff --git a/eclair-node/src/test/resources/api/received-expired b/eclair-node/src/test/resources/api/received-expired index edb6d000b4..d754eceb51 100644 --- a/eclair-node/src/test/resources/api/received-expired +++ b/eclair-node/src/test/resources/api/received-expired @@ -1 +1 @@ -{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]},"routingInfo":[]},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"expired"}} \ No newline at end of file +{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]},"routingInfo":[]},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":{"iso":"1970-01-01T00:00:00.042Z","unix":0},"status":{"type":"expired"}} \ No newline at end of file diff --git a/eclair-node/src/test/resources/api/received-pending b/eclair-node/src/test/resources/api/received-pending index 89c0e9e53f..9e43ab9b12 100644 --- a/eclair-node/src/test/resources/api/received-pending +++ b/eclair-node/src/test/resources/api/received-pending @@ -1 +1 @@ -{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]},"routingInfo":[]},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"pending"}} \ No newline at end of file +{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]},"routingInfo":[]},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":{"iso":"1970-01-01T00:00:00.042Z","unix":0},"status":{"type":"pending"}} \ No newline at end of file diff --git a/eclair-node/src/test/resources/api/received-success b/eclair-node/src/test/resources/api/received-success index 0214913c86..b54154b991 100644 --- a/eclair-node/src/test/resources/api/received-success +++ b/eclair-node/src/test/resources/api/received-success @@ -1 +1 @@ -{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]},"routingInfo":[]},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"received","amount":42,"receivedAt":45}} \ No newline at end of file +{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]},"routingInfo":[]},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":{"iso":"1970-01-01T00:00:00.042Z","unix":0},"status":{"type":"received","amount":42,"receivedAt":{"iso":"2021-10-05T13:12:23.777Z","unix":1633439543}}} \ No newline at end of file diff --git a/eclair-node/src/test/resources/api/sent-failed b/eclair-node/src/test/resources/api/sent-failed index d9f5d99dd7..a86e01c5bc 100644 --- a/eclair-node/src/test/resources/api/sent-failed +++ b/eclair-node/src/test/resources/api/sent-failed @@ -1 +1 @@ -[{"id":"00000000-0000-0000-0000-000000000000","parentId":"11111111-1111-1111-1111-111111111111","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","amount":42,"recipientAmount":50,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","createdAt":1,"status":{"type":"failed","failures":[],"completedAt":2}}] \ No newline at end of file +[{"id":"00000000-0000-0000-0000-000000000000","parentId":"11111111-1111-1111-1111-111111111111","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","amount":42,"recipientAmount":50,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","createdAt":{"iso":"2021-10-05T13:10:29.123Z","unix":1633439429},"status":{"type":"failed","failures":[],"completedAt":{"iso":"2021-10-05T13:12:23.777Z","unix":1633439543}}}] \ No newline at end of file diff --git a/eclair-node/src/test/resources/api/sent-pending b/eclair-node/src/test/resources/api/sent-pending index 46c8d7375c..caebcf3f5d 100644 --- a/eclair-node/src/test/resources/api/sent-pending +++ b/eclair-node/src/test/resources/api/sent-pending @@ -1 +1 @@ -[{"id":"00000000-0000-0000-0000-000000000000","parentId":"11111111-1111-1111-1111-111111111111","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","amount":42,"recipientAmount":50,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","createdAt":1,"status":{"type":"pending"}}] \ No newline at end of file +[{"id":"00000000-0000-0000-0000-000000000000","parentId":"11111111-1111-1111-1111-111111111111","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","amount":42,"recipientAmount":50,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","createdAt":{"iso":"2021-10-05T13:10:29.123Z","unix":1633439429},"status":{"type":"pending"}}] \ No newline at end of file diff --git a/eclair-node/src/test/resources/api/sent-success b/eclair-node/src/test/resources/api/sent-success index cc34b1c335..bec125f93b 100644 --- a/eclair-node/src/test/resources/api/sent-success +++ b/eclair-node/src/test/resources/api/sent-success @@ -1 +1 @@ -[{"id":"00000000-0000-0000-0000-000000000000","parentId":"11111111-1111-1111-1111-111111111111","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","amount":42,"recipientAmount":50,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","createdAt":1,"status":{"type":"sent","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","feesPaid":5,"route":[],"completedAt":3}}] \ No newline at end of file +[{"id":"00000000-0000-0000-0000-000000000000","parentId":"11111111-1111-1111-1111-111111111111","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","amount":42,"recipientAmount":50,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","createdAt":{"iso":"2021-10-05T13:10:29.123Z","unix":1633439429},"status":{"type":"sent","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","feesPaid":5,"route":[],"completedAt":{"iso":"2021-10-05T13:12:23.777Z","unix":1633439543}}}] \ No newline at end of file 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 af7e1729d1..bf707447df 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 @@ -612,7 +612,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM } val uuid = UUID.fromString("487da196-a4dc-4b1e-92b4-3e5e905e9f3f") - val paymentSent = PaymentSent(uuid, ByteVector32.Zeroes, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PartialPayment(uuid, 21 msat, 1 msat, ByteVector32.Zeroes, None, 1553784337711L))) + val paymentSent = PaymentSent(uuid, ByteVector32.Zeroes, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PartialPayment(uuid, 21 msat, 1 msat, ByteVector32.Zeroes, None, TimestampMilli(1553784337711L)))) eclair.sendBlocking(any, any, any, any, any, any)(any[Timeout]).returns(Future.successful(Right(paymentSent))) Post("/payinvoice", FormData("invoice" -> invoice, "blocking" -> "true").toEntity) ~> addCredentials(BasicHttpCredentials("", mockApi().password)) ~> @@ -621,11 +621,11 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM assert(handled) assert(status == OK) val response = entityAs[String] - val expected = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"feesPaid":1,"toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":1553784337711}]}""" + val expected = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"feesPaid":1,"toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}]}""" assert(response === expected) } - val paymentFailed = PaymentFailed(uuid, ByteVector32.Zeroes, failures = Seq.empty, timestamp = 1553784963659L) + val paymentFailed = PaymentFailed(uuid, ByteVector32.Zeroes, failures = Seq.empty, timestamp = TimestampMilli(1553784963659L)) eclair.sendBlocking(any, any, any, any, any, any)(any[Timeout]).returns(Future.successful(Right(paymentFailed))) Post("/payinvoice", FormData("invoice" -> invoice, "blocking" -> "true").toEntity) ~> addCredentials(BasicHttpCredentials("", mockApi().password)) ~> @@ -634,7 +634,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM assert(handled) assert(status == OK) val response = entityAs[String] - val expected = """{"type":"payment-failed","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","failures":[],"timestamp":1553784963659}""" + val expected = """{"type":"payment-failed","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","failures":[],"timestamp":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}""" assert(response === expected) } } @@ -783,7 +783,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM test("'getreceivedinfo' 1") { val invoice = "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp" - val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42, IncomingPaymentStatus.Pending) + val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42 unixms, IncomingPaymentStatus.Pending) val eclair = mock[Eclair] val notFound = randomBytes32() eclair.receivedInfo(notFound)(any) returns Future.successful(None) @@ -803,7 +803,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM test("'getreceivedinfo' 2") { val invoice = "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp" - val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42, IncomingPaymentStatus.Pending) + val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42 unixms, IncomingPaymentStatus.Pending) val eclair = mock[Eclair] val pending = randomBytes32() eclair.receivedInfo(pending)(any) returns Future.successful(Some(defaultPayment)) @@ -823,7 +823,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM test("'getreceivedinfo' 3") { val invoice = "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp" - val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42, IncomingPaymentStatus.Pending) + val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42 unixms, IncomingPaymentStatus.Pending) val eclair = mock[Eclair] val expired = randomBytes32() eclair.receivedInfo(expired)(any) returns Future.successful(Some(defaultPayment.copy(status = IncomingPaymentStatus.Expired))) @@ -843,10 +843,10 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM test("'getreceivedinfo' 4") { val invoice = "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp" - val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42, IncomingPaymentStatus.Pending) + val defaultPayment = IncomingPayment(PaymentRequest.read(invoice), ByteVector32.One, PaymentType.Standard, 42 unixms, IncomingPaymentStatus.Pending) val eclair = mock[Eclair] val received = randomBytes32() - eclair.receivedInfo(received)(any) returns Future.successful(Some(defaultPayment.copy(status = IncomingPaymentStatus.Received(42 msat, 45)))) + eclair.receivedInfo(received)(any) returns Future.successful(Some(defaultPayment.copy(status = IncomingPaymentStatus.Received(42 msat, TimestampMilli(1633439543777L))))) val mockService = new MockService(eclair) Post("/getreceivedinfo", FormData("paymentHash" -> received.toHex).toEntity) ~> @@ -862,7 +862,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM } test("'getsentinfo' 1") { - val defaultPayment = OutgoingPayment(UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("11111111-1111-1111-1111-111111111111"), None, ByteVector32.Zeroes, PaymentType.Standard, 42 msat, 50 msat, aliceNodeId, 1, None, OutgoingPaymentStatus.Pending) + val defaultPayment = OutgoingPayment(UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("11111111-1111-1111-1111-111111111111"), None, ByteVector32.Zeroes, PaymentType.Standard, 42 msat, 50 msat, aliceNodeId, TimestampMilli(1633439429123L), None, OutgoingPaymentStatus.Pending) val eclair = mock[Eclair] val pending = UUID.randomUUID() eclair.sentInfo(Left(pending))(any) returns Future.successful(Seq(defaultPayment)) @@ -881,10 +881,10 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM } test("'getsentinfo' 2") { - val defaultPayment = OutgoingPayment(UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("11111111-1111-1111-1111-111111111111"), None, ByteVector32.Zeroes, PaymentType.Standard, 42 msat, 50 msat, aliceNodeId, 1, None, OutgoingPaymentStatus.Pending) + val defaultPayment = OutgoingPayment(UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("11111111-1111-1111-1111-111111111111"), None, ByteVector32.Zeroes, PaymentType.Standard, 42 msat, 50 msat, aliceNodeId, TimestampMilli(1633439429123L), None, OutgoingPaymentStatus.Pending) val eclair = mock[Eclair] val failed = UUID.randomUUID() - eclair.sentInfo(Left(failed))(any) returns Future.successful(Seq(defaultPayment.copy(status = OutgoingPaymentStatus.Failed(Nil, 2)))) + eclair.sentInfo(Left(failed))(any) returns Future.successful(Seq(defaultPayment.copy(status = OutgoingPaymentStatus.Failed(Nil, TimestampMilli(1633439543777L))))) val mockService = new MockService(eclair) Post("/getsentinfo", FormData("id" -> failed.toString).toEntity) ~> @@ -900,10 +900,10 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM } test("'getsentinfo' 3") { - val defaultPayment = OutgoingPayment(UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("11111111-1111-1111-1111-111111111111"), None, ByteVector32.Zeroes, PaymentType.Standard, 42 msat, 50 msat, aliceNodeId, 1, None, OutgoingPaymentStatus.Pending) + val defaultPayment = OutgoingPayment(UUID.fromString("00000000-0000-0000-0000-000000000000"), UUID.fromString("11111111-1111-1111-1111-111111111111"), None, ByteVector32.Zeroes, PaymentType.Standard, 42 msat, 50 msat, aliceNodeId, TimestampMilli(1633439429123L), None, OutgoingPaymentStatus.Pending) val eclair = mock[Eclair] val sent = UUID.randomUUID() - eclair.sentInfo(Left(sent))(any) returns Future.successful(Seq(defaultPayment.copy(status = OutgoingPaymentStatus.Succeeded(ByteVector32.One, 5 msat, Nil, 3)))) + eclair.sentInfo(Left(sent))(any) returns Future.successful(Seq(defaultPayment.copy(status = OutgoingPaymentStatus.Succeeded(ByteVector32.One, 5 msat, Nil, TimestampMilli(1633439543777L))))) val mockService = new MockService(eclair) Post("/getsentinfo", FormData("id" -> sent.toString).toEntity) ~> @@ -974,7 +974,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM signature = randomBytes64(), chainHash = randomBytes32(), shortChannelId = ShortChannelId(1, 2, 3), - timestamp = 0, + timestamp = 0 unixsec, channelFlags = ChannelUpdate.ChannelFlags.DUMMY, cltvExpiryDelta = CltvExpiryDelta(0), htlcMinimumMsat = MilliSatoshi(1), @@ -1087,38 +1087,38 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM addCredentials(BasicHttpCredentials("", mockApi().password)) ~> mockService.webSocket ~> check { - val pf = PaymentFailed(fixedUUID, ByteVector32.Zeroes, failures = Seq.empty, timestamp = 1553784963659L) - val expectedSerializedPf = """{"type":"payment-failed","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","failures":[],"timestamp":1553784963659}""" + val pf = PaymentFailed(fixedUUID, ByteVector32.Zeroes, failures = Seq.empty, timestamp = TimestampMilli(1553784963659L)) + val expectedSerializedPf = """{"type":"payment-failed","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","failures":[],"timestamp":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}""" assert(serialization.write(pf) === expectedSerializedPf) system.eventStream.publish(pf) wsClient.expectMessage(expectedSerializedPf) - val ps = PaymentSent(fixedUUID, ByteVector32.Zeroes, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PartialPayment(fixedUUID, 21 msat, 1 msat, ByteVector32.Zeroes, None, 1553784337711L))) - val expectedSerializedPs = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"feesPaid":1,"toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":1553784337711}]}""" + val ps = PaymentSent(fixedUUID, ByteVector32.Zeroes, ByteVector32.One, 25 msat, aliceNodeId, Seq(PaymentSent.PartialPayment(fixedUUID, 21 msat, 1 msat, ByteVector32.Zeroes, None, TimestampMilli(1553784337711L)))) + val expectedSerializedPs = """{"type":"payment-sent","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","recipientAmount":25,"recipientNodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","parts":[{"id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"feesPaid":1,"toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":{"iso":"2019-03-28T14:45:37.711Z","unix":1553784337}}]}""" assert(serialization.write(ps) === expectedSerializedPs) system.eventStream.publish(ps) wsClient.expectMessage(expectedSerializedPs) - val prel = ChannelPaymentRelayed(21 msat, 20 msat, ByteVector32.Zeroes, ByteVector32.Zeroes, ByteVector32.One, 1553784963659L) - val expectedSerializedPrel = """{"type":"payment-relayed","amountIn":21,"amountOut":20,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","toChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}""" + val prel = ChannelPaymentRelayed(21 msat, 20 msat, ByteVector32.Zeroes, ByteVector32.Zeroes, ByteVector32.One, TimestampMilli(1553784963659L)) + val expectedSerializedPrel = """{"type":"payment-relayed","amountIn":21,"amountOut":20,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","toChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}""" assert(serialization.write(prel) === expectedSerializedPrel) system.eventStream.publish(prel) wsClient.expectMessage(expectedSerializedPrel) - val ptrel = TrampolinePaymentRelayed(ByteVector32.Zeroes, Seq(PaymentRelayed.Part(21 msat, ByteVector32.Zeroes)), Seq(PaymentRelayed.Part(8 msat, ByteVector32.Zeroes), PaymentRelayed.Part(10 msat, ByteVector32.One)), bobNodeId, 17 msat, 1553784963659L) - val expectedSerializedPtrel = """{"type":"trampoline-payment-relayed","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","incoming":[{"amount":21,"channelId":"0000000000000000000000000000000000000000000000000000000000000000"}],"outgoing":[{"amount":8,"channelId":"0000000000000000000000000000000000000000000000000000000000000000"},{"amount":10,"channelId":"0100000000000000000000000000000000000000000000000000000000000000"}],"nextTrampolineNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","nextTrampolineAmount":17,"timestamp":1553784963659}""" + val ptrel = TrampolinePaymentRelayed(ByteVector32.Zeroes, Seq(PaymentRelayed.Part(21 msat, ByteVector32.Zeroes)), Seq(PaymentRelayed.Part(8 msat, ByteVector32.Zeroes), PaymentRelayed.Part(10 msat, ByteVector32.One)), bobNodeId, 17 msat, TimestampMilli(1553784963659L)) + val expectedSerializedPtrel = """{"type":"trampoline-payment-relayed","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","incoming":[{"amount":21,"channelId":"0000000000000000000000000000000000000000000000000000000000000000"}],"outgoing":[{"amount":8,"channelId":"0000000000000000000000000000000000000000000000000000000000000000"},{"amount":10,"channelId":"0100000000000000000000000000000000000000000000000000000000000000"}],"nextTrampolineNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","nextTrampolineAmount":17,"timestamp":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}""" assert(serialization.write(ptrel) === expectedSerializedPtrel) system.eventStream.publish(ptrel) wsClient.expectMessage(expectedSerializedPtrel) - val precv = PaymentReceived(ByteVector32.Zeroes, Seq(PaymentReceived.PartialPayment(21 msat, ByteVector32.Zeroes, 1553784963659L))) - val expectedSerializedPrecv = """{"type":"payment-received","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","parts":[{"amount":21,"fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}]}""" + val precv = PaymentReceived(ByteVector32.Zeroes, Seq(PaymentReceived.PartialPayment(21 msat, ByteVector32.Zeroes, TimestampMilli(1553784963659L)))) + val expectedSerializedPrecv = """{"type":"payment-received","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","parts":[{"amount":21,"fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":{"iso":"2019-03-28T14:56:03.659Z","unix":1553784963}}]}""" assert(serialization.write(precv) === expectedSerializedPrecv) system.eventStream.publish(precv) wsClient.expectMessage(expectedSerializedPrecv) - val pset = PaymentSettlingOnChain(fixedUUID, 21 msat, ByteVector32.One, timestamp = 1553785442676L) - val expectedSerializedPset = """{"type":"payment-settling-onchain","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"paymentHash":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553785442676}""" + val pset = PaymentSettlingOnChain(fixedUUID, 21 msat, ByteVector32.One, timestamp = TimestampMilli(1553785442676L)) + val expectedSerializedPset = """{"type":"payment-settling-onchain","id":"487da196-a4dc-4b1e-92b4-3e5e905e9f3f","amount":21,"paymentHash":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":{"iso":"2019-03-28T15:04:02.676Z","unix":1553785442}}""" assert(serialization.write(pset) === expectedSerializedPset) system.eventStream.publish(pset) wsClient.expectMessage(expectedSerializedPset)