From d58244975c6195b58bbf22d36cb30288da7ef8d4 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Wed, 21 Aug 2019 15:58:48 +0200 Subject: [PATCH 1/5] Define CltvExpiry types. --- .../scala/fr/acinq/eclair/CltvExpiry.scala | 67 +++++++++++++++++++ .../fr/acinq/eclair/CltvExpirySpec.scala | 63 +++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala create mode 100644 eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala new file mode 100644 index 0000000000..9828a14454 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala @@ -0,0 +1,67 @@ +/* + * 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 + +/** + * Created by t-bast on 21/08/2019. + */ + +/** + * Bitcoin scripts (in particular HTLCs) need an absolute block expiry (greater than the current block count) to work + * with OP_CLTV. + * + * @param get the absolute cltv expiry value (current block count + some delta). + */ +case class CltvExpiry(get: Long) { + // @formatter:off + def +(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(get + d.delta) + def -(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(get - d.delta) + def -(other: CltvExpiry): CltvExpiryDelta = CltvExpiryDelta((get - other.get).toInt) + def compare(other: CltvExpiry): Int = if (get == other.get) 0 else if (get < other.get) -1 else 1 + def <=(that: CltvExpiry): Boolean = compare(that) <= 0 + def >=(that: CltvExpiry): Boolean = compare(that) >= 0 + def <(that: CltvExpiry): Boolean = compare(that) < 0 + def >(that: CltvExpiry): Boolean = compare(that) > 0 + // @formatter:on +} + +/** + * Channels advertise a cltv expiry delta that should be used when routing through them. + * This value needs to be converted to a [[fr.acinq.eclair.CltvExpiry]] to be used in OP_CLTV. + * + * CltvExpiryDelta can also be used when working with OP_CSV which is by design a delta. + * + * @param delta the cltv expiry delta value. + */ +case class CltvExpiryDelta(delta: Int) { + + /** + * Adds the current block height to the given delta to obtain an absolute expiry. + */ + def toCltvExpiry = CltvExpiry(Globals.blockCount.get() + delta) + + // @formatter:off + def +(other: Int): CltvExpiryDelta = CltvExpiryDelta(delta + other) + def +(other: CltvExpiryDelta): CltvExpiryDelta = CltvExpiryDelta(delta + other.delta) + def compare(other: CltvExpiryDelta): Int = if (delta == other.delta) 0 else if (delta < other.delta) -1 else 1 + def <=(that: CltvExpiryDelta): Boolean = compare(that) <= 0 + def >=(that: CltvExpiryDelta): Boolean = compare(that) >= 0 + def <(that: CltvExpiryDelta): Boolean = compare(that) < 0 + def >(that: CltvExpiryDelta): Boolean = compare(that) > 0 + // @formatter:on + +} diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala new file mode 100644 index 0000000000..c7c0c230a0 --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala @@ -0,0 +1,63 @@ +/* + * Copyright 2019 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair + +import org.scalatest.FunSuite + +/** + * Created by t-bast on 21/08/2019. + */ + +class CltvExpirySpec extends FunSuite { + + test("cltv expiry delta") { + val d = CltvExpiryDelta(561) + assert(d.delta === 561) + + // add + assert(d + 5 === CltvExpiryDelta(566)) + assert(d + CltvExpiryDelta(5) === CltvExpiryDelta(566)) + + // compare + assert(d <= CltvExpiryDelta(561)) + assert(d < CltvExpiryDelta(562)) + assert(d >= CltvExpiryDelta(561)) + assert(d > CltvExpiryDelta(560)) + + // convert to cltv expiry + Globals.blockCount.set(1105) + assert(d.toCltvExpiry === CltvExpiry(1666)) + Globals.blockCount.set(1106) + assert(d.toCltvExpiry === CltvExpiry(1667)) + } + + test("cltv expiry") { + val e = CltvExpiry(1105) + + // add + assert(e + CltvExpiryDelta(561) === CltvExpiry(1666)) + assert(e - CltvExpiryDelta(561) === CltvExpiry(544)) + assert(e - CltvExpiry(561) === CltvExpiryDelta(544)) + + // compare + assert(e <= CltvExpiry(1105)) + assert(e < CltvExpiry(1106)) + assert(e >= CltvExpiry(1105)) + assert(e > CltvExpiry(1104)) + } + +} From e8b27c30fb0437fc155a53d5b9f4b9f57e417251 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Thu, 22 Aug 2019 09:28:52 +0200 Subject: [PATCH 2/5] Use typed cltv expiry everywhere. --- .../main/scala/fr/acinq/eclair/Eclair.scala | 26 +- .../scala/fr/acinq/eclair/NodeParams.scala | 53 +- .../fr/acinq/eclair/api/JsonSerializers.scala | 14 +- .../scala/fr/acinq/eclair/api/Service.scala | 12 +- .../fr/acinq/eclair/channel/Channel.scala | 8 +- .../eclair/channel/ChannelExceptions.scala | 12 +- .../acinq/eclair/channel/ChannelTypes.scala | 13 +- .../fr/acinq/eclair/channel/Commitments.scala | 65 +-- .../fr/acinq/eclair/channel/Helpers.scala | 539 +++++++++--------- .../scala/fr/acinq/eclair/db/ChannelsDb.scala | 5 +- .../eclair/db/sqlite/SqliteChannelsDb.scala | 13 +- .../eclair/payment/LocalPaymentHandler.scala | 21 +- .../eclair/payment/PaymentLifecycle.scala | 84 ++- .../acinq/eclair/payment/PaymentRequest.scala | 12 +- .../fr/acinq/eclair/payment/Relayer.scala | 76 +-- .../acinq/eclair/router/Announcements.scala | 6 +- .../scala/fr/acinq/eclair/router/Graph.scala | 218 +++---- .../scala/fr/acinq/eclair/router/Router.scala | 80 +-- .../acinq/eclair/transactions/Scripts.scala | 103 ++-- .../eclair/transactions/Transactions.scala | 138 ++--- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 4 +- .../fr/acinq/eclair/wire/CommonCodecs.scala | 37 +- .../fr/acinq/eclair/wire/FailureMessage.scala | 36 +- .../eclair/wire/LightningMessageCodecs.scala | 16 +- .../eclair/wire/LightningMessageTypes.scala | 16 +- .../scala/fr/acinq/eclair/wire/Onion.scala | 20 +- .../fr/acinq/eclair/EclairImplSpec.scala | 46 +- .../scala/fr/acinq/eclair/TestConstants.scala | 24 +- .../fr/acinq/eclair/api/ApiServiceSpec.scala | 6 +- .../fr/acinq/eclair/channel/FuzzySpec.scala | 8 +- .../acinq/eclair/channel/ThroughputSpec.scala | 2 +- .../states/StateTestsHelperMethods.scala | 29 +- .../a/WaitForAcceptChannelStateSpec.scala | 12 +- .../a/WaitForOpenChannelStateSpec.scala | 8 +- .../channel/states/e/NormalStateSpec.scala | 115 ++-- .../channel/states/e/OfflineStateSpec.scala | 24 +- .../channel/states/f/ShutdownStateSpec.scala | 17 +- .../states/g/NegotiatingStateSpec.scala | 14 +- .../channel/states/h/ClosingStateSpec.scala | 74 ++- .../eclair/db/SqliteChannelsDbSpec.scala | 6 +- .../acinq/eclair/db/SqliteNetworkDbSpec.scala | 10 +- .../eclair/integration/IntegrationSpec.scala | 2 +- .../rustytests/SynchronizationPipe.scala | 8 +- .../fr/acinq/eclair/io/HtlcReaperSpec.scala | 13 +- .../eclair/payment/ChannelSelectionSpec.scala | 29 +- .../eclair/payment/HtlcGenerationSpec.scala | 21 +- .../eclair/payment/PaymentHandlerSpec.scala | 20 +- .../eclair/payment/PaymentLifecycleSpec.scala | 26 +- .../eclair/payment/PaymentRequestSpec.scala | 22 +- .../fr/acinq/eclair/payment/RelayerSpec.scala | 18 +- .../AnnouncementsBatchValidationSpec.scala | 11 +- .../eclair/router/AnnouncementsSpec.scala | 14 +- .../acinq/eclair/router/BaseRouterSpec.scala | 24 +- .../eclair/router/RouteCalculationSpec.scala | 178 +++--- .../fr/acinq/eclair/router/RouterSpec.scala | 27 +- .../acinq/eclair/router/RoutingSyncSpec.scala | 8 +- .../transactions/CommitmentSpecSpec.scala | 10 +- .../eclair/transactions/TestVectorsSpec.scala | 17 +- .../transactions/TransactionsSpec.scala | 36 +- .../acinq/eclair/wire/ChannelCodecsSpec.scala | 33 +- .../wire/FailureMessageCodecsSpec.scala | 14 +- .../wire/LightningMessageCodecsSpec.scala | 14 +- .../acinq/eclair/wire/OnionCodecsSpec.scala | 8 +- .../scala/fr/acinq/eclair/gui/Handlers.scala | 4 +- 64 files changed, 1280 insertions(+), 1299 deletions(-) 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 e22b621d35..72c8b3ae6d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -23,20 +23,20 @@ import akka.pattern._ import akka.util.Timeout import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, Satoshi} +import fr.acinq.eclair.TimestampQueryFilters._ import fr.acinq.eclair.channel.Register.{Forward, ForwardShortId} import fr.acinq.eclair.channel._ import fr.acinq.eclair.db.{IncomingPayment, NetworkFee, OutgoingPayment, Stats} import fr.acinq.eclair.io.Peer.{GetPeerInfo, PeerInfo} import fr.acinq.eclair.io.{NodeURI, Peer} import fr.acinq.eclair.payment.PaymentLifecycle._ +import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.{ChannelDesc, RouteRequest, RouteResponse, Router} +import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, NodeAddress, NodeAnnouncement} import scodec.bits.ByteVector -import scala.concurrent.Future import scala.concurrent.duration._ -import fr.acinq.eclair.payment.{GetUsableBalances, PaymentReceived, PaymentRelayed, PaymentRequest, PaymentSent, UsableBalances} -import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, NodeAddress, NodeAnnouncement} -import TimestampQueryFilters._ +import scala.concurrent.{ExecutionContext, Future} case class GetInfoResponse(nodeId: PublicKey, alias: String, chainHash: ByteVector32, blockHeight: Int, publicAddresses: Seq[NodeAddress]) @@ -78,13 +78,13 @@ trait Eclair { def receivedInfo(paymentHash: ByteVector32)(implicit timeout: Timeout): Future[Option[IncomingPayment]] - def send(recipientNodeId: PublicKey, amount: MilliSatoshi, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None, feeThresholdSat_opt: Option[Satoshi] = None, maxFeePct_opt: Option[Double] = None)(implicit timeout: Timeout): Future[UUID] + def send(recipientNodeId: PublicKey, amount: MilliSatoshi, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiryDelta_opt: Option[CltvExpiryDelta] = None, maxAttempts_opt: Option[Int] = None, feeThresholdSat_opt: Option[Satoshi] = None, maxFeePct_opt: Option[Double] = None)(implicit timeout: Timeout): Future[UUID] def sentInfo(id: Either[UUID, ByteVector32])(implicit timeout: Timeout): Future[Seq[OutgoingPayment]] def findRoute(targetNodeId: PublicKey, amount: MilliSatoshi, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty)(implicit timeout: Timeout): Future[RouteResponse] - def sendToRoute(route: Seq[PublicKey], amount: MilliSatoshi, paymentHash: ByteVector32, finalCltvExpiry: Long)(implicit timeout: Timeout): Future[UUID] + def sendToRoute(route: Seq[PublicKey], amount: MilliSatoshi, paymentHash: ByteVector32, finalCltvExpiryDelta: CltvExpiryDelta)(implicit timeout: Timeout): Future[UUID] def audit(from_opt: Option[Long], to_opt: Option[Long])(implicit timeout: Timeout): Future[AuditResponse] @@ -111,7 +111,7 @@ trait Eclair { class EclairImpl(appKit: Kit) extends Eclair { - implicit val ec = appKit.system.dispatcher + implicit val ec: ExecutionContext = appKit.system.dispatcher override def connect(target: Either[NodeURI, PublicKey])(implicit timeout: Timeout): Future[String] = target match { case Left(uri) => (appKit.switchboard ? Peer.Connect(uri)).mapTo[String] @@ -186,11 +186,11 @@ class EclairImpl(appKit: Kit) extends Eclair { (appKit.router ? RouteRequest(appKit.nodeParams.nodeId, targetNodeId, amount, assistedRoutes)).mapTo[RouteResponse] } - override def sendToRoute(route: Seq[PublicKey], amount: MilliSatoshi, paymentHash: ByteVector32, finalCltvExpiry: Long)(implicit timeout: Timeout): Future[UUID] = { - (appKit.paymentInitiator ? SendPaymentToRoute(amount, paymentHash, route, finalCltvExpiry)).mapTo[UUID] + override def sendToRoute(route: Seq[PublicKey], amount: MilliSatoshi, paymentHash: ByteVector32, finalCltvExpiryDelta: CltvExpiryDelta)(implicit timeout: Timeout): Future[UUID] = { + (appKit.paymentInitiator ? SendPaymentToRoute(amount, paymentHash, route, finalCltvExpiryDelta)).mapTo[UUID] } - override def send(recipientNodeId: PublicKey, amount: MilliSatoshi, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long], maxAttempts_opt: Option[Int], feeThreshold_opt: Option[Satoshi], maxFeePct_opt: Option[Double])(implicit timeout: Timeout): Future[UUID] = { + override def send(recipientNodeId: PublicKey, amount: MilliSatoshi, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiryDelta_opt: Option[CltvExpiryDelta], maxAttempts_opt: Option[Int], feeThreshold_opt: Option[Satoshi], maxFeePct_opt: Option[Double])(implicit timeout: Timeout): Future[UUID] = { val maxAttempts = maxAttempts_opt.getOrElse(appKit.nodeParams.maxPaymentAttempts) val defaultRouteParams = Router.getDefaultRouteParams(appKit.nodeParams.routerConf) @@ -199,8 +199,8 @@ class EclairImpl(appKit: Kit) extends Eclair { maxFeeBase = feeThreshold_opt.map(_.toMilliSatoshi).getOrElse(defaultRouteParams.maxFeeBase) ) - val sendPayment = minFinalCltvExpiry_opt match { - case Some(minCltv) => SendPayment(amount, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams)) + val sendPayment = minFinalCltvExpiryDelta_opt match { + case Some(minCltv) => SendPayment(amount, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiryDelta = minCltv, maxAttempts = maxAttempts, routeParams = Some(routeParams)) case None => SendPayment(amount, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts, routeParams = Some(routeParams)) } (appKit.paymentInitiator ? sendPayment).mapTo[UUID] @@ -255,8 +255,6 @@ class EclairImpl(appKit: Kit) extends Eclair { * Sends a request to a channel and expects a response * * @param channelIdentifier either a shortChannelId (BOLT encoded) or a channelId (32-byte hex encoded) - * @param request - * @return */ def sendToChannel(channelIdentifier: Either[ByteVector32, ShortChannelId], request: Any)(implicit timeout: Timeout): Future[Any] = channelIdentifier match { case Left(channelId) => appKit.register ? Forward(channelId, request) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 2daa66510f..45a8d40b00 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -33,12 +33,13 @@ import fr.acinq.eclair.router.RouterConf import fr.acinq.eclair.tor.Socks5ProxyParams import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector + import scala.collection.JavaConversions._ import scala.concurrent.duration.FiniteDuration /** - * Created by PM on 26/02/2017. - */ + * Created by PM on 26/02/2017. + */ case class NodeParams(keyManager: KeyManager, alias: String, color: Color, @@ -50,11 +51,11 @@ case class NodeParams(keyManager: KeyManager, onChainFeeConf: OnChainFeeConf, maxHtlcValueInFlightMsat: UInt64, maxAcceptedHtlcs: Int, - expiryDeltaBlocks: Int, - fulfillSafetyBeforeTimeoutBlocks: Int, + expiryDeltaBlocks: CltvExpiryDelta, + fulfillSafetyBeforeTimeoutBlocks: CltvExpiryDelta, htlcMinimum: MilliSatoshi, - toRemoteDelayBlocks: Int, - maxToLocalDelayBlocks: Int, + toRemoteDelayBlocks: CltvExpiryDelta, + maxToLocalDelayBlocks: CltvExpiryDelta, minDepthBlocks: Int, feeBase: MilliSatoshi, feeProportionalMillionth: Int, @@ -90,12 +91,12 @@ object NodeParams { object ELECTRUM extends WatcherType /** - * Order of precedence for the configuration parameters: - * 1) Java environment variables (-D...) - * 2) Configuration file eclair.conf - * 3) Optionally provided config - * 4) Default values in reference.conf - */ + * Order of precedence for the configuration parameters: + * 1) Java environment variables (-D...) + * 2) Configuration file eclair.conf + * 3) Optionally provided config + * 4) Default values in reference.conf + */ def loadConfiguration(datadir: File, overrideDefaults: Config = ConfigFactory.empty()) = ConfigFactory.parseProperties(System.getProperties) .withFallback(ConfigFactory.parseFile(new File(datadir, "eclair.conf"))) @@ -104,13 +105,13 @@ object NodeParams { def getSeed(datadir: File): ByteVector = { val seedPath = new File(datadir, "seed.dat") - seedPath.exists() match { - case true => ByteVector(Files.readAllBytes(seedPath.toPath)) - case false => - datadir.mkdirs() - val seed = randomBytes32 - Files.write(seedPath.toPath, seed.toArray) - seed + if (seedPath.exists()) { + ByteVector(Files.readAllBytes(seedPath.toPath)) + } else { + datadir.mkdirs() + val seed = randomBytes32 + Files.write(seedPath.toPath, seed.toArray) + seed } } @@ -144,12 +145,12 @@ object NodeParams { val maxAcceptedHtlcs = config.getInt("max-accepted-htlcs") require(maxAcceptedHtlcs <= Channel.MAX_ACCEPTED_HTLCS, s"max-accepted-htlcs must be lower than ${Channel.MAX_ACCEPTED_HTLCS}") - val maxToLocalCLTV = config.getInt("max-to-local-delay-blocks") - val offeredCLTV = config.getInt("to-remote-delay-blocks") + val maxToLocalCLTV = CltvExpiryDelta(config.getInt("max-to-local-delay-blocks")) + val offeredCLTV = CltvExpiryDelta(config.getInt("to-remote-delay-blocks")) require(maxToLocalCLTV <= Channel.MAX_TO_SELF_DELAY && offeredCLTV <= Channel.MAX_TO_SELF_DELAY, s"CLTV delay values too high, max is ${Channel.MAX_TO_SELF_DELAY}") - val expiryDeltaBlocks = config.getInt("expiry-delta-blocks") - val fulfillSafetyBeforeTimeoutBlocks = config.getInt("fulfill-safety-before-timeout-blocks") + val expiryDeltaBlocks = CltvExpiryDelta(config.getInt("expiry-delta-blocks")) + val fulfillSafetyBeforeTimeoutBlocks = CltvExpiryDelta(config.getInt("fulfill-safety-before-timeout-blocks")) require(fulfillSafetyBeforeTimeoutBlocks < expiryDeltaBlocks, "fulfill-safety-before-timeout-blocks must be smaller than expiry-delta-blocks") val nodeAlias = config.getString("node-alias") @@ -206,8 +207,8 @@ object NodeParams { expiryDeltaBlocks = expiryDeltaBlocks, fulfillSafetyBeforeTimeoutBlocks = fulfillSafetyBeforeTimeoutBlocks, htlcMinimum = MilliSatoshi(config.getInt("htlc-minimum-msat")), - toRemoteDelayBlocks = config.getInt("to-remote-delay-blocks"), - maxToLocalDelayBlocks = config.getInt("max-to-local-delay-blocks"), + toRemoteDelayBlocks = CltvExpiryDelta(config.getInt("to-remote-delay-blocks")), + maxToLocalDelayBlocks = CltvExpiryDelta(config.getInt("max-to-local-delay-blocks")), minDepthBlocks = config.getInt("mindepth-blocks"), feeBase = MilliSatoshi(config.getInt("fee-base-msat")), feeProportionalMillionth = config.getInt("fee-proportional-millionths"), @@ -231,7 +232,7 @@ object NodeParams { routerBroadcastInterval = FiniteDuration(config.getDuration("router.broadcast-interval").getSeconds, TimeUnit.SECONDS), randomizeRouteSelection = config.getBoolean("router.randomize-route-selection"), searchMaxRouteLength = config.getInt("router.path-finding.max-route-length"), - searchMaxCltv = config.getInt("router.path-finding.max-cltv"), + searchMaxCltv = CltvExpiryDelta(config.getInt("router.path-finding.max-cltv")), searchMaxFeeBase = Satoshi(config.getLong("router.path-finding.fee-threshold-sat")), searchMaxFeePct = config.getDouble("router.path-finding.max-fee-pct"), searchHeuristicsEnabled = config.getBoolean("router.path-finding.heuristics-enable"), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index 203c6c7f35..db225d949e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.router.RouteResponse import fr.acinq.eclair.transactions.Direction import fr.acinq.eclair.transactions.Transactions.{InputInfo, TransactionWithInputInfo} import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64} import org.json4s.JsonAST._ import org.json4s.{CustomKeySerializer, CustomSerializer, TypeHints, jackson} import scodec.bits.ByteVector @@ -65,6 +65,14 @@ class MilliSatoshiSerializer extends CustomSerializer[MilliSatoshi](format => ({ case x: MilliSatoshi => JInt(x.amount) })) +class CltvExpirySerializer extends CustomSerializer[CltvExpiry](format => ({ null }, { + case x: CltvExpiry => JLong(x.get) +})) + +class CltvExpiryDeltaSerializer extends CustomSerializer[CltvExpiryDelta](format => ({ null }, { + case x: CltvExpiryDelta => JInt(x.delta) +})) + class ShortChannelIdSerializer extends CustomSerializer[ShortChannelId](format => ({ null }, { case x: ShortChannelId => JString(x.toString()) })) @@ -154,7 +162,7 @@ class PaymentRequestSerializer extends CustomSerializer[PaymentRequest](format = }, { case p: PaymentRequest => { val expiry = p.expiry.map(ex => JField("expiry", JLong(ex))).toSeq - val minFinalCltvExpiry = p.minFinalCltvExpiry.map(mfce => JField("minFinalCltvExpiry", JLong(mfce))).toSeq + val minFinalCltvExpiry = p.minFinalCltvExpiryDelta.map(mfce => JField("minFinalCltvExpiry", JLong(mfce.delta))).toSeq val amount = p.amount.map(msat => JField("amount", JLong(msat.toLong))).toSeq val fieldList = List(JField("prefix", JString(p.prefix)), @@ -193,6 +201,8 @@ object JsonSupport extends Json4sSupport { new UInt64Serializer + new SatoshiSerializer + new MilliSatoshiSerializer + + new CltvExpirySerializer + + new CltvExpiryDeltaSerializer + new ShortChannelIdSerializer + new StateSerializer + new ShaChainSerializer + diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala index 7d50d75217..fe252a0269 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -31,14 +31,14 @@ import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Source} import akka.stream.{ActorMaterializer, OverflowStrategy} import akka.util.Timeout import com.google.common.net.HostAndPort -import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.bitcoin.Crypto.PublicKey +import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.eclair.api.FormParamExtractors._ import fr.acinq.eclair.api.JsonSupport.CustomTypeHints import fr.acinq.eclair.io.NodeURI import fr.acinq.eclair.payment.PaymentLifecycle.PaymentFailed import fr.acinq.eclair.payment.{PaymentReceived, PaymentRequest, _} -import fr.acinq.eclair.{Eclair, MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{CltvExpiryDelta, Eclair, MilliSatoshi} import grizzled.slf4j.Logging import org.json4s.jackson.Serialization import scodec.bits.ByteVector @@ -224,9 +224,9 @@ trait Service extends ExtraDirectives with Logging { path("payinvoice") { formFields(invoiceFormParam, amountMsatFormParam.?, "maxAttempts".as[Int].?, "feeThresholdSat".as[Satoshi].?, "maxFeePct".as[Double].?) { case (invoice@PaymentRequest(_, Some(amount), _, nodeId, _, _), None, maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(nodeId, amount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt)) + complete(eclairApi.send(nodeId, amount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiryDelta, maxAttempts, feeThresholdSat_opt, maxFeePct_opt)) case (invoice, Some(overrideAmount), maxAttempts, feeThresholdSat_opt, maxFeePct_opt) => - complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts, feeThresholdSat_opt, maxFeePct_opt)) + complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiryDelta, maxAttempts, feeThresholdSat_opt, maxFeePct_opt)) case _ => reject(MalformedFormFieldRejection("invoice", "The invoice must have an amount or you need to specify one using the field 'amountMsat'")) } } ~ @@ -236,8 +236,8 @@ trait Service extends ExtraDirectives with Logging { } } ~ path("sendtoroute") { - formFields(amountMsatFormParam, paymentHashFormParam, "finalCltvExpiry".as[Long], "route".as[List[PublicKey]](pubkeyListUnmarshaller)) { (amountMsat, paymentHash, finalCltvExpiry, route) => - complete(eclairApi.sendToRoute(route, amountMsat, paymentHash, finalCltvExpiry)) + formFields(amountMsatFormParam, paymentHashFormParam, "finalCltvExpiry".as[Int], "route".as[List[PublicKey]](pubkeyListUnmarshaller)) { (amountMsat, paymentHash, finalCltvExpiry, route) => + complete(eclairApi.sendToRoute(route, amountMsat, paymentHash, CltvExpiryDelta(finalCltvExpiry))) } } ~ path("getsentinfo") { 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 d095f595fb..384afd8d87 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 @@ -58,11 +58,11 @@ object Channel { val MAX_NEGOTIATION_ITERATIONS = 20 // this is defined in BOLT 11 - val MIN_CLTV_EXPIRY = 9L - val MAX_CLTV_EXPIRY = 7 * 144L // one week + val MIN_CLTV_EXPIRY_DELTA = CltvExpiryDelta(9) + val MAX_CLTV_EXPIRY_DELTA = CltvExpiryDelta(7 * 144) // one week // since BOLT 1.1, there is a max value for the refund delay of the main commitment tx - val MAX_TO_SELF_DELAY = 2016 + val MAX_TO_SELF_DELAY = CltvExpiryDelta(2016) // as a fundee, we will wait that much time for the funding tx to confirm (funder will rely on the funding tx being double-spent) val FUNDING_TIMEOUT_FUNDEE = 5 days @@ -1306,7 +1306,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val revokedCommitPublished1 = d.revokedCommitPublished.map(Closing.updateRevokedCommitPublished(_, tx)) // if the local commitment tx just got confirmed, let's send an event telling when we will get the main output refund if (localCommitPublished1.map(_.commitTx.txid).contains(tx.txid)) { - context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.remoteParams.toSelfDelay)) + context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.remoteParams.toSelfDelay.delta)) } // we may need to fail some htlcs in case a commitment tx was published and they have reached the timeout threshold val timedoutHtlcs = diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala index 56fc890eb5..c177c92ad2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala @@ -18,13 +18,13 @@ package fr.acinq.eclair.channel import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{ByteVector32, Satoshi, Transaction} -import fr.acinq.eclair.{MilliSatoshi, UInt64} import fr.acinq.eclair.payment.Origin import fr.acinq.eclair.wire.{ChannelUpdate, UpdateAddHtlc} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, UInt64} /** - * Created by PM on 11/04/2017. - */ + * Created by PM on 11/04/2017. + */ class ChannelException(val channelId: ByteVector32, message: String) extends RuntimeException(message) @@ -37,7 +37,7 @@ case class InvalidMaxAcceptedHtlcs (override val channelId: ByteVect case class DustLimitTooSmall (override val channelId: ByteVector32, dustLimit: Satoshi, min: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is too small (min=$min)") case class DustLimitTooLarge (override val channelId: ByteVector32, dustLimit: Satoshi, max: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is too large (max=$max)") case class DustLimitAboveOurChannelReserve (override val channelId: ByteVector32, dustLimit: Satoshi, channelReserve: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is above our channelReserve=$channelReserve") -case class ToSelfDelayTooHigh (override val channelId: ByteVector32, toSelfDelay: Int, max: Int) extends ChannelException(channelId, s"unreasonable to_self_delay=$toSelfDelay (max=$max)") +case class ToSelfDelayTooHigh (override val channelId: ByteVector32, toSelfDelay: CltvExpiryDelta, max: CltvExpiryDelta) extends ChannelException(channelId, s"unreasonable to_self_delay=$toSelfDelay (max=$max)") case class ChannelReserveTooHigh (override val channelId: ByteVector32, channelReserve: Satoshi, reserveToFundingRatio: Double, maxReserveToFundingRatio: Double) extends ChannelException(channelId, s"channelReserve too high: reserve=$channelReserve fundingRatio=$reserveToFundingRatio maxFundingRatio=$maxReserveToFundingRatio") case class ChannelReserveBelowOurDustLimit (override val channelId: ByteVector32, channelReserve: Satoshi, dustLimit: Satoshi) extends ChannelException(channelId, s"their channelReserve=$channelReserve is below our dustLimit=$dustLimit") case class ChannelReserveNotMet (override val channelId: ByteVector32, toLocal: MilliSatoshi, toRemote: MilliSatoshi, reserve: Satoshi) extends ChannelException(channelId, s"channel reserve is not met toLocal=$toLocal toRemote=$toRemote reserve=$reserve") @@ -61,8 +61,8 @@ case class InvalidCloseFee (override val channelId: ByteVect case class HtlcSigCountMismatch (override val channelId: ByteVector32, expected: Int, actual: Int) extends ChannelException(channelId, s"htlc sig count mismatch: expected=$expected actual: $actual") case class ForcedLocalCommit (override val channelId: ByteVector32) extends ChannelException(channelId, s"forced local commit") case class UnexpectedHtlcId (override val channelId: ByteVector32, expected: Long, actual: Long) extends ChannelException(channelId, s"unexpected htlc id: expected=$expected actual=$actual") -case class ExpiryTooSmall (override val channelId: ByteVector32, minimum: Long, actual: Long, blockCount: Long) extends ChannelException(channelId, s"expiry too small: minimum=$minimum actual=$actual blockCount=$blockCount") -case class ExpiryTooBig (override val channelId: ByteVector32, maximum: Long, actual: Long, blockCount: Long) extends ChannelException(channelId, s"expiry too big: maximum=$maximum actual=$actual blockCount=$blockCount") +case class ExpiryTooSmall (override val channelId: ByteVector32, minimum: CltvExpiry, actual: CltvExpiry, blockCount: Long) extends ChannelException(channelId, s"expiry too small: minimum=$minimum actual=$actual blockCount=$blockCount") +case class ExpiryTooBig (override val channelId: ByteVector32, maximum: CltvExpiry, actual: CltvExpiry, blockCount: Long) extends ChannelException(channelId, s"expiry too big: maximum=$maximum actual=$actual blockCount=$blockCount") case class HtlcValueTooSmall (override val channelId: ByteVector32, minimum: MilliSatoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"htlc value too small: minimum=$minimum actual=$actual") case class HtlcValueTooHighInFlight (override val channelId: ByteVector32, maximum: UInt64, actual: UInt64) extends ChannelException(channelId, s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual") case class TooManyAcceptedHtlcs (override val channelId: ByteVector32, maximum: Long) extends ChannelException(channelId, s"too many accepted htlcs: maximum=$maximum") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 151cf582d5..6facbf7e52 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -21,17 +21,16 @@ import java.util.UUID import akka.actor.ActorRef import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction} -import fr.acinq.eclair.api.MilliSatoshiSerializer import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions.CommitTx import fr.acinq.eclair.wire.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64} import scodec.bits.{BitVector, ByteVector} /** - * Created by PM on 20/05/2016. - */ + * Created by PM on 20/05/2016. + */ // @formatter:off @@ -107,7 +106,7 @@ case class BITCOIN_PARENT_TX_CONFIRMED(childTx: Transaction) extends BitcoinEven */ sealed trait Command -final case class CMD_ADD_HTLC(amount: MilliSatoshi, paymentHash: ByteVector32, cltvExpiry: Long, onion: OnionRoutingPacket, upstream: Either[UUID, UpdateAddHtlc], commit: Boolean = false, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends Command +final case class CMD_ADD_HTLC(amount: MilliSatoshi, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onion: OnionRoutingPacket, upstream: Either[UUID, UpdateAddHtlc], commit: Boolean = false, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends Command final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false) extends Command final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], commit: Boolean = false) extends Command final case class CMD_FAIL_MALFORMED_HTLC(id: Long, onionHash: ByteVector32, failureCode: Int, commit: Boolean = false) extends Command @@ -196,7 +195,7 @@ final case class LocalParams(nodeId: PublicKey, maxHtlcValueInFlightMsat: UInt64, channelReserve: Satoshi, htlcMinimum: MilliSatoshi, - toSelfDelay: Int, + toSelfDelay: CltvExpiryDelta, maxAcceptedHtlcs: Int, isFunder: Boolean, defaultFinalScriptPubKey: ByteVector, @@ -208,7 +207,7 @@ final case class RemoteParams(nodeId: PublicKey, maxHtlcValueInFlightMsat: UInt64, channelReserve: Satoshi, htlcMinimum: MilliSatoshi, - toSelfDelay: Int, + toSelfDelay: CltvExpiryDelta, maxAcceptedHtlcs: Int, fundingPubKey: PublicKey, revocationBasepoint: PublicKey, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 21533f0dba..0c7807840c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -19,15 +19,13 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi} -import fr.acinq.eclair -import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets} import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx} import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, MilliSatoshi, UInt64} +import fr.acinq.eclair.{Globals, MilliSatoshi, UInt64, _} // @formatter:off case class LocalChanges(proposed: List[UpdateMessage], signed: List[UpdateMessage], acked: List[UpdateMessage]) { @@ -43,13 +41,13 @@ case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, // @formatter:on /** - * about remoteNextCommitInfo: - * we either: - * - have built and signed their next commit tx with their next revocation hash which can now be discarded - * - have their next per-commitment point - * So, when we've signed and sent a commit message and are waiting for their revocation message, - * theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point - */ + * about remoteNextCommitInfo: + * we either: + * - have built and signed their next commit tx with their next revocation hash which can now be discarded + * - have their next per-commitment point + * So, when we've signed and sent a commit message and are waiting for their revocation message, + * theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point + */ case class Commitments(channelVersion: ChannelVersion, localParams: LocalParams, remoteParams: RemoteParams, channelFlags: Byte, @@ -64,19 +62,19 @@ case class Commitments(channelVersion: ChannelVersion, def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight def timedOutOutgoingHtlcs(blockheight: Long): Set[UpdateAddHtlc] = - (localCommit.spec.htlcs.filter(htlc => htlc.direction == OUT && blockheight >= htlc.add.cltvExpiry) ++ - remoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry) ++ - remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry)).getOrElse(Set.empty[DirectedHtlc])).map(_.add) + (localCommit.spec.htlcs.filter(htlc => htlc.direction == OUT && blockheight >= htlc.add.cltvExpiry.get) ++ + remoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.get) ++ + remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.get)).getOrElse(Set.empty[DirectedHtlc])).map(_.add) /** - * HTLCs that are close to timing out upstream are potentially dangerous. If we received the pre-image for those - * HTLCs, we need to get a remote signed updated commitment that removes this HTLC. - * Otherwise when we get close to the upstream timeout, we risk an on-chain race condition between their HTLC timeout - * and our HTLC success in case of a force-close. - */ - def almostTimedOutIncomingHtlcs(blockheight: Long, fulfillSafety: Int): Set[UpdateAddHtlc] = { + * HTLCs that are close to timing out upstream are potentially dangerous. If we received the pre-image for those + * HTLCs, we need to get a remote signed updated commitment that removes this HTLC. + * Otherwise when we get close to the upstream timeout, we risk an on-chain race condition between their HTLC timeout + * and our HTLC success in case of a force-close. + */ + def almostTimedOutIncomingHtlcs(blockheight: Long, fulfillSafety: CltvExpiryDelta): Set[UpdateAddHtlc] = { localCommit.spec.htlcs.collect { - case htlc if htlc.direction == IN && blockheight >= htlc.add.cltvExpiry - fulfillSafety => htlc.add + case htlc if htlc.direction == IN && blockheight >= (htlc.add.cltvExpiry - fulfillSafety).get => htlc.add } } @@ -102,12 +100,12 @@ case class Commitments(channelVersion: ChannelVersion, object Commitments { /** - * Add a change to our proposed change list. - * - * @param commitments current commitments. - * @param proposal proposed change to add. - * @return an updated commitment instance. - */ + * Add a change to our proposed change list. + * + * @param commitments current commitments. + * @param proposal proposed change to add. + * @return an updated commitment instance. + */ private def addLocalProposal(commitments: Commitments, proposal: UpdateMessage): Commitments = commitments.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal)) @@ -115,20 +113,19 @@ object Commitments { commitments.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal)) /** - * - * @param commitments current commitments - * @param cmd add HTLC command - * @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right((new commitments, updateAddHtlc) - */ + * + * @param commitments current commitments + * @param cmd add HTLC command + * @return either Left(failure, error message) where failure is a failure message (see BOLT #4 and the Failure Message class) or Right((new commitments, updateAddHtlc) + */ def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC, origin: Origin): Either[ChannelException, (Commitments, UpdateAddHtlc)] = { - val blockCount = Globals.blockCount.get() // our counterparty needs a reasonable amount of time to pull the funds from downstream before we can get refunded (see BOLT 2 and BOLT 11 for a calculation and rationale) - val minExpiry = blockCount + Channel.MIN_CLTV_EXPIRY + val minExpiry = Channel.MIN_CLTV_EXPIRY_DELTA.toCltvExpiry if (cmd.cltvExpiry < minExpiry) { return Left(ExpiryTooSmall(commitments.channelId, minimum = minExpiry, actual = cmd.cltvExpiry, blockCount = blockCount)) } - val maxExpiry = blockCount + Channel.MAX_CLTV_EXPIRY + val maxExpiry = Channel.MAX_CLTV_EXPIRY_DELTA.toCltvExpiry // we don't want to use too high a refund timeout, because our funds will be locked during that time if the payment is never fulfilled if (cmd.cltvExpiry >= maxExpiry) { return Left(ExpiryTooBig(commitments.channelId, maximum = maxExpiry, actual = cmd.cltvExpiry, blockCount = blockCount)) 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 1f49e73429..dcdf7bdcab 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 @@ -20,19 +20,16 @@ import akka.event.LoggingAdapter import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, ripemd160, sha256} import fr.acinq.bitcoin.Script._ import fr.acinq.bitcoin.{OutPoint, _} -import fr.acinq.eclair -import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.EclairWallet import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets} import fr.acinq.eclair.channel.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL import fr.acinq.eclair.crypto.{Generators, KeyManager} import fr.acinq.eclair.db.ChannelsDb -import fr.acinq.eclair.payment.{Local, Origin} import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId, addressToPublicKeyScript} +import fr.acinq.eclair.{NodeParams, ShortChannelId, addressToPublicKeyScript, _} import scodec.bits.ByteVector import scala.compat.Platform @@ -41,17 +38,17 @@ import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} /** - * Created by PM on 20/05/2016. - */ + * Created by PM on 20/05/2016. + */ object Helpers { /** - * Depending on the state, returns the current temporaryChannelId or channelId - * - * @param stateData - * @return the long identifier of the channel - */ + * Depending on the state, returns the current temporaryChannelId or channelId + * + * @param stateData + * @return the long identifier of the channel + */ def getChannelId(stateData: Data): ByteVector32 = stateData match { case Nothing => ByteVector32.Zeroes case d: DATA_WAIT_FOR_OPEN_CHANNEL => d.initFundee.temporaryChannelId @@ -63,11 +60,11 @@ object Helpers { } /** - * We update local/global features at reconnection - * - * @param data - * @return - */ + * We update local/global features at reconnection + * + * @param data + * @return + */ def updateFeatures(data: HasCommitments, localInit: Init, remoteInit: Init): HasCommitments = { val commitments1 = data.commitments.copy( localParams = data.commitments.localParams.copy(globalFeatures = localInit.globalFeatures, localFeatures = localInit.localFeatures), @@ -84,8 +81,8 @@ object Helpers { } /** - * Called by the fundee - */ + * Called by the fundee + */ def validateParamsFundee(nodeParams: NodeParams, open: OpenChannel): Unit = { // BOLT #2: if the chain_hash value, within the open_channel, message is set to a hash of a chain that is unknown to the receiver: // MUST reject the channel. @@ -129,8 +126,8 @@ object Helpers { } /** - * Called by the funder - */ + * Called by the funder + */ def validateParamsFunder(nodeParams: NodeParams, open: OpenChannel, accept: AcceptChannel): Unit = { if (accept.maxAcceptedHtlcs > Channel.MAX_ACCEPTED_HTLCS) throw InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, accept.maxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS) // only enforce dust limit check on mainnet @@ -158,14 +155,14 @@ object Helpers { } /** - * Compute the delay until we need to refresh the channel_update for our channel not to be considered stale by - * other nodes. - * - * If current update more than [[Channel.REFRESH_CHANNEL_UPDATE_INTERVAL]] old then the delay will be zero. - * - * @param currentUpdateTimestamp - * @return the delay until the next update - */ + * Compute the delay until we need to refresh the channel_update for our channel not to be considered stale by + * other nodes. + * + * If current update more than [[Channel.REFRESH_CHANNEL_UPDATE_INTERVAL]] old then the delay will be zero. + * + * @param currentUpdateTimestamp + * @return the delay until the next update + */ def nextChannelUpdateRefresh(currentUpdateTimestamp: Long)(implicit log: LoggingAdapter): FiniteDuration = { val age = Platform.currentTime.milliseconds - currentUpdateTimestamp.seconds val delay = 0.days.max(REFRESH_CHANNEL_UPDATE_INTERVAL - age) @@ -174,11 +171,11 @@ object Helpers { } /** - * - * @param remoteFeeratePerKw remote fee rate per kiloweight - * @param localFeeratePerKw local fee rate per kiloweight - * @return the "normalized" difference between local and remote fee rate, i.e. |remote - local| / avg(local, remote) - */ + * + * @param remoteFeeratePerKw remote fee rate per kiloweight + * @param localFeeratePerKw local fee rate per kiloweight + * @return the "normalized" difference between local and remote fee rate, i.e. |remote - local| / avg(local, remote) + */ def feeRateMismatch(remoteFeeratePerKw: Long, localFeeratePerKw: Long): Double = Math.abs((2.0 * (remoteFeeratePerKw - localFeeratePerKw)) / (localFeeratePerKw + remoteFeeratePerKw)) @@ -186,21 +183,21 @@ object Helpers { feeRateMismatch(networkFeeratePerKw, commitmentFeeratePerKw) > updateFeeMinDiffRatio /** - * - * @param remoteFeeratePerKw remote fee rate per kiloweight - * @param localFeeratePerKw local fee rate per kiloweight - * @param maxFeerateMismatchRatio maximum fee rate mismatch ratio - * @return true if the difference between local and remote fee rates is too high. - * the actual check is |remote - local| / avg(local, remote) > mismatch ratio - */ + * + * @param remoteFeeratePerKw remote fee rate per kiloweight + * @param localFeeratePerKw local fee rate per kiloweight + * @param maxFeerateMismatchRatio maximum fee rate mismatch ratio + * @return true if the difference between local and remote fee rates is too high. + * the actual check is |remote - local| / avg(local, remote) > mismatch ratio + */ def isFeeDiffTooHigh(remoteFeeratePerKw: Long, localFeeratePerKw: Long, maxFeerateMismatchRatio: Double): Boolean = feeRateMismatch(remoteFeeratePerKw, localFeeratePerKw) > maxFeerateMismatchRatio /** - * - * @param remoteFeeratePerKw remote fee rate per kiloweight - * @return true if the remote fee rate is too small - */ + * + * @param remoteFeeratePerKw remote fee rate per kiloweight + * @return true if the remote fee rate is too small + */ def isFeeTooSmall(remoteFeeratePerKw: Long): Boolean = { remoteFeeratePerKw < fr.acinq.eclair.MinimumFeeratePerKw } @@ -212,10 +209,10 @@ object Helpers { } /** - * This indicates whether our side of the channel is above the reserve requested by our counterparty. In other words, - * this tells if we can use the channel to make a payment. - * - */ + * This indicates whether our side of the channel is above the reserve requested by our counterparty. In other words, + * this tells if we can use the channel to make a payment. + * + */ def aboveReserve(commitments: Commitments)(implicit log: LoggingAdapter): Boolean = { val remoteCommit = commitments.remoteNextCommitInfo match { case Left(waitingForRevocation) => waitingForRevocation.nextRemoteCommit @@ -244,19 +241,19 @@ object Helpers { } /** - * Creates both sides's first commitment transaction - * - * @param localParams - * @param remoteParams - * @param pushMsat - * @param fundingTxHash - * @param fundingTxOutputIndex - * @param remoteFirstPerCommitmentPoint - * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) - */ + * Creates both sides's first commitment transaction + * + * @param localParams + * @param remoteParams + * @param pushMsat + * @param fundingTxHash + * @param fundingTxOutputIndex + * @param remoteFirstPerCommitmentPoint + * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) + */ def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushMsat: MilliSatoshi, initialFeeratePerKw: Long, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: PublicKey, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { val toLocalMsat = if (localParams.isFunder) fundingAmount.toMilliSatoshi - pushMsat else pushMsat - val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingAmount.toMilliSatoshi - pushMsat + val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingAmount.toMilliSatoshi - pushMsat val localSpec = CommitmentSpec(Set.empty[DirectedHtlc], feeratePerKw = initialFeeratePerKw, toLocal = toLocalMsat, toRemote = toRemoteMsat) val remoteSpec = CommitmentSpec(Set.empty[DirectedHtlc], feeratePerKw = initialFeeratePerKw, toLocal = toRemoteMsat, toRemote = toLocalMsat) @@ -282,14 +279,14 @@ object Helpers { } /** - * Tells whether or not their expected next remote commitment number matches with our data - * - * @param d - * @param nextRemoteRevocationNumber - * @return - * - true if parties are in sync or remote is behind - * - false if we are behind - */ + * Tells whether or not their expected next remote commitment number matches with our data + * + * @param d + * @param nextRemoteRevocationNumber + * @return + * - true if parties are in sync or remote is behind + * - false if we are behind + */ def checkLocalCommit(d: HasCommitments, nextRemoteRevocationNumber: Long): Boolean = { if (d.commitments.localCommit.index == nextRemoteRevocationNumber) { // they just sent a new commit_sig, we have received it but they didn't receive our revocation @@ -307,14 +304,14 @@ object Helpers { } /** - * Tells whether or not their expected next local commitment number matches with our data - * - * @param d - * @param nextLocalCommitmentNumber - * @return - * - true if parties are in sync or remote is behind - * - false if we are behind - */ + * Tells whether or not their expected next local commitment number matches with our data + * + * @param d + * @param nextLocalCommitmentNumber + * @return + * - true if parties are in sync or remote is behind + * - false if we are behind + */ def checkRemoteCommit(d: HasCommitments, nextLocalCommitmentNumber: Long): Boolean = { d.commitments.remoteNextCommitInfo match { case Left(waitingForRevocation) if nextLocalCommitmentNumber == waitingForRevocation.nextRemoteCommit.index => @@ -353,11 +350,11 @@ object Helpers { // @formatter:on /** - * Indicates whether local has anything at stake in this channel - * - * @param data - * @return true if channel was never open, or got closed immediately, had never any htlcs and local never had a positive balance - */ + * Indicates whether local has anything at stake in this channel + * + * @param data + * @return true if channel was never open, or got closed immediately, had never any htlcs and local never had a positive balance + */ def nothingAtStake(data: HasCommitments): Boolean = data.commitments.localCommit.index == 0 && data.commitments.localCommit.spec.toLocal == MilliSatoshi(0) && @@ -366,15 +363,15 @@ object Helpers { data.commitments.remoteNextCommitInfo.isRight /** - * As soon as a tx spending the funding tx has reached min_depth, we know what the closing type will be, before - * the whole closing process finishes(e.g. there may still be delayed or unconfirmed child transactions). It can - * save us from attempting to publish some transactions. - * - * Note that we can't tell for mutual close before it is already final, because only one tx needs to be confirmed. - * - * @param closing channel state data - * @return the channel closing type, if applicable - */ + * As soon as a tx spending the funding tx has reached min_depth, we know what the closing type will be, before + * the whole closing process finishes(e.g. there may still be delayed or unconfirmed child transactions). It can + * save us from attempting to publish some transactions. + * + * Note that we can't tell for mutual close before it is already final, because only one tx needs to be confirmed. + * + * @param closing channel state data + * @return the channel closing type, if applicable + */ def isClosingTypeAlreadyKnown(closing: DATA_CLOSING): Option[ClosingType] = closing match { case _ if closing.localCommitPublished.exists(lcp => lcp.irrevocablySpent.values.toSet.contains(lcp.commitTx.txid)) => Some(LocalClose) @@ -390,13 +387,13 @@ object Helpers { } /** - * Checks if a channel is closed (i.e. its closing tx has been confirmed) - * - * @param data channel state data - * @param additionalConfirmedTx_opt additional confirmed transaction; we need this for the mutual close scenario - * because we don't store the closing tx in the channel state - * @return the channel closing type, if applicable - */ + * Checks if a channel is closed (i.e. its closing tx has been confirmed) + * + * @param data channel state data + * @param additionalConfirmedTx_opt additional confirmed transaction; we need this for the mutual close scenario + * because we don't store the closing tx in the channel state + * @return the channel closing type, if applicable + */ def isClosed(data: HasCommitments, additionalConfirmedTx_opt: Option[Transaction]): Option[ClosingType] = data match { case closing: DATA_CLOSING if additionalConfirmedTx_opt.exists(closing.mutualClosePublished.contains) => Some(MutualClose) @@ -491,13 +488,13 @@ object Helpers { } /** - * - * Claim all the HTLCs that we've received from our current commit tx. This will be - * done using 2nd stage HTLC transactions - * - * @param commitments our commitment data, which include payment preimages - * @return a list of transactions (one per HTLC that we can claim) - */ + * + * Claim all the HTLCs that we've received from our current commit tx. This will be + * done using 2nd stage HTLC transactions + * + * @param commitments our commitment data, which include payment preimages + * @return a list of transactions (one per HTLC that we can claim) + */ def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") @@ -561,14 +558,14 @@ object Helpers { } /** - * - * Claim all the HTLCs that we've received from their current commit tx - * - * @param commitments our commitment data, which include payment preimages - * @param remoteCommit the remote commitment data to use to claim outputs (it can be their current or next commitment) - * @param tx the remote commitment transaction that has just been published - * @return a list of transactions (one per HTLC that we can claim) - */ + * + * Claim all the HTLCs that we've received from their current commit tx + * + * @param commitments our commitment data, which include payment preimages + * @param remoteCommit the remote commitment data to use to claim outputs (it can be their current or next commitment) + * @param tx the remote commitment transaction that has just been published + * @return a list of transactions (one per HTLC that we can claim) + */ def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") @@ -616,16 +613,16 @@ object Helpers { } /** - * - * Claim our Main output only - * - * @param commitments either our current commitment data in case of usual remote uncooperative closing - * or our outdated commitment data in case of data loss protection procedure; in any case it is used only - * to get some constant parameters, not commitment data - * @param remotePerCommitmentPoint the remote perCommitmentPoint corresponding to this commitment - * @param tx the remote commitment transaction that has just been published - * @return a list of transactions (one per HTLC that we can claim) - */ + * + * Claim our Main output only + * + * @param commitments either our current commitment data in case of usual remote uncooperative closing + * or our outdated commitment data in case of data loss protection procedure; in any case it is used only + * to get some constant parameters, not commitment data + * @param remotePerCommitmentPoint the remote perCommitmentPoint corresponding to this commitment + * @param tx the remote commitment transaction that has just been published + * @return a list of transactions (one per HTLC that we can claim) + */ def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) @@ -648,14 +645,14 @@ object Helpers { } /** - * When an unexpected transaction spending the funding tx is detected: - * 1) we find out if the published transaction is one of remote's revoked txs - * 2) and then: - * a) if it is a revoked tx we build a set of transactions that will punish them by stealing all their funds - * b) otherwise there is nothing we can do - * - * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment - */ + * When an unexpected transaction spending the funding tx is detected: + * 1) we find out if the published transaction is one of remote's revoked txs + * 2) and then: + * a) if it is a revoked tx we build a set of transactions that will punish them by stealing all their funds + * b) otherwise there is nothing we can do + * + * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment + */ def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { import commitments._ require(tx.txIn.size == 1, "commitment tx should have 1 input") @@ -705,15 +702,15 @@ object Helpers { // and finally we steal the htlc outputs var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs - val htlcPenaltyTxs = tx.txOut.collect { case txOut if htlcsRedeemScripts.contains(txOut.publicKeyScript) => - val htlcRedeemScript = htlcsRedeemScripts(txOut.publicKeyScript) - generateTx("htlc-penalty")(Try { - val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) - outputsAlreadyUsed = outputsAlreadyUsed + htlcPenalty.input.outPoint.index.toInt - val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) - Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey) - }) - }.toList.flatten + val htlcPenaltyTxs = tx.txOut.collect { case txOut if htlcsRedeemScripts.contains(txOut.publicKeyScript) => + val htlcRedeemScript = htlcsRedeemScripts(txOut.publicKeyScript) + generateTx("htlc-penalty")(Try { + val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) + outputsAlreadyUsed = outputsAlreadyUsed + htlcPenalty.input.outPoint.index.toInt + val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) + Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey) + }) + }.toList.flatten RevokedCommitPublished( commitTx = tx, @@ -727,21 +724,21 @@ object Helpers { } /** - * Claims the output of an [[HtlcSuccessTx]] or [[HtlcTimeoutTx]] transaction using a revocation key. - * - * In case a revoked commitment with pending HTLCs is published, there are two ways the HTLC outputs can be taken as punishment: - * - by spending the corresponding output of the commitment tx, using [[HtlcPenaltyTx]] that we generate as soon as we detect that a revoked commit - * as been spent; note that those transactions will compete with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] published by the counterparty. - * - by spending the delayed output of [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] if those get confirmed; because the output of these txes is protected by - * an OP_CSV delay, we will have time to spend them with a revocation key. In that case, we generate the spending transactions "on demand", - * this is the purpose of this method. - * - * @param keyManager - * @param commitments - * @param revokedCommitPublished - * @param htlcTx - * @return - */ + * Claims the output of an [[HtlcSuccessTx]] or [[HtlcTimeoutTx]] transaction using a revocation key. + * + * In case a revoked commitment with pending HTLCs is published, there are two ways the HTLC outputs can be taken as punishment: + * - by spending the corresponding output of the commitment tx, using [[HtlcPenaltyTx]] that we generate as soon as we detect that a revoked commit + * as been spent; note that those transactions will compete with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] published by the counterparty. + * - by spending the delayed output of [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] if those get confirmed; because the output of these txes is protected by + * an OP_CSV delay, we will have time to spend them with a revocation key. In that case, we generate the spending transactions "on demand", + * this is the purpose of this method. + * + * @param keyManager + * @param commitments + * @param revokedCommitPublished + * @param htlcTx + * @return + */ def claimRevokedHtlcTxOutputs(keyManager: KeyManager, commitments: Commitments, revokedCommitPublished: RevokedCommitPublished, htlcTx: Transaction, feeEstimator: FeeEstimator)(implicit log: LoggingAdapter): (RevokedCommitPublished, Option[Transaction]) = { if (htlcTx.txIn.map(_.outPoint.txid).contains(revokedCommitPublished.commitTx.txid) && !(revokedCommitPublished.claimMainOutputTx ++ revokedCommitPublished.mainPenaltyTx ++ revokedCommitPublished.htlcPenaltyTxs).map(_.txid).toSet.contains(htlcTx.txid)) { @@ -784,18 +781,18 @@ object Helpers { } /** - * In CLOSING state, any time we see a new transaction, we try to extract a preimage from it in order to fulfill the - * corresponding incoming htlc in an upstream channel. - * - * Not doing that would result in us losing money, because the downstream node would pull money from one side, and - * the upstream node would get refunded after a timeout. - * - * @param localCommit - * @param tx - * @return a set of pairs (add, fulfills) if extraction was successful: - * - add is the htlc in the downstream channel from which we extracted the preimage - * - fulfill needs to be sent to the upstream channel - */ + * In CLOSING state, any time we see a new transaction, we try to extract a preimage from it in order to fulfill the + * corresponding incoming htlc in an upstream channel. + * + * Not doing that would result in us losing money, because the downstream node would pull money from one side, and + * the upstream node would get refunded after a timeout. + * + * @param localCommit + * @param tx + * @return a set of pairs (add, fulfills) if extraction was successful: + * - add is the htlc in the downstream channel from which we extracted the preimage + * - fulfill needs to be sent to the upstream channel + */ def extractPreimages(localCommit: LocalCommit, tx: Transaction)(implicit log: LoggingAdapter): Set[(UpdateAddHtlc, UpdateFulfillHtlc)] = { val paymentPreimages = tx.txIn.map(_.witness match { case ScriptWitness(Seq(localSig, paymentPreimage, htlcOfferedScript)) if paymentPreimage.size == 32 => @@ -821,14 +818,14 @@ object Helpers { } /** - * In CLOSING state, when we are notified that a transaction has been confirmed, we analyze it to find out if one or - * more htlcs have timed out and need to be failed in an upstream channel. - * - * @param localCommit - * @param localDustLimit - * @param tx a tx that has reached mindepth - * @return a set of htlcs that need to be failed upstream - */ + * In CLOSING state, when we are notified that a transaction has been confirmed, we analyze it to find out if one or + * more htlcs have timed out and need to be failed in an upstream channel. + * + * @param localCommit + * @param localDustLimit + * @param tx a tx that has reached mindepth + * @return a set of htlcs that need to be failed upstream + */ def timedoutHtlcs(localCommit: LocalCommit, localDustLimit: Satoshi, tx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == localCommit.publishableTxs.commitTx.tx.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) @@ -845,14 +842,14 @@ object Helpers { } /** - * In CLOSING state, when we are notified that a transaction has been confirmed, we analyze it to find out if one or - * more htlcs have timed out and need to be failed in an upstream channel. - * - * @param remoteCommit - * @param remoteDustLimit - * @param tx a tx that has reached mindepth - * @return a set of htlcs that need to be failed upstream - */ + * In CLOSING state, when we are notified that a transaction has been confirmed, we analyze it to find out if one or + * more htlcs have timed out and need to be failed in an upstream channel. + * + * @param remoteCommit + * @param remoteDustLimit + * @param tx a tx that has reached mindepth + * @return a set of htlcs that need to be failed upstream + */ def timedoutHtlcs(remoteCommit: RemoteCommit, remoteDustLimit: Satoshi, tx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = if (tx.txid == remoteCommit.txid) { // the tx is a commitment tx, we can immediately fail all dust htlcs (they don't have an output in the tx) @@ -869,14 +866,14 @@ object Helpers { } /** - * As soon as a local or remote commitment reaches min_depth, we know which htlcs will be settled on-chain (whether - * or not they actually have an output in the commitment tx). - * - * @param localCommit - * @param remoteCommit - * @param nextRemoteCommit_opt - * @param tx a transaction that is sufficiently buried in the blockchain - */ + * As soon as a local or remote commitment reaches min_depth, we know which htlcs will be settled on-chain (whether + * or not they actually have an output in the commitment tx). + * + * @param localCommit + * @param remoteCommit + * @param nextRemoteCommit_opt + * @param tx a transaction that is sufficiently buried in the blockchain + */ def onchainOutgoingHtlcs(localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[RemoteCommit], tx: Transaction): Set[UpdateAddHtlc] = { if (localCommit.publishableTxs.commitTx.tx.txid == tx.txid) { localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add) @@ -890,17 +887,17 @@ object Helpers { } /** - * If a local commitment tx reaches min_depth, we need to fail the outgoing htlcs that only us had signed, because - * they will never reach the blockchain. - * - * Those are only present in the remote's commitment. - * - * @param localCommit - * @param remoteCommit - * @param tx - * @param log - * @return - */ + * If a local commitment tx reaches min_depth, we need to fail the outgoing htlcs that only us had signed, because + * they will never reach the blockchain. + * + * Those are only present in the remote's commitment. + * + * @param localCommit + * @param remoteCommit + * @param tx + * @param log + * @return + */ def overriddenOutgoingHtlcs(localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[RemoteCommit], tx: Transaction)(implicit log: LoggingAdapter): Set[UpdateAddHtlc] = if (localCommit.publishableTxs.commitTx.tx.txid == tx.txid) { // our commit got confirmed, so any htlc that we signed but they didn't sign will never reach the chain @@ -924,17 +921,17 @@ object Helpers { } else Set.empty /** - * In CLOSING state, when we are notified that a transaction has been confirmed, we check if this tx belongs in the - * local commit scenario and keep track of it. - * - * We need to keep track of all transactions spending the outputs of the commitment tx, because some outputs can be - * spent both by us and our counterparty. Because of that, some of our transactions may never confirm and we don't - * want to wait forever before declaring that the channel is CLOSED. - * - * @param localCommitPublished - * @param tx a transaction that has been irrevocably confirmed - * @return - */ + * In CLOSING state, when we are notified that a transaction has been confirmed, we check if this tx belongs in the + * local commit scenario and keep track of it. + * + * We need to keep track of all transactions spending the outputs of the commitment tx, because some outputs can be + * spent both by us and our counterparty. Because of that, some of our transactions may never confirm and we don't + * want to wait forever before declaring that the channel is CLOSED. + * + * @param localCommitPublished + * @param tx a transaction that has been irrevocably confirmed + * @return + */ def updateLocalCommitPublished(localCommitPublished: LocalCommitPublished, tx: Transaction) = { // even if our txes only have one input, maybe our counterparty uses a different scheme so we need to iterate // over all of them to check if they are relevant @@ -953,17 +950,17 @@ object Helpers { } /** - * In CLOSING state, when we are notified that a transaction has been confirmed, we check if this tx belongs in the - * remote commit scenario and keep track of it. - * - * We need to keep track of all transactions spending the outputs of the commitment tx, because some outputs can be - * spent both by us and our counterparty. Because of that, some of our transactions may never confirm and we don't - * want to wait forever before declaring that the channel is CLOSED. - * - * @param remoteCommitPublished - * @param tx a transaction that has been irrevocably confirmed - * @return - */ + * In CLOSING state, when we are notified that a transaction has been confirmed, we check if this tx belongs in the + * remote commit scenario and keep track of it. + * + * We need to keep track of all transactions spending the outputs of the commitment tx, because some outputs can be + * spent both by us and our counterparty. Because of that, some of our transactions may never confirm and we don't + * want to wait forever before declaring that the channel is CLOSED. + * + * @param remoteCommitPublished + * @param tx a transaction that has been irrevocably confirmed + * @return + */ def updateRemoteCommitPublished(remoteCommitPublished: RemoteCommitPublished, tx: Transaction) = { // even if our txes only have one input, maybe our counterparty uses a different scheme so we need to iterate // over all of them to check if they are relevant @@ -979,17 +976,17 @@ object Helpers { } /** - * In CLOSING state, when we are notified that a transaction has been confirmed, we check if this tx belongs in the - * revoked commit scenario and keep track of it. - * - * We need to keep track of all transactions spending the outputs of the commitment tx, because some outputs can be - * spent both by us and our counterparty. Because of that, some of our transactions may never confirm and we don't - * want to wait forever before declaring that the channel is CLOSED. - * - * @param revokedCommitPublished - * @param tx a transaction that has been irrevocably confirmed - * @return - */ + * In CLOSING state, when we are notified that a transaction has been confirmed, we check if this tx belongs in the + * revoked commit scenario and keep track of it. + * + * We need to keep track of all transactions spending the outputs of the commitment tx, because some outputs can be + * spent both by us and our counterparty. Because of that, some of our transactions may never confirm and we don't + * want to wait forever before declaring that the channel is CLOSED. + * + * @param revokedCommitPublished + * @param tx a transaction that has been irrevocably confirmed + * @return + */ def updateRevokedCommitPublished(revokedCommitPublished: RevokedCommitPublished, tx: Transaction) = { // even if our txes only have one input, maybe our counterparty uses a different scheme so we need to iterate // over all of them to check if they are relevant @@ -1008,13 +1005,13 @@ object Helpers { } /** - * A local commit is considered done when: - * - all commitment tx outputs that we can spend have been spent and confirmed (even if the spending tx was not ours) - * - all 3rd stage txes (txes spending htlc txes) have been confirmed - * - * @param localCommitPublished - * @return - */ + * A local commit is considered done when: + * - all commitment tx outputs that we can spend have been spent and confirmed (even if the spending tx was not ours) + * - all 3rd stage txes (txes spending htlc txes) have been confirmed + * + * @param localCommitPublished + * @return + */ def isLocalCommitDone(localCommitPublished: LocalCommitPublished) = { // is the commitment tx buried? (we need to check this because we may not have any outputs) val isCommitTxConfirmed = localCommitPublished.irrevocablySpent.values.toSet.contains(localCommitPublished.commitTx.txid) @@ -1029,12 +1026,12 @@ object Helpers { } /** - * A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed - * (even if the spending tx was not ours). - * - * @param remoteCommitPublished - * @return - */ + * A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed + * (even if the spending tx was not ours). + * + * @param remoteCommitPublished + * @return + */ def isRemoteCommitDone(remoteCommitPublished: RemoteCommitPublished) = { // is the commitment tx buried? (we need to check this because we may not have any outputs) val isCommitTxConfirmed = remoteCommitPublished.irrevocablySpent.values.toSet.contains(remoteCommitPublished.commitTx.txid) @@ -1045,12 +1042,12 @@ object Helpers { } /** - * A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed - * (even if the spending tx was not ours). - * - * @param revokedCommitPublished - * @return - */ + * A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed + * (even if the spending tx was not ours). + * + * @param revokedCommitPublished + * @return + */ def isRevokedCommitDone(revokedCommitPublished: RevokedCommitPublished) = { // is the commitment tx buried? (we need to check this because we may not have any outputs) val isCommitTxConfirmed = revokedCommitPublished.irrevocablySpent.values.toSet.contains(revokedCommitPublished.commitTx.txid) @@ -1065,17 +1062,17 @@ object Helpers { } /** - * This helper function tells if the utxo consumed by the given transaction has already been irrevocably spent (possibly by this very transaction) - * - * It can be useful to: - * - not attempt to publish this tx when we know this will fail - * - not watch for confirmations if we know the tx is already confirmed - * - not watch the corresponding utxo when we already know the final spending tx - * - * @param tx a tx with only one input - * @param irrevocablySpent a map of known spent outpoints - * @return true if we know for sure that the utxos consumed by the tx have already irrevocably been spent, false otherwise - */ + * This helper function tells if the utxo consumed by the given transaction has already been irrevocably spent (possibly by this very transaction) + * + * It can be useful to: + * - not attempt to publish this tx when we know this will fail + * - not watch for confirmations if we know the tx is already confirmed + * - not watch the corresponding utxo when we already know the final spending tx + * + * @param tx a tx with only one input + * @param irrevocablySpent a map of known spent outpoints + * @return true if we know for sure that the utxos consumed by the tx have already irrevocably been spent, false otherwise + */ def inputsAlreadySpent(tx: Transaction, irrevocablySpent: Map[OutPoint, ByteVector32]): Boolean = { require(tx.txIn.size == 1, "only tx with one input is supported") val outPoint = tx.txIn.head.outPoint @@ -1083,14 +1080,14 @@ object Helpers { } /** - * This helper function returns the fee paid by the given transaction. - * - * It relies on the current channel data to find the parent tx and compute the fee, and also provides a description. - * - * @param tx a tx for which we want to compute the fee - * @param d current channel data - * @return if the parent tx is found, a tuple (fee, description) - */ + * This helper function returns the fee paid by the given transaction. + * + * It relies on the current channel data to find the parent tx and compute the fee, and also provides a description. + * + * @param tx a tx for which we want to compute the fee + * @param d current channel data + * @return if the parent tx is found, a tuple (fee, description) + */ def networkFeePaid(tx: Transaction, d: DATA_CLOSING): Option[(Satoshi, String)] = { // only funder pays the fee if (d.commitments.localParams.isFunder) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala index dd9fb9e120..16a7213a84 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair.db import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.eclair.CltvExpiry import fr.acinq.eclair.channel.HasCommitments trait ChannelsDb { @@ -27,9 +28,9 @@ trait ChannelsDb { def listLocalChannels(): Seq[HasCommitments] - def addOrUpdateHtlcInfo(channelId: ByteVector32, commitmentNumber: Long, paymentHash: ByteVector32, cltvExpiry: Long) + def addOrUpdateHtlcInfo(channelId: ByteVector32, commitmentNumber: Long, paymentHash: ByteVector32, cltvExpiry: CltvExpiry) - def listHtlcInfos(channelId: ByteVector32, commitmentNumber: Long): Seq[(ByteVector32, Long)] + def listHtlcInfos(channelId: ByteVector32, commitmentNumber: Long): Seq[(ByteVector32, CltvExpiry)] def close(): Unit 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 d747a3b9dd..4ef958b876 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 @@ -19,6 +19,7 @@ package fr.acinq.eclair.db.sqlite import java.sql.{Connection, Statement} import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.eclair.CltvExpiry import fr.acinq.eclair.channel.HasCommitments import fr.acinq.eclair.db.ChannelsDb import fr.acinq.eclair.wire.ChannelCodecs.stateDataCodec @@ -56,7 +57,7 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb with Logging { override def addOrUpdateChannel(state: HasCommitments): Unit = { val data = stateDataCodec.encode(state).require.toByteArray - using (sqlite.prepareStatement("UPDATE local_channels SET data=? WHERE channel_id=?")) { update => + using(sqlite.prepareStatement("UPDATE local_channels SET data=? WHERE channel_id=?")) { update => update.setBytes(1, data) update.setBytes(2, state.channelId.toArray) if (update.executeUpdate() == 0) { @@ -93,24 +94,24 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb with Logging { } } - def addOrUpdateHtlcInfo(channelId: ByteVector32, commitmentNumber: Long, paymentHash: ByteVector32, cltvExpiry: Long): Unit = { + def addOrUpdateHtlcInfo(channelId: ByteVector32, commitmentNumber: Long, paymentHash: ByteVector32, cltvExpiry: CltvExpiry): Unit = { using(sqlite.prepareStatement("INSERT OR IGNORE INTO htlc_infos VALUES (?, ?, ?, ?)")) { statement => statement.setBytes(1, channelId.toArray) statement.setLong(2, commitmentNumber) statement.setBytes(3, paymentHash.toArray) - statement.setLong(4, cltvExpiry) + statement.setLong(4, cltvExpiry.get) statement.executeUpdate() } } - def listHtlcInfos(channelId: ByteVector32, commitmentNumber: Long): Seq[(ByteVector32, Long)] = { + def listHtlcInfos(channelId: ByteVector32, commitmentNumber: Long): Seq[(ByteVector32, CltvExpiry)] = { using(sqlite.prepareStatement("SELECT payment_hash, cltv_expiry FROM htlc_infos WHERE channel_id=? AND commitment_number=?")) { statement => statement.setBytes(1, channelId.toArray) statement.setLong(2, commitmentNumber) val rs = statement.executeQuery - var q: Queue[(ByteVector32, Long)] = Queue() + var q: Queue[(ByteVector32, CltvExpiry)] = Queue() while (rs.next()) { - q = q :+ (ByteVector32(rs.getByteVector32("payment_hash")), rs.getLong("cltv_expiry")) + q = q :+ (ByteVector32(rs.getByteVector32("payment_hash")), CltvExpiry(rs.getLong("cltv_expiry"))) } q } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala index d936a1474a..9bc6e425d0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/LocalPaymentHandler.scala @@ -17,25 +17,24 @@ package fr.acinq.eclair.payment import akka.actor.{Actor, ActorLogging, Props, Status} -import fr.acinq.bitcoin.{Crypto} +import fr.acinq.bitcoin.Crypto import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, Channel} import fr.acinq.eclair.db.IncomingPayment import fr.acinq.eclair.payment.PaymentLifecycle.ReceivePayment import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, NodeParams, randomBytes32} -import concurrent.duration._ +import fr.acinq.eclair.{NodeParams, randomBytes32} + import scala.compat.Platform import scala.concurrent.ExecutionContext -import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} /** - * Simple payment handler that generates payment requests and fulfills incoming htlcs. - * - * Note that unfulfilled payment requests are kept forever if they don't have an expiry! - * - * Created by PM on 17/06/2016. - */ + * Simple payment handler that generates payment requests and fulfills incoming htlcs. + * + * Note that unfulfilled payment requests are kept forever if they don't have an expiry! + * + * Created by PM on 17/06/2016. + */ class LocalPaymentHandler(nodeParams: NodeParams) extends Actor with ActorLogging { implicit val ec: ExecutionContext = context.system.dispatcher @@ -60,7 +59,7 @@ class LocalPaymentHandler(nodeParams: NodeParams) extends Actor with ActorLoggin case htlc: UpdateAddHtlc => paymentDb.getPendingPaymentRequestAndPreimage(htlc.paymentHash) match { case Some((paymentPreimage, paymentRequest)) => - val minFinalExpiry = Globals.blockCount.get() + paymentRequest.minFinalCltvExpiry.getOrElse(Channel.MIN_CLTV_EXPIRY) + val minFinalExpiry = paymentRequest.minFinalCltvExpiryDelta.getOrElse(Channel.MIN_CLTV_EXPIRY_DELTA).toCltvExpiry // The htlc amount must be equal or greater than the requested amount. A slight overpaying is permitted, however // it must not be greater than two times the requested amount. // see https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#failure-messages diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index 74374ec9d5..a62424f4e0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -19,8 +19,8 @@ package fr.acinq.eclair.payment import java.util.UUID import akka.actor.{ActorRef, FSM, Props, Status} +import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.Crypto.PublicKey -import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.eclair._ import fr.acinq.eclair.channel.{AddHtlcFailed, CMD_ADD_HTLC, Channel, Register} import fr.acinq.eclair.crypto.{Sphinx, TransportHandler} @@ -32,13 +32,12 @@ import fr.acinq.eclair.wire._ import scodec.Attempt import scodec.bits.ByteVector -import concurrent.duration._ import scala.compat.Platform import scala.util.{Failure, Success} /** - * Created by PM on 26/08/2016. - */ + * Created by PM on 26/08/2016. + */ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, register: ActorRef) extends FSM[PaymentLifecycle.State, PaymentLifecycle.Data] { val paymentsDb = nodeParams.db.payments @@ -47,7 +46,7 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis when(WAITING_FOR_REQUEST) { case Event(c: SendPaymentToRoute, WaitingForRequest) => - val send = SendPayment(c.amount, c.paymentHash, c.hops.last, finalCltvExpiry = c.finalCltvExpiry, maxAttempts = 1) + val send = SendPayment(c.amount, c.paymentHash, c.hops.last, finalCltvExpiryDelta = c.finalCltvExpiryDelta, maxAttempts = 1) paymentsDb.addOutgoingPayment(OutgoingPayment(id, c.paymentHash, None, c.amount, Platform.currentTime, None, OutgoingPaymentStatus.PENDING)) router ! FinalizeRoute(c.hops) goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, send, failures = Nil) @@ -63,9 +62,9 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis log.info(s"route found: attempt=${failures.size + 1}/${c.maxAttempts} route=${hops.map(_.nextNodeId).mkString("->")} channels=${hops.map(_.lastUpdate.shortChannelId).mkString("->")}") val firstHop = hops.head // we add one block in order to not have our htlc fail when a new block has just been found - val finalExpiry = Globals.blockCount.get().toInt + c.finalCltvExpiry.toInt + 1 + val finalExpiry = (c.finalCltvExpiryDelta + 1).toCltvExpiry - val (cmd, sharedSecrets) = buildCommand(id,c.amount, finalExpiry, c.paymentHash, hops) + val (cmd, sharedSecrets) = buildCommand(id, c.amount, finalExpiry, c.paymentHash, hops) register ! Register.ForwardShortId(firstHop.lastUpdate.shortChannelId, cmd) goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, c, cmd, failures, sharedSecrets, ignoreNodes, ignoreChannels, hops) @@ -185,7 +184,7 @@ class PaymentLifecycle(nodeParams: NodeParams, id: UUID, router: ActorRef, regis case Event(_: TransportHandler.ReadAck, _) => stay // ignored, router replies with this when we forward a channel_update } - def reply(to: ActorRef, e: PaymentResult) = { + def reply(to: ActorRef, e: PaymentResult): Unit = { to ! e context.system.eventStream.publish(e) } @@ -200,12 +199,12 @@ object PaymentLifecycle { // @formatter:off case class ReceivePayment(amount_opt: Option[MilliSatoshi], description: String, expirySeconds_opt: Option[Long] = None, extraHops: List[List[ExtraHop]] = Nil, fallbackAddress: Option[String] = None, paymentPreimage: Option[ByteVector32] = None) sealed trait GenericSendPayment - case class SendPaymentToRoute(amount: MilliSatoshi, paymentHash: ByteVector32, hops: Seq[PublicKey], finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY) extends GenericSendPayment + case class SendPaymentToRoute(amount: MilliSatoshi, paymentHash: ByteVector32, hops: Seq[PublicKey], finalCltvExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA) extends GenericSendPayment case class SendPayment(amount: MilliSatoshi, paymentHash: ByteVector32, targetNodeId: PublicKey, assistedRoutes: Seq[Seq[ExtraHop]] = Nil, - finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY, + finalCltvExpiryDelta: CltvExpiryDelta = Channel.MIN_CLTV_EXPIRY_DELTA, maxAttempts: Int, routeParams: Option[RouteParams] = None) extends GenericSendPayment { require(amount > MilliSatoshi(0), s"amountMsat must be > 0") @@ -245,23 +244,23 @@ object PaymentLifecycle { } /** - * - * @param finalAmount the final htlc amount in millisatoshis - * @param finalExpiry the final htlc expiry in number of blocks - * @param hops the hops as computed by the router + extra routes from payment request - * @return a (firstAmountMsat, firstExpiry, payloads) tuple where: - * - firstAmountMsat is the amount for the first htlc in the route - * - firstExpiry is the cltv expiry for the first htlc in the route - * - a sequence of payloads that will be used to build the onion - */ - def buildPayloads(finalAmount: MilliSatoshi, finalExpiry: Long, hops: Seq[Hop]): (MilliSatoshi, Long, Seq[PerHopPayload]) = + * + * @param finalAmount the final htlc amount in millisatoshis + * @param finalExpiry the final htlc expiry in number of blocks + * @param hops the hops as computed by the router + extra routes from payment request + * @return a (firstAmountMsat, firstExpiry, payloads) tuple where: + * - firstAmountMsat is the amount for the first htlc in the route + * - firstExpiry is the cltv expiry for the first htlc in the route + * - a sequence of payloads that will be used to build the onion + */ + def buildPayloads(finalAmount: MilliSatoshi, finalExpiry: CltvExpiry, hops: Seq[Hop]): (MilliSatoshi, CltvExpiry, Seq[PerHopPayload]) = hops.reverse.foldLeft((finalAmount, finalExpiry, PerHopPayload(ShortChannelId(0L), finalAmount, finalExpiry) :: Nil)) { case ((msat, expiry, payloads), hop) => val nextFee = nodeFee(hop.lastUpdate.feeBaseMsat, hop.lastUpdate.feeProportionalMillionths, msat) (msat + nextFee, expiry + hop.lastUpdate.cltvExpiryDelta, PerHopPayload(hop.lastUpdate.shortChannelId, msat, expiry) +: payloads) } - def buildCommand(id: UUID, finalAmount: MilliSatoshi, finalExpiry: Long, paymentHash: ByteVector32, hops: Seq[Hop]): (CMD_ADD_HTLC, Seq[(ByteVector32, PublicKey)]) = { + def buildCommand(id: UUID, finalAmount: MilliSatoshi, finalExpiry: CltvExpiry, paymentHash: ByteVector32, hops: Seq[Hop]): (CMD_ADD_HTLC, Seq[(ByteVector32, PublicKey)]) = { val (firstAmount, firstExpiry, payloads) = buildPayloads(finalAmount, finalExpiry, hops.drop(1)) val nodes = hops.map(_.nextNodeId) // BOLT 2 requires that associatedData == paymentHash @@ -270,16 +269,16 @@ object PaymentLifecycle { } /** - * Rewrites a list of failures to retrieve the meaningful part. - *

- * If a list of failures with many elements ends up with a LocalFailure RouteNotFound, this RouteNotFound failure - * should be removed. This last failure is irrelevant information. In such a case only the n-1 attempts were rejected - * with a **significant reason** ; the final RouteNotFound error provides no meaningful insight. - *

- * This method should be used by the user interface to provide a non-exhaustive but more useful feedback. - * - * @param failures a list of payment failures for a payment - */ + * Rewrites a list of failures to retrieve the meaningful part. + *

+ * If a list of failures with many elements ends up with a LocalFailure RouteNotFound, this RouteNotFound failure + * should be removed. This last failure is irrelevant information. In such a case only the n-1 attempts were rejected + * with a **significant reason** ; the final RouteNotFound error provides no meaningful insight. + *

+ * This method should be used by the user interface to provide a non-exhaustive but more useful feedback. + * + * @param failures a list of payment failures for a payment + */ def transformForUser(failures: Seq[PaymentFailure]): Seq[PaymentFailure] = { failures.map { case LocalFailure(AddHtlcFailed(_, _, t, _, _, _)) => LocalFailure(t) // we're interested in the error which caused the add-htlc to fail @@ -291,24 +290,17 @@ object PaymentLifecycle { } /** - * This method retrieves the channel update that we used when we built a route. - * - * It just iterates over the hops, but there are at most 20 of them. - * - * @param nodeId - * @param hops - * @return the channel update if found - */ + * This method retrieves the channel update that we used when we built a route. + * It just iterates over the hops, but there are at most 20 of them. + * + * @return the channel update if found + */ def getChannelUpdateForNode(nodeId: PublicKey, hops: Seq[Hop]): Option[ChannelUpdate] = hops.find(_.nodeId == nodeId).map(_.lastUpdate) /** - * This allows us to detect if a bad node always answers with a new update (e.g. with a slightly different expiry or fee) - * in order to mess with us. - * - * @param nodeId - * @param failures - * @return - */ + * This allows us to detect if a bad node always answers with a new update (e.g. with a slightly different expiry or fee) + * in order to mess with us. + */ def hasAlreadyFailedOnce(nodeId: PublicKey, failures: Seq[PaymentFailure]): Boolean = failures .collectFirst { case RemoteFailure(_, Sphinx.DecryptedFailurePacket(origin, u: Update)) if origin == nodeId => u.update } 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 fc6ea0ca3e..2a1ab71bdc 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 @@ -18,7 +18,7 @@ package fr.acinq.eclair.payment import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, Block, ByteVector32, ByteVector64, Crypto} import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, ShortChannelId} import fr.acinq.eclair.payment.PaymentRequest._ import scodec.Codec import scodec.bits.{BitVector, ByteOrdering, ByteVector} @@ -74,8 +74,8 @@ case class PaymentRequest(prefix: String, amount: Option[MilliSatoshi], timestam case expiry: PaymentRequest.Expiry => expiry.toLong } - lazy val minFinalCltvExpiry: Option[Long] = tags.collectFirst { - case cltvExpiry: PaymentRequest.MinFinalCltvExpiry => cltvExpiry.toLong + lazy val minFinalCltvExpiryDelta: Option[CltvExpiryDelta] = tags.collectFirst { + case cltvExpiry: PaymentRequest.MinFinalCltvExpiry => cltvExpiry.toCltvExpiryDelta } def isExpired: Boolean = expiry match { @@ -268,7 +268,7 @@ object PaymentRequest { * @param feeProportionalMillionths node proportional fee * @param cltvExpiryDelta node cltv expiry delta */ - case class ExtraHop(nodeId: PublicKey, shortChannelId: ShortChannelId, feeBaseMsat: Long, feeProportionalMillionths: Long, cltvExpiryDelta: Int) + case class ExtraHop(nodeId: PublicKey, shortChannelId: ShortChannelId, feeBaseMsat: Long, feeProportionalMillionths: Long, cltvExpiryDelta: CltvExpiryDelta) /** * Routing Info @@ -296,7 +296,7 @@ object PaymentRequest { * */ case class MinFinalCltvExpiry(bin: BitVector) extends TaggedField { - def toLong: Long = bin.toLong(signed = false) + def toCltvExpiryDelta = CltvExpiryDelta(bin.toInt(signed = false)) } object MinFinalCltvExpiry { @@ -320,7 +320,7 @@ object PaymentRequest { ("shortChannelId" | shortchannelid) :: ("fee_base_msat" | uint32) :: ("fee_proportional_millionth" | uint32) :: - ("cltv_expiry_delta" | uint16) + ("cltv_expiry_delta" | cltvExpiryDelta) ).as[ExtraHop] val extraHopsLengthCodec = Codec[Int]( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala index 7112bdd0ba..ac1eff6719 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala @@ -20,15 +20,15 @@ import java.util.UUID import akka.actor.{Actor, ActorLogging, ActorRef, Props, Status} import akka.event.LoggingAdapter -import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.db.OutgoingPaymentStatus import fr.acinq.eclair.payment.PaymentLifecycle.{PaymentFailed, PaymentSucceeded} import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{MilliSatoshi, NodeParams, ShortChannelId, nodeFee} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, NodeParams, ShortChannelId, nodeFee} import grizzled.slf4j.Logging import scodec.{Attempt, DecodeResult} @@ -53,8 +53,8 @@ case class UsableBalances(remoteNodeId: PublicKey, shortChannelId: ShortChannelI /** - * Created by PM on 01/02/2017. - */ + * Created by PM on 01/02/2017. + */ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorRef) extends Actor with ActorLogging { import Relayer._ @@ -216,18 +216,18 @@ object Relayer extends Logging { case class FinalPayload(add: UpdateAddHtlc, payload: PerHopPayload) extends NextPayload case class RelayPayload(add: UpdateAddHtlc, payload: PerHopPayload, nextPacket: OnionRoutingPacket) extends NextPayload { val relayFeeMsat: MilliSatoshi = add.amountMsat - payload.amtToForward - val expiryDelta: Long = add.cltvExpiry - payload.outgoingCltvValue + val expiryDelta: CltvExpiryDelta = add.cltvExpiry - payload.outgoingCltvValue } // @formatter:on /** - * Decrypt the onion of a received htlc, and find out if the payment is to be relayed, - * or if our node is the last one in the route - * - * @param add incoming htlc - * @param privateKey this node's private key - * @return the payload for the next hop or an error. - */ + * Decrypt the onion of a received htlc, and find out if the payment is to be relayed, + * or if our node is the last one in the route + * + * @param add incoming htlc + * @param privateKey this node's private key + * @return the payload for the next hop or an error. + */ def decryptPacket(add: UpdateAddHtlc, privateKey: PrivateKey): Either[BadOnion, NextPayload] = Sphinx.PaymentPacket.peel(privateKey, add.paymentHash, add.onionRoutingPacket) match { case Right(p@Sphinx.DecryptedPacket(payload, nextPacket, _)) => @@ -249,13 +249,13 @@ object Relayer extends Logging { } /** - * Handle an incoming htlc when we are the last node - * - * @param finalPayload payload - * @return either: - * - a CMD_FAIL_HTLC to be sent back upstream - * - an UpdateAddHtlc to forward - */ + * Handle an incoming htlc when we are the last node + * + * @param finalPayload payload + * @return either: + * - a CMD_FAIL_HTLC to be sent back upstream + * - an UpdateAddHtlc to forward + */ def handleFinal(finalPayload: FinalPayload): Either[CMD_FAIL_HTLC, UpdateAddHtlc] = { import finalPayload.add finalPayload.payload match { @@ -275,13 +275,13 @@ object Relayer extends Logging { // @formatter:on /** - * Handle an incoming htlc when we are a relaying node - * - * @param relayPayload payload - * @return either: - * - a CMD_FAIL_HTLC to be sent back upstream - * - a CMD_ADD_HTLC to propagate downstream - */ + * Handle an incoming htlc when we are a relaying node + * + * @param relayPayload payload + * @return either: + * - a CMD_FAIL_HTLC to be sent back upstream + * - a CMD_ADD_HTLC to propagate downstream + */ def handleRelay(relayPayload: RelayPayload, channelUpdates: Map[ShortChannelId, OutgoingChannel], node2channels: mutable.Map[PublicKey, mutable.Set[ShortChannelId]] with mutable.MultiMap[PublicKey, ShortChannelId], previousFailures: Seq[AddHtlcFailed], chainHash: ByteVector32)(implicit log: LoggingAdapter): RelayResult = { import relayPayload._ log.info(s"relaying htlc #${add.id} paymentHash={} from channelId={} to requestedShortChannelId={} previousAttempts={}", add.paymentHash, add.channelId, relayPayload.payload.shortChannelId, previousFailures.size) @@ -302,11 +302,11 @@ object Relayer extends Logging { } /** - * Select a channel to the same node to relay the payment to, that has the lowest balance and is compatible in - * terms of fees, expiry_delta, etc. - * - * If no suitable channel is found we default to the originally requested channel. - */ + * Select a channel to the same node to relay the payment to, that has the lowest balance and is compatible in + * terms of fees, expiry_delta, etc. + * + * If no suitable channel is found we default to the originally requested channel. + */ def selectPreferredChannel(relayPayload: RelayPayload, channelUpdates: Map[ShortChannelId, OutgoingChannel], node2channels: mutable.Map[PublicKey, mutable.Set[ShortChannelId]] with mutable.MultiMap[PublicKey, ShortChannelId], alreadyTried: Seq[ShortChannelId])(implicit log: LoggingAdapter): Option[ShortChannelId] = { import relayPayload.add val requestedShortChannelId = relayPayload.payload.shortChannelId @@ -356,10 +356,10 @@ object Relayer extends Logging { } /** - * This helper method will tell us if it is not even worth attempting to relay the payment to our local outgoing - * channel, because some parameters don't match with our settings for that channel. In that case we directly fail the - * htlc. - */ + * This helper method will tell us if it is not even worth attempting to relay the payment to our local outgoing + * channel, because some parameters don't match with our settings for that channel. In that case we directly fail the + * htlc. + */ def relayOrFail(relayPayload: RelayPayload, channelUpdate_opt: Option[ChannelUpdate], previousFailures: Seq[AddHtlcFailed] = Seq.empty)(implicit log: LoggingAdapter): RelayResult = { import relayPayload._ channelUpdate_opt match { @@ -379,9 +379,9 @@ object Relayer extends Logging { } /** - * This helper method translates relaying errors (returned by the downstream outgoing channel) to BOLT 4 standard - * errors that we should return upstream. - */ + * This helper method translates relaying errors (returned by the downstream outgoing channel) to BOLT 4 standard + * errors that we should return upstream. + */ private def translateError(failure: AddHtlcFailed): FailureMessage = { val error = failure.t val channelUpdate_opt = failure.channelUpdate 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 abbfd61d4e..2baa73e7a9 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 @@ -19,7 +19,7 @@ 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.wire._ -import fr.acinq.eclair.{Features, MilliSatoshi, ShortChannelId, serializationResult} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, serializationResult} import scodec.bits.{BitVector, ByteVector} import shapeless.HNil @@ -37,7 +37,7 @@ object Announcements { def nodeAnnouncementWitnessEncode(timestamp: Long, nodeId: PublicKey, rgbColor: Color, alias: String, features: ByteVector, addresses: List[NodeAddress], unknownFields: ByteVector): ByteVector = sha256(sha256(serializationResult(LightningMessageCodecs.nodeAnnouncementWitnessCodec.encode(features :: timestamp :: nodeId :: rgbColor :: alias :: addresses :: unknownFields :: HNil)))) - def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: Long, messageFlags: Byte, channelFlags: Byte, cltvExpiryDelta: Int, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: Option[MilliSatoshi], unknownFields: ByteVector): ByteVector = + def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: Long, messageFlags: Byte, channelFlags: Byte, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: Option[MilliSatoshi], unknownFields: ByteVector): ByteVector = sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: messageFlags :: channelFlags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: htlcMaximumMsat :: unknownFields :: HNil)))) def signChannelAnnouncement(chainHash: ByteVector32, shortChannelId: ShortChannelId, localNodeSecret: PrivateKey, remoteNodeId: PublicKey, localFundingPrivKey: PrivateKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector64, ByteVector64) = { @@ -128,7 +128,7 @@ object Announcements { def makeChannelFlags(isNode1: Boolean, enable: Boolean): Byte = BitVector.bits(!enable :: !isNode1 :: Nil).padLeft(8).toByte() - def makeChannelUpdate(chainHash: ByteVector32, nodeSecret: PrivateKey, remoteNodeId: PublicKey, shortChannelId: ShortChannelId, cltvExpiryDelta: Int, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: MilliSatoshi, enable: Boolean = true, timestamp: Long = Platform.currentTime.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: Long = Platform.currentTime.milliseconds.toSeconds): ChannelUpdate = { val messageFlags = makeMessageFlags(hasOptionChannelHtlcMax = true) // NB: we always support option_channel_htlc_max val channelFlags = makeChannelFlags(isNode1 = isNode1(nodeSecret.publicKey, remoteNodeId), enable = enable) val htlcMaximumMsatOpt = Some(htlcMaximumMsat) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala index 27e92a3238..ec047b1d5c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala @@ -29,7 +29,7 @@ object Graph { // @formatter:off // A compound weight for an edge, weight is obtained with (cost X factor),'cost' contains the actual amount+fees in millisatoshi, 'cltvCumulative' the total CLTV necessary to reach this edge - case class RichWeight(cost: MilliSatoshi, length: Int, cltv: Int, weight: Double) extends Ordered[RichWeight] { + case class RichWeight(cost: MilliSatoshi, length: Int, cltv: CltvExpiryDelta, weight: Double) extends Ordered[RichWeight] { override def compare(that: RichWeight): Int = this.weight.compareTo(that.weight) } case class WeightRatios(cltvDeltaFactor: Double, ageFactor: Double, capacityFactor: Double) { @@ -40,9 +40,9 @@ object Graph { // @formatter:on /** - * This comparator must be consistent with the "equals" behavior, thus for two weighted nodes with - * the same weight we distinguish them by their public key. See https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html - */ + * This comparator must be consistent with the "equals" behavior, thus for two weighted nodes with + * the same weight we distinguish them by their public key. See https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html + */ object QueueComparator extends Ordering[WeightedNode] { override def compare(x: WeightedNode, y: WeightedNode): Int = { val weightCmp = x.weight.compareTo(y.weight) @@ -56,19 +56,19 @@ object Graph { } /** - * Yen's algorithm to find the k-shortest (loopless) paths in a graph, uses dijkstra as search algo. Is guaranteed to terminate finding - * at most @pathsToFind paths sorted by cost (the cheapest is in position 0). - * - * @param graph - * @param sourceNode - * @param targetNode - * @param amount - * @param pathsToFind - * @param wr an object containing the ratios used to 'weight' edges when searching for the shortest path - * @param currentBlockHeight the height of the chain tip (latest block) - * @param boundaries a predicate function that can be used to impose limits on the outcome of the search - * @return - */ + * Yen's algorithm to find the k-shortest (loopless) paths in a graph, uses dijkstra as search algo. Is guaranteed to terminate finding + * at most @pathsToFind paths sorted by cost (the cheapest is in position 0). + * + * @param graph + * @param sourceNode + * @param targetNode + * @param amount + * @param pathsToFind + * @param wr an object containing the ratios used to 'weight' edges when searching for the shortest path + * @param currentBlockHeight the height of the chain tip (latest block) + * @param boundaries a predicate function that can be used to impose limits on the outcome of the search + * @return + */ def yenKshortestPaths(graph: DirectedGraph, sourceNode: PublicKey, targetNode: PublicKey, @@ -88,7 +88,7 @@ object Graph { val candidates = new mutable.PriorityQueue[WeightedPath] // find the shortest path, k = 0 - val initialWeight = RichWeight(cost = amount, 0, 0, 0) + val initialWeight = RichWeight(cost = amount, 0, CltvExpiryDelta(0), 0) val shortestPath = dijkstraShortestPath(graph, sourceNode, targetNode, ignoredEdges, extraEdges, initialWeight, boundaries, currentBlockHeight, wr) shortestPaths += WeightedPath(shortestPath, pathWeight(shortestPath, amount, isPartial = false, currentBlockHeight, wr)) @@ -159,20 +159,20 @@ object Graph { } /** - * Finds the shortest path in the graph, uses a modified version of Dijsktra's algorithm that computes - * the shortest path from the target to the source (this is because we want to calculate the weight of the - * edges correctly). The graph @param g is optimized for querying the incoming edges given a vertex. - * - * @param g the graph on which will be performed the search - * @param sourceNode the starting node of the path we're looking for - * @param targetNode the destination node of the path - * @param ignoredEdges a list of edges we do not want to consider - * @param extraEdges a list of extra edges we want to consider but are not currently in the graph - * @param wr an object containing the ratios used to 'weight' edges when searching for the shortest path - * @param currentBlockHeight the height of the chain tip (latest block) - * @param boundaries a predicate function that can be used to impose limits on the outcome of the search - * @return - */ + * Finds the shortest path in the graph, uses a modified version of Dijsktra's algorithm that computes + * the shortest path from the target to the source (this is because we want to calculate the weight of the + * edges correctly). The graph @param g is optimized for querying the incoming edges given a vertex. + * + * @param g the graph on which will be performed the search + * @param sourceNode the starting node of the path we're looking for + * @param targetNode the destination node of the path + * @param ignoredEdges a list of edges we do not want to consider + * @param extraEdges a list of extra edges we want to consider but are not currently in the graph + * @param wr an object containing the ratios used to 'weight' edges when searching for the shortest path + * @param currentBlockHeight the height of the chain tip (latest block) + * @param boundaries a predicate function that can be used to impose limits on the outcome of the search + * @return + */ def dijkstraShortestPath(g: DirectedGraph, sourceNode: PublicKey, @@ -237,7 +237,7 @@ object Graph { // we call containsKey first because "getOrDefault" is not available in JDK7 val neighborCost = weight.containsKey(neighbor) match { - case false => RichWeight(MilliSatoshi(Long.MaxValue), Int.MaxValue, Int.MaxValue, Double.MaxValue) + case false => RichWeight(MilliSatoshi(Long.MaxValue), Int.MaxValue, CltvExpiryDelta(Int.MaxValue), Double.MaxValue) case true => weight.get(neighbor) } @@ -294,8 +294,8 @@ object Graph { val edgeMaxCapacity = edge.update.htlcMaximumMsat.getOrElse(CAPACITY_CHANNEL_LOW) val capFactor = 1 - normalize(edgeMaxCapacity.toLong, CAPACITY_CHANNEL_LOW.toLong, CAPACITY_CHANNEL_HIGH.toLong) - // Every edge is weighted by its clvt-delta value, normalized - val channelCltvDelta = edge.update.cltvExpiryDelta + // Every edge is weighted by its cltv-delta value, normalized + val channelCltvDelta = edge.update.cltvExpiryDelta.delta val cltvFactor = normalize(channelCltvDelta, CLTV_LOW, CLTV_HIGH) // NB 'edgeCost' includes the amount to be sent plus the fees that must be paid to traverse this @param edge @@ -309,16 +309,16 @@ object Graph { } /** - * This forces channel_update(s) with fees=0 to have a minimum of 1msat for the baseFee. Note that - * the update is not being modified and the result of the route computation will still have the update - * with fees=0 which is what will be used to build the onion. - * - * @param edge the edge for which we want to compute the weight - * @param amountWithFees the value that this edge will have to carry along - * @return the new amount updated with the necessary fees for this edge - */ - private def edgeFeeCost(edge: GraphEdge, amountWithFees: MilliSatoshi):MilliSatoshi = { - if(edgeHasZeroFee(edge)) amountWithFees + nodeFee(baseMsat = MilliSatoshi(1), proportional = 0, amountWithFees) + * This forces channel_update(s) with fees=0 to have a minimum of 1msat for the baseFee. Note that + * the update is not being modified and the result of the route computation will still have the update + * with fees=0 which is what will be used to build the onion. + * + * @param edge the edge for which we want to compute the weight + * @param amountWithFees the value that this edge will have to carry along + * @return the new amount updated with the necessary fees for this edge + */ + private def edgeFeeCost(edge: GraphEdge, amountWithFees: MilliSatoshi): MilliSatoshi = { + if (edgeHasZeroFee(edge)) amountWithFees + nodeFee(baseMsat = MilliSatoshi(1), proportional = 0, amountWithFees) else amountWithFees + nodeFee(edge.update.feeBaseMsat, edge.update.feeProportionalMillionths, amountWithFees) } @@ -328,7 +328,7 @@ object Graph { // Calculates the total cost of a path (amount + fees), direct channels with the source will have a cost of 0 (pay no fees) def pathWeight(path: Seq[GraphEdge], amountMsat: MilliSatoshi, isPartial: Boolean, currentBlockHeight: Long, wr: Option[WeightRatios]): RichWeight = { - path.drop(if (isPartial) 0 else 1).foldRight(RichWeight(amountMsat, 0, 0, 0)) { (edge, prev) => + path.drop(if (isPartial) 0 else 1).foldRight(RichWeight(amountMsat, 0, CltvExpiryDelta(0), 0)) { (edge, prev) => edgeWeight(edge, prev, false, currentBlockHeight, wr) } } @@ -348,14 +348,14 @@ object Graph { val CLTV_HIGH = 2016 /** - * Normalize the given value between (0, 1). If the @param value is outside the min/max window we flatten it to something very close to the - * extremes but always bigger than zero so it's guaranteed to never return zero - * - * @param value - * @param min - * @param max - * @return - */ + * Normalize the given value between (0, 1). If the @param value is outside the min/max window we flatten it to something very close to the + * extremes but always bigger than zero so it's guaranteed to never return zero + * + * @param value + * @param min + * @param max + * @return + */ def normalize(value: Double, min: Double, max: Double) = { if (value <= min) 0.00001D else if (value > max) 0.99999D @@ -364,16 +364,16 @@ object Graph { } /** - * A graph data structure that uses the adjacency lists, stores the incoming edges of the neighbors - */ + * A graph data structure that uses the adjacency lists, stores the incoming edges of the neighbors + */ object GraphStructure { /** - * Representation of an edge of the graph - * - * @param desc channel description - * @param update channel info - */ + * Representation of an edge of the graph + * + * @param desc channel description + * @param update channel info + */ case class GraphEdge(desc: ChannelDesc, update: ChannelUpdate) case class DirectedGraph(private val vertices: Map[PublicKey, List[GraphEdge]]) { @@ -385,11 +385,11 @@ object Graph { } /** - * Adds and edge to the graph, if one of the two vertices is not found, it will be created. - * - * @param edge the edge that is going to be added to the graph - * @return a new graph containing this edge - */ + * Adds and edge to the graph, if one of the two vertices is not found, it will be created. + * + * @param edge the edge that is going to be added to the graph + * @return a new graph containing this edge + */ def addEdge(edge: GraphEdge): DirectedGraph = { val vertexIn = edge.desc.a @@ -405,12 +405,12 @@ object Graph { } /** - * Removes the edge corresponding to the given pair channel-desc/channel-update, - * NB: this operation does NOT remove any vertex - * - * @param desc the channel description associated to the edge that will be removed - * @return - */ + * Removes the edge corresponding to the given pair channel-desc/channel-update, + * NB: this operation does NOT remove any vertex + * + * @param desc the channel description associated to the edge that will be removed + * @return + */ def removeEdge(desc: ChannelDesc): DirectedGraph = { containsEdge(desc) match { case true => DirectedGraph(vertices.updated(desc.b, vertices(desc.b).filterNot(_.desc == desc))) @@ -423,9 +423,9 @@ object Graph { } /** - * @param edge - * @return For edges to be considered equal they must have the same in/out vertices AND same shortChannelId - */ + * @param edge + * @return For edges to be considered equal they must have the same in/out vertices AND same shortChannelId + */ def getEdge(edge: GraphEdge): Option[GraphEdge] = getEdge(edge.desc) def getEdge(desc: ChannelDesc): Option[GraphEdge] = { @@ -435,10 +435,10 @@ object Graph { } /** - * @param keyA the key associated with the starting vertex - * @param keyB the key associated with the ending vertex - * @return all the edges going from keyA --> keyB (there might be more than one if it refers to different shortChannelId) - */ + * @param keyA the key associated with the starting vertex + * @param keyB the key associated with the ending vertex + * @return all the edges going from keyA --> keyB (there might be more than one if it refers to different shortChannelId) + */ def getEdgesBetween(keyA: PublicKey, keyB: PublicKey): Seq[GraphEdge] = { vertices.get(keyB) match { case None => Seq.empty @@ -447,31 +447,31 @@ object Graph { } /** - * The the incoming edges for vertex @param keyB - * - * @param keyB - * @return - */ + * The the incoming edges for vertex @param keyB + * + * @param keyB + * @return + */ def getIncomingEdgesOf(keyB: PublicKey): Seq[GraphEdge] = { vertices.getOrElse(keyB, List.empty) } /** - * Removes a vertex and all it's associated edges (both incoming and outgoing) - * - * @param key - * @return - */ + * Removes a vertex and all it's associated edges (both incoming and outgoing) + * + * @param key + * @return + */ def removeVertex(key: PublicKey): DirectedGraph = { DirectedGraph(removeEdges(getIncomingEdgesOf(key).map(_.desc)).vertices - key) } /** - * Adds a new vertex to the graph, starting with no edges - * - * @param key - * @return - */ + * Adds a new vertex to the graph, starting with no edges + * + * @param key + * @return + */ def addVertex(key: PublicKey): DirectedGraph = { vertices.get(key) match { case None => DirectedGraph(vertices + (key -> List.empty)) @@ -480,35 +480,35 @@ object Graph { } /** - * Note this operation will traverse all edges in the graph (expensive) - * - * @param key - * @return a list of the outgoing edges of vertex @param key, if the edge doesn't exists an empty list is returned - */ + * Note this operation will traverse all edges in the graph (expensive) + * + * @param key + * @return a list of the outgoing edges of vertex @param key, if the edge doesn't exists an empty list is returned + */ def edgesOf(key: PublicKey): Seq[GraphEdge] = { edgeSet().filter(_.desc.a == key).toSeq } /** - * @return the set of all the vertices in this graph - */ + * @return the set of all the vertices in this graph + */ def vertexSet(): Set[PublicKey] = vertices.keySet /** - * @return an iterator of all the edges in this graph - */ + * @return an iterator of all the edges in this graph + */ def edgeSet(): Iterable[GraphEdge] = vertices.values.flatten /** - * @param key - * @return true if this graph contain a vertex with this key, false otherwise - */ + * @param key + * @return true if this graph contain a vertex with this key, false otherwise + */ def containsVertex(key: PublicKey): Boolean = vertices.contains(key) /** - * @param desc - * @return true if this edge desc is in the graph. For edges to be considered equal they must have the same in/out vertices AND same shortChannelId - */ + * @param desc + * @return true if this edge desc is in the graph. For edges to be considered equal they must have the same in/out vertices AND same shortChannelId + */ def containsEdge(desc: ChannelDesc): Boolean = { vertices.get(desc.b) match { case None => false diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 59d241fa72..f9983d76fe 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -49,7 +49,7 @@ case class RouterConf(randomizeRouteSelection: Boolean, searchMaxFeeBase: Satoshi, searchMaxFeePct: Double, searchMaxRouteLength: Int, - searchMaxCltv: Int, + searchMaxCltv: CltvExpiryDelta, searchHeuristicsEnabled: Boolean, searchRatioCltv: Double, searchRatioChannelAge: Double, @@ -57,7 +57,7 @@ case class RouterConf(randomizeRouteSelection: Boolean, case class ChannelDesc(shortChannelId: ShortChannelId, a: PublicKey, b: PublicKey) case class Hop(nodeId: PublicKey, nextNodeId: PublicKey, lastUpdate: ChannelUpdate) -case class RouteParams(randomize: Boolean, maxFeeBase: MilliSatoshi, maxFeePct: Double, routeMaxLength: Int, routeMaxCltv: Int, ratios: Option[WeightRatios]) +case class RouteParams(randomize: Boolean, maxFeeBase: MilliSatoshi, maxFeePct: Double, routeMaxLength: Int, routeMaxCltv: CltvExpiryDelta, ratios: Option[WeightRatios]) case class RouteRequest(source: PublicKey, target: PublicKey, amount: MilliSatoshi, @@ -68,7 +68,7 @@ case class RouteRequest(source: PublicKey, case class FinalizeRoute(hops:Seq[PublicKey]) case class RouteResponse(hops: Seq[Hop], ignoreNodes: Set[PublicKey], ignoreChannels: Set[ChannelDesc]) { - require(hops.size > 0, "route cannot be empty") + require(hops.nonEmpty, "route cannot be empty") } case class ExcludeChannel(desc: ChannelDesc) // this is used when we get a TemporaryChannelFailure, to give time for the channel to recover (note that exclusions are directed) case class LiftChannelExclusion(desc: ChannelDesc) @@ -103,8 +103,8 @@ case object TickPruneStaleChannels // @formatter:on /** - * Created by PM on 24/05/2016. - */ + * Created by PM on 24/05/2016. + */ class Router(nodeParams: NodeParams, watcher: ActorRef, initialized: Option[Promise[Done]] = None) extends FSMDiagnosticActorLogging[State, Data] { @@ -307,10 +307,10 @@ class Router(nodeParams: NodeParams, watcher: ActorRef, initialized: Option[Prom // let's clean the db and send the events log.info("pruning shortChannelId={} (spent)", shortChannelId) db.removeChannel(shortChannelId) // NB: this also removes channel updates - // we also need to remove updates from the graph - val graph1 = d.graph - .removeEdge(ChannelDesc(lostChannel.shortChannelId, lostChannel.nodeId1, lostChannel.nodeId2)) - .removeEdge(ChannelDesc(lostChannel.shortChannelId, lostChannel.nodeId2, lostChannel.nodeId1)) + // we also need to remove updates from the graph + val graph1 = d.graph + .removeEdge(ChannelDesc(lostChannel.shortChannelId, lostChannel.nodeId1, lostChannel.nodeId2)) + .removeEdge(ChannelDesc(lostChannel.shortChannelId, lostChannel.nodeId2, lostChannel.nodeId1)) context.system.eventStream.publish(ChannelLost(shortChannelId)) lostNodes.foreach { @@ -758,16 +758,16 @@ object Router { } /** - * Is stale a channel that: - * (1) is older than 2 weeks (2*7*144 = 2016 blocks) - * AND - * (2) has no channel_update younger than 2 weeks - * - * @param channel - * @param update1_opt update corresponding to one side of the channel, if we have it - * @param update2_opt update corresponding to the other side of the channel, if we have it - * @return - */ + * Is stale a channel that: + * (1) is older than 2 weeks (2*7*144 = 2016 blocks) + * AND + * (2) has no channel_update younger than 2 weeks + * + * @param channel + * @param update1_opt update corresponding to one side of the channel, if we have it + * @param update2_opt update corresponding to the other side of the channel, if we have it + * @return + */ def isStale(channel: ChannelAnnouncement, update1_opt: Option[ChannelUpdate], update2_opt: Option[ChannelUpdate]): Boolean = { // BOLT 7: "nodes MAY prune channels should the timestamp of the latest channel_update be older than 2 weeks (1209600 seconds)" // but we don't want to prune brand new channels for which we didn't yet receive a channel update, so we keep them as long as they are less than 2 weeks (2016 blocks) old @@ -786,8 +786,8 @@ object Router { } /** - * Filters channels that we want to send to nodes asking for a channel range - */ + * Filters channels that we want to send to nodes asking for a channel range + */ def keep(firstBlockNum: Long, numberOfBlocks: Long, id: ShortChannelId, channels: Map[ShortChannelId, ChannelAnnouncement], updates: Map[ChannelDesc, ChannelUpdate]): Boolean = { val TxCoordinates(height, _, _) = ShortChannelId.coordinates(id) height >= firstBlockNum && height <= (firstBlockNum + numberOfBlocks) @@ -801,8 +801,8 @@ object Router { } /** - * This method is used after a payment failed, and we want to exclude some nodes that we know are failing - */ + * This method is used after a payment failed, and we want to exclude some nodes that we know are failing + */ def getIgnoredChannelDesc(updates: Map[ChannelDesc, ChannelUpdate], ignoreNodes: Set[PublicKey]): Iterable[ChannelDesc] = { val desc = if (ignoreNodes.isEmpty) { Iterable.empty[ChannelDesc] @@ -814,12 +814,12 @@ object Router { } /** - * https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#clarifications - */ + * https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#clarifications + */ val ROUTE_MAX_LENGTH = 20 // Max allowed CLTV for a route - val DEFAULT_ROUTE_MAX_CLTV = 1008 + val DEFAULT_ROUTE_MAX_CLTV = CltvExpiryDelta(1008) // The default amount of routes we'll search for when findRoute is called val DEFAULT_ROUTES_COUNT = 3 @@ -841,19 +841,19 @@ object Router { ) /** - * Find a route in the graph between localNodeId and targetNodeId, returns the route. - * Will perform a k-shortest path selection given the @param numRoutes and randomly select one of the result. - * - * @param g - * @param localNodeId - * @param targetNodeId - * @param amount the amount that will be sent along this route - * @param numRoutes the number of shortest-paths to find - * @param extraEdges a set of extra edges we want to CONSIDER during the search - * @param ignoredEdges a set of extra edges we want to IGNORE during the search - * @param routeParams a set of parameters that can restrict the route search - * @return the computed route to the destination @targetNodeId - */ + * Find a route in the graph between localNodeId and targetNodeId, returns the route. + * Will perform a k-shortest path selection given the @param numRoutes and randomly select one of the result. + * + * @param g + * @param localNodeId + * @param targetNodeId + * @param amount the amount that will be sent along this route + * @param numRoutes the number of shortest-paths to find + * @param extraEdges a set of extra edges we want to CONSIDER during the search + * @param ignoredEdges a set of extra edges we want to IGNORE during the search + * @param routeParams a set of parameters that can restrict the route search + * @return the computed route to the destination @targetNodeId + */ def findRoute(g: DirectedGraph, localNodeId: PublicKey, targetNodeId: PublicKey, @@ -878,7 +878,7 @@ object Router { def lengthOk(length: Int): Boolean = length <= routeParams.routeMaxLength && length <= ROUTE_MAX_LENGTH - def cltvOk(cltv: Int): Boolean = cltv <= routeParams.routeMaxCltv + def cltvOk(cltv: CltvExpiryDelta): Boolean = cltv <= routeParams.routeMaxCltv val boundaries: RichWeight => Boolean = { weight => feeOk(weight.cost - amount, amount) && lengthOk(weight.length) && cltvOk(weight.cltv) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala index 41b5ca3a47..7889fb18a4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala @@ -16,14 +16,15 @@ package fr.acinq.eclair.transactions -import fr.acinq.bitcoin.Crypto.{PublicKey, ripemd160} +import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.Script._ -import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, LexicographicalOrdering, LockTimeThreshold, OP_0, OP_1, OP_1NEGATE, OP_2, OP_2DROP, OP_ADD, OP_CHECKLOCKTIMEVERIFY, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKSIG, OP_DROP, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_NOTIF, OP_PUSHDATA, OP_SIZE, OP_SWAP, Satoshi, Script, ScriptElt, ScriptWitness, Transaction, TxIn} +import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, LexicographicalOrdering, LockTimeThreshold, OP_0, OP_1, OP_1NEGATE, OP_2, OP_CHECKLOCKTIMEVERIFY, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKSIG, OP_DROP, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_NOTIF, OP_PUSHDATA, OP_SIZE, OP_SWAP, Satoshi, Script, ScriptElt, ScriptWitness, Transaction, TxIn} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta} import scodec.bits.ByteVector /** - * Created by PM on 02/12/2016. - */ + * Created by PM on 02/12/2016. + */ object Scripts { def der(sig: ByteVector64): ByteVector = Crypto.compact2der(sig) :+ 1 @@ -34,13 +35,13 @@ object Scripts { Script.createMultiSigMofN(2, Seq(pubkey2, pubkey1)) /** - * - * @param sig1 - * @param sig2 - * @param pubkey1 - * @param pubkey2 - * @return a script witness that matches the msig 2-of-2 pubkey script for pubkey1 and pubkey2 - */ + * + * @param sig1 + * @param sig2 + * @param pubkey1 + * @param pubkey2 + * @return a script witness that matches the msig 2-of-2 pubkey script for pubkey1 and pubkey2 + */ def witness2of2(sig1: ByteVector64, sig2: ByteVector64, pubkey1: PublicKey, pubkey2: PublicKey): ScriptWitness = { if (LexicographicalOrdering.isLessThan(pubkey1.value, pubkey2.value)) ScriptWitness(Seq(ByteVector.empty, der(sig1), der(sig2), write(multiSig2of2(pubkey1, pubkey2)))) @@ -50,13 +51,13 @@ object Scripts { } /** - * minimal encoding of a number into a script element: - * - OP_0 to OP_16 if 0 <= n <= 16 - * - OP_PUSHDATA(encodeNumber(n)) otherwise - * - * @param n input number - * @return a script element that represents n - */ + * minimal encoding of a number into a script element: + * - OP_0 to OP_16 if 0 <= n <= 16 + * - OP_PUSHDATA(encodeNumber(n)) otherwise + * + * @param n input number + * @return a script element that represents n + */ def encodeNumber(n: Long): ScriptElt = n match { case 0 => OP_0 case -1 => OP_1NEGATE @@ -74,13 +75,13 @@ object Scripts { } /** - * This function interprets the locktime for the given transaction, and returns the block height before which this tx cannot be published. - * By convention in bitcoin, depending of the value of locktime it might be a number of blocks or a number of seconds since epoch. - * This function does not support the case when the locktime is a number of seconds that is not way in the past. - * NB: We use this property in lightning to store data in this field. - * - * @return the block height before which this tx cannot be published. - */ + * This function interprets the locktime for the given transaction, and returns the block height before which this tx cannot be published. + * By convention in bitcoin, depending of the value of locktime it might be a number of blocks or a number of seconds since epoch. + * This function does not support the case when the locktime is a number of seconds that is not way in the past. + * NB: We use this property in lightning to store data in this field. + * + * @return the block height before which this tx cannot be published. + */ def cltvTimeout(tx: Transaction): Long = { if (tx.lockTime <= LockTimeThreshold) { // locktime is a number of blocks @@ -95,10 +96,10 @@ object Scripts { } /** - * - * @param tx - * @return the number of confirmations of the tx parent before which it can be published - */ + * + * @param tx + * @return the number of confirmations of the tx parent before which it can be published + */ def csvTimeout(tx: Transaction): Long = { def sequenceToBlockHeight(sequence: Long): Long = { if ((sequence & TxIn.SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) 0 @@ -112,12 +113,12 @@ object Scripts { else tx.txIn.map(_.sequence).map(sequenceToBlockHeight).max } - def toLocalDelayed(revocationPubkey: PublicKey, toSelfDelay: Int, localDelayedPaymentPubkey: PublicKey) = { + def toLocalDelayed(revocationPubkey: PublicKey, toSelfDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey) = { // @formatter:off OP_IF :: OP_PUSHDATA(revocationPubkey) :: OP_ELSE :: - encodeNumber(toSelfDelay) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: + encodeNumber(toSelfDelay.delta) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_PUSHDATA(localDelayedPaymentPubkey) :: OP_ENDIF :: OP_CHECKSIG :: Nil @@ -125,15 +126,15 @@ object Scripts { } /** - * This witness script spends a [[toLocalDelayed]] output using a local sig after a delay - */ + * This witness script spends a [[toLocalDelayed]] output using a local sig after a delay + */ def witnessToLocalDelayedAfterDelay(localSig: ByteVector64, toLocalDelayedScript: ByteVector) = ScriptWitness(der(localSig) :: ByteVector.empty :: toLocalDelayedScript :: Nil) /** - * This witness script spends (steals) a [[toLocalDelayed]] output using a revocation key as a punishment - * for having published a revoked transaction - */ + * This witness script spends (steals) a [[toLocalDelayed]] output using a revocation key as a punishment + * for having published a revoked transaction + */ def witnessToLocalDelayedWithRevocationSig(revocationSig: ByteVector64, toLocalScript: ByteVector) = ScriptWitness(der(revocationSig) :: ByteVector(1) :: toLocalScript :: Nil) @@ -157,19 +158,19 @@ object Scripts { } /** - * This is the witness script of the 2nd-stage HTLC Success transaction (consumes htlcOffered script from commit tx) - */ + * This is the witness script of the 2nd-stage HTLC Success transaction (consumes htlcOffered script from commit tx) + */ def witnessHtlcSuccess(localSig: ByteVector64, remoteSig: ByteVector64, paymentPreimage: ByteVector32, htlcOfferedScript: ByteVector) = ScriptWitness(ByteVector.empty :: der(remoteSig) :: der(localSig) :: paymentPreimage.bytes :: htlcOfferedScript :: Nil) /** - * If local publishes its commit tx where there was a local->remote htlc, then remote uses this script to - * claim its funds using a payment preimage (consumes htlcOffered script from commit tx) - */ + * If local publishes its commit tx where there was a local->remote htlc, then remote uses this script to + * claim its funds using a payment preimage (consumes htlcOffered script from commit tx) + */ def witnessClaimHtlcSuccessFromCommitTx(localSig: ByteVector64, paymentPreimage: ByteVector32, htlcOfferedScript: ByteVector) = ScriptWitness(der(localSig) :: paymentPreimage.bytes :: htlcOfferedScript :: Nil) - def htlcReceived(localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, revocationPubKey: PublicKey, paymentHash: ByteVector, lockTime: Long) = { + def htlcReceived(localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, revocationPubKey: PublicKey, paymentHash: ByteVector, lockTime: CltvExpiry) = { // @formatter:off // To you with revocation key OP_DUP :: OP_HASH160 :: OP_PUSHDATA(revocationPubKey.hash160) :: OP_EQUAL :: @@ -183,7 +184,7 @@ object Scripts { OP_2 :: OP_SWAP :: OP_PUSHDATA(localHtlcPubkey) :: OP_2 :: OP_CHECKMULTISIG :: OP_ELSE :: // To you after timeout. - OP_DROP :: encodeNumber(lockTime) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: + OP_DROP :: encodeNumber(lockTime.get) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: OP_CHECKSIG :: OP_ENDIF :: OP_ENDIF :: Nil @@ -191,22 +192,22 @@ object Scripts { } /** - * This is the witness script of the 2nd-stage HTLC Timeout transaction (consumes htlcReceived script from commit tx) - */ + * This is the witness script of the 2nd-stage HTLC Timeout transaction (consumes htlcReceived script from commit tx) + */ def witnessHtlcTimeout(localSig: ByteVector64, remoteSig: ByteVector64, htlcReceivedScript: ByteVector) = ScriptWitness(ByteVector.empty :: der(remoteSig) :: der(localSig) :: ByteVector.empty :: htlcReceivedScript :: Nil) /** - * If local publishes its commit tx where there was a remote->local htlc, then remote uses this script to - * claim its funds after timeout (consumes htlcReceived script from commit tx) - */ + * If local publishes its commit tx where there was a remote->local htlc, then remote uses this script to + * claim its funds after timeout (consumes htlcReceived script from commit tx) + */ def witnessClaimHtlcTimeoutFromCommitTx(localSig: ByteVector64, htlcReceivedScript: ByteVector) = ScriptWitness(der(localSig) :: ByteVector.empty :: htlcReceivedScript :: Nil) /** - * This witness script spends (steals) a [[htlcOffered]] or [[htlcReceived]] output using a revocation key as a punishment - * for having published a revoked transaction - */ + * This witness script spends (steals) a [[htlcOffered]] or [[htlcReceived]] output using a revocation key as a punishment + * for having published a revoked transaction + */ def witnessHtlcWithRevocationSig(revocationSig: ByteVector64, revocationPubkey: PublicKey, htlcScript: ByteVector) = ScriptWitness(der(revocationSig) :: revocationPubkey.value :: htlcScript :: Nil) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 9ea53c8a1b..9ed1e9467d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -30,8 +30,8 @@ import scodec.bits.ByteVector import scala.util.Try /** - * Created by PM on 15/12/2016. - */ + * Created by PM on 15/12/2016. + */ object Transactions { // @formatter:off @@ -69,38 +69,38 @@ object Transactions { // @formatter:on /** - * When *local* *current* [[CommitTx]] is published: - * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay - * - [[HtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage - * - [[ClaimDelayedOutputTx]] spends [[HtlcSuccessTx]] after a delay - * - [[HtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout - * - [[ClaimDelayedOutputTx]] spends [[HtlcTimeoutTx]] after a delay - * - * When *remote* *current* [[CommitTx]] is published: - * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] - * - [[ClaimHtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage - * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout - * - * When *remote* *revoked* [[CommitTx]] is published: - * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] - * - [[MainPenaltyTx]] spends remote main output using the per-commitment secret - * - [[HtlcSuccessTx]] spends htlc-sent outputs of [[CommitTx]] for which they have the preimage (published by remote) - * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcSuccessTx]] using the revocation secret (published by local) - * - [[HtlcTimeoutTx]] spends htlc-received outputs of [[CommitTx]] after a timeout (published by remote) - * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcTimeoutTx]] using the revocation secret (published by local) - * - [[HtlcPenaltyTx]] spends competes with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] for the same outputs (published by local) - */ + * When *local* *current* [[CommitTx]] is published: + * - [[ClaimDelayedOutputTx]] spends to-local output of [[CommitTx]] after a delay + * - [[HtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage + * - [[ClaimDelayedOutputTx]] spends [[HtlcSuccessTx]] after a delay + * - [[HtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout + * - [[ClaimDelayedOutputTx]] spends [[HtlcTimeoutTx]] after a delay + * + * When *remote* *current* [[CommitTx]] is published: + * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] + * - [[ClaimHtlcSuccessTx]] spends htlc-received outputs of [[CommitTx]] for which we have the preimage + * - [[ClaimHtlcTimeoutTx]] spends htlc-sent outputs of [[CommitTx]] after a timeout + * + * When *remote* *revoked* [[CommitTx]] is published: + * - [[ClaimP2WPKHOutputTx]] spends to-local output of [[CommitTx]] + * - [[MainPenaltyTx]] spends remote main output using the per-commitment secret + * - [[HtlcSuccessTx]] spends htlc-sent outputs of [[CommitTx]] for which they have the preimage (published by remote) + * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcSuccessTx]] using the revocation secret (published by local) + * - [[HtlcTimeoutTx]] spends htlc-received outputs of [[CommitTx]] after a timeout (published by remote) + * - [[ClaimDelayedOutputPenaltyTx]] spends [[HtlcTimeoutTx]] using the revocation secret (published by local) + * - [[HtlcPenaltyTx]] spends competes with [[HtlcSuccessTx]] and [[HtlcTimeoutTx]] for the same outputs (published by local) + */ /** - * these values are defined in the RFC - */ + * these values are defined in the RFC + */ val commitWeight = 724 val htlcTimeoutWeight = 663 val htlcSuccessWeight = 703 /** - * these values specific to us and used to estimate fees - */ + * these values specific to us and used to estimate fees + */ val claimP2WPKHOutputWeight = 438 val claimHtlcDelayedWeight = 483 val claimHtlcSuccessWeight = 571 @@ -111,11 +111,11 @@ object Transactions { def weight2fee(feeratePerKw: Long, weight: Int) = Satoshi((feeratePerKw * weight) / 1000) /** - * - * @param fee tx fee - * @param weight tx weight - * @return the fee rate (in Satoshi/Kw) for this tx - */ + * + * @param fee tx fee + * @param weight tx weight + * @return the fee rate (in Satoshi/Kw) for this tx + */ def fee2rate(fee: Satoshi, weight: Int) = (fee.amount * 1000L) / weight def trimOfferedHtlcs(dustLimit: Satoshi, spec: CommitmentSpec): Seq[DirectedHtlc] = { @@ -142,13 +142,13 @@ object Transactions { } /** - * - * @param commitTxNumber commit tx number - * @param isFunder true if local node is funder - * @param localPaymentBasePoint local payment base point - * @param remotePaymentBasePoint remote payment base point - * @return the obscured tx number as defined in BOLT #3 (a 48 bits integer) - */ + * + * @param commitTxNumber commit tx number + * @param isFunder true if local node is funder + * @param localPaymentBasePoint local payment base point + * @param remotePaymentBasePoint remote payment base point + * @return the obscured tx number as defined in BOLT #3 (a 48 bits integer) + */ def obscuredCommitTxNumber(commitTxNumber: Long, isFunder: Boolean, localPaymentBasePoint: PublicKey, remotePaymentBasePoint: PublicKey): Long = { // from BOLT 3: SHA256(payment-basepoint from open_channel || payment-basepoint from accept_channel) val h = if (isFunder) @@ -161,13 +161,13 @@ object Transactions { } /** - * - * @param commitTx commit tx - * @param isFunder true if local node is funder - * @param localPaymentBasePoint local payment base point - * @param remotePaymentBasePoint remote payment base point - * @return the actual commit tx number that was blinded and stored in locktime and sequence fields - */ + * + * @param commitTx commit tx + * @param isFunder true if local node is funder + * @param localPaymentBasePoint local payment base point + * @param remotePaymentBasePoint remote payment base point + * @return the actual commit tx number that was blinded and stored in locktime and sequence fields + */ def getCommitTxNumber(commitTx: Transaction, isFunder: Boolean, localPaymentBasePoint: PublicKey, remotePaymentBasePoint: PublicKey): Long = { val blind = obscuredCommitTxNumber(0, isFunder, localPaymentBasePoint, remotePaymentBasePoint) val obscured = decodeTxNumber(commitTx.txIn.head.sequence, commitTx.lockTime) @@ -175,11 +175,11 @@ object Transactions { } /** - * This is a trick to split and encode a 48-bit txnumber into the sequence and locktime fields of a tx - * - * @param txnumber commitment number - * @return (sequence, locktime) - */ + * This is a trick to split and encode a 48-bit txnumber into the sequence and locktime fields of a tx + * + * @param txnumber commitment number + * @return (sequence, locktime) + */ def encodeTxNumber(txnumber: Long): (Long, Long) = { require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long") (0x80000000L | (txnumber >> 24), (txnumber & 0xffffffL) | 0x20000000) @@ -187,7 +187,7 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: PublicKey, remotePaymentBasePoint: PublicKey, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): CommitTx = { + def makeCommitTx(commitTxInput: InputInfo, commitTxNumber: Long, localPaymentBasePoint: PublicKey, remotePaymentBasePoint: PublicKey, localIsFunder: Boolean, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, remotePaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): CommitTx = { val commitFee = commitTxFee(localDustLimit, spec) val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localIsFunder) { @@ -215,7 +215,7 @@ object Transactions { CommitTx(commitTxInput, LexicographicalOrdering.sort(tx)) } - def makeHtlcTimeoutTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, feeratePerKw: Long, htlc: UpdateAddHtlc): HtlcTimeoutTx = { + def makeHtlcTimeoutTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, feeratePerKw: Long, htlc: UpdateAddHtlc): HtlcTimeoutTx = { val fee = weight2fee(feeratePerKw, htlcTimeoutWeight) val redeemScript = htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.paymentHash.bytes)) val pubkeyScript = write(pay2wsh(redeemScript)) @@ -229,10 +229,10 @@ object Transactions { version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0x00000000L) :: Nil, txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil, - lockTime = htlc.cltvExpiry)) + lockTime = htlc.cltvExpiry.get)) } - def makeHtlcSuccessTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, feeratePerKw: Long, htlc: UpdateAddHtlc): HtlcSuccessTx = { + def makeHtlcSuccessTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, feeratePerKw: Long, htlc: UpdateAddHtlc): HtlcSuccessTx = { val fee = weight2fee(feeratePerKw, htlcSuccessWeight) val redeemScript = htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.paymentHash.bytes), htlc.cltvExpiry) val pubkeyScript = write(pay2wsh(redeemScript)) @@ -249,7 +249,7 @@ object Transactions { lockTime = 0), htlc.paymentHash) } - def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + def makeHtlcTxs(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, spec: CommitmentSpec): (Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { var outputsAlreadyUsed = Set.empty[Int] // this is needed to handle cases where we have several identical htlcs val htlcTimeoutTxs = trimOfferedHtlcs(localDustLimit, spec).map { htlc => val htlcTx = makeHtlcTimeoutTx(commitTx, outputsAlreadyUsed, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec.feeratePerKw, htlc.add) @@ -298,7 +298,7 @@ object Transactions { version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0x00000000L) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = htlc.cltvExpiry) + lockTime = htlc.cltvExpiry.get) val weight = addSigs(ClaimHtlcTimeoutTx(input, tx), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) @@ -338,7 +338,7 @@ object Transactions { ClaimP2WPKHOutputTx(input, tx1) } - def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: Long): ClaimDelayedOutputTx = { + def makeClaimDelayedOutputTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: Long): ClaimDelayedOutputTx = { val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) @@ -347,7 +347,7 @@ object Transactions { // unsigned transaction val tx = Transaction( version = 2, - txIn = TxIn(input.outPoint, ByteVector.empty, toLocalDelay) :: Nil, + txIn = TxIn(input.outPoint, ByteVector.empty, toLocalDelay.delta) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, lockTime = 0) @@ -364,7 +364,7 @@ object Transactions { ClaimDelayedOutputTx(input, tx1) } - def makeClaimDelayedOutputPenaltyTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: Int, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: Long): ClaimDelayedOutputPenaltyTx = { + def makeClaimDelayedOutputPenaltyTx(delayedOutputTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: Long): ClaimDelayedOutputPenaltyTx = { val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(delayedOutputTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) @@ -390,7 +390,7 @@ object Transactions { ClaimDelayedOutputPenaltyTx(input, tx1) } - def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, toRemoteDelay: Int, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long): MainPenaltyTx = { + def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, toRemoteDelay: CltvExpiryDelta, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: Long): MainPenaltyTx = { val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript, outputsAlreadyUsed = Set.empty, amount_opt = None) @@ -417,8 +417,8 @@ object Transactions { } /** - * We already have the redeemScript, no need to build it - */ + * We already have the redeemScript, no need to build it + */ def makeHtlcPenaltyTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], redeemScript: ByteVector, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, feeratePerKw: Long): HtlcPenaltyTx = { val pubkeyScript = write(pay2wsh(redeemScript)) val outputIndex = findPubKeyScriptIndex(commitTx, pubkeyScript, outputsAlreadyUsed, amount_opt = None) @@ -467,7 +467,7 @@ object Transactions { def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: ByteVector, outputsAlreadyUsed: Set[Int], amount_opt: Option[Satoshi]): Int = { val outputIndex = tx.txOut .zipWithIndex - .indexWhere { case (txOut, index) => amount_opt.map(_ == txOut.amount).getOrElse(true) && txOut.publicKeyScript == pubkeyScript && !outputsAlreadyUsed.contains(index)} // it's not enough to only resolve on pubkeyScript because we may have duplicates + .indexWhere { case (txOut, index) => amount_opt.map(_ == txOut.amount).getOrElse(true) && txOut.publicKeyScript == pubkeyScript && !outputsAlreadyUsed.contains(index) } // it's not enough to only resolve on pubkeyScript because we may have duplicates if (outputIndex >= 0) { outputIndex } else { @@ -476,14 +476,14 @@ object Transactions { } /** - * Default public key used for fee estimation - */ + * Default public key used for fee estimation + */ val PlaceHolderPubKey = PrivateKey(ByteVector32.One).publicKey /** - * This default sig takes 72B when encoded in DER (incl. 1B for the trailing sig hash), it is used for fee estimation - * It is 72 bytes because our signatures are normalized (low-s) and will take up 72 bytes at most in DER format - */ + * This default sig takes 72B when encoded in DER (incl. 1B for the trailing sig hash), it is used for fee estimation + * It is 72 bytes because our signatures are normalized (low-s) and will take up 72 bytes at most in DER format + */ val PlaceHolderSig = ByteVector64(ByteVector.fill(64)(0xaa)) assert(der(PlaceHolderSig).size == 72) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index df95cd7972..536023b513 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -65,7 +65,7 @@ object ChannelCodecs extends Logging { ("maxHtlcValueInFlightMsat" | uint64) :: ("channelReserve" | satoshi) :: ("htlcMinimum" | millisatoshi) :: - ("toSelfDelay" | uint16) :: + ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: ("isFunder" | bool) :: ("defaultFinalScriptPubKey" | varsizebinarydata) :: @@ -78,7 +78,7 @@ object ChannelCodecs extends Logging { ("maxHtlcValueInFlightMsat" | uint64) :: ("channelReserve" | satoshi) :: ("htlcMinimum" | millisatoshi) :: - ("toSelfDelay" | uint16) :: + ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: ("fundingPubKey" | publicKey) :: ("revocationBasepoint" | publicKey) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala index fc749c8fe8..e3a31f06ef 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala @@ -19,9 +19,9 @@ package fr.acinq.eclair.wire import java.net.{Inet4Address, Inet6Address, InetAddress} import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.eclair.crypto.Mac32 import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.eclair.crypto.Mac32 +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64} import org.apache.commons.codec.binary.Base32 import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -31,14 +31,14 @@ import scala.Ordering.Implicits._ import scala.util.Try /** - * Created by t-bast on 20/06/2019. - */ + * Created by t-bast on 20/06/2019. + */ object CommonCodecs { /** - * Discriminator codec with a default fallback codec (of the same type). - */ + * Discriminator codec with a default fallback codec (of the same type). + */ def discriminatorWithDefault[A](discriminator: Codec[A], fallback: Codec[A]): Codec[A] = new Codec[A] { def sizeBound: SizeBound = discriminator.sizeBound | fallback.sizeBound @@ -57,15 +57,18 @@ object CommonCodecs { val satoshi: Codec[Satoshi] = uint64overflow.xmapc(l => Satoshi(l))(_.toLong) val millisatoshi: Codec[MilliSatoshi] = uint64overflow.xmapc(l => MilliSatoshi(l))(_.amount) + val cltvExpiry: Codec[CltvExpiry] = uint32.xmapc(CltvExpiry)((_: CltvExpiry).get) + val cltvExpiryDelta: Codec[CltvExpiryDelta] = uint16.xmapc(CltvExpiryDelta)((_: CltvExpiryDelta).delta) + /** - * We impose a minimal encoding on some values (such as varint and truncated int) to ensure that signed hashes can be - * re-computed correctly. - * If a value could be encoded with less bytes, it's considered invalid and results in a failed decoding attempt. - * - * @param codec the value codec (depends on the value). - * @param min the minimal value that should be encoded. - */ - def minimalvalue[A : Ordering](codec: Codec[A], min: A): Codec[A] = codec.exmap({ + * We impose a minimal encoding on some values (such as varint and truncated int) to ensure that signed hashes can be + * re-computed correctly. + * If a value could be encoded with less bytes, it's considered invalid and results in a failed decoding attempt. + * + * @param codec the value codec (depends on the value). + * @param min the minimal value that should be encoded. + */ + def minimalvalue[A: Ordering](codec: Codec[A], min: A): Codec[A] = codec.exmap({ case i if i < min => Attempt.failure(Err("value was not minimally encoded")) case i => Attempt.successful(i) }, Attempt.successful) @@ -129,9 +132,9 @@ object CommonCodecs { def zeropaddedstring(size: Int): Codec[String] = fixedSizeBytes(32, utf8).xmap(s => s.takeWhile(_ != '\u0000'), s => s) /** - * When encoding, prepend a valid mac to the output of the given codec. - * When decoding, verify that a valid mac is prepended. - */ + * When encoding, prepend a valid mac to the output of the given codec. + * When decoding, verify that a valid mac is prepended. + */ def prependmac[A](codec: Codec[A], mac: Mac32) = Codec[A]( (a: A) => codec.encode(a).map(bits => mac.mac(bits.toByteVector).bits ++ bits), (bits: BitVector) => ("mac" | bytes32).decode(bits) match { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala index db816407be..166736d709 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala @@ -17,17 +17,17 @@ package fr.acinq.eclair.wire import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.crypto.Mac32 -import fr.acinq.eclair.wire.CommonCodecs.{sha256, millisatoshi} +import fr.acinq.eclair.wire.CommonCodecs.{cltvExpiry, millisatoshi, sha256} import fr.acinq.eclair.wire.LightningMessageCodecs.{channelUpdateCodec, lightningMessageCodec} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi} import scodec.codecs._ import scodec.{Attempt, Codec} /** - * see https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md - * Created by fabrice on 14/03/17. - */ + * see https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md + * Created by fabrice on 14/03/17. + */ // @formatter:off sealed trait FailureMessage { def message: String } @@ -51,12 +51,12 @@ case object UnknownNextPeer extends Perm { def message = "processing node does n case class AmountBelowMinimum(amount: MilliSatoshi, update: ChannelUpdate) extends Update { def message = s"payment amount was below the minimum required by the channel" } case class FeeInsufficient(amount: MilliSatoshi, update: ChannelUpdate) extends Update { def message = s"payment fee was below the minimum required by the channel" } case class ChannelDisabled(messageFlags: Byte, channelFlags: Byte, update: ChannelUpdate) extends Update { def message = "channel is currently disabled" } -case class IncorrectCltvExpiry(expiry: Long, update: ChannelUpdate) extends Update { def message = "payment expiry doesn't match the value in the onion" } +case class IncorrectCltvExpiry(expiry: CltvExpiry, update: ChannelUpdate) extends Update { def message = "payment expiry doesn't match the value in the onion" } case class IncorrectOrUnknownPaymentDetails(amount: MilliSatoshi) extends Perm { def message = "incorrect payment amount or unknown payment hash" } case object IncorrectPaymentAmount extends Perm { def message = "payment amount is incorrect" } case class ExpiryTooSoon(update: ChannelUpdate) extends Update { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } case object FinalExpiryTooSoon extends FailureMessage { def message = "payment expiry is too close to the current block height for safe handling by the final node" } -case class FinalIncorrectCltvExpiry(expiry: Long) extends FailureMessage { def message = "payment expiry doesn't match the value in the onion" } +case class FinalIncorrectCltvExpiry(expiry: CltvExpiry) extends FailureMessage { def message = "payment expiry doesn't match the value in the onion" } case class FinalIncorrectHtlcAmount(amount: MilliSatoshi) extends FailureMessage { def message = "payment amount is incorrect in the final htlc" } case object ExpiryTooFar extends FailureMessage { def message = "payment expiry is too far in the future" } // @formatter:on @@ -88,29 +88,29 @@ object FailureMessageCodecs { .typecase(PERM | 10, provide(UnknownNextPeer)) .typecase(UPDATE | 11, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[AmountBelowMinimum]) .typecase(UPDATE | 12, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient]) - .typecase(UPDATE | 13, (("expiry" | uint32) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry]) + .typecase(UPDATE | 13, (("expiry" | cltvExpiry) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry]) .typecase(UPDATE | 14, ("channelUpdate" | channelUpdateWithLengthCodec).as[ExpiryTooSoon]) .typecase(UPDATE | 20, (("messageFlags" | byte) :: ("channelFlags" | byte) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[ChannelDisabled]) .typecase(PERM | 15, ("amountMsat" | withDefaultValue(optional(bitsRemaining, millisatoshi), MilliSatoshi(0))).as[IncorrectOrUnknownPaymentDetails]) .typecase(PERM | 16, provide(IncorrectPaymentAmount)) .typecase(17, provide(FinalExpiryTooSoon)) - .typecase(18, ("expiry" | uint32).as[FinalIncorrectCltvExpiry]) + .typecase(18, ("expiry" | cltvExpiry).as[FinalIncorrectCltvExpiry]) .typecase(19, ("amountMsat" | millisatoshi).as[FinalIncorrectHtlcAmount]) .typecase(21, provide(ExpiryTooFar)) /** - * Return the failure code for a given failure message. This method actually encodes the failure message, which is a - * bit clunky and not particularly efficient. It shouldn't be used on the application's hot path. - */ + * Return the failure code for a given failure message. This method actually encodes the failure message, which is a + * bit clunky and not particularly efficient. It shouldn't be used on the application's hot path. + */ def failureCode(failure: FailureMessage): Int = failureMessageCodec.encode(failure).flatMap(uint16.decode).require.value /** - * An onion-encrypted failure from an intermediate node: - * +----------------+----------------------------------+-----------------+----------------------+-----+ - * | HMAC(32 bytes) | failure message length (2 bytes) | failure message | pad length (2 bytes) | pad | - * +----------------+----------------------------------+-----------------+----------------------+-----+ - * with failure message length + pad length = 256 - */ + * An onion-encrypted failure from an intermediate node: + * +----------------+----------------------------------+-----------------+----------------------+-----+ + * | HMAC(32 bytes) | failure message length (2 bytes) | failure message | pad length (2 bytes) | pad | + * +----------------+----------------------------------+-----------------+----------------------+-----+ + * with failure message length + pad length = 256 + */ def failureOnionCodec(mac: Mac32): Codec[FailureMessage] = CommonCodecs.prependmac( paddedFixedSizeBytesDependent( 260, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala index 055d9ffa16..4765487d68 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala @@ -16,16 +16,14 @@ package fr.acinq.eclair.wire -import fr.acinq.eclair.crypto.Sphinx -import fr.acinq.eclair.{MilliSatoshi, wire} import fr.acinq.eclair.wire.CommonCodecs._ +import fr.acinq.eclair.{MilliSatoshi, wire} import scodec.Codec -import scodec.bits.ByteVector import scodec.codecs._ /** - * Created by PM on 15/11/2016. - */ + * Created by PM on 15/11/2016. + */ object LightningMessageCodecs { val initCodec: Codec[Init] = ( @@ -60,7 +58,7 @@ object LightningMessageCodecs { ("channelReserveSatoshis" | satoshi) :: ("htlcMinimumMsat" | millisatoshi) :: ("feeratePerKw" | uint32) :: - ("toSelfDelay" | uint16) :: + ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: ("fundingPubkey" | publicKey) :: ("revocationBasepoint" | publicKey) :: @@ -77,7 +75,7 @@ object LightningMessageCodecs { ("channelReserveSatoshis" | satoshi) :: ("htlcMinimumMsat" | millisatoshi) :: ("minimumDepth" | uint32) :: - ("toSelfDelay" | uint16) :: + ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: ("fundingPubkey" | publicKey) :: ("revocationBasepoint" | publicKey) :: @@ -114,7 +112,7 @@ object LightningMessageCodecs { ("id" | uint64overflow) :: ("amountMsat" | millisatoshi) :: ("paymentHash" | bytes32) :: - ("expiry" | uint32) :: + ("expiry" | cltvExpiry) :: ("onionRoutingPacket" | OnionCodecs.paymentOnionPacketCodec)).as[UpdateAddHtlc] val updateFulfillHtlcCodec: Codec[UpdateFulfillHtlc] = ( @@ -190,7 +188,7 @@ object LightningMessageCodecs { ("timestamp" | uint32) :: (("messageFlags" | byte) >>:~ { messageFlags => ("channelFlags" | byte) :: - ("cltvExpiryDelta" | uint16) :: + ("cltvExpiryDelta" | cltvExpiryDelta) :: ("htlcMinimumMsat" | millisatoshi) :: ("feeBaseMsat" | uint32.xmapc(l => MilliSatoshi(l))(_.amount)) :: ("feeProportionalMillionths" | uint32) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala index 57d3738918..48bfb8c49e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala @@ -20,16 +20,16 @@ import java.net.{Inet4Address, Inet6Address, InetAddress, InetSocketAddress} import java.nio.charset.StandardCharsets import com.google.common.base.Charsets -import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, UInt64} import scodec.bits.ByteVector import scala.util.Try /** - * Created by PM on 15/11/2016. - */ + * Created by PM on 15/11/2016. + */ // @formatter:off sealed trait LightningMessage @@ -75,7 +75,7 @@ case class OpenChannel(chainHash: ByteVector32, channelReserveSatoshis: Satoshi, htlcMinimumMsat: MilliSatoshi, feeratePerKw: Long, - toSelfDelay: Int, + toSelfDelay: CltvExpiryDelta, maxAcceptedHtlcs: Int, fundingPubkey: PublicKey, revocationBasepoint: PublicKey, @@ -91,7 +91,7 @@ case class AcceptChannel(temporaryChannelId: ByteVector32, channelReserveSatoshis: Satoshi, htlcMinimumMsat: MilliSatoshi, minimumDepth: Long, - toSelfDelay: Int, + toSelfDelay: CltvExpiryDelta, maxAcceptedHtlcs: Int, fundingPubkey: PublicKey, revocationBasepoint: PublicKey, @@ -122,7 +122,7 @@ case class UpdateAddHtlc(channelId: ByteVector32, id: Long, amountMsat: MilliSatoshi, paymentHash: ByteVector32, - cltvExpiry: Long, + cltvExpiry: CltvExpiry, onionRoutingPacket: OnionRoutingPacket) extends HtlcMessage with UpdateMessage with HasChannelId case class UpdateFulfillHtlc(channelId: ByteVector32, @@ -216,7 +216,7 @@ case class ChannelUpdate(signature: ByteVector64, timestamp: Long, messageFlags: Byte, channelFlags: Byte, - cltvExpiryDelta: Int, + cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/Onion.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/Onion.scala index 716f11b16e..f1737eb606 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/Onion.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/Onion.scala @@ -17,15 +17,15 @@ package fr.acinq.eclair.wire import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} import fr.acinq.eclair.crypto.Sphinx +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, ShortChannelId} import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Codec, DecodeResult, Decoder} /** - * Created by t-bast on 05/07/2019. - */ + * Created by t-bast on 05/07/2019. + */ case class OnionRoutingPacket(version: Int, publicKey: ByteVector, @@ -34,7 +34,7 @@ case class OnionRoutingPacket(version: Int, case class PerHopPayload(shortChannelId: ShortChannelId, amtToForward: MilliSatoshi, - outgoingCltvValue: Long) + outgoingCltvValue: CltvExpiry) object OnionCodecs { @@ -50,15 +50,15 @@ object OnionCodecs { ("realm" | constant(ByteVector.fromByte(0))) :: ("short_channel_id" | CommonCodecs.shortchannelid) :: ("amt_to_forward" | CommonCodecs.millisatoshi) :: - ("outgoing_cltv_value" | uint32) :: + ("outgoing_cltv_value" | CommonCodecs.cltvExpiry) :: ("unused_with_v0_version_on_header" | ignore(8 * 12))).as[PerHopPayload] /** - * The 1.1 BOLT spec changed the onion frame format to use variable-length per-hop payloads. - * The first bytes contain a varint encoding the length of the payload data (not including the trailing mac). - * That varint is considered to be part of the payload, so the payload length includes the number of bytes used by - * the varint prefix. - */ + * The 1.1 BOLT spec changed the onion frame format to use variable-length per-hop payloads. + * The first bytes contain a varint encoding the length of the payload data (not including the trailing mac). + * That varint is considered to be part of the payload, so the payload length includes the number of bytes used by + * the varint prefix. + */ val payloadLengthDecoder = Decoder[Long]((bits: BitVector) => CommonCodecs.varintoverflow.decode(bits).map(d => DecodeResult(d.value + (bits.length - d.remainder.length) / 8, d.remainder))) 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 728815f5a3..60376621bf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -18,26 +18,25 @@ package fr.acinq.eclair import akka.actor.ActorSystem import akka.testkit.{TestKit, TestProbe} -import fr.acinq.bitcoin.{ByteVector32, Crypto, Satoshi} import akka.util.Timeout import fr.acinq.bitcoin.Crypto.PublicKey +import fr.acinq.bitcoin.{ByteVector32, Crypto, Satoshi} +import fr.acinq.eclair.TestConstants._ import fr.acinq.eclair.blockchain.TestWallet +import fr.acinq.eclair.channel.{CMD_FORCECLOSE, Register, _} +import fr.acinq.eclair.db._ import fr.acinq.eclair.io.Peer.OpenChannel +import fr.acinq.eclair.payment.LocalPaymentHandler import fr.acinq.eclair.payment.PaymentLifecycle.{ReceivePayment, SendPayment, SendPaymentToRoute} -import fr.acinq.eclair.payment.PaymentLifecycle.SendPayment import fr.acinq.eclair.payment.PaymentRequest.ExtraHop -import org.scalatest.{Matchers, Outcome, fixture} -import scodec.bits._ -import TestConstants._ -import fr.acinq.eclair.channel.{CMD_FORCECLOSE, Register} -import fr.acinq.eclair.payment.LocalPaymentHandler -import fr.acinq.eclair.channel._ -import fr.acinq.eclair.db._ import fr.acinq.eclair.router.RouteCalculationSpec.makeUpdate import org.mockito.scalatest.IdiomaticMockito +import org.scalatest.{Outcome, fixture} +import scodec.bits._ + import scala.concurrent.Await -import scala.util.{Failure, Success} import scala.concurrent.duration._ +import scala.util.Success class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSuiteLike with IdiomaticMockito { @@ -94,7 +93,7 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu val eclair = new EclairImpl(kit) val nodeId = PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87") - eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiry_opt = None) + eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiryDelta_opt = None) val send = paymentInitiator.expectMsgType[SendPayment] assert(send.targetNodeId == nodeId) assert(send.amount == MilliSatoshi(123)) @@ -102,8 +101,8 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu assert(send.assistedRoutes == Seq.empty) // with assisted routes - val hints = Seq(Seq(ExtraHop(Bob.nodeParams.nodeId, ShortChannelId("569178x2331x1"), feeBaseMsat = 10, feeProportionalMillionths = 1, cltvExpiryDelta = 12))) - eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = hints, minFinalCltvExpiry_opt = None) + val hints = Seq(Seq(ExtraHop(Bob.nodeParams.nodeId, ShortChannelId("569178x2331x1"), feeBaseMsat = 10, feeProportionalMillionths = 1, cltvExpiryDelta = CltvExpiryDelta(12)))) + eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = hints, minFinalCltvExpiryDelta_opt = None) val send1 = paymentInitiator.expectMsgType[SendPayment] assert(send1.targetNodeId == nodeId) assert(send1.amount == MilliSatoshi(123)) @@ -111,15 +110,15 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu assert(send1.assistedRoutes == hints) // with finalCltvExpiry - eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiry_opt = Some(96)) + eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiryDelta_opt = Some(CltvExpiryDelta(96))) val send2 = paymentInitiator.expectMsgType[SendPayment] assert(send2.targetNodeId == nodeId) assert(send2.amount == MilliSatoshi(123)) assert(send2.paymentHash == ByteVector32.Zeroes) - assert(send2.finalCltvExpiry == 96) + assert(send2.finalCltvExpiryDelta == CltvExpiryDelta(96)) // with custom route fees parameters - eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiry_opt = None, feeThreshold_opt = Some(Satoshi(123)), maxFeePct_opt = Some(4.20)) + eclair.send(recipientNodeId = nodeId, amount = MilliSatoshi(123), paymentHash = ByteVector32.Zeroes, assistedRoutes = Seq.empty, minFinalCltvExpiryDelta_opt = None, feeThreshold_opt = Some(Satoshi(123)), maxFeePct_opt = Some(4.20)) val send3 = paymentInitiator.expectMsgType[SendPayment] assert(send3.targetNodeId == nodeId) assert(send3.amount == MilliSatoshi(123)) @@ -134,11 +133,11 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu val (a, b, c, d, e) = (randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey) val updates = List( - makeUpdate(1L, a, b, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 13), - makeUpdate(4L, a, e, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12), - makeUpdate(2L, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 500), - makeUpdate(3L, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 500), - makeUpdate(7L, e, c, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12) + makeUpdate(1L, a, b, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(13)), + makeUpdate(4L, a, e, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), + makeUpdate(2L, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(500)), + makeUpdate(3L, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(500)), + makeUpdate(7L, e, c, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(12)) ).toMap val eclair = new EclairImpl(kit) @@ -246,15 +245,14 @@ class EclairImplSpec extends TestKit(ActorSystem("mySystem")) with fixture.FunSu val route = Seq(PublicKey(hex"030bb6a5e0c6b203c7e2180fb78c7ba4bdce46126761d8201b91ddac089cdecc87")) val eclair = new EclairImpl(kit) - eclair.sendToRoute(route, MilliSatoshi(1234), ByteVector32.One, 123) + eclair.sendToRoute(route, MilliSatoshi(1234), ByteVector32.One, CltvExpiryDelta(123)) val send = paymentInitiator.expectMsgType[SendPaymentToRoute] assert(send.hops == route) assert(send.amount == MilliSatoshi(1234)) - assert(send.finalCltvExpiry == 123) + assert(send.finalCltvExpiryDelta == CltvExpiryDelta(123)) assert(send.paymentHash == ByteVector32.One) } - } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 9c340e5a8e..188ec8794b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -32,8 +32,8 @@ import scodec.bits.ByteVector import scala.concurrent.duration._ /** - * Created by PM on 26/04/2016. - */ + * Created by PM on 26/04/2016. + */ object TestConstants { val fundingSatoshis = Satoshi(1000000L) @@ -79,12 +79,12 @@ object TestConstants { ), maxHtlcValueInFlightMsat = UInt64(150000000), maxAcceptedHtlcs = 100, - expiryDeltaBlocks = 144, - fulfillSafetyBeforeTimeoutBlocks = 6, + expiryDeltaBlocks = CltvExpiryDelta(144), + fulfillSafetyBeforeTimeoutBlocks = CltvExpiryDelta(6), htlcMinimum = MilliSatoshi(0), minDepthBlocks = 3, - toRemoteDelayBlocks = 144, - maxToLocalDelayBlocks = 1000, + toRemoteDelayBlocks = CltvExpiryDelta(144), + maxToLocalDelayBlocks = CltvExpiryDelta(1000), feeBase = MilliSatoshi(546000), feeProportionalMillionth = 10, reserveToFundingRatio = 0.01, // note: not used (overridden below) @@ -108,7 +108,7 @@ object TestConstants { routerBroadcastInterval = 5 seconds, searchMaxFeeBase = Satoshi(21), searchMaxFeePct = 0.03, - searchMaxCltv = 2016, + searchMaxCltv = CltvExpiryDelta(2016), searchMaxRouteLength = 20, searchHeuristicsEnabled = false, searchRatioCltv = 0.0, @@ -149,12 +149,12 @@ object TestConstants { ), maxHtlcValueInFlightMsat = UInt64.MaxValue, // Bob has no limit on the combined max value of in-flight htlcs maxAcceptedHtlcs = 30, - expiryDeltaBlocks = 144, - fulfillSafetyBeforeTimeoutBlocks = 6, + expiryDeltaBlocks = CltvExpiryDelta(144), + fulfillSafetyBeforeTimeoutBlocks = CltvExpiryDelta(6), htlcMinimum = MilliSatoshi(1000), minDepthBlocks = 3, - toRemoteDelayBlocks = 144, - maxToLocalDelayBlocks = 1000, + toRemoteDelayBlocks = CltvExpiryDelta(144), + maxToLocalDelayBlocks = CltvExpiryDelta(1000), feeBase = MilliSatoshi(546000), feeProportionalMillionth = 10, reserveToFundingRatio = 0.01, // note: not used (overridden below) @@ -178,7 +178,7 @@ object TestConstants { routerBroadcastInterval = 5 seconds, searchMaxFeeBase = Satoshi(21), searchMaxFeePct = 0.03, - searchMaxCltv = 2016, + searchMaxCltv = CltvExpiryDelta(2016), searchMaxRouteLength = 20, searchHeuristicsEnabled = false, searchRatioCltv = 0.0, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 87fbffeefe..ea93a65d7e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -312,7 +312,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock val jsonNodes = serialization.write(expectedRoute) val eclair = mock[Eclair] - eclair.sendToRoute(any[List[PublicKey]], any[MilliSatoshi], any[ByteVector32], anyLong)(any[Timeout]) returns Future.successful(paymentUUID) + eclair.sendToRoute(any[List[PublicKey]], any[MilliSatoshi], any[ByteVector32], any[CltvExpiryDelta])(any[Timeout]) returns Future.successful(paymentUUID) val mockService = new MockService(eclair) Post("/sendtoroute", FormData("route" -> jsonNodes, "amountMsat" -> "1234", "paymentHash" -> ByteVector32.Zeroes.toHex, "finalCltvExpiry" -> "190").toEntity) ~> @@ -323,7 +323,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock assert(handled) assert(status == OK) assert(entityAs[String] == "\""+rawUUID+"\"") - eclair.sendToRoute(expectedRoute, MilliSatoshi(1234), ByteVector32.Zeroes, 190)(any[Timeout]).wasCalled(once) + eclair.sendToRoute(expectedRoute, MilliSatoshi(1234), ByteVector32.Zeroes, CltvExpiryDelta(190))(any[Timeout]).wasCalled(once) } // this test uses CSV encoded route @@ -335,7 +335,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest with IdiomaticMock assert(handled) assert(status == OK) assert(entityAs[String] == "\""+rawUUID+"\"") - eclair.sendToRoute(expectedRoute, MilliSatoshi(1234), ByteVector32.One, 190)(any[Timeout]).wasCalled(once) + eclair.sendToRoute(expectedRoute, MilliSatoshi(1234), ByteVector32.One, CltvExpiryDelta(190))(any[Timeout]).wasCalled(once) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala index 188ceada39..53a0ab5914 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala @@ -21,8 +21,8 @@ import java.util.concurrent.CountDownLatch import akka.actor.{Actor, ActorLogging, ActorRef, Props, Status} import akka.testkit.{TestFSMRef, TestProbe} +import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.Crypto.PublicKey -import fr.acinq.bitcoin.{ByteVector32} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ import fr.acinq.eclair.blockchain._ @@ -32,7 +32,7 @@ import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire._ import grizzled.slf4j.Logging -import org.scalatest.Tag +import org.scalatest.{Outcome, Tag} import scala.collection.immutable.Nil import scala.concurrent.duration._ @@ -46,7 +46,7 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], pipe: ActorRef, relayerA: ActorRef, relayerB: ActorRef, paymentHandlerA: ActorRef, paymentHandlerB: ActorRef) - override def withFixture(test: OneArgTest) = { + override def withFixture(test: OneArgTest): Outcome = { val fuzzy = test.tags.contains("fuzzy") val pipe = system.actorOf(Props(new FuzzyPipe(fuzzy))) val alice2blockchain = TestProbe() @@ -94,7 +94,7 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi def buildCmdAdd(paymentHash: ByteVector32, dest: PublicKey) = { // allow overpaying (no more than 2 times the required amount) val amount = MilliSatoshi(requiredAmount + Random.nextInt(requiredAmount)) - val expiry = Globals.blockCount.get().toInt + Channel.MIN_CLTV_EXPIRY + 1 + val expiry = (Channel.MIN_CLTV_EXPIRY_DELTA + 1).toCltvExpiry PaymentLifecycle.buildCommand(UUID.randomUUID(), amount, expiry, paymentHash, Hop(null, dest, null) :: Nil)._1 } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala index 08aa1f88d6..b96005b4ec 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala @@ -51,7 +51,7 @@ class ThroughputSpec extends FunSuite { case ('add, tgt: ActorRef) => val r = randomBytes32 val h = Crypto.sha256(r) - tgt ! CMD_ADD_HTLC(MilliSatoshi(1), h, 1, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + tgt ! CMD_ADD_HTLC(MilliSatoshi(1), h, CltvExpiry(1), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) context.become(run(h2r + (h -> r))) case ('sig, tgt: ActorRef) => tgt ! CMD_SIGN diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index e614c054d7..16ba77054d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -20,7 +20,6 @@ import java.util.UUID import akka.testkit.{TestFSMRef, TestKitBase, TestProbe} import fr.acinq.bitcoin.{ByteVector32, Crypto} -import fr.acinq.eclair import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeTargets @@ -29,24 +28,23 @@ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment.PaymentLifecycle import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, NodeParams, TestConstants, randomBytes32} -import fr.acinq.eclair._ +import fr.acinq.eclair.{NodeParams, TestConstants, randomBytes32, _} /** - * Created by PM on 23/08/2016. - */ + * Created by PM on 23/08/2016. + */ trait StateTestsHelperMethods extends TestKitBase { case class SetupFixture(alice: TestFSMRef[State, Data, Channel], - bob: TestFSMRef[State, Data, Channel], - alice2bob: TestProbe, - bob2alice: TestProbe, - alice2blockchain: TestProbe, - bob2blockchain: TestProbe, - router: TestProbe, - relayerA: TestProbe, - relayerB: TestProbe, - channelUpdateListener: TestProbe) + bob: TestFSMRef[State, Data, Channel], + alice2bob: TestProbe, + bob2alice: TestProbe, + alice2blockchain: TestProbe, + bob2blockchain: TestProbe, + router: TestProbe, + relayerA: TestProbe, + relayerB: TestProbe, + channelUpdateListener: TestProbe) def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, wallet: EclairWallet = new TestWallet): SetupFixture = { val alice2bob = TestProbe() @@ -111,7 +109,7 @@ trait StateTestsHelperMethods extends TestKitBase { val H: ByteVector32 = Crypto.sha256(R) val sender = TestProbe() val receiverPubkey = r.underlyingActor.nodeParams.nodeId - val expiry = 400144 + val expiry = CltvExpiryDelta(144).toCltvExpiry val cmd = PaymentLifecycle.buildCommand(UUID.randomUUID, amount, expiry, H, Hop(null, receiverPubkey, null) :: Nil)._1.copy(commit = false) sender.send(s, cmd) sender.expectMsg("ok") @@ -175,4 +173,5 @@ trait StateTestsHelperMethods extends TestKitBase { def feeEstimator: TestFeeEstimator = a.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator] def feeTargets: FeeTargets = a.underlyingActor.nodeParams.onChainFeeConf.feeTargets } + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 980384bc58..ad69384c76 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -24,16 +24,16 @@ import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL, _} import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel} -import fr.acinq.eclair.{TestConstants, TestkitBaseClass} +import fr.acinq.eclair.{CltvExpiryDelta, TestConstants, TestkitBaseClass} import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector -import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ +import scala.concurrent.{Future, Promise} /** - * Created by PM on 05/07/2016. - */ + * Created by PM on 05/07/2016. + */ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelperMethods { @@ -41,7 +41,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp override def withFixture(test: OneArgTest): Outcome = { val noopWallet = new TestWallet { - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed } val setup = if (test.tags.contains("mainnet")) { init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), wallet = noopWallet) @@ -93,7 +93,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (to_self_delay too high)") { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] - val delayTooHigh = 10000 + val delayTooHigh = CltvExpiryDelta(10000) alice ! accept.copy(toSelfDelay = delayTooHigh) val error = alice2bob.expectMsgType[Error] assert(error === Error(accept.temporaryChannelId, ToSelfDelayTooHigh(accept.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index 549ee6f692..6ed2c473df 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -22,14 +22,14 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.wire.{Error, Init, OpenChannel} -import fr.acinq.eclair.{MilliSatoshi, TestConstants, TestkitBaseClass, ToMilliSatoshiConversion} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, TestConstants, TestkitBaseClass, ToMilliSatoshiConversion} import org.scalatest.Outcome import scala.concurrent.duration._ /** - * Created by PM on 05/07/2016. - */ + * Created by PM on 05/07/2016. + */ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelperMethods { @@ -109,7 +109,7 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelper test("recv OpenChannel (to_self_delay too high)") { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] - val delayTooHigh = 10000 + val delayTooHigh = CltvExpiryDelta(10000) bob ! open.copy(toSelfDelay = delayTooHigh) val error = bob2alice.expectMsgType[Error] assert(error === Error(open.temporaryChannelId, ToSelfDelayTooHigh(open.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 2300d25e05..643385ab79 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -23,8 +23,7 @@ import akka.actor.Status.Failure import akka.testkit.TestProbe import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi, ScriptFlags, Transaction} -import fr.acinq.eclair._ -import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} +import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.UInt64.Conversions._ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw @@ -38,15 +37,15 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, htlcSuccessWeight, htlcTimeoutWeight, weight2fee} import fr.acinq.eclair.transactions.{IN, OUT, Transactions} import fr.acinq.eclair.wire.{AnnouncementSignatures, ChannelUpdate, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc} -import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass, randomBytes32} +import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass, randomBytes32, _} import org.scalatest.{Outcome, Tag} import scodec.bits._ import scala.concurrent.duration._ /** - * Created by PM on 05/07/2016. - */ + * Created by PM on 05/07/2016. + */ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { @@ -70,7 +69,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val sender = TestProbe() val h = randomBytes32 - val add = CMD_ADD_HTLC(MilliSatoshi(50000000), h, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(50000000), h, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) sender.expectMsg("ok") val htlc = alice2bob.expectMsgType[UpdateAddHtlc] @@ -88,7 +87,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val h = randomBytes32 for (i <- 0 until 10) { - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(50000000), h, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(50000000), h, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") val htlc = alice2bob.expectMsgType[UpdateAddHtlc] assert(htlc.id == i && htlc.paymentHash == h) @@ -100,8 +99,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val sender = TestProbe() val h = randomBytes32 - val originHtlc = UpdateAddHtlc(channelId = randomBytes32, id = 5656, amountMsat = MilliSatoshi(50000000), cltvExpiry = 400144, paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket) - val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - MilliSatoshi(10000), h, originHtlc.cltvExpiry - 7, TestConstants.emptyOnionPacket, upstream = Right(originHtlc)) + val originHtlc = UpdateAddHtlc(channelId = randomBytes32, id = 5656, amountMsat = MilliSatoshi(50000000), cltvExpiry = CltvExpiryDelta(144).toCltvExpiry, paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket) + val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - MilliSatoshi(10000), h, originHtlc.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, upstream = Right(originHtlc)) sender.send(alice, cmd) sender.expectMsg("ok") val htlc = alice2bob.expectMsgType[UpdateAddHtlc] @@ -119,10 +118,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val currentBlockCount = Globals.blockCount.get - val expiryTooSmall = currentBlockCount + 3 + val expiryTooSmall = CltvExpiry(currentBlockCount + 3) val add = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, expiryTooSmall, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) - val error = ExpiryTooSmall(channelId(alice), currentBlockCount + Channel.MIN_CLTV_EXPIRY, expiryTooSmall, currentBlockCount) + val error = ExpiryTooSmall(channelId(alice), Channel.MIN_CLTV_EXPIRY_DELTA.toCltvExpiry, expiryTooSmall, currentBlockCount) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) alice2bob.expectNoMsg(200 millis) } @@ -132,10 +131,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val currentBlockCount = Globals.blockCount.get - val expiryTooBig = currentBlockCount + Channel.MAX_CLTV_EXPIRY + 1 + val expiryTooBig = (Channel.MAX_CLTV_EXPIRY_DELTA + 1).toCltvExpiry val add = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, expiryTooBig, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) - val error = ExpiryTooBig(channelId(alice), maximum = currentBlockCount + Channel.MAX_CLTV_EXPIRY, actual = expiryTooBig, blockCount = currentBlockCount) + val error = ExpiryTooBig(channelId(alice), maximum = Channel.MAX_CLTV_EXPIRY_DELTA.toCltvExpiry, actual = expiryTooBig, blockCount = currentBlockCount) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) alice2bob.expectNoMsg(200 millis) } @@ -144,7 +143,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val add = CMD_ADD_HTLC(MilliSatoshi(50), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(50), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = HtlcValueTooSmall(channelId(alice), MilliSatoshi(1000), MilliSatoshi(50)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) @@ -155,7 +154,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val add = CMD_ADD_HTLC(MilliSatoshi(Int.MaxValue), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(Int.MaxValue), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = InsufficientFunds(channelId(alice), amount = MilliSatoshi(Int.MaxValue), missing = Satoshi(1376443), reserve = Satoshi(20000), fees = Satoshi(8960)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) @@ -166,16 +165,16 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(200000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(200000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(67600000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(67600000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] - val add = CMD_ADD_HTLC(MilliSatoshi(1000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(1000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = InsufficientFunds(channelId(alice), amount = MilliSatoshi(1000000), missing = Satoshi(1000), reserve = Satoshi(20000), fees = Satoshi(12400)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) @@ -186,13 +185,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(300000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(300000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(300000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(300000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] - val add = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = InsufficientFunds(channelId(alice), amount = MilliSatoshi(500000000), missing = Satoshi(332400), reserve = Satoshi(20000), fees = Satoshi(12400)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) @@ -203,7 +202,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val add = CMD_ADD_HTLC(MilliSatoshi(151000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(151000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(bob, add) val error = HtlcValueTooHighInFlight(channelId(bob), maximum = 150000000, actual = 151000000) sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) @@ -216,11 +215,11 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] // Bob accepts a maximum of 30 htlcs for (i <- 0 until 30) { - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] } - val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) @@ -231,7 +230,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { import f._ val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add1) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] @@ -239,7 +238,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.expectMsg("ok") alice2bob.expectMsgType[CommitSig] // this is over channel-capacity - val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis.toMilliSatoshi * 2 / 3, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add2) val error = InsufficientFunds(channelId(alice), add2.amount, Satoshi(564013), Satoshi(20000), Satoshi(10680)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Local(add2.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add2)))) @@ -256,7 +255,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined && alice.stateData.asInstanceOf[DATA_NORMAL].remoteShutdown.isEmpty) // actual test starts here - val add = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = NoMoreHtlcsClosingInProgress(channelId(alice)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) @@ -268,14 +267,14 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val sender = TestProbe() val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] // let's make alice send an htlc - val add1 = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add1 = CMD_ADD_HTLC(MilliSatoshi(500000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add1) sender.expectMsg("ok") // at the same time bob initiates a closing sender.send(bob, CMD_CLOSE(None)) sender.expectMsg("ok") // this command will be received by alice right after having received the shutdown - val add2 = CMD_ADD_HTLC(MilliSatoshi(100000000), randomBytes32, 300000, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add2 = CMD_ADD_HTLC(MilliSatoshi(100000000), randomBytes32, CltvExpiry(300000), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) // messages cross alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.forward(bob) @@ -289,7 +288,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc") { f => import f._ val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] - val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(150000), randomBytes32, 400144, TestConstants.emptyOnionPacket) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(150000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket) bob ! htlc awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) // bob won't forward the add before it is cross-signed @@ -299,7 +298,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc (unexpected id)") { f => import f._ val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 42, MilliSatoshi(150000), randomBytes32, 400144, TestConstants.emptyOnionPacket) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 42, MilliSatoshi(150000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket) bob ! htlc.copy(id = 0) bob ! htlc.copy(id = 1) bob ! htlc.copy(id = 2) @@ -316,7 +315,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc (value too small)") { f => import f._ val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(150), randomBytes32, cltvExpiry = 400144, TestConstants.emptyOnionPacket) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(150), randomBytes32, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket) alice2bob.forward(bob, htlc) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === HtlcValueTooSmall(channelId(bob), minimum = MilliSatoshi(1000), actual = MilliSatoshi(150)).getMessage) @@ -331,7 +330,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc (insufficient funds)") { f => import f._ val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(Long.MaxValue), randomBytes32, 400144, TestConstants.emptyOnionPacket) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(Long.MaxValue), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket) alice2bob.forward(bob, htlc) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === InsufficientFunds(channelId(bob), amount = MilliSatoshi(Long.MaxValue), missing = Satoshi(9223372036083735L), reserve = Satoshi(20000), fees = Satoshi(8960)).getMessage) @@ -346,10 +345,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 1/2)") { f => import f._ val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(400000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(200000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(167600000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 3, MilliSatoshi(10000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(400000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(200000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(167600000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 3, MilliSatoshi(10000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === InsufficientFunds(channelId(bob), amount = MilliSatoshi(10000000), missing = Satoshi(11720), reserve = Satoshi(20000), fees = Satoshi(14120)).getMessage) awaitCond(bob.stateName == CLOSING) @@ -363,9 +362,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 2/2)") { f => import f._ val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(300000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(300000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(500000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(300000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(300000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(500000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === InsufficientFunds(channelId(bob), amount = MilliSatoshi(500000000), missing = Satoshi(332400), reserve = Satoshi(20000), fees = Satoshi(12400)).getMessage) awaitCond(bob.stateName == CLOSING) @@ -379,7 +378,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv UpdateAddHtlc (over max inflight htlc value)") { f => import f._ val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - alice2bob.forward(alice, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(151000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) + alice2bob.forward(alice, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(151000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) val error = alice2bob.expectMsgType[Error] assert(new String(error.data.toArray) === HtlcValueTooHighInFlight(channelId(alice), maximum = 150000000, actual = 151000000).getMessage) awaitCond(alice.stateName == CLOSING) @@ -395,9 +394,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx // Bob accepts a maximum of 30 htlcs for (i <- 0 until 30) { - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, i, MilliSatoshi(1000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, i, MilliSatoshi(1000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) } - alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 30, MilliSatoshi(1000000), randomBytes32, 400144, TestConstants.emptyOnionPacket)) + alice2bob.forward(bob, UpdateAddHtlc(ByteVector32.Zeroes, 30, MilliSatoshi(1000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket)) val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray) === TooManyAcceptedHtlcs(channelId(bob), maximum = 30).getMessage) awaitCond(bob.stateName == CLOSING) @@ -422,7 +421,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv CMD_SIGN (two identical htlcs in each direction)") { f => import f._ val sender = TestProbe() - val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] @@ -469,19 +468,19 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(a2b_2 > aliceMinOffer && a2b_2 > bobMinReceive) assert(b2a_1 > aliceMinReceive && b2a_1 > bobMinOffer) assert(b2a_2 < aliceMinReceive && b2a_2 > bobMinOffer) - sender.send(alice, CMD_ADD_HTLC(a2b_1.toMilliSatoshi, randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(a2b_1.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.forward(bob) - sender.send(alice, CMD_ADD_HTLC(a2b_2.toMilliSatoshi, randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(a2b_2.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.forward(bob) - sender.send(bob, CMD_ADD_HTLC(b2a_1.toMilliSatoshi, randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(bob, CMD_ADD_HTLC(b2a_1.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") bob2alice.expectMsgType[UpdateAddHtlc] bob2alice.forward(alice) - sender.send(bob, CMD_ADD_HTLC(b2a_2.toMilliSatoshi, randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(bob, CMD_ADD_HTLC(b2a_2.toMilliSatoshi, randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") bob2alice.expectMsgType[UpdateAddHtlc] bob2alice.forward(alice) @@ -496,14 +495,14 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { assert(alice.underlyingActor.nodeParams.db.channels.listHtlcInfos(alice.stateData.asInstanceOf[DATA_NORMAL].channelId, 2).size == 4) assert(bob.underlyingActor.nodeParams.db.channels.listHtlcInfos(bob.stateData.asInstanceOf[DATA_NORMAL].channelId, 0).size == 0) assert(bob.underlyingActor.nodeParams.db.channels.listHtlcInfos(bob.stateData.asInstanceOf[DATA_NORMAL].channelId, 1).size == 3) - } + } test("recv CMD_SIGN (htlcs with same pubkeyScript but different amounts)") { f => import f._ val sender = TestProbe() - val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) val epsilons = List(3, 1, 5, 7, 6) // unordered on purpose - val htlcCount = epsilons.size + val htlcCount = epsilons.size for (i <- epsilons) { sender.send(alice, add.copy(amount = MilliSatoshi(add.amount.toLong + i * 1000))) sender.expectMsg("ok") @@ -699,12 +698,12 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val r = randomBytes32 val h = Crypto.sha256(r) - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(50000000), h, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(50000000), h, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.forward(bob) - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(50000000), h, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(50000000), h, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) sender.expectMsg("ok") val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.forward(bob) @@ -1715,7 +1714,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = true)) sender.expectMsg("ok") bob2alice.expectMsgType[UpdateFulfillHtlc] - sender.send(bob, CurrentBlockCount(htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) @@ -1750,7 +1749,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = false)) sender.expectMsg("ok") bob2alice.expectMsgType[UpdateFulfillHtlc] - sender.send(bob, CurrentBlockCount(htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) @@ -1790,7 +1789,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) alice2bob.expectMsgType[RevokeAndAck] alice2bob.forward(bob) - sender.send(bob, CurrentBlockCount(htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) @@ -2031,7 +2030,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // alice = 800 000 // bob = 200 000 - val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(10000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 916b54c184..c5953a6d73 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -31,14 +31,14 @@ import fr.acinq.eclair.payment.CommandBuffer.CommandSend import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.HtlcSuccessTx import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{MilliSatoshi, TestConstants, TestkitBaseClass, randomBytes32} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, TestConstants, TestkitBaseClass, randomBytes32} import org.scalatest.Outcome import scala.concurrent.duration._ /** - * Created by PM on 05/07/2016. - */ + * Created by PM on 05/07/2016. + */ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { @@ -60,13 +60,13 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { def bobInit = Init(TestConstants.Bob.nodeParams.globalFeatures, TestConstants.Bob.nodeParams.localFeatures) /** - * This test checks the case where a disconnection occurs *right before* the counterparty receives a new sig - */ + * This test checks the case where a disconnection occurs *right before* the counterparty receives a new sig + */ test("re-send update+sig after first commitment") { f => import f._ val sender = TestProbe() - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(1000000), ByteVector32.Zeroes, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(1000000), ByteVector32.Zeroes, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) val ab_add_0 = alice2bob.expectMsgType[UpdateAddHtlc] // add ->b alice2bob.forward(bob) @@ -137,13 +137,13 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { } /** - * This test checks the case where a disconnection occurs *right after* the counterparty receives a new sig - */ + * This test checks the case where a disconnection occurs *right after* the counterparty receives a new sig + */ test("re-send lost revocation") { f => import f._ val sender = TestProbe() - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(1000000), randomBytes32, 400144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(1000000), randomBytes32, CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) val ab_add_0 = alice2bob.expectMsgType[UpdateAddHtlc] // add ->b alice2bob.forward(bob, ab_add_0) @@ -387,7 +387,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { channelUpdateListener.expectNoMsg(300 millis) // we attempt to send a payment - sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(4200), randomBytes32, 123456, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) + sender.send(alice, CMD_ADD_HTLC(MilliSatoshi(4200), randomBytes32, CltvExpiry(123456), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID()))) val failure = sender.expectMsgType[Status.Failure] val AddHtlcFailed(_, _, ChannelUnavailable(_), _, _, _) = failure.cause @@ -419,7 +419,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // We simulate a pending fulfill on that HTLC but not relayed. // When it is close to expiring upstream, we should close the channel. sender.send(commandBuffer, CommandSend(htlc.channelId, htlc.id, CMD_FULFILL_HTLC(htlc.id, r, commit = true))) - sender.send(bob, CurrentBlockCount(htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) @@ -453,7 +453,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // We simulate a pending failure on that HTLC. // Even if we get close to expiring upstream we shouldn't close the channel, because we have nothing to lose. sender.send(commandBuffer, CommandSend(htlc.channelId, htlc.id, CMD_FAIL_HTLC(htlc.id, Right(IncorrectOrUnknownPaymentDetails(MilliSatoshi(0)))))) - sender.send(bob, CurrentBlockCount(htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) bob2blockchain.expectNoMsg(250 millis) alice2blockchain.expectNoMsg(250 millis) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 0ca1582caa..ef3d516f89 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -22,7 +22,6 @@ import akka.actor.Status.Failure import akka.testkit.TestProbe import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, Satoshi, ScriptFlags, Transaction} -import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.channel._ @@ -30,15 +29,15 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire.{CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc} -import fr.acinq.eclair.{Globals, MilliSatoshi, TestConstants, TestkitBaseClass, randomBytes32} -import org.scalatest.{Outcome, Tag} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, TestConstants, TestkitBaseClass, randomBytes32} +import org.scalatest.Outcome import scodec.bits.ByteVector import scala.concurrent.duration._ /** - * Created by PM on 05/07/2016. - */ + * Created by PM on 05/07/2016. + */ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { @@ -56,7 +55,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // alice sends an HTLC to bob val h1 = Crypto.sha256(r1) val amount1 = MilliSatoshi(300000000) - val expiry1 = 400144 + val expiry1 = CltvExpiryDelta(144).toCltvExpiry val cmd1 = PaymentLifecycle.buildCommand(UUID.randomUUID, amount1, expiry1, h1, Hop(null, TestConstants.Bob.nodeParams.nodeId, null) :: Nil)._1.copy(commit = false) sender.send(alice, cmd1) sender.expectMsg("ok") @@ -66,7 +65,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // alice sends another HTLC to bob val h2 = Crypto.sha256(r2) val amount2 = MilliSatoshi(200000000) - val expiry2 = 400144 + val expiry2 = CltvExpiryDelta(144).toCltvExpiry val cmd2 = PaymentLifecycle.buildCommand(UUID.randomUUID, amount2, expiry2, h2, Hop(null, TestConstants.Bob.nodeParams.nodeId, null) :: Nil)._1.copy(commit = false) sender.send(alice, cmd2) sender.expectMsg("ok") @@ -104,7 +103,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { test("recv CMD_ADD_HTLC") { f => import f._ val sender = TestProbe() - val add = CMD_ADD_HTLC(MilliSatoshi(500000000), r1, cltvExpiry = 300000, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(500000000), r1, cltvExpiry = CltvExpiry(300000), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = ChannelUnavailable(channelId(alice)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), None, Some(add)))) @@ -638,7 +637,7 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - val watch = alice2blockchain.expectMsgType[WatchConfirmed] + val watch = alice2blockchain.expectMsgType[WatchConfirmed] assert(watch.event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index f5c5a59cd4..ee0c89eafc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -22,15 +22,15 @@ import akka.actor.Status.Failure import akka.event.LoggingAdapter import akka.testkit.TestProbe import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi} -import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} +import fr.acinq.eclair.TestConstants.Bob import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} +import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.payment.Local import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown} -import fr.acinq.eclair.{Globals, MilliSatoshi, TestConstants, TestkitBaseClass} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, TestConstants, TestkitBaseClass} import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -38,8 +38,8 @@ import scala.concurrent.duration._ import scala.util.Success /** - * Created by PM on 05/07/2016. - */ + * Created by PM on 05/07/2016. + */ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { @@ -85,7 +85,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods import f._ alice2bob.expectMsgType[ClosingSigned] val sender = TestProbe() - val add = CMD_ADD_HTLC(MilliSatoshi(5000000000L), ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = 300000, onion = TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(5000000000L), ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = ChannelUnavailable(channelId(alice)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), None, Some(add)))) @@ -136,7 +136,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods val sender = TestProbe() val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx sender.send(bob, aliceCloseSig.copy(feeSatoshis = Satoshi(99000))) // sig doesn't matter, it is checked later - val error = bob2alice.expectMsgType[Error] + val error = bob2alice.expectMsgType[Error] assert(new String(error.data.toArray).startsWith("invalid close fee: fee_satoshis=Satoshi(99000)")) bob2blockchain.expectMsg(PublishAsap(tx)) bob2blockchain.expectMsgType[PublishAsap] 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 8cff5d868f..2df05bca3e 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 @@ -21,19 +21,17 @@ import java.util.UUID import akka.actor.Status import akka.actor.Status.Failure import akka.testkit.{TestFSMRef, TestProbe} -import com.typesafe.sslconfig.util.NoopLogger import fr.acinq.bitcoin.{ByteVector32, OutPoint, Satoshi, ScriptFlags, Transaction, TxIn} -import fr.acinq.eclair.TestConstants.{Alice, Bob, TestFeeEstimator} +import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.channel.Helpers.Closing -import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeratesPerKw} import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{Data, State, _} import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.{Scripts, Transactions} import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, MilliSatoshi, TestConstants, TestkitBaseClass, randomBytes32} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, TestConstants, TestkitBaseClass, randomBytes32} import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -41,8 +39,8 @@ import scala.compat.Platform import scala.concurrent.duration._ /** - * Created by PM on 05/07/2016. - */ + * Created by PM on 05/07/2016. + */ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { @@ -86,27 +84,27 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, Nil))) } } else { - within(30 seconds) { - reachNormal(setup) - val bobCommitTxes: List[PublishableTxs] = (for (amt <- List(100000000, 200000000, 300000000)) yield { - val (r, htlc) = addHtlc(MilliSatoshi(amt), alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - relayerB.expectMsgType[ForwardAdd] - val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs - fulfillHtlc(htlc.id, r, bob, alice, bob2alice, alice2bob) - // alice forwards the fulfill upstream - relayerA.expectMsgType[ForwardFulfill] - crossSign(bob, alice, bob2alice, alice2bob) - // bob confirms that it has forwarded the fulfill to alice - relayerB.expectMsgType[CommandBuffer.CommandAck] - val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs - bobCommitTx1 :: bobCommitTx2 :: Nil - }).flatten - - awaitCond(alice.stateName == NORMAL) - awaitCond(bob.stateName == NORMAL) - withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, bobCommitTxes))) - } + within(30 seconds) { + reachNormal(setup) + val bobCommitTxes: List[PublishableTxs] = (for (amt <- List(100000000, 200000000, 300000000)) yield { + val (r, htlc) = addHtlc(MilliSatoshi(amt), alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + relayerB.expectMsgType[ForwardAdd] + val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs + fulfillHtlc(htlc.id, r, bob, alice, bob2alice, alice2bob) + // alice forwards the fulfill upstream + relayerA.expectMsgType[ForwardFulfill] + crossSign(bob, alice, bob2alice, alice2bob) + // bob confirms that it has forwarded the fulfill to alice + relayerB.expectMsgType[CommandBuffer.CommandAck] + val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs + bobCommitTx1 :: bobCommitTx2 :: Nil + }).flatten + + awaitCond(alice.stateName == NORMAL) + awaitCond(bob.stateName == NORMAL) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, bobCommitTxes))) + } } } @@ -301,7 +299,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // actual test starts here val sender = TestProbe() - val add = CMD_ADD_HTLC(MilliSatoshi(500000000), ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = 300000, onion = TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + val add = CMD_ADD_HTLC(MilliSatoshi(500000000), ByteVector32(ByteVector.fill(32)(1)), cltvExpiry = CltvExpiry(300000), onion = TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) sender.send(alice, add) val error = ChannelUnavailable(channelId(alice)) sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(add.upstream.left.get, Some(sender.ref)), None, Some(add)))) @@ -426,9 +424,9 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx alice ! Error(ByteVector32.Zeroes, "oops") alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx - val claimMainDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-delayed-output - val htlcTimeoutTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-timeout - val claimDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-delayed-output + val claimMainDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-delayed-output + val htlcTimeoutTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-timeout + val claimDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-delayed-output assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // main-delayed-output assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // claim-delayed-output @@ -439,7 +437,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // actual test starts here alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 42, 0, aliceCommitTx) - assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay) + assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay.delta) assert(listener.expectMsgType[PaymentSettlingOnChain].paymentHash == htlca1.paymentHash) alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainDelayedTx), 200, 0, claimMainDelayedTx) alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcTimeoutTx), 201, 0, htlcTimeoutTx) @@ -655,8 +653,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx) // alice publishes and watches the penalty tx val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main - val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty - val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty + val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty + val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit alice2blockchain.expectMsgType[WatchConfirmed] // claim-main alice2blockchain.expectMsgType[WatchSpent] // main-penalty @@ -670,10 +668,10 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0, mainPenaltyTx) alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx) // we published this alice2blockchain.expectMsgType[WatchConfirmed] // htlc-penalty - val bobHtlcSuccessTx = bobRevokedTx.htlcTxsAndSigs.head.txinfo.tx + val bobHtlcSuccessTx = bobRevokedTx.htlcTxsAndSigs.head.txinfo.tx alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, bobHtlcSuccessTx) // bob published his HtlcSuccess tx alice2blockchain.expectMsgType[WatchConfirmed] // htlc-success - val claimHtlcDelayedPenaltyTxs = alice2blockchain.expectMsgType[PublishAsap].tx // we publish a tx spending the output of bob's HtlcSuccess tx + val claimHtlcDelayedPenaltyTxs = alice2blockchain.expectMsgType[PublishAsap].tx // we publish a tx spending the output of bob's HtlcSuccess tx alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobHtlcSuccessTx), 0, 0, bobHtlcSuccessTx) // bob won alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimHtlcDelayedPenaltyTxs), 0, 0, claimHtlcDelayedPenaltyTxs) // bob won awaitCond(alice.stateName == CLOSED) @@ -687,8 +685,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx) // alice publishes and watches the penalty tx val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main - val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty - val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty + val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty + val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit alice2blockchain.expectMsgType[WatchConfirmed] // claim-main alice2blockchain.expectMsgType[WatchSpent] // main-penalty diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala index 88052c3158..ac7d61ed37 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala @@ -17,11 +17,11 @@ package fr.acinq.eclair.db import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.TestConstants import fr.acinq.eclair.db.sqlite.SqliteUtils.{getVersion, using} import fr.acinq.eclair.db.sqlite.{SqliteChannelsDb, SqlitePendingRelayDb} import fr.acinq.eclair.wire.ChannelCodecs.stateDataCodec import fr.acinq.eclair.wire.ChannelCodecsSpec +import fr.acinq.eclair.{CltvExpiry, TestConstants} import org.scalatest.FunSuite import org.sqlite.SQLiteException import scodec.bits.ByteVector @@ -44,9 +44,9 @@ class SqliteChannelsDbSpec extends FunSuite { val commitNumber = 42 val paymentHash1 = ByteVector32.Zeroes - val cltvExpiry1 = 123 + val cltvExpiry1 = CltvExpiry(123) val paymentHash2 = ByteVector32(ByteVector.fill(32)(1)) - val cltvExpiry2 = 656 + val cltvExpiry2 = CltvExpiry(656) intercept[SQLiteException](db.addOrUpdateHtlcInfo(channel.channelId, commitNumber, paymentHash1, cltvExpiry1)) // no related channel diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala index a721c4d557..f326d3eb89 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.{Block, Crypto, Satoshi} import fr.acinq.eclair.db.sqlite.SqliteNetworkDb import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.wire.{Color, NodeAddress, Tor2} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TestConstants, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, ShortChannelId, TestConstants, randomBytes32, randomKey} import org.scalatest.FunSuite import org.sqlite.SQLiteException @@ -85,9 +85,9 @@ class SqliteNetworkDbSpec extends FunSuite { db.removeChannel(channel_2.shortChannelId) assert(db.listChannels().toSet === Set((channel_1, (txid_1, capacity)), (channel_3, (txid_3, capacity)))) - val channel_update_1 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(42), 5, MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) - val channel_update_2 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(43), 5, MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) - val channel_update_3 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(44), 5, MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) + val channel_update_1 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(42), CltvExpiryDelta(5), MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) + val channel_update_2 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(43), CltvExpiryDelta(5), MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) + val channel_update_3 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(44), CltvExpiryDelta(5), MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) assert(db.listChannelUpdates().toSet === Set.empty) db.addChannelUpdate(channel_update_1) @@ -110,7 +110,7 @@ class SqliteNetworkDbSpec extends FunSuite { val capacity = Satoshi(10000) val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, id, pub, pub, pub, pub, sig, sig, sig, sig)) - val template = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv, pub, ShortChannelId(42), 5, MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) + val template = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv, pub, ShortChannelId(42), CltvExpiryDelta(5), MilliSatoshi(7000000), MilliSatoshi(50000), 100, MilliSatoshi(500000000L), true) val updates = shortChannelIds.map(id => template.copy(shortChannelId = id)) val txid = randomBytes32 channels.foreach(ca => db.addChannel(ca, txid, capacity)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala index 0f9857d006..fda650db0d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala @@ -68,7 +68,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService randomize = false, maxFeeBase = MilliSatoshi(Long.MaxValue), maxFeePct = Double.MaxValue, - routeMaxCltv = Int.MaxValue, + routeMaxCltv = CltvExpiryDelta(Int.MaxValue), routeMaxLength = ROUTE_MAX_LENGTH, ratios = Some(WeightRatios( cltvDeltaFactor = 0.1, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala index 1a2f5eabe6..abfb034db9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala @@ -22,13 +22,13 @@ import java.util.concurrent.CountDownLatch import akka.actor.{Actor, ActorLogging, ActorRef, Stash} import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.{MilliSatoshi, TestConstants, TestUtils} import fr.acinq.eclair.channel._ import fr.acinq.eclair.transactions.{IN, OUT} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, TestConstants, TestUtils} /** - * Created by PM on 30/05/2016. - */ + * Created by PM on 30/05/2016. + */ /* @@ -57,7 +57,7 @@ class SynchronizationPipe(latch: CountDownLatch) extends Actor with ActorLogging script match { case offer(x, amount, rhash) :: rest => - resolve(x) ! CMD_ADD_HTLC(MilliSatoshi(amount.toInt), ByteVector32.fromValidHex(rhash), 144, TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) + resolve(x) ! CMD_ADD_HTLC(MilliSatoshi(amount.toInt), ByteVector32.fromValidHex(rhash), CltvExpiry(144), TestConstants.emptyOnionPacket, upstream = Left(UUID.randomUUID())) exec(rest, a, b) case fulfill(x, id, r) :: rest => resolve(x) ! CMD_FULFILL_HTLC(id.toInt, ByteVector32.fromValidHex(r)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala index 57debf6a86..021c4048a2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/HtlcReaperSpec.scala @@ -18,17 +18,16 @@ package fr.acinq.eclair.io import akka.actor.{ActorSystem, Props} import akka.testkit.{TestKit, TestProbe} -import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.channel._ -import fr.acinq.eclair.{TestConstants, randomBytes32} import fr.acinq.eclair.wire.{ChannelCodecsSpec, TemporaryNodeFailure, UpdateAddHtlc} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, TestConstants, randomBytes32} import org.scalatest.FunSuiteLike import scala.concurrent.duration._ /** - * Created by PM on 27/01/2017. - */ + * Created by PM on 27/01/2017. + */ class HtlcReaperSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { @@ -37,11 +36,11 @@ class HtlcReaperSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { val data = ChannelCodecsSpec.normal // assuming that data has incoming htlcs 0 and 1, we don't care about the amount/payment_hash/onion fields - val add0 = UpdateAddHtlc(data.channelId, 0, MilliSatoshi(20000), randomBytes32, 100, TestConstants.emptyOnionPacket) - val add1 = UpdateAddHtlc(data.channelId, 1, MilliSatoshi(30000), randomBytes32, 100, TestConstants.emptyOnionPacket) + val add0 = UpdateAddHtlc(data.channelId, 0, MilliSatoshi(20000), randomBytes32, CltvExpiry(100), TestConstants.emptyOnionPacket) + val add1 = UpdateAddHtlc(data.channelId, 1, MilliSatoshi(30000), randomBytes32, CltvExpiry(100), TestConstants.emptyOnionPacket) // unrelated htlc - val add99 = UpdateAddHtlc(randomBytes32, 0, MilliSatoshi(12345678), randomBytes32, 100, TestConstants.emptyOnionPacket) + val add99 = UpdateAddHtlc(randomBytes32, 0, MilliSatoshi(12345678), randomBytes32, CltvExpiry(100), TestConstants.emptyOnionPacket) val brokenHtlcs = Seq(add0, add1, add99) val brokenHtlcKiller = system.actorOf(Props[HtlcReaper], name = "htlc-reaper") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/ChannelSelectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/ChannelSelectionSpec.scala index 71027c5e10..95e5928222 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/ChannelSelectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/ChannelSelectionSpec.scala @@ -16,14 +16,14 @@ package fr.acinq.eclair.payment -import fr.acinq.bitcoin.{Block, ByteVector32} import fr.acinq.bitcoin.Crypto.PublicKey +import fr.acinq.bitcoin.{Block, ByteVector32} import fr.acinq.eclair.channel.{CMD_ADD_HTLC, CMD_FAIL_HTLC} +import fr.acinq.eclair.payment.HtlcGenerationSpec.makeCommitments import fr.acinq.eclair.payment.Relayer.{OutgoingChannel, RelayFailure, RelayPayload, RelaySuccess} import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TestConstants, randomBytes32, randomKey} -import fr.acinq.eclair.payment.HtlcGenerationSpec.makeCommitments +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, TestConstants, randomBytes32, randomKey} import org.scalatest.FunSuite import scala.collection.mutable @@ -31,19 +31,19 @@ import scala.collection.mutable class ChannelSelectionSpec extends FunSuite { /** - * This is just a simplified helper function with random values for fields we are not using here - */ - def dummyUpdate(shortChannelId: ShortChannelId, cltvExpiryDelta: Int, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: Long, feeProportionalMillionths: Long, htlcMaximumMsat: Long, enable: Boolean = true) = + * This is just a simplified helper function with random values for fields we are not using here + */ + def dummyUpdate(shortChannelId: ShortChannelId, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: Long, feeProportionalMillionths: Long, htlcMaximumMsat: Long, enable: Boolean = true) = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, shortChannelId, cltvExpiryDelta, htlcMinimumMsat, MilliSatoshi(feeBaseMsat), feeProportionalMillionths, MilliSatoshi(htlcMaximumMsat), enable) test("convert to CMD_FAIL_HTLC/CMD_ADD_HTLC") { val relayPayload = RelayPayload( - add = UpdateAddHtlc(randomBytes32, 42, MilliSatoshi(1000000), randomBytes32, 70, TestConstants.emptyOnionPacket), - payload = PerHopPayload(ShortChannelId(12345), amtToForward = MilliSatoshi(998900), outgoingCltvValue = 60), + add = UpdateAddHtlc(randomBytes32, 42, MilliSatoshi(1000000), randomBytes32, CltvExpiry(70), TestConstants.emptyOnionPacket), + payload = PerHopPayload(ShortChannelId(12345), amtToForward = MilliSatoshi(998900), outgoingCltvValue = CltvExpiry(60)), nextPacket = TestConstants.emptyOnionPacket // just a placeholder ) - val channelUpdate = dummyUpdate(ShortChannelId(12345), 10, MilliSatoshi(100), 1000, 100, 10000000, true) + val channelUpdate = dummyUpdate(ShortChannelId(12345), CltvExpiryDelta(10), MilliSatoshi(100), 1000, 100, 10000000, true) implicit val log = akka.event.NoLogging @@ -58,7 +58,7 @@ class ChannelSelectionSpec extends FunSuite { val relayPayload_toolow = relayPayload.copy(payload = relayPayload.payload.copy(amtToForward = MilliSatoshi(99))) assert(Relayer.relayOrFail(relayPayload_toolow, Some(channelUpdate)) === RelayFailure(CMD_FAIL_HTLC(relayPayload.add.id, Right(AmountBelowMinimum(relayPayload_toolow.payload.amtToForward, channelUpdate)), commit = true))) // incorrect cltv expiry - val relayPayload_incorrectcltv = relayPayload.copy(payload = relayPayload.payload.copy(outgoingCltvValue = 42)) + val relayPayload_incorrectcltv = relayPayload.copy(payload = relayPayload.payload.copy(outgoingCltvValue = CltvExpiry(42))) assert(Relayer.relayOrFail(relayPayload_incorrectcltv, Some(channelUpdate)) === RelayFailure(CMD_FAIL_HTLC(relayPayload.add.id, Right(IncorrectCltvExpiry(relayPayload_incorrectcltv.payload.outgoingCltvValue, channelUpdate)), commit = true))) // insufficient fee val relayPayload_insufficientfee = relayPayload.copy(payload = relayPayload.payload.copy(amtToForward = MilliSatoshi(998910))) @@ -71,13 +71,13 @@ class ChannelSelectionSpec extends FunSuite { test("channel selection") { val relayPayload = RelayPayload( - add = UpdateAddHtlc(randomBytes32, 42, MilliSatoshi(1000000), randomBytes32, 70, TestConstants.emptyOnionPacket), - payload = PerHopPayload(ShortChannelId(12345), amtToForward = MilliSatoshi(998900), outgoingCltvValue = 60), + add = UpdateAddHtlc(randomBytes32, 42, MilliSatoshi(1000000), randomBytes32, CltvExpiry(70), TestConstants.emptyOnionPacket), + payload = PerHopPayload(ShortChannelId(12345), amtToForward = MilliSatoshi(998900), outgoingCltvValue = CltvExpiry(60)), nextPacket = TestConstants.emptyOnionPacket // just a placeholder ) val (a, b) = (randomKey.publicKey, randomKey.publicKey) - val channelUpdate = dummyUpdate(ShortChannelId(12345), 10, MilliSatoshi(100), 1000, 100, 10000000, true) + val channelUpdate = dummyUpdate(ShortChannelId(12345), CltvExpiryDelta(10), MilliSatoshi(100), 1000, 100, 10000000, true) val channelUpdates = Map( ShortChannelId(11111) -> OutgoingChannel(a, channelUpdate, makeCommitments(ByteVector32.Zeroes, MilliSatoshi(100000000))), @@ -110,8 +110,7 @@ class ChannelSelectionSpec extends FunSuite { // payment too high, no suitable channel found assert(Relayer.selectPreferredChannel(relayPayload.modify(_.payload.amtToForward).setTo(MilliSatoshi(1000000000)), channelUpdates, node2channels, Seq.empty) === Some(ShortChannelId(12345))) // invalid cltv expiry, no suitable channel, we keep the requested one - assert(Relayer.selectPreferredChannel(relayPayload.modify(_.payload.outgoingCltvValue).setTo(40), channelUpdates, node2channels, Seq.empty) === Some(ShortChannelId(12345))) - + assert(Relayer.selectPreferredChannel(relayPayload.modify(_.payload.outgoingCltvValue).setTo(CltvExpiry(40)), channelUpdates, node2channels, Seq.empty) === Some(ShortChannelId(12345))) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala index 02252bc923..3f5d105386 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala @@ -20,20 +20,19 @@ import java.util.UUID import fr.acinq.bitcoin.DeterministicWallet.ExtendedPrivateKey import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet} -import fr.acinq.eclair.maxOf import fr.acinq.eclair.channel.{Channel, ChannelVersion, Commitments} import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx.{DecryptedPacket, PacketAndSecrets} import fr.acinq.eclair.payment.PaymentLifecycle._ import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire.{ChannelUpdate, OnionCodecs, PerHopPayload} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TestConstants, nodeFee, randomBytes32} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, TestConstants, maxOf, nodeFee, randomBytes32} import org.scalatest.FunSuite import scodec.bits.ByteVector /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class HtlcGenerationSpec extends FunSuite { @@ -161,11 +160,11 @@ object HtlcGenerationSpec { val (priv_a, priv_b, priv_c, priv_d, priv_e) = (TestConstants.Alice.keyManager.nodeKey, TestConstants.Bob.keyManager.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, 1, 0, 0, MilliSatoshi(42000), MilliSatoshi(0), 0, Some(MilliSatoshi(500000000L))) - val channelUpdate_ab = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(1), cltvExpiryDelta = 4, feeBaseMsat = MilliSatoshi(642000), feeProportionalMillionths = 7) - val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(2), cltvExpiryDelta = 5, feeBaseMsat = MilliSatoshi(153000), feeProportionalMillionths = 4) - val channelUpdate_cd = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(3), cltvExpiryDelta = 10, feeBaseMsat = MilliSatoshi(60000), feeProportionalMillionths = 1) - val channelUpdate_de = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(4), cltvExpiryDelta = 7, feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10) + val defaultChannelUpdate = ChannelUpdate(sig, Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0, 1, 0, CltvExpiryDelta(0), MilliSatoshi(42000), MilliSatoshi(0), 0, Some(MilliSatoshi(500000000L))) + val channelUpdate_ab = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(1), cltvExpiryDelta = CltvExpiryDelta(4), feeBaseMsat = MilliSatoshi(642000), feeProportionalMillionths = 7) + val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(2), cltvExpiryDelta = CltvExpiryDelta(5), feeBaseMsat = MilliSatoshi(153000), feeProportionalMillionths = 4) + val channelUpdate_cd = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(3), cltvExpiryDelta = CltvExpiryDelta(10), feeBaseMsat = MilliSatoshi(60000), feeProportionalMillionths = 1) + val channelUpdate_de = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(4), cltvExpiryDelta = CltvExpiryDelta(7), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10) // simple route a -> b -> c -> d -> e @@ -177,11 +176,11 @@ object HtlcGenerationSpec { val finalAmountMsat = MilliSatoshi(42000000L) val currentBlockCount = 420000 - val finalExpiry = currentBlockCount + Channel.MIN_CLTV_EXPIRY + val finalExpiry = CltvExpiry(currentBlockCount) + Channel.MIN_CLTV_EXPIRY_DELTA val paymentPreimage = randomBytes32 val paymentHash = Crypto.sha256(paymentPreimage) - val expiry_de = currentBlockCount + Channel.MIN_CLTV_EXPIRY + val expiry_de = CltvExpiry(currentBlockCount) + Channel.MIN_CLTV_EXPIRY_DELTA val amount_de = finalAmountMsat val fee_d = nodeFee(channelUpdate_de.feeBaseMsat, channelUpdate_de.feeProportionalMillionths, amount_de) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala index 4a1b21f452..ee772d3334 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala @@ -16,8 +16,8 @@ package fr.acinq.eclair.payment -import akka.actor.Status.Failure import akka.actor.ActorSystem +import akka.actor.Status.Failure import akka.testkit.{TestActorRef, TestKit, TestProbe} import fr.acinq.bitcoin.ByteVector32 import fr.acinq.eclair.TestConstants.Alice @@ -25,15 +25,15 @@ import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC} import fr.acinq.eclair.payment.PaymentLifecycle.ReceivePayment import fr.acinq.eclair.payment.PaymentRequest.ExtraHop import fr.acinq.eclair.wire.{FinalExpiryTooSoon, UpdateAddHtlc} -import fr.acinq.eclair.{Globals, MilliSatoshi, ShortChannelId, TestConstants, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, ShortChannelId, TestConstants, randomKey} import org.scalatest.FunSuiteLike import scodec.bits.ByteVector import scala.concurrent.duration._ /** - * Created by PM on 24/03/2017. - */ + * Created by PM on 24/03/2017. + */ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { @@ -45,7 +45,7 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike system.eventStream.subscribe(eventListener.ref, classOf[PaymentReceived]) val amountMsat = MilliSatoshi(42000) - val expiry = Globals.blockCount.get() + 12 + val expiry = CltvExpiryDelta(12).toCltvExpiry { sender.send(handler, ReceivePayment(Some(amountMsat), "1 coffee")) @@ -81,7 +81,7 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike val pr = sender.expectMsgType[PaymentRequest] assert(nodeParams.db.payments.getIncomingPayment(pr.paymentHash).isEmpty) - val add = UpdateAddHtlc(ByteVector32(ByteVector.fill(32)(1)), 0, amountMsat, pr.paymentHash, cltvExpiry = Globals.blockCount.get() + 3, TestConstants.emptyOnionPacket) + val add = UpdateAddHtlc(ByteVector32(ByteVector.fill(32)(1)), 0, amountMsat, pr.paymentHash, cltvExpiry = CltvExpiryDelta(3).toCltvExpiry, TestConstants.emptyOnionPacket) sender.send(handler, add) assert(sender.expectMsgType[CMD_FAIL_HTLC].reason == Right(FinalExpiryTooSoon)) eventListener.expectNoMsg(300 milliseconds) @@ -138,9 +138,9 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike val x = randomKey.publicKey val y = randomKey.publicKey - val extraHop_x_y = ExtraHop(x, ShortChannelId(1), 10, 11, 12) - val extraHop_y_z = ExtraHop(y, ShortChannelId(2), 20, 21, 22) - val extraHop_x_t = ExtraHop(x, ShortChannelId(3), 30, 31, 32) + val extraHop_x_y = ExtraHop(x, ShortChannelId(1), 10, 11, CltvExpiryDelta(12)) + val extraHop_y_z = ExtraHop(y, ShortChannelId(2), 20, 21, CltvExpiryDelta(22)) + val extraHop_x_t = ExtraHop(x, ShortChannelId(3), 30, 31, CltvExpiryDelta(32)) val route_x_z = extraHop_x_y :: extraHop_y_z :: Nil val route_x_t = extraHop_x_t :: Nil @@ -159,7 +159,7 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike system.eventStream.subscribe(eventListener.ref, classOf[PaymentReceived]) val amountMsat = MilliSatoshi(42000) - val expiry = Globals.blockCount.get() + 12 + val expiry = CltvExpiryDelta(12).toCltvExpiry sender.send(handler, ReceivePayment(Some(amountMsat), "some desc", expirySeconds_opt = Some(0))) val pr = sender.expectMsgType[PaymentRequest] 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 9d178c0819..e2c1925073 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 @@ -23,6 +23,7 @@ import akka.actor.Status import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.Script.{pay2wsh, write} import fr.acinq.bitcoin.{Block, ByteVector32, Satoshi, Transaction, TxOut} +import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.{UtxoStatus, ValidateRequest, ValidateResult, WatchSpentBasic} import fr.acinq.eclair.channel.Register.ForwardShortId import fr.acinq.eclair.channel.{AddHtlcFailed, ChannelUnavailable} @@ -34,11 +35,10 @@ import fr.acinq.eclair.router.Announcements.{makeChannelUpdate, makeNodeAnnounce import fr.acinq.eclair.router._ import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire._ -import fr.acinq.eclair._ /** - * Created by PM on 29/08/2016. - */ + * Created by PM on 29/08/2016. + */ class PaymentLifecycleSpec extends BaseRouterSpec { @@ -60,7 +60,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]]) // pre-computed route going from A to D - val request = SendPaymentToRoute(defaultAmountMsat, defaultPaymentHash, Seq(a,b,c,d)) + val request = SendPaymentToRoute(defaultAmountMsat, defaultPaymentHash, Seq(a, b, c, d)) sender.send(paymentFSM, request) val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]]) @@ -109,7 +109,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { paymentFSM ! SubscribeTransitionCallBack(monitor.ref) val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]]) - val request = SendPayment(defaultAmountMsat, randomBytes32, d, routeParams = Some(RouteParams(randomize = false, maxFeeBase = MilliSatoshi(100), maxFeePct = 0.0, routeMaxLength = 20, routeMaxCltv = 2016, ratios = None)), maxAttempts = 5) + val request = SendPayment(defaultAmountMsat, randomBytes32, d, routeParams = Some(RouteParams(randomize = false, maxFeeBase = MilliSatoshi(100), maxFeePct = 0.0, routeMaxLength = 20, routeMaxCltv = CltvExpiryDelta(2016), ratios = None)), maxAttempts = 5) sender.send(paymentFSM, request) val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]]) @@ -183,7 +183,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { routerForwarder.expectMsg(RouteRequest(nodeParams.nodeId, d, defaultAmountMsat, assistedRoutes = Nil, ignoreNodes = Set.empty, ignoreChannels = Set.empty)) routerForwarder.forward(router) awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE) - val WaitingForComplete(_, _, cmd1, Nil, _, _, _, hops) = paymentFSM.stateData + val WaitingForComplete(_, _, cmd1, Nil, _, _, _, _) = paymentFSM.stateData relayer.expectMsg(ForwardShortId(channelId_ab, cmd1)) sender.send(paymentFSM, Status.Failure(AddHtlcFailed(ByteVector32.Zeroes, request.paymentHash, ChannelUnavailable(ByteVector32.Zeroes), Local(id, Some(paymentFSM.underlying.self)), None, None))) @@ -216,7 +216,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { routerForwarder.expectMsg(RouteRequest(nodeParams.nodeId, d, defaultAmountMsat, assistedRoutes = Nil, ignoreNodes = Set.empty, ignoreChannels = Set.empty)) routerForwarder.forward(router) awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE) - val WaitingForComplete(_, _, cmd1, Nil, _, _, _, hops) = paymentFSM.stateData + val WaitingForComplete(_, _, cmd1, Nil, _, _, _, _) = paymentFSM.stateData relayer.expectMsg(ForwardShortId(channelId_ab, cmd1)) sender.send(paymentFSM, UpdateFailMalformedHtlc(ByteVector32.Zeroes, 0, randomBytes32, FailureMessageCodecs.BADONION)) @@ -291,8 +291,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec { relayer.expectMsg(ForwardShortId(channelId_ab, cmd1)) // we change the cltv expiry - val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 42, htlcMinimumMsat = channelUpdate_bc.htlcMinimumMsat, feeBaseMsat = channelUpdate_bc.feeBaseMsat, feeProportionalMillionths = channelUpdate_bc.feeProportionalMillionths, htlcMaximumMsat = channelUpdate_bc.htlcMaximumMsat.get) - val failure = IncorrectCltvExpiry(5, channelUpdate_bc_modified) + val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, CltvExpiryDelta(42), htlcMinimumMsat = channelUpdate_bc.htlcMinimumMsat, feeBaseMsat = channelUpdate_bc.feeBaseMsat, feeProportionalMillionths = channelUpdate_bc.feeProportionalMillionths, htlcMaximumMsat = channelUpdate_bc.htlcMaximumMsat.get) + val failure = IncorrectCltvExpiry(CltvExpiry(5), channelUpdate_bc_modified) // and node replies with a failure containing a new channel update sender.send(paymentFSM, UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets1.head._1, failure))) @@ -308,8 +308,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec { relayer.expectMsg(ForwardShortId(channelId_ab, cmd2)) // we change the cltv expiry one more time - val channelUpdate_bc_modified_2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 43, htlcMinimumMsat = channelUpdate_bc.htlcMinimumMsat, feeBaseMsat = channelUpdate_bc.feeBaseMsat, feeProportionalMillionths = channelUpdate_bc.feeProportionalMillionths, htlcMaximumMsat = channelUpdate_bc.htlcMaximumMsat.get) - val failure2 = IncorrectCltvExpiry(5, channelUpdate_bc_modified_2) + val channelUpdate_bc_modified_2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, CltvExpiryDelta(43), htlcMinimumMsat = channelUpdate_bc.htlcMinimumMsat, feeBaseMsat = channelUpdate_bc.feeBaseMsat, feeProportionalMillionths = channelUpdate_bc.feeProportionalMillionths, htlcMaximumMsat = channelUpdate_bc.htlcMaximumMsat.get) + val failure2 = IncorrectCltvExpiry(CltvExpiry(5), channelUpdate_bc_modified_2) // and node replies with a failure containing a new channel update sender.send(paymentFSM, UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets2.head._1, failure2))) @@ -409,8 +409,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val ann_g = makeNodeAnnouncement(priv_g, "node-G", Color(-30, 10, -50), Nil) val channelId_bg = ShortChannelId(420000, 5, 0) val chan_bg = channelAnnouncement(channelId_bg, priv_b, priv_g, priv_funding_b, priv_funding_g) - val channelUpdate_bg = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, g, channelId_bg, cltvExpiryDelta = 9, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(0), feeProportionalMillionths = 0, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_gb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, b, channelId_bg, cltvExpiryDelta = 9, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 8, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_bg = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, g, channelId_bg, CltvExpiryDelta(9), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(0), feeProportionalMillionths = 0, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_gb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, b, channelId_bg, CltvExpiryDelta(9), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 8, htlcMaximumMsat = MilliSatoshi(500000000L)) assert(Router.getDesc(channelUpdate_bg, chan_bg) === ChannelDesc(chan_bg.shortChannelId, priv_b.publicKey, priv_g.publicKey)) router ! PeerRoutingMessage(null, remoteNodeId, chan_bg) router ! PeerRoutingMessage(null, remoteNodeId, ann_g) 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 19706bcac9..383575d897 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,19 +17,18 @@ package fr.acinq.eclair.payment import java.nio.ByteOrder + import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.{Block, Btc, ByteVector32, Crypto, MilliBtc, Protocol, Satoshi} -import fr.acinq.bitcoin._ -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} +import fr.acinq.bitcoin.{Block, Btc, ByteVector32, Crypto, MilliBtc, Protocol, Satoshi, _} import fr.acinq.eclair.payment.PaymentRequest._ +import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, _} import org.scalatest.FunSuite import scodec.DecodeResult import scodec.bits._ -import fr.acinq.eclair._ /** - * Created by fabrice on 15/05/17. - */ + * Created by fabrice on 15/05/17. + */ class PaymentRequestSpec extends FunSuite { @@ -151,8 +150,8 @@ class PaymentRequestSpec extends FunSuite { 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")) assert(pr.routingInfo === List(List( - ExtraHop(PublicKey(hex"029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"), ShortChannelId(72623859790382856L), 1, 20, 3), - ExtraHop(PublicKey(hex"039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"), ShortChannelId(217304205466536202L), 2, 30, 4) + ExtraHop(PublicKey(hex"029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"), ShortChannelId(72623859790382856L), 1, 20, CltvExpiryDelta(3)), + ExtraHop(PublicKey(hex"039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"), ShortChannelId(217304205466536202L), 2, 30, CltvExpiryDelta(4)) ))) assert(Protocol.writeUInt64(0x0102030405060708L, ByteOrder.BIG_ENDIAN) == hex"0102030405060708") assert(Protocol.writeUInt64(0x030405060708090aL, ByteOrder.BIG_ENDIAN) == hex"030405060708090a") @@ -160,7 +159,6 @@ class PaymentRequestSpec extends FunSuite { assert(PaymentRequest.write(pr.sign(priv)) == ref) } - test("On mainnet, with fallback (p2sh) address 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX") { val ref = "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppj3a24vwu6r8ejrss3axul8rxldph2q7z9kk822r8plup77n9yq5ep2dfpcydrjwzxs0la84v3tfw43t3vqhek7f05m6uf8lmfkjn7zv7enn76sq65d8u9lxav2pl6x3xnc2ww3lqpagnh0u" val pr = PaymentRequest.read(ref) @@ -214,7 +212,7 @@ class PaymentRequestSpec extends FunSuite { 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")) - assert(pr.minFinalCltvExpiry === Some(12)) + assert(pr.minFinalCltvExpiryDelta === Some(CltvExpiryDelta(12))) assert(pr.tags.size == 4) assert(PaymentRequest.write(pr.sign(priv)) == ref) } @@ -330,6 +328,8 @@ class PaymentRequestSpec extends FunSuite { "lnbc100n1pw9qjdgpp5lmycszp7pzce0rl29s40fhkg02v7vgrxaznr6ys5cawg437h80nsdpstfshq5n9v9jzucm0d5s8vmm5v5s8qmmnwssyj3p6yqenwdejcqzysxqrrss47kl34flydtmu2wnszuddrd0nwa6rnu4d339jfzje6hzk6an0uax3kteee2lgx5r0629wehjeseksz0uuakzwy47lmvy2g7hja7mnpsqjmdct9" ) - for (req <- requests) { assert(PaymentRequest.write(PaymentRequest.read(req)) == req) } + for (req <- requests) { + assert(PaymentRequest.write(PaymentRequest.read(req)) == req) + } } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala index 97db737ee4..e81aa59b2d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala @@ -26,15 +26,15 @@ import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.payment.PaymentLifecycle.buildCommand import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TestConstants, TestkitBaseClass, UInt64, randomBytes32} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, TestConstants, TestkitBaseClass, UInt64, randomBytes32} import org.scalatest.Outcome import scodec.bits.ByteVector import scala.concurrent.duration._ /** - * Created by PM on 29/08/2016. - */ + * Created by PM on 29/08/2016. + */ class RelayerSpec extends TestkitBaseClass { @@ -267,7 +267,7 @@ class RelayerSpec extends TestkitBaseClass { import f._ val sender = TestProbe() - val hops1 = hops.updated(1, hops(1).copy(lastUpdate = hops(1).lastUpdate.copy(cltvExpiryDelta = 0))) + val hops1 = hops.updated(1, hops(1).copy(lastUpdate = hops(1).lastUpdate.copy(cltvExpiryDelta = CltvExpiryDelta(0)))) val (cmd, _) = buildCommand(UUID.randomUUID(), finalAmountMsat, finalExpiry, paymentHash, hops1) // and then manually build an htlc val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion) @@ -332,7 +332,7 @@ class RelayerSpec extends TestkitBaseClass { val hops1 = hops.head :: Nil val (cmd, _) = buildCommand(UUID.randomUUID(), finalAmountMsat, finalExpiry, paymentHash, hops1) // and then manually build an htlc with a wrong expiry - val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amount, cmd.paymentHash, cmd.cltvExpiry - 1, cmd.onion) + val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amount, cmd.paymentHash, cmd.cltvExpiry - CltvExpiryDelta(1), cmd.onion) relayer ! LocalChannelUpdate(null, channelId_bc, channelUpdate_bc.shortChannelId, c, None, channelUpdate_bc, makeCommitments(channelId_bc)) sender.send(relayer, ForwardAdd(add_ab)) @@ -352,10 +352,10 @@ class RelayerSpec extends TestkitBaseClass { val paymentHash = randomBytes32 val origin = Relayed(channelId_ab, originHtlcId = 42, amountIn = MilliSatoshi(1100000), amountOut = MilliSatoshi(1000000)) - sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, ExpiryTooSmall(channelId_bc, 100, 0, 0), origin, Some(channelUpdate_bc), None))) + sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, ExpiryTooSmall(channelId_bc, CltvExpiry(100), CltvExpiry(0), 0), origin, Some(channelUpdate_bc), None))) assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(ExpiryTooSoon(channelUpdate_bc))) - sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, ExpiryTooBig(channelId_bc, 100, 200, 0), origin, Some(channelUpdate_bc), None))) + sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, ExpiryTooBig(channelId_bc, CltvExpiry(100), CltvExpiry(200), 0), origin, Some(channelUpdate_bc), None))) assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(ExpiryTooFar)) sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, InsufficientFunds(channelId_bc, origin.amountOut, Satoshi(100), Satoshi(0), Satoshi(0)), origin, Some(channelUpdate_bc), None))) @@ -383,7 +383,7 @@ class RelayerSpec extends TestkitBaseClass { system.eventStream.subscribe(eventListener.ref, classOf[PaymentEvent]) // we build a fake htlc for the downstream channel - val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 72, amountMsat = MilliSatoshi(10000000L), paymentHash = ByteVector32.Zeroes, cltvExpiry = 4200, onionRoutingPacket = TestConstants.emptyOnionPacket) + val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 72, amountMsat = MilliSatoshi(10000000L), paymentHash = ByteVector32.Zeroes, CltvExpiry(4200), onionRoutingPacket = TestConstants.emptyOnionPacket) val fulfill_ba = UpdateFulfillHtlc(channelId = channelId_bc, id = 42, paymentPreimage = ByteVector32.Zeroes) val origin = Relayed(channelId_ab, 150, MilliSatoshi(11000000L), MilliSatoshi(10000000L)) sender.send(relayer, ForwardFulfill(fulfill_ba, origin, add_bc)) @@ -401,7 +401,7 @@ class RelayerSpec extends TestkitBaseClass { val sender = TestProbe() // we build a fake htlc for the downstream channel - val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 72, amountMsat = MilliSatoshi(10000000L), paymentHash = ByteVector32.Zeroes, cltvExpiry = 4200, onionRoutingPacket = TestConstants.emptyOnionPacket) + val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 72, amountMsat = MilliSatoshi(10000000L), paymentHash = ByteVector32.Zeroes, CltvExpiry(4200), onionRoutingPacket = TestConstants.emptyOnionPacket) val fail_ba = UpdateFailHtlc(channelId = channelId_bc, id = 42, reason = Sphinx.FailurePacket.create(ByteVector32(ByteVector.fill(32)(1)), TemporaryChannelFailure(channelUpdate_cd))) val origin = Relayed(channelId_ab, 150, MilliSatoshi(11000000L), MilliSatoshi(10000000L)) sender.send(relayer, ForwardFail(fail_ba, origin, add_bc)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala index a3c29fda8e..72da233fca 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala @@ -27,15 +27,16 @@ import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, ExtendedBitcoinClient} import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate} -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshi, ShortChannelId, randomKey} import org.scalatest.FunSuite import scodec.bits.ByteVector + import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext} /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class AnnouncementsBatchValidationSpec extends FunSuite { @@ -45,7 +46,7 @@ class AnnouncementsBatchValidationSpec extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global implicit val system = ActorSystem() - implicit val sttpBackend = OkHttpFutureBackend() + implicit val sttpBackend = OkHttpFutureBackend() implicit val extendedBitcoinClient = new ExtendedBitcoinClient(new BasicBitcoinJsonRPCClient(user = "foo", password = "bar", host = "localhost", port = 18332)) val channels = for (i <- 0 until 50) yield { @@ -103,6 +104,6 @@ object AnnouncementsBatchValidationSpec { } def makeChannelUpdate(c: SimulatedChannel, shortChannelId: ShortChannelId): ChannelUpdate = - Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, c.node1Key, c.node2Key.publicKey, shortChannelId, 10, MilliSatoshi(1000), MilliSatoshi(10), 100, MilliSatoshi(500000000L)) + Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, c.node1Key, c.node2Key.publicKey, shortChannelId, CltvExpiryDelta(10), MilliSatoshi(1000), MilliSatoshi(10), 100, MilliSatoshi(500000000L)) } \ No newline at end of file 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 41b5c722f1..5fd7b5e8f0 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 @@ -16,7 +16,7 @@ package fr.acinq.eclair.router -import fr.acinq.bitcoin.{Block} +import fr.acinq.bitcoin.Block import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.eclair.TestConstants.Alice import fr.acinq.eclair._ @@ -25,8 +25,8 @@ import org.scalatest.FunSuite import scodec.bits._ /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class AnnouncementsSpec extends FunSuite { @@ -66,10 +66,10 @@ class AnnouncementsSpec extends FunSuite { // NB: node1 < node2 (public keys) assert(isNode1(node1_priv.publicKey, node2_priv.publicKey)) assert(!isNode1(node2_priv.publicKey, node1_priv.publicKey)) - val channelUpdate1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), 0, MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = true) - val channelUpdate1_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), 0, MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = false) - val channelUpdate2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = true) - val channelUpdate2_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = false) + val channelUpdate1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), CltvExpiryDelta(0), MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = true) + val channelUpdate1_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), CltvExpiryDelta(0), MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = false) + val channelUpdate2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), CltvExpiryDelta(0), MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = true) + val channelUpdate2_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), CltvExpiryDelta(0), MilliSatoshi(0), MilliSatoshi(0), 0, MilliSatoshi(500000000L), enable = false) assert(channelUpdate1.channelFlags == 0) // ....00 assert(channelUpdate1_disabled.channelFlags == 2) // ....10 assert(channelUpdate2.channelFlags == 1) // ....01 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index 4acdc1db69..46e7dffe93 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -35,10 +35,10 @@ import scodec.bits.ByteVector import scala.concurrent.duration._ /** - * Base class for router testing. - * It is re-used in payment FSM tests - * Created by PM on 29/08/2016. - */ + * Base class for router testing. + * It is re-used in payment FSM tests + * Created by PM on 29/08/2016. + */ abstract class BaseRouterSpec extends TestkitBaseClass { @@ -78,14 +78,14 @@ abstract class BaseRouterSpec extends TestkitBaseClass { val chan_cd = channelAnnouncement(channelId_cd, priv_c, priv_d, priv_funding_c, priv_funding_d) val chan_ef = channelAnnouncement(channelId_ef, priv_e, priv_f, priv_funding_e, priv_funding_f) - val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, b, channelId_ab, cltvExpiryDelta = 7, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, a, channelId_ab, cltvExpiryDelta = 7, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_bc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 5, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 1, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_cb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, b, channelId_bc, cltvExpiryDelta = 5, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 1, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_cd = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, cltvExpiryDelta = 3, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 4, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_dc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_d, c, channelId_cd, cltvExpiryDelta = 3, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 4, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_ef = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_e, f, channelId_ef, cltvExpiryDelta = 9, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 8, htlcMaximumMsat = MilliSatoshi(500000000L)) - val channelUpdate_fe = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_f, e, channelId_ef, cltvExpiryDelta = 9, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 8, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, b, channelId_ab, CltvExpiryDelta(7), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, a, channelId_ab, CltvExpiryDelta(7), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_bc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, CltvExpiryDelta(5), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 1, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_cb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, b, channelId_bc, CltvExpiryDelta(5), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 1, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_cd = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, CltvExpiryDelta(3), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 4, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_dc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_d, c, channelId_cd, CltvExpiryDelta(3), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 4, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_ef = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_e, f, channelId_ef, CltvExpiryDelta(9), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 8, htlcMaximumMsat = MilliSatoshi(500000000L)) + val channelUpdate_fe = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_f, e, channelId_ef, CltvExpiryDelta(9), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(10), feeProportionalMillionths = 8, htlcMaximumMsat = MilliSatoshi(500000000L)) override def withFixture(test: OneArgTest): Outcome = { // the network will be a --(1)--> b ---(2)--> c --(3)--> d and e --(4)--> f (we are a) 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 6eaddc5277..958d0e4113 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 @@ -24,15 +24,15 @@ import fr.acinq.eclair.router.Graph.GraphStructure.{DirectedGraph, GraphEdge} import fr.acinq.eclair.router.Graph.{RichWeight, WeightRatios} import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire._ -import fr.acinq.eclair.{Globals, MilliSatoshi, ShortChannelId, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, Globals, MilliSatoshi, ShortChannelId, randomKey} import org.scalatest.FunSuite import scodec.bits._ import scala.util.{Failure, Success} /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class RouteCalculationSpec extends FunSuite { @@ -43,10 +43,10 @@ class RouteCalculationSpec extends FunSuite { test("calculate simple route") { val updates = List( - makeUpdate(1L, a, b, MilliSatoshi(1), 10, cltvDelta = 1), - makeUpdate(2L, b, c, MilliSatoshi(1), 10, cltvDelta = 1), - makeUpdate(3L, c, d, MilliSatoshi(1), 10, cltvDelta = 1), - makeUpdate(4L, d, e, MilliSatoshi(1), 10, cltvDelta = 1) + makeUpdate(1L, a, b, MilliSatoshi(1), 10, cltvDelta = CltvExpiryDelta(1)), + makeUpdate(2L, b, c, MilliSatoshi(1), 10, cltvDelta = CltvExpiryDelta(1)), + makeUpdate(3L, c, d, MilliSatoshi(1), 10, cltvDelta = CltvExpiryDelta(1)), + makeUpdate(4L, d, e, MilliSatoshi(1), 10, cltvDelta = CltvExpiryDelta(1)) ).toMap val g = makeGraph(updates) @@ -67,10 +67,10 @@ class RouteCalculationSpec extends FunSuite { // of the amount being paid val updates = List( - makeUpdate(1L, a, b, MilliSatoshi(10), 10, cltvDelta = 1), - makeUpdate(2L, b, c, MilliSatoshi(10), 10, cltvDelta = 1), - makeUpdate(3L, c, d, MilliSatoshi(10), 10, cltvDelta = 1), - makeUpdate(4L, d, e, MilliSatoshi(10), 10, cltvDelta = 1) + makeUpdate(1L, a, b, MilliSatoshi(10), 10, cltvDelta = CltvExpiryDelta(1)), + makeUpdate(2L, b, c, MilliSatoshi(10), 10, cltvDelta = CltvExpiryDelta(1)), + makeUpdate(3L, c, d, MilliSatoshi(10), 10, cltvDelta = CltvExpiryDelta(1)), + makeUpdate(4L, d, e, MilliSatoshi(10), 10, cltvDelta = CltvExpiryDelta(1)) ).toMap val g = makeGraph(updates) @@ -213,7 +213,7 @@ class RouteCalculationSpec extends FunSuite { PublicKey(hex"02999fa724ec3c244e4da52b4a91ad421dc96c9a810587849cd4b2469313519c73"), // F source PublicKey(hex"03f1cb1af20fe9ccda3ea128e27d7c39ee27375c8480f11a87c17197e97541ca6a"), // G PublicKey(hex"0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484"), // H - PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target + PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target ) val updates = List( @@ -234,7 +234,7 @@ class RouteCalculationSpec extends FunSuite { PublicKey(hex"02999fa724ec3c244e4da52b4a91ad421dc96c9a810587849cd4b2469313519c73"), // F source PublicKey(hex"03f1cb1af20fe9ccda3ea128e27d7c39ee27375c8480f11a87c17197e97541ca6a"), // G PublicKey(hex"0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484"), // H - PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target + PublicKey(hex"029e059b6780f155f38e83601969919aae631ddf6faed58fe860c72225eb327d7c") // I target ) val updates = List( @@ -404,14 +404,14 @@ class RouteCalculationSpec extends FunSuite { val DUMMY_SIG = Transactions.PlaceHolderSig - val uab = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 0L, 0, 0, 1, MilliSatoshi(42), MilliSatoshi(2500), 140, None) - val uba = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 1L, 0, 1, 1, MilliSatoshi(43), MilliSatoshi(2501), 141, None) - val ubc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, 0, 0, 1, MilliSatoshi(44), MilliSatoshi(2502), 142, None) - val ucb = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, 0, 1, 1, MilliSatoshi(45), MilliSatoshi(2503), 143, None) - val ucd = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, 1, 0, 1, MilliSatoshi(46), MilliSatoshi(2504), 144, Some(MilliSatoshi(500000000L))) - val udc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, 0, 1, 1, MilliSatoshi(47), MilliSatoshi(2505), 145, None) - val ude = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, 0, 0, 1, MilliSatoshi(48), MilliSatoshi(2506), 146, None) - val ued = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, 0, 1, 1, MilliSatoshi(49), MilliSatoshi(2507), 147, None) + val uab = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 0L, 0, 0, CltvExpiryDelta(1), MilliSatoshi(42), MilliSatoshi(2500), 140, None) + val uba = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 1L, 0, 1, CltvExpiryDelta(1), MilliSatoshi(43), MilliSatoshi(2501), 141, None) + val ubc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, 0, 0, CltvExpiryDelta(1), MilliSatoshi(44), MilliSatoshi(2502), 142, None) + val ucb = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, 0, 1, CltvExpiryDelta(1), MilliSatoshi(45), MilliSatoshi(2503), 143, None) + val ucd = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, 1, 0, CltvExpiryDelta(1), MilliSatoshi(46), MilliSatoshi(2504), 144, Some(MilliSatoshi(500000000L))) + val udc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, 0, 1, CltvExpiryDelta(1), MilliSatoshi(47), MilliSatoshi(2505), 145, None) + val ude = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, 0, 0, CltvExpiryDelta(1), MilliSatoshi(48), MilliSatoshi(2506), 146, None) + val ued = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, 0, 1, CltvExpiryDelta(1), MilliSatoshi(49), MilliSatoshi(2507), 147, None) val updates = Map( ChannelDesc(ShortChannelId(1L), a, b) -> uab, @@ -438,10 +438,10 @@ class RouteCalculationSpec extends FunSuite { val d = randomKey.publicKey val e = randomKey.publicKey - val extraHop1 = ExtraHop(a, ShortChannelId(1), 10, 11, 12) - val extraHop2 = ExtraHop(b, ShortChannelId(2), 20, 21, 22) - val extraHop3 = ExtraHop(c, ShortChannelId(3), 30, 31, 32) - val extraHop4 = ExtraHop(d, ShortChannelId(4), 40, 41, 42) + val extraHop1 = ExtraHop(a, ShortChannelId(1), 10, 11, CltvExpiryDelta(12)) + val extraHop2 = ExtraHop(b, ShortChannelId(2), 20, 21, CltvExpiryDelta(22)) + val extraHop3 = ExtraHop(c, ShortChannelId(3), 30, 31, CltvExpiryDelta(32)) + val extraHop4 = ExtraHop(d, ShortChannelId(4), 40, 41, CltvExpiryDelta(42)) val extraHops = extraHop1 :: extraHop2 :: extraHop3 :: extraHop4 :: Nil @@ -605,15 +605,15 @@ class RouteCalculationSpec extends FunSuite { val f = randomKey.publicKey val g = makeGraph(List( - makeUpdate(1, a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 50), - makeUpdate(2, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 50), - makeUpdate(3, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 50), - makeUpdate(4, a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(5, e, f, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(6, f, d, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9) + makeUpdate(1, a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(50)), + makeUpdate(2, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(50)), + makeUpdate(3, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(50)), + makeUpdate(4, a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(5, e, f, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(6, f, d, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)) ).toMap) - val route = Router.findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(routeMaxCltv = 28)) + val route = Router.findRoute(g, a, d, DEFAULT_AMOUNT_MSAT, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(routeMaxCltv = CltvExpiryDelta(28))) assert(route.map(hops2Ids) === Success(4 :: 5 :: 6 :: Nil)) } @@ -622,12 +622,12 @@ class RouteCalculationSpec extends FunSuite { val f = randomKey.publicKey val g = makeGraph(List( - makeUpdate(1, a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(2, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(3, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(4, d, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(5, e, f, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(6, b, f, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9) + makeUpdate(1, a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(2, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(3, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(4, d, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(5, e, f, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(6, b, f, feeBase = MilliSatoshi(5), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)) ).toMap) val route = Router.findRoute(g, a, f, DEFAULT_AMOUNT_MSAT, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(routeMaxLength = 3)) @@ -657,10 +657,10 @@ class RouteCalculationSpec extends FunSuite { makeUpdate(1L, a, b, MilliSatoshi(10), 10), // a -> b makeUpdate(2L, b, c, MilliSatoshi(10), 10), makeUpdate(4L, c, d, MilliSatoshi(10), 10), - makeUpdate(3L, b, e, MilliSatoshi(0), 0), // b -> e - makeUpdate(6L, e, f, MilliSatoshi(0), 0), // e -> f - makeUpdate(6L, f, e, MilliSatoshi(0), 0), // e <- f - makeUpdate(5L, e, d, MilliSatoshi(0), 0) // e -> d + makeUpdate(3L, b, e, MilliSatoshi(0), 0), // b -> e + makeUpdate(6L, e, f, MilliSatoshi(0), 0), // e -> f + makeUpdate(6L, f, e, MilliSatoshi(0), 0), // e <- f + makeUpdate(5L, e, d, MilliSatoshi(0), 0) // e -> d ).toMap val g = makeGraph(updates) @@ -670,18 +670,18 @@ class RouteCalculationSpec extends FunSuite { } /** - * - * +---+ +---+ +---+ - * | A +-----+ | B +----------> | C | - * +-+-+ | +-+-+ +-+-+ - * ^ | ^ | - * | | | | - * | v----> + | | - * +-+-+ <-+-+ +-+-+ - * | D +----------> | E +----------> | F | - * +---+ +---+ +---+ - * - */ + * + * +---+ +---+ +---+ + * | A +-----+ | B +----------> | C | + * +-+-+ | +-+-+ +-+-+ + * ^ | ^ | + * | | | | + * | v----> + | | + * +-+-+ <-+-+ +-+-+ + * | D +----------> | E +----------> | F | + * +---+ +---+ +---+ + * + */ test("find the k-shortest paths in a graph, k=4") { val (a, b, c, d, e, f) = ( @@ -817,13 +817,13 @@ class RouteCalculationSpec extends FunSuite { // A -> E -> F -> D is 'timeout optimized', lower CLTV route (totFees = 3, totCltv = 18) // A -> E -> C -> D is 'capacity optimized', more recent channel/larger capacity route val updates = List( - makeUpdate(1L, a, b, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 13), - makeUpdate(4L, a, e, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12), - makeUpdate(2L, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 500), - makeUpdate(3L, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 500), - makeUpdate(5L, e, f, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(6L, f, d, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 9), - makeUpdate(7L, e, c, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = Some(largeCapacity), cltvDelta = 12) + makeUpdate(1L, a, b, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(13)), + makeUpdate(4L, a, e, feeBase = MilliSatoshi(0), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(12)), + makeUpdate(2L, b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(500)), + makeUpdate(3L, c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(500)), + makeUpdate(5L, e, f, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(6L, f, d, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, CltvExpiryDelta(9)), + makeUpdate(7L, e, c, feeBase = MilliSatoshi(2), 0, minHtlc = MilliSatoshi(0), maxHtlc = Some(largeCapacity), CltvExpiryDelta(12)) ).toMap val g = makeGraph(updates) @@ -853,12 +853,12 @@ class RouteCalculationSpec extends FunSuite { val currentBlockHeight = 554000 val g = makeGraph(List( - makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x1"), a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), - makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x4"), a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), - makeUpdateShort(ShortChannelId(s"${currentBlockHeight - 3000}x0x2"), b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), // younger channel - makeUpdateShort(ShortChannelId(s"${currentBlockHeight - 3000}x0x3"), c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), - makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x5"), e, f, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), - makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x6"), f, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144) + makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x1"), a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), + makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x4"), a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), + makeUpdateShort(ShortChannelId(s"${currentBlockHeight - 3000}x0x2"), b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), // younger channel + makeUpdateShort(ShortChannelId(s"${currentBlockHeight - 3000}x0x3"), c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), + makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x5"), e, f, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), + makeUpdateShort(ShortChannelId(s"${currentBlockHeight}x0x6"), f, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)) ).toMap) Globals.blockCount.set(currentBlockHeight) @@ -875,12 +875,12 @@ class RouteCalculationSpec extends FunSuite { test("prefer a route with a smaller total CLTV if fees and score are the same") { val g = makeGraph(List( - makeUpdateShort(ShortChannelId(s"0x0x1"), a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12), - makeUpdateShort(ShortChannelId(s"0x0x4"), a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12), - makeUpdateShort(ShortChannelId(s"0x0x2"), b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 10), // smaller CLTV - makeUpdateShort(ShortChannelId(s"0x0x3"), c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12), - makeUpdateShort(ShortChannelId(s"0x0x5"), e, f, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12), - makeUpdateShort(ShortChannelId(s"0x0x6"), f, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 12) + makeUpdateShort(ShortChannelId(s"0x0x1"), a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), + makeUpdateShort(ShortChannelId(s"0x0x4"), a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), + makeUpdateShort(ShortChannelId(s"0x0x2"), b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(10)), // smaller CLTV + makeUpdateShort(ShortChannelId(s"0x0x3"), c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), + makeUpdateShort(ShortChannelId(s"0x0x5"), e, f, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(12)), + makeUpdateShort(ShortChannelId(s"0x0x6"), f, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(12)) ).toMap) @@ -899,12 +899,12 @@ class RouteCalculationSpec extends FunSuite { // A -> B -> C -> D is cheaper but has a total CLTV > 2016! // A -> E -> F -> D is more expensive but has a total CLTV < 2016 val g = makeGraph(List( - makeUpdateShort(ShortChannelId(s"0x0x1"), a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), - makeUpdateShort(ShortChannelId(s"0x0x4"), a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), - makeUpdateShort(ShortChannelId(s"0x0x2"), b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 1000), - makeUpdateShort(ShortChannelId(s"0x0x3"), c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 900), - makeUpdateShort(ShortChannelId(s"0x0x5"), e, f, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144), - makeUpdateShort(ShortChannelId(s"0x0x6"), f, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = 144) + makeUpdateShort(ShortChannelId(s"0x0x1"), a, b, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), + makeUpdateShort(ShortChannelId(s"0x0x4"), a, e, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), + makeUpdateShort(ShortChannelId(s"0x0x2"), b, c, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(1000)), + makeUpdateShort(ShortChannelId(s"0x0x3"), c, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(900)), + makeUpdateShort(ShortChannelId(s"0x0x5"), e, f, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)), + makeUpdateShort(ShortChannelId(s"0x0x6"), f, d, feeBase = MilliSatoshi(1), 0, minHtlc = MilliSatoshi(0), maxHtlc = None, cltvDelta = CltvExpiryDelta(144)) ).toMap) val Success(routeScoreOptimized) = Router.findRoute(g, a, d, DEFAULT_AMOUNT_MSAT / 2, numRoutes = 1, routeParams = DEFAULT_ROUTE_PARAMS.copy(ratios = Some(WeightRatios( @@ -921,17 +921,17 @@ class RouteCalculationSpec extends FunSuite { // This test have a channel (542280x2156x0) that according to heuristics is very convenient but actually useless to reach the target, // then if the cost function is not monotonic the path-finding breaks because the result path contains a loop. val updates = List( - ChannelDesc(ShortChannelId("565643x1216x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, 1.toByte, 1.toByte, 144, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(1000), 100, Some(MilliSatoshi(15000000000L))), - ChannelDesc(ShortChannelId("565643x1216x0"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, 1.toByte, 0.toByte, 14, htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(1000), 10, Some(MilliSatoshi(4294967295L))), - ChannelDesc(ShortChannelId("542280x2156x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, 1.toByte, 1.toByte, 144, htlcMinimumMsat = MilliSatoshi(1000), feeBaseMsat = MilliSatoshi(1000), 100, Some(MilliSatoshi(16777000000L))), - ChannelDesc(ShortChannelId("542280x2156x0"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, 1.toByte, 0.toByte, 144, htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(667), 1, Some(MilliSatoshi(16777000000L))), - ChannelDesc(ShortChannelId("565779x2711x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, 1.toByte, 3.toByte, 144, htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(1000), 100, Some(MilliSatoshi(230000000L))), - ChannelDesc(ShortChannelId("565779x2711x0"), PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, 1.toByte, 0.toByte, 144, htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(1000), 100, Some(MilliSatoshi(230000000L))) + ChannelDesc(ShortChannelId("565643x1216x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, 1.toByte, 1.toByte, CltvExpiryDelta(144), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(1000), 100, Some(MilliSatoshi(15000000000L))), + ChannelDesc(ShortChannelId("565643x1216x0"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0, 1.toByte, 0.toByte, CltvExpiryDelta(14), htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(1000), 10, Some(MilliSatoshi(4294967295L))), + ChannelDesc(ShortChannelId("542280x2156x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, 1.toByte, 1.toByte, CltvExpiryDelta(144), htlcMinimumMsat = MilliSatoshi(1000), feeBaseMsat = MilliSatoshi(1000), 100, Some(MilliSatoshi(16777000000L))), + ChannelDesc(ShortChannelId("542280x2156x0"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0, 1.toByte, 0.toByte, CltvExpiryDelta(144), htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(667), 1, Some(MilliSatoshi(16777000000L))), + ChannelDesc(ShortChannelId("565779x2711x0"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, 1.toByte, 3.toByte, CltvExpiryDelta(144), htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(1000), 100, Some(MilliSatoshi(230000000L))), + ChannelDesc(ShortChannelId("565779x2711x0"), PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")) -> ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565779x2711x0"), 0, 1.toByte, 0.toByte, CltvExpiryDelta(144), htlcMinimumMsat = MilliSatoshi(1), MilliSatoshi(1000), 100, Some(MilliSatoshi(230000000L))) ).toMap val g = DirectedGraph.makeGraph(updates) - val params = RouteParams(randomize = false, maxFeeBase = MilliSatoshi(21000), maxFeePct = 0.03, routeMaxCltv = 1008, routeMaxLength = 6, ratios = Some( + val params = RouteParams(randomize = false, maxFeeBase = MilliSatoshi(21000), maxFeePct = 0.03, routeMaxCltv = CltvExpiryDelta(1008), routeMaxLength = 6, ratios = Some( WeightRatios(cltvDeltaFactor = 0.15, ageFactor = 0.35, capacityFactor = 0.5) )) val thisNode = PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96") @@ -952,7 +952,7 @@ object RouteCalculationSpec { val DEFAULT_AMOUNT_MSAT = MilliSatoshi(10000000) - val DEFAULT_ROUTE_PARAMS = RouteParams(randomize = false, maxFeeBase = MilliSatoshi(21000), maxFeePct = 0.03, routeMaxCltv = 2016, routeMaxLength = 6, ratios = None) + val DEFAULT_ROUTE_PARAMS = RouteParams(randomize = false, maxFeeBase = MilliSatoshi(21000), maxFeePct = 0.03, routeMaxCltv = CltvExpiryDelta(2016), routeMaxLength = 6, ratios = None) val DUMMY_SIG = Transactions.PlaceHolderSig @@ -961,11 +961,11 @@ object RouteCalculationSpec { ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, ByteVector.empty, Block.RegtestGenesisBlock.hash, ShortChannelId(shortChannelId), nodeId1, nodeId2, randomKey.publicKey, randomKey.publicKey) } - def makeUpdate(shortChannelId: Long, nodeId1: PublicKey, nodeId2: PublicKey, feeBase: MilliSatoshi, feeProportionalMillionth: Int, minHtlc: MilliSatoshi = DEFAULT_AMOUNT_MSAT, maxHtlc: Option[MilliSatoshi] = None, cltvDelta: Int = 0): (ChannelDesc, ChannelUpdate) = { + def makeUpdate(shortChannelId: Long, nodeId1: PublicKey, nodeId2: PublicKey, feeBase: MilliSatoshi, feeProportionalMillionth: Int, minHtlc: MilliSatoshi = DEFAULT_AMOUNT_MSAT, maxHtlc: Option[MilliSatoshi] = None, cltvDelta: CltvExpiryDelta = CltvExpiryDelta(0)): (ChannelDesc, ChannelUpdate) = { makeUpdateShort(ShortChannelId(shortChannelId), nodeId1, nodeId2, feeBase, feeProportionalMillionth, minHtlc, maxHtlc, cltvDelta) } - def makeUpdateShort(shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, feeBase: MilliSatoshi, feeProportionalMillionth: Int, minHtlc: MilliSatoshi = DEFAULT_AMOUNT_MSAT, maxHtlc: Option[MilliSatoshi] = None, cltvDelta: Int = 0): (ChannelDesc, 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)): (ChannelDesc, ChannelUpdate) = ChannelDesc(shortChannelId, nodeId1, nodeId2) -> 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 c5bc27ba6b..f5bddc0f68 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 @@ -30,7 +30,7 @@ import fr.acinq.eclair.router.Announcements.makeChannelUpdate import fr.acinq.eclair.router.RouteCalculationSpec.DEFAULT_AMOUNT_MSAT import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.QueryShortChannelIds -import fr.acinq.eclair.{Globals, MilliSatoshi, ShortChannelId, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, Globals, MilliSatoshi, ShortChannelId, randomKey} import scodec.bits._ import scala.collection.SortedSet @@ -38,8 +38,8 @@ import scala.compat.Platform import scala.concurrent.duration._ /** - * Created by PM on 29/08/2016. - */ + * Created by PM on 29/08/2016. + */ class RouterSpec extends BaseRouterSpec { @@ -52,21 +52,21 @@ class RouterSpec extends BaseRouterSpec { val channelId_ac = ShortChannelId(420000, 5, 0) val chan_ac = channelAnnouncement(channelId_ac, priv_a, priv_c, priv_funding_a, priv_funding_c) - val update_ac = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId_ac, cltvExpiryDelta = 7, MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) + val update_ac = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId_ac, CltvExpiryDelta(7), MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) // a-x will not be found val priv_x = randomKey val chan_ax = channelAnnouncement(ShortChannelId(42001), priv_a, priv_x, priv_funding_a, randomKey) - val update_ax = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_x.publicKey, chan_ax.shortChannelId, cltvExpiryDelta = 7, MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) + val update_ax = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_x.publicKey, chan_ax.shortChannelId, CltvExpiryDelta(7), MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) // a-y will have an invalid script val priv_y = randomKey val priv_funding_y = randomKey val chan_ay = channelAnnouncement(ShortChannelId(42002), priv_a, priv_y, priv_funding_a, priv_funding_y) - val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, chan_ay.shortChannelId, cltvExpiryDelta = 7, MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) + val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, chan_ay.shortChannelId, CltvExpiryDelta(7), MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) // a-z will be spent val priv_z = randomKey val priv_funding_z = randomKey val chan_az = channelAnnouncement(ShortChannelId(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) - val update_az = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_z.publicKey, chan_az.shortChannelId, cltvExpiryDelta = 7, MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) + val update_az = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_z.publicKey, chan_az.shortChannelId, CltvExpiryDelta(7), MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L)) router ! PeerRoutingMessage(null, remoteNodeId, chan_ac) router ! PeerRoutingMessage(null, remoteNodeId, chan_ax) @@ -184,9 +184,9 @@ class RouterSpec extends BaseRouterSpec { val x = PublicKey(hex"02999fa724ec3c244e4da52b4a91ad421dc96c9a810587849cd4b2469313519c73") val y = PublicKey(hex"03f1cb1af20fe9ccda3ea128e27d7c39ee27375c8480f11a87c17197e97541ca6a") val z = PublicKey(hex"0358e32d245ff5f5a3eb14c78c6f69c67cea7846bdf9aeeb7199e8f6fbb0306484") - val extraHop_cx = ExtraHop(c, ShortChannelId(1), 10, 11, 12) - val extraHop_xy = ExtraHop(x, ShortChannelId(2), 10, 11, 12) - val extraHop_yz = ExtraHop(y, ShortChannelId(3), 20, 21, 22) + val extraHop_cx = ExtraHop(c, ShortChannelId(1), 10, 11, CltvExpiryDelta(12)) + val extraHop_xy = ExtraHop(x, ShortChannelId(2), 10, 11, CltvExpiryDelta(12)) + val extraHop_yz = ExtraHop(y, ShortChannelId(3), 20, 21, CltvExpiryDelta(22)) sender.send(router, RouteRequest(a, z, DEFAULT_AMOUNT_MSAT, assistedRoutes = Seq(extraHop_cx :: extraHop_xy :: extraHop_yz :: Nil))) val res = sender.expectMsgType[RouteResponse] assert(res.hops.map(_.nodeId).toList === a :: b :: c :: x :: y :: Nil) @@ -201,7 +201,7 @@ class RouterSpec extends BaseRouterSpec { assert(res.hops.map(_.nodeId).toList === a :: b :: c :: Nil) assert(res.hops.last.nextNodeId === d) - val channelUpdate_cd1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, cltvExpiryDelta = 3, MilliSatoshi(0), feeBaseMsat = MilliSatoshi(153000), feeProportionalMillionths = 4, htlcMaximumMsat = MilliSatoshi(500000000L), enable = false) + val channelUpdate_cd1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, CltvExpiryDelta(3), MilliSatoshi(0), feeBaseMsat = MilliSatoshi(153000), feeProportionalMillionths = 4, htlcMaximumMsat = MilliSatoshi(500000000L), enable = false) sender.send(router, PeerRoutingMessage(null, remoteNodeId, channelUpdate_cd1)) sender.expectMsg(TransportHandler.ReadAck(channelUpdate_cd1)) sender.send(router, RouteRequest(a, d, DEFAULT_AMOUNT_MSAT, routeParams = relaxedRouteParams)) @@ -257,7 +257,7 @@ class RouterSpec extends BaseRouterSpec { val channelId = ShortChannelId(blockHeight, 5, 0) val announcement = channelAnnouncement(channelId, priv_a, priv_c, priv_funding_a, priv_funding_c) val timestamp = (Platform.currentTime.milliseconds - 14.days - 1.day).toSeconds - val update = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, cltvExpiryDelta = 7, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(5), timestamp = timestamp) + val update = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, CltvExpiryDelta(7), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(5), timestamp = timestamp) val probe = TestProbe() probe.ignoreMsg { case _: TransportHandler.ReadAck => true } probe.send(router, PeerRoutingMessage(null, remoteNodeId, announcement)) @@ -270,8 +270,7 @@ class RouterSpec extends BaseRouterSpec { sender.send(router, GetRoutingState) val state = sender.expectMsgType[RoutingState] - - val update1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, cltvExpiryDelta = 7, htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(500000000L), timestamp = Platform.currentTime.millisecond.toSeconds) + val update1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, CltvExpiryDelta(7), htlcMinimumMsat = MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, htlcMaximumMsat = MilliSatoshi(500000000L), timestamp = Platform.currentTime.millisecond.toSeconds) // we want to make sure that transport receives the query val transport = TestProbe() 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 34905e88ae..05db9102d3 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 @@ -18,8 +18,7 @@ package fr.acinq.eclair.router import akka.actor.ActorSystem import akka.testkit.{TestFSMRef, TestKit, TestProbe} -import fr.acinq.bitcoin.{Block} -import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.bitcoin.Block import fr.acinq.eclair._ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer.PeerRoutingMessage @@ -126,14 +125,13 @@ class RoutingSyncSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { } } - object RoutingSyncSpec { def makeFakeRoutingInfo(shortChannelId: ShortChannelId): (ChannelAnnouncement, ChannelUpdate, ChannelUpdate, NodeAnnouncement, NodeAnnouncement) = { val (priv_a, priv_b, priv_funding_a, priv_funding_b) = (randomKey, randomKey, randomKey, randomKey) val channelAnn_ab = channelAnnouncement(shortChannelId, priv_a, priv_b, priv_funding_a, priv_funding_b) val TxCoordinates(blockHeight, _, _) = ShortChannelId.coordinates(shortChannelId) - val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_b.publicKey, shortChannelId, cltvExpiryDelta = 7, MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L), timestamp = blockHeight) - val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, priv_a.publicKey, shortChannelId, cltvExpiryDelta = 7, MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L), timestamp = blockHeight) + val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_b.publicKey, shortChannelId, CltvExpiryDelta(7), MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L), timestamp = blockHeight) + val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, priv_a.publicKey, shortChannelId, CltvExpiryDelta(7), MilliSatoshi(0), feeBaseMsat = MilliSatoshi(766000), feeProportionalMillionths = 10, MilliSatoshi(500000000L), timestamp = blockHeight) val nodeAnnouncement_a = makeNodeAnnouncement(priv_a, "a", Color(0, 0, 0), List()) val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Color(0, 0, 0), List()) (channelAnn_ab, channelUpdate_ab, channelUpdate_ba, nodeAnnouncement_a, nodeAnnouncement_b) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala index 3df953ab0b..4df1d50010 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala @@ -17,8 +17,8 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.{ByteVector32, Crypto} -import fr.acinq.eclair.{MilliSatoshi, TestConstants, randomBytes32} import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc} +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, TestConstants, randomBytes32} import org.scalatest.FunSuite @@ -28,11 +28,11 @@ class CommitmentSpecSpec extends FunSuite { val R = randomBytes32 val H = Crypto.sha256(R) - val add1 = UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(2000 * 1000), H, 400, TestConstants.emptyOnionPacket) + val add1 = UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(2000 * 1000), H, CltvExpiry(400), TestConstants.emptyOnionPacket) val spec1 = CommitmentSpec.reduce(spec, add1 :: Nil, Nil) assert(spec1 === spec.copy(htlcs = Set(DirectedHtlc(OUT, add1)), toLocal = MilliSatoshi(3000000))) - val add2 = UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(1000 * 1000), H, 400, TestConstants.emptyOnionPacket) + val add2 = UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(1000 * 1000), H, CltvExpiry(400), TestConstants.emptyOnionPacket) val spec2 = CommitmentSpec.reduce(spec1, add2 :: Nil, Nil) assert(spec2 === spec1.copy(htlcs = Set(DirectedHtlc(OUT, add1), DirectedHtlc(OUT, add2)), toLocal = MilliSatoshi(2000000))) @@ -50,11 +50,11 @@ class CommitmentSpecSpec extends FunSuite { val R = randomBytes32 val H = Crypto.sha256(R) - val add1 = UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(2000 * 1000), H, 400, TestConstants.emptyOnionPacket) + val add1 = UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(2000 * 1000), H, CltvExpiry(400), TestConstants.emptyOnionPacket) val spec1 = CommitmentSpec.reduce(spec, Nil, add1 :: Nil) assert(spec1 === spec.copy(htlcs = Set(DirectedHtlc(IN, add1)), toRemote = MilliSatoshi(3000 * 1000))) - val add2 = UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(1000 * 1000), H, 400, TestConstants.emptyOnionPacket) + val add2 = UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(1000 * 1000), H, CltvExpiry(400), TestConstants.emptyOnionPacket) val spec2 = CommitmentSpec.reduce(spec1, Nil, add2 :: Nil) assert(spec2 === spec1.copy(htlcs = Set(DirectedHtlc(IN, add1), DirectedHtlc(IN, add2)), toRemote = MilliSatoshi(2000 * 1000))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index d52e1f13cc..aded1beb8a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -17,13 +17,12 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin import fr.acinq.bitcoin.{ByteVector32, Crypto, Satoshi, Script, ScriptFlags, Transaction} -import fr.acinq.eclair.{MilliSatoshi, TestConstants} import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.crypto.Generators import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo} import fr.acinq.eclair.wire.UpdateAddHtlc +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, TestConstants} import grizzled.slf4j.Logging import org.scalatest.FunSuite import scodec.bits._ @@ -66,7 +65,7 @@ class TestVectorsSpec extends FunSuite with Logging { */ object Local { val commitTxNumber = 42 - val toSelfDelay = 144 + val toSelfDelay = CltvExpiryDelta(144) val dustLimit = Satoshi(546) val payment_basepoint_secret = PrivateKey(hex"1111111111111111111111111111111111111111111111111111111111111111") val payment_basepoint = payment_basepoint_secret.publicKey @@ -108,7 +107,7 @@ class TestVectorsSpec extends FunSuite with Logging { object Remote { val commitTxNumber = 42 - val toSelfDelay = 144 + val toSelfDelay = CltvExpiryDelta(144) val dustLimit = Satoshi(546) val payment_basepoint_secret = PrivateKey(hex"4444444444444444444444444444444444444444444444444444444444444444") val payment_basepoint = payment_basepoint_secret.publicKey @@ -155,11 +154,11 @@ class TestVectorsSpec extends FunSuite with Logging { ) val htlcs = Seq( - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(1000000), Crypto.sha256(paymentPreimages(0)), 500, TestConstants.emptyOnionPacket)), - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(1)), 501, TestConstants.emptyOnionPacket)), - DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(2)), 502, TestConstants.emptyOnionPacket)), - DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(3000000), Crypto.sha256(paymentPreimages(3)), 503, TestConstants.emptyOnionPacket)), - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(4000000), Crypto.sha256(paymentPreimages(4)), 504, TestConstants.emptyOnionPacket)) + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(1000000), Crypto.sha256(paymentPreimages(0)), CltvExpiry(500), TestConstants.emptyOnionPacket)), + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(1)), CltvExpiry(501), TestConstants.emptyOnionPacket)), + DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(2)), CltvExpiry(502), TestConstants.emptyOnionPacket)), + DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(3000000), Crypto.sha256(paymentPreimages(3)), CltvExpiry(503), TestConstants.emptyOnionPacket)), + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(4000000), Crypto.sha256(paymentPreimages(4)), CltvExpiry(504), TestConstants.emptyOnionPacket)) ) val htlcScripts = htlcs.map(htlc => htlc.direction match { case OUT => Scripts.htlcOffered(Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, Local.revocation_pubkey, Crypto.ripemd160(htlc.add.paymentHash)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index e323788552..ddd1d4bab2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -22,11 +22,10 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, ripemd160, sha256} import fr.acinq.bitcoin.Script.{pay2wpkh, pay2wsh, write} import fr.acinq.bitcoin.{Btc, ByteVector32, Crypto, MilliBtc, Protocol, Satoshi, Script, Transaction, TxOut, millibtc2satoshi} import fr.acinq.eclair.channel.Helpers.Funding -import fr.acinq.eclair.{MilliSatoshi, TestConstants, randomBytes32} -import fr.acinq.eclair._ import fr.acinq.eclair.transactions.Scripts.{htlcOffered, htlcReceived, toLocalDelayed} import fr.acinq.eclair.transactions.Transactions.{addSigs, _} import fr.acinq.eclair.wire.UpdateAddHtlc +import fr.acinq.eclair.{MilliSatoshi, TestConstants, randomBytes32, _} import grizzled.slf4j.Logging import org.scalatest.FunSuite @@ -34,8 +33,8 @@ import scala.io.Source import scala.util.{Failure, Random, Success, Try} /** - * Created by PM on 16/12/2016. - */ + * Created by PM on 16/12/2016. + */ class TransactionsSpec extends FunSuite with Logging { @@ -64,10 +63,10 @@ class TransactionsSpec extends FunSuite with Logging { test("compute fees") { // see BOLT #3 specs val htlcs = Set( - DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(5000000), ByteVector32.Zeroes, 552, TestConstants.emptyOnionPacket)), - DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(1000000), ByteVector32.Zeroes, 553, TestConstants.emptyOnionPacket)), - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(7000000), ByteVector32.Zeroes, 550, TestConstants.emptyOnionPacket)), - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(800000), ByteVector32.Zeroes, 551, TestConstants.emptyOnionPacket)) + DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(5000000), ByteVector32.Zeroes, CltvExpiry(552), TestConstants.emptyOnionPacket)), + DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(1000000), ByteVector32.Zeroes, CltvExpiry(553), TestConstants.emptyOnionPacket)), + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(7000000), ByteVector32.Zeroes, CltvExpiry(550), TestConstants.emptyOnionPacket)), + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(800000), ByteVector32.Zeroes, CltvExpiry(551), TestConstants.emptyOnionPacket)) ) val spec = CommitmentSpec(htlcs, feeratePerKw = 5000, toLocal = MilliSatoshi(0), toRemote = MilliSatoshi(0)) val fee = Transactions.commitTxFee(Satoshi(546), spec) @@ -83,7 +82,7 @@ class TransactionsSpec extends FunSuite with Logging { val localFinalPriv = PrivateKey(randomBytes32) val finalPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)) val localDustLimit = Satoshi(546) - val toLocalDelay = 144 + val toLocalDelay = CltvExpiryDelta(144) val feeratePerKw = fr.acinq.eclair.MinimumFeeratePerKw { @@ -126,7 +125,7 @@ class TransactionsSpec extends FunSuite with Logging { // HtlcPenaltyTx // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimHtlcSuccessTx val paymentPreimage = randomBytes32 - val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(20000 * 1000), sha256(paymentPreimage), cltvExpiry = 400144, TestConstants.emptyOnionPacket) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(20000 * 1000), sha256(paymentPreimage), CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket) val redeemScript = htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, ripemd160(htlc.paymentHash), htlc.cltvExpiry) val pubKeyScript = write(pay2wsh(redeemScript)) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(htlc.amountMsat.truncateToSatoshi, pubKeyScript) :: Nil, lockTime = 0) @@ -141,7 +140,7 @@ class TransactionsSpec extends FunSuite with Logging { // ClaimHtlcSuccessTx // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimHtlcSuccessTx val paymentPreimage = randomBytes32 - val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(20000 * 1000), sha256(paymentPreimage), cltvExpiry = 400144, TestConstants.emptyOnionPacket) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(20000 * 1000), sha256(paymentPreimage), CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket) val pubKeyScript = write(pay2wsh(htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, ripemd160(htlc.paymentHash)))) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(htlc.amountMsat.truncateToSatoshi, pubKeyScript) :: Nil, lockTime = 0) val claimHtlcSuccessTx = makeClaimHtlcSuccessTx(commitTx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw) @@ -155,7 +154,7 @@ class TransactionsSpec extends FunSuite with Logging { // ClaimHtlcTimeoutTx // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimHtlcSuccessTx val paymentPreimage = randomBytes32 - val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(20000 * 1000), sha256(paymentPreimage), cltvExpiry = 400144, TestConstants.emptyOnionPacket) + val htlc = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(20000 * 1000), sha256(paymentPreimage), CltvExpiryDelta(144).toCltvExpiry, TestConstants.emptyOnionPacket) val pubKeyScript = write(pay2wsh(htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, ripemd160(htlc.paymentHash), htlc.cltvExpiry))) val commitTx = Transaction(version = 0, txIn = Nil, txOut = TxOut(htlc.amountMsat.truncateToSatoshi, pubKeyScript) :: Nil, lockTime = 0) val claimClaimHtlcTimeoutTx = makeClaimHtlcTimeoutTx(commitTx, outputsAlreadyUsed = Set.empty, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw) @@ -177,21 +176,20 @@ class TransactionsSpec extends FunSuite with Logging { val remoteHtlcPriv = PrivateKey(randomBytes32 :+ 1.toByte) val finalPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32).publicKey)) val commitInput = Funding.makeFundingInputInfo(randomBytes32, 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey) - val toLocalDelay = 144 + val toLocalDelay = CltvExpiryDelta(144) val localDustLimit = Satoshi(546) val feeratePerKw = 22000 - // htlc1 and htlc2 are regular IN/OUT htlcs val paymentPreimage1 = randomBytes32 - val htlc1 = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliBtc(100).toMilliSatoshi, sha256(paymentPreimage1), 300, TestConstants.emptyOnionPacket) + val htlc1 = UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliBtc(100).toMilliSatoshi, sha256(paymentPreimage1), CltvExpiry(300), TestConstants.emptyOnionPacket) val paymentPreimage2 = randomBytes32 - val htlc2 = UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliBtc(200).toMilliSatoshi, sha256(paymentPreimage2), 300, TestConstants.emptyOnionPacket) + val htlc2 = UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliBtc(200).toMilliSatoshi, sha256(paymentPreimage2), CltvExpiry(300), TestConstants.emptyOnionPacket) // htlc3 and htlc4 are dust htlcs IN/OUT htlcs, with an amount large enough to be included in the commit tx, but too small to be claimed at 2nd stage val paymentPreimage3 = randomBytes32 - val htlc3 = UpdateAddHtlc(ByteVector32.Zeroes, 2, (localDustLimit + weight2fee(feeratePerKw, htlcTimeoutWeight)).toMilliSatoshi, sha256(paymentPreimage3), 300, TestConstants.emptyOnionPacket) + val htlc3 = UpdateAddHtlc(ByteVector32.Zeroes, 2, (localDustLimit + weight2fee(feeratePerKw, htlcTimeoutWeight)).toMilliSatoshi, sha256(paymentPreimage3), CltvExpiry(300), TestConstants.emptyOnionPacket) val paymentPreimage4 = randomBytes32 - val htlc4 = UpdateAddHtlc(ByteVector32.Zeroes, 3, (localDustLimit + weight2fee(feeratePerKw, htlcSuccessWeight)).toMilliSatoshi, sha256(paymentPreimage4), 300, TestConstants.emptyOnionPacket) + val htlc4 = UpdateAddHtlc(ByteVector32.Zeroes, 3, (localDustLimit + weight2fee(feeratePerKw, htlcSuccessWeight)).toMilliSatoshi, sha256(paymentPreimage4), CltvExpiry(300), TestConstants.emptyOnionPacket) val spec = CommitmentSpec( htlcs = Set( DirectedHtlc(OUT, htlc1), @@ -321,7 +319,7 @@ class TransactionsSpec extends FunSuite with Logging { } def htlc(direction: Direction, amount: Satoshi): DirectedHtlc = - DirectedHtlc(direction, UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, 144, TestConstants.emptyOnionPacket)) + DirectedHtlc(direction, UpdateAddHtlc(ByteVector32.Zeroes, 0, amount.toMilliSatoshi, ByteVector32.Zeroes, CltvExpiry(144), TestConstants.emptyOnionPacket)) test("BOLT 2 fee tests") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index c5a0af0449..6aa892b938 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -22,7 +22,6 @@ import akka.actor.ActorSystem import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet, OutPoint, Satoshi, Transaction} -import fr.acinq.eclair._ import fr.acinq.eclair.api.JsonSupport import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ @@ -32,8 +31,8 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.CommitTx import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.ChannelCodecs._ +import fr.acinq.eclair.{TestConstants, UInt64, randomBytes, randomBytes32, randomKey, _} import org.json4s.jackson.Serialization -import fr.acinq.eclair.{TestConstants, UInt64, randomBytes, randomBytes32, randomKey} import org.scalatest.FunSuite import scodec.bits._ import scodec.{Attempt, DecodeResult} @@ -44,8 +43,8 @@ import scala.io.Source import scala.util.Random /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class ChannelCodecsSpec extends FunSuite { @@ -88,7 +87,7 @@ class ChannelCodecsSpec extends FunSuite { maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), channelReserve = Satoshi(Random.nextInt(Int.MaxValue)), htlcMinimum = MilliSatoshi(Random.nextInt(Int.MaxValue)), - toSelfDelay = Random.nextInt(Short.MaxValue), + toSelfDelay = CltvExpiryDelta(Random.nextInt(Short.MaxValue)), maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), isFunder = Random.nextBoolean(), @@ -106,7 +105,7 @@ class ChannelCodecsSpec extends FunSuite { maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), channelReserve = Satoshi(Random.nextInt(Int.MaxValue)), htlcMinimum = MilliSatoshi(Random.nextInt(Int.MaxValue)), - toSelfDelay = Random.nextInt(Short.MaxValue), + toSelfDelay = CltvExpiryDelta(Random.nextInt(Short.MaxValue)), maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), fundingPubKey = randomKey.publicKey, revocationBasepoint = randomKey.publicKey, @@ -130,7 +129,7 @@ class ChannelCodecsSpec extends FunSuite { channelId = randomBytes32, id = Random.nextInt(Int.MaxValue), amountMsat = MilliSatoshi(Random.nextInt(Int.MaxValue)), - cltvExpiry = Random.nextInt(Int.MaxValue), + cltvExpiry = CltvExpiry(Random.nextInt(Int.MaxValue)), paymentHash = randomBytes32, onionRoutingPacket = TestConstants.emptyOnionPacket) val htlc1 = DirectedHtlc(direction = IN, add = add) @@ -144,14 +143,14 @@ class ChannelCodecsSpec extends FunSuite { channelId = randomBytes32, id = Random.nextInt(Int.MaxValue), amountMsat = MilliSatoshi(Random.nextInt(Int.MaxValue)), - cltvExpiry = Random.nextInt(Int.MaxValue), + cltvExpiry = CltvExpiry(Random.nextInt(Int.MaxValue)), paymentHash = randomBytes32, onionRoutingPacket = TestConstants.emptyOnionPacket) val add2 = UpdateAddHtlc( channelId = randomBytes32, id = Random.nextInt(Int.MaxValue), amountMsat = MilliSatoshi(Random.nextInt(Int.MaxValue)), - cltvExpiry = Random.nextInt(Int.MaxValue), + cltvExpiry = CltvExpiry(Random.nextInt(Int.MaxValue)), paymentHash = randomBytes32, onionRoutingPacket = TestConstants.emptyOnionPacket) val htlc1 = DirectedHtlc(direction = IN, add = add1) @@ -348,7 +347,7 @@ object ChannelCodecsSpec { maxHtlcValueInFlightMsat = UInt64(50000000), channelReserve = Satoshi(10000), htlcMinimum = MilliSatoshi(10000), - toSelfDelay = 144, + toSelfDelay = CltvExpiryDelta(144), maxAcceptedHtlcs = 50, defaultFinalScriptPubKey = ByteVector.empty, isFunder = true, @@ -361,7 +360,7 @@ object ChannelCodecsSpec { maxHtlcValueInFlightMsat = UInt64(5000000), channelReserve = Satoshi(10000), htlcMinimum = MilliSatoshi(5000), - toSelfDelay = 144, + toSelfDelay = CltvExpiryDelta(144), maxAcceptedHtlcs = 50, fundingPubKey = PrivateKey(ByteVector32(ByteVector.fill(32)(1)) :+ 1.toByte).publicKey, revocationBasepoint = PrivateKey(ByteVector.fill(32)(2)).publicKey, @@ -380,11 +379,11 @@ object ChannelCodecsSpec { ) val htlcs = Seq( - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(1000000), Crypto.sha256(paymentPreimages(0)), 500, TestConstants.emptyOnionPacket)), - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(1)), 501, TestConstants.emptyOnionPacket)), - DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 30, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(2)), 502, TestConstants.emptyOnionPacket)), - DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 31, MilliSatoshi(3000000), Crypto.sha256(paymentPreimages(3)), 503, TestConstants.emptyOnionPacket)), - DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(4000000), Crypto.sha256(paymentPreimages(4)), 504, TestConstants.emptyOnionPacket)) + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 0, MilliSatoshi(1000000), Crypto.sha256(paymentPreimages(0)), CltvExpiry(500), TestConstants.emptyOnionPacket)), + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 1, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(1)), CltvExpiry(501), TestConstants.emptyOnionPacket)), + DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 30, MilliSatoshi(2000000), Crypto.sha256(paymentPreimages(2)), CltvExpiry(502), TestConstants.emptyOnionPacket)), + DirectedHtlc(OUT, UpdateAddHtlc(ByteVector32.Zeroes, 31, MilliSatoshi(3000000), Crypto.sha256(paymentPreimages(3)), CltvExpiry(503), TestConstants.emptyOnionPacket)), + DirectedHtlc(IN, UpdateAddHtlc(ByteVector32.Zeroes, 2, MilliSatoshi(4000000), Crypto.sha256(paymentPreimages(4)), CltvExpiry(504), TestConstants.emptyOnionPacket)) ) val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000") @@ -400,7 +399,7 @@ object ChannelCodecsSpec { remoteNextCommitInfo = Right(randomKey.publicKey), commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = ByteVector32.Zeroes) - val channelUpdate = Announcements.makeChannelUpdate(ByteVector32(ByteVector.fill(32)(1)), randomKey, randomKey.publicKey, ShortChannelId(142553), 42, MilliSatoshi(15), MilliSatoshi(575), 53, Channel.MAX_FUNDING.toMilliSatoshi) + val channelUpdate = Announcements.makeChannelUpdate(ByteVector32(ByteVector.fill(32)(1)), randomKey, randomKey.publicKey, ShortChannelId(142553), CltvExpiryDelta(42), MilliSatoshi(15), MilliSatoshi(575), 53, Channel.MAX_FUNDING.toMilliSatoshi) val normal = DATA_NORMAL(commitments, ShortChannelId(42), true, None, channelUpdate, None, None) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala index 7f1f2ef22f..01af3a32e2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala @@ -18,14 +18,14 @@ package fr.acinq.eclair.wire import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64} import fr.acinq.eclair.crypto.Hmac256 -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, randomBytes32, randomBytes64} import fr.acinq.eclair.wire.FailureMessageCodecs._ +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, randomBytes32, randomBytes64} import org.scalatest.FunSuite import scodec.bits._ /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class FailureMessageCodecsSpec extends FunSuite { val channelUpdate = ChannelUpdate( @@ -33,7 +33,7 @@ class FailureMessageCodecsSpec extends FunSuite { chainHash = Block.RegtestGenesisBlock.hash, shortChannelId = ShortChannelId(12345), timestamp = 1234567L, - cltvExpiryDelta = 100, + cltvExpiryDelta = CltvExpiryDelta(100), messageFlags = 0, channelFlags = 1, htlcMinimumMsat = MilliSatoshi(1000), @@ -46,8 +46,8 @@ class FailureMessageCodecsSpec extends FunSuite { InvalidRealm :: TemporaryNodeFailure :: PermanentNodeFailure :: RequiredNodeFeatureMissing :: InvalidOnionVersion(randomBytes32) :: InvalidOnionHmac(randomBytes32) :: InvalidOnionKey(randomBytes32) :: InvalidOnionPayload(randomBytes32) :: TemporaryChannelFailure(channelUpdate) :: PermanentChannelFailure :: RequiredChannelFeatureMissing :: UnknownNextPeer :: - AmountBelowMinimum(MilliSatoshi(123456), channelUpdate) :: FeeInsufficient(MilliSatoshi(546463), channelUpdate) :: IncorrectCltvExpiry(1211, channelUpdate) :: ExpiryTooSoon(channelUpdate) :: - IncorrectOrUnknownPaymentDetails(MilliSatoshi(123456L)) :: IncorrectPaymentAmount :: FinalExpiryTooSoon :: FinalIncorrectCltvExpiry(1234) :: ChannelDisabled(0, 1, channelUpdate) :: ExpiryTooFar :: Nil + AmountBelowMinimum(MilliSatoshi(123456), channelUpdate) :: FeeInsufficient(MilliSatoshi(546463), channelUpdate) :: IncorrectCltvExpiry(CltvExpiry(1211), channelUpdate) :: ExpiryTooSoon(channelUpdate) :: + IncorrectOrUnknownPaymentDetails(MilliSatoshi(123456L)) :: IncorrectPaymentAmount :: FinalExpiryTooSoon :: FinalIncorrectCltvExpiry(CltvExpiry(1234)) :: ChannelDisabled(0, 1, channelUpdate) :: ExpiryTooFar :: Nil msgs.foreach { msg => { @@ -112,7 +112,7 @@ class FailureMessageCodecsSpec extends FunSuite { 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, 0, 3, 14, MilliSatoshi(1000), MilliSatoshi(1), 1, None)) + val ref = TemporaryChannelFailure(ChannelUpdate(ByteVector64(hex"cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f4578219"), Block.LivenetGenesisBlock.hash, ShortChannelId(0x826050004130000L), 1536275759, 0, 3, CltvExpiryDelta(14), MilliSatoshi(1000), MilliSatoshi(1), 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/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala index 90edaabaee..a63a15f025 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala @@ -27,8 +27,8 @@ import org.scalatest.FunSuite import scodec.bits.{ByteVector, HexStringSyntax} /** - * Created by PM on 31/05/2016. - */ + * Created by PM on 31/05/2016. + */ class LightningMessageCodecsSpec extends FunSuite { @@ -52,15 +52,15 @@ class LightningMessageCodecsSpec extends FunSuite { } test("encode/decode all channel messages") { - val open = OpenChannel(randomBytes32, randomBytes32, Satoshi(3), MilliSatoshi(4), Satoshi(5), UInt64(6), Satoshi(7), MilliSatoshi(8), 9, 10, 11, publicKey(1), point(2), point(3), point(4), point(5), point(6), 0.toByte) - val accept = AcceptChannel(randomBytes32, Satoshi(3), UInt64(4), Satoshi(5), MilliSatoshi(6), 7, 8, 9, publicKey(1), point(2), point(3), point(4), point(5), point(6)) + val open = OpenChannel(randomBytes32, randomBytes32, Satoshi(3), MilliSatoshi(4), Satoshi(5), UInt64(6), Satoshi(7), MilliSatoshi(8), 9, CltvExpiryDelta(10), 11, publicKey(1), point(2), point(3), point(4), point(5), point(6), 0.toByte) + val accept = AcceptChannel(randomBytes32, Satoshi(3), UInt64(4), Satoshi(5), MilliSatoshi(6), 7, CltvExpiryDelta(8), 9, publicKey(1), point(2), point(3), point(4), point(5), point(6)) val funding_created = FundingCreated(randomBytes32, bin32(0), 3, randomBytes64) val funding_signed = FundingSigned(randomBytes32, randomBytes64) val funding_locked = FundingLocked(randomBytes32, point(2)) val update_fee = UpdateFee(randomBytes32, 2) val shutdown = Shutdown(randomBytes32, bin(47, 0)) val closing_signed = ClosingSigned(randomBytes32, Satoshi(2), randomBytes64) - val update_add_htlc = UpdateAddHtlc(randomBytes32, 2, MilliSatoshi(3), bin32(0), 4, TestConstants.emptyOnionPacket) + val update_add_htlc = UpdateAddHtlc(randomBytes32, 2, MilliSatoshi(3), bin32(0), CltvExpiry(4), TestConstants.emptyOnionPacket) val update_fulfill_htlc = UpdateFulfillHtlc(randomBytes32, 2, bin32(0)) val update_fail_htlc = UpdateFailHtlc(randomBytes32, 2, bin(154, 0)) val update_fail_malformed_htlc = UpdateFailMalformedHtlc(randomBytes32, 2, randomBytes32, 1111) @@ -68,7 +68,7 @@ class LightningMessageCodecsSpec extends FunSuite { val revoke_and_ack = RevokeAndAck(randomBytes32, scalar(0), point(1)) val channel_announcement = ChannelAnnouncement(randomBytes64, randomBytes64, randomBytes64, randomBytes64, bin(7, 9), Block.RegtestGenesisBlock.hash, ShortChannelId(1), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey) val node_announcement = NodeAnnouncement(randomBytes64, 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, 42, 0, 3, MilliSatoshi(4), MilliSatoshi(5), 6, None) + val channel_update = ChannelUpdate(randomBytes64, Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2, 42, 0, CltvExpiryDelta(3), MilliSatoshi(4), MilliSatoshi(5), 6, None) val announcement_signatures = AnnouncementSignatures(randomBytes32, ShortChannelId(42), randomBytes64, randomBytes64) val gossip_timestamp_filter = GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, 100000, 1500) val query_short_channel_id = QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, randomBytes(7515)) @@ -96,7 +96,7 @@ class LightningMessageCodecsSpec extends FunSuite { // 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, 1, 1, 6, MilliSatoshi(1), MilliSatoshi(1), 10, Some(MilliSatoshi(980000000L)))) + assert(update === ChannelUpdate(ByteVector64(hex"58fff7d0e987e2cdd560e3bb5a046b4efe7b26c969c2f51da1dceec7bcb8ae1b634790503d5290c1a6c51d681cf8f4211d27ed33a257dcc1102862571bf17923"), ByteVector32(hex"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"), ShortChannelId(0x5a10000020000L), 1539791129, 1, 1, CltvExpiryDelta(6), MilliSatoshi(1), MilliSatoshi(1), 10, Some(MilliSatoshi(980000000L)))) val nodeId = PublicKey(hex"03370c9bac836e557eb4f017fe8f9cc047f44db39c1c4e410ff0f7be142b817ae4") assert(Announcements.checkSig(update, nodeId)) val bin2 = ByteVector(lightningMessageCodec.encode(update).require.toByteArray) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/OnionCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/OnionCodecsSpec.scala index bf236ed7b7..43726c94fa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/OnionCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/OnionCodecsSpec.scala @@ -17,14 +17,14 @@ package fr.acinq.eclair.wire import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId} import fr.acinq.eclair.wire.OnionCodecs._ +import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, ShortChannelId} import org.scalatest.FunSuite import scodec.bits.HexStringSyntax /** - * Created by t-bast on 05/07/2019. - */ + * Created by t-bast on 05/07/2019. + */ class OnionCodecsSpec extends FunSuite { @@ -40,7 +40,7 @@ class OnionCodecsSpec extends FunSuite { } test("encode/decode per-hop payload") { - val payload = PerHopPayload(shortChannelId = ShortChannelId(42), amtToForward = MilliSatoshi(142000), outgoingCltvValue = 500000) + val payload = PerHopPayload(shortChannelId = ShortChannelId(42), amtToForward = MilliSatoshi(142000), outgoingCltvValue = CltvExpiry(500000)) val bin = perHopPayloadCodec.encode(payload).require assert(bin.toByteVector.size === 33) val payload1 = perHopPayloadCodec.decode(bin).require.value diff --git a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala index 54765f834a..c5094e63d7 100644 --- a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala +++ b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/Handlers.scala @@ -87,9 +87,9 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte logger.info(s"sending $amountMsat to ${req.paymentHash} @ ${req.nodeId}") (for { kit <- fKit - sendPayment = req.minFinalCltvExpiry match { + sendPayment = req.minFinalCltvExpiryDelta match { case None => SendPayment(MilliSatoshi(amountMsat), req.paymentHash, req.nodeId, req.routingInfo, maxAttempts = kit.nodeParams.maxPaymentAttempts) - case Some(minFinalCltvExpiry) => SendPayment(MilliSatoshi(amountMsat), req.paymentHash, req.nodeId, req.routingInfo, finalCltvExpiry = minFinalCltvExpiry, maxAttempts = kit.nodeParams.maxPaymentAttempts) + case Some(minFinalCltvExpiry) => SendPayment(MilliSatoshi(amountMsat), req.paymentHash, req.nodeId, req.routingInfo, finalCltvExpiryDelta = minFinalCltvExpiry, maxAttempts = kit.nodeParams.maxPaymentAttempts) } res <- (kit.paymentInitiator ? sendPayment).mapTo[UUID] } yield res).recover { From b217fc04827b139a3008b9d2965ecb6bfab4a3e1 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Thu, 22 Aug 2019 14:24:01 +0200 Subject: [PATCH 3/5] Hide CltvExpiry underlying value. Expose via toLong/toInt. --- .../scala/fr/acinq/eclair/CltvExpiry.scala | 26 ++++++++++--------- .../fr/acinq/eclair/api/JsonSerializers.scala | 6 ++--- .../fr/acinq/eclair/channel/Channel.scala | 2 +- .../fr/acinq/eclair/channel/Commitments.scala | 8 +++--- .../eclair/db/sqlite/SqliteChannelsDb.scala | 2 +- .../scala/fr/acinq/eclair/router/Graph.scala | 2 +- .../acinq/eclair/transactions/Scripts.scala | 4 +-- .../eclair/transactions/Transactions.scala | 6 ++--- .../fr/acinq/eclair/wire/CommonCodecs.scala | 4 +-- .../fr/acinq/eclair/CltvExpirySpec.scala | 3 ++- .../channel/states/e/NormalStateSpec.scala | 6 ++--- .../channel/states/e/OfflineStateSpec.scala | 4 +-- .../channel/states/h/ClosingStateSpec.scala | 2 +- 13 files changed, 39 insertions(+), 36 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala index 9828a14454..905413cd11 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala @@ -24,18 +24,19 @@ package fr.acinq.eclair * Bitcoin scripts (in particular HTLCs) need an absolute block expiry (greater than the current block count) to work * with OP_CLTV. * - * @param get the absolute cltv expiry value (current block count + some delta). + * @param underlying the absolute cltv expiry value (current block count + some delta). */ -case class CltvExpiry(get: Long) { +case class CltvExpiry(private val underlying: Long) { // @formatter:off - def +(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(get + d.delta) - def -(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(get - d.delta) - def -(other: CltvExpiry): CltvExpiryDelta = CltvExpiryDelta((get - other.get).toInt) - def compare(other: CltvExpiry): Int = if (get == other.get) 0 else if (get < other.get) -1 else 1 + def +(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(underlying + d.toInt) + def -(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(underlying - d.toInt) + def -(other: CltvExpiry): CltvExpiryDelta = CltvExpiryDelta((underlying - other.underlying).toInt) + def compare(other: CltvExpiry): Int = if (underlying == other.underlying) 0 else if (underlying < other.underlying) -1 else 1 def <=(that: CltvExpiry): Boolean = compare(that) <= 0 def >=(that: CltvExpiry): Boolean = compare(that) >= 0 def <(that: CltvExpiry): Boolean = compare(that) < 0 def >(that: CltvExpiry): Boolean = compare(that) > 0 + def toLong: Long = underlying // @formatter:on } @@ -45,23 +46,24 @@ case class CltvExpiry(get: Long) { * * CltvExpiryDelta can also be used when working with OP_CSV which is by design a delta. * - * @param delta the cltv expiry delta value. + * @param underlying the cltv expiry delta value. */ -case class CltvExpiryDelta(delta: Int) { +case class CltvExpiryDelta(private val underlying: Int) { /** * Adds the current block height to the given delta to obtain an absolute expiry. */ - def toCltvExpiry = CltvExpiry(Globals.blockCount.get() + delta) + def toCltvExpiry = CltvExpiry(Globals.blockCount.get() + underlying) // @formatter:off - def +(other: Int): CltvExpiryDelta = CltvExpiryDelta(delta + other) - def +(other: CltvExpiryDelta): CltvExpiryDelta = CltvExpiryDelta(delta + other.delta) - def compare(other: CltvExpiryDelta): Int = if (delta == other.delta) 0 else if (delta < other.delta) -1 else 1 + def +(other: Int): CltvExpiryDelta = CltvExpiryDelta(underlying + other) + def +(other: CltvExpiryDelta): CltvExpiryDelta = CltvExpiryDelta(underlying + other.underlying) + def compare(other: CltvExpiryDelta): Int = if (underlying == other.underlying) 0 else if (underlying < other.underlying) -1 else 1 def <=(that: CltvExpiryDelta): Boolean = compare(that) <= 0 def >=(that: CltvExpiryDelta): Boolean = compare(that) >= 0 def <(that: CltvExpiryDelta): Boolean = compare(that) < 0 def >(that: CltvExpiryDelta): Boolean = compare(that) > 0 + def toInt: Int = underlying // @formatter:on } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index db225d949e..49294f3c84 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -66,11 +66,11 @@ class MilliSatoshiSerializer extends CustomSerializer[MilliSatoshi](format => ({ })) class CltvExpirySerializer extends CustomSerializer[CltvExpiry](format => ({ null }, { - case x: CltvExpiry => JLong(x.get) + case x: CltvExpiry => JLong(x.toLong) })) class CltvExpiryDeltaSerializer extends CustomSerializer[CltvExpiryDelta](format => ({ null }, { - case x: CltvExpiryDelta => JInt(x.delta) + case x: CltvExpiryDelta => JInt(x.toInt) })) class ShortChannelIdSerializer extends CustomSerializer[ShortChannelId](format => ({ null }, { @@ -162,7 +162,7 @@ class PaymentRequestSerializer extends CustomSerializer[PaymentRequest](format = }, { case p: PaymentRequest => { val expiry = p.expiry.map(ex => JField("expiry", JLong(ex))).toSeq - val minFinalCltvExpiry = p.minFinalCltvExpiryDelta.map(mfce => JField("minFinalCltvExpiry", JLong(mfce.delta))).toSeq + val minFinalCltvExpiry = p.minFinalCltvExpiryDelta.map(mfce => JField("minFinalCltvExpiry", JInt(mfce.toInt))).toSeq val amount = p.amount.map(msat => JField("amount", JLong(msat.toLong))).toSeq val fieldList = List(JField("prefix", JString(p.prefix)), 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 384afd8d87..d82a17c3fe 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 @@ -1306,7 +1306,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val revokedCommitPublished1 = d.revokedCommitPublished.map(Closing.updateRevokedCommitPublished(_, tx)) // if the local commitment tx just got confirmed, let's send an event telling when we will get the main output refund if (localCommitPublished1.map(_.commitTx.txid).contains(tx.txid)) { - context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.remoteParams.toSelfDelay.delta)) + context.system.eventStream.publish(LocalCommitConfirmed(self, remoteNodeId, d.channelId, blockHeight + d.commitments.remoteParams.toSelfDelay.toInt)) } // we may need to fail some htlcs in case a commitment tx was published and they have reached the timeout threshold val timedoutHtlcs = diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 0c7807840c..4518f075ff 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -62,9 +62,9 @@ case class Commitments(channelVersion: ChannelVersion, def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight def timedOutOutgoingHtlcs(blockheight: Long): Set[UpdateAddHtlc] = - (localCommit.spec.htlcs.filter(htlc => htlc.direction == OUT && blockheight >= htlc.add.cltvExpiry.get) ++ - remoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.get) ++ - remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.get)).getOrElse(Set.empty[DirectedHtlc])).map(_.add) + (localCommit.spec.htlcs.filter(htlc => htlc.direction == OUT && blockheight >= htlc.add.cltvExpiry.toLong) ++ + remoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.toLong) ++ + remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit.spec.htlcs.filter(htlc => htlc.direction == IN && blockheight >= htlc.add.cltvExpiry.toLong)).getOrElse(Set.empty[DirectedHtlc])).map(_.add) /** * HTLCs that are close to timing out upstream are potentially dangerous. If we received the pre-image for those @@ -74,7 +74,7 @@ case class Commitments(channelVersion: ChannelVersion, */ def almostTimedOutIncomingHtlcs(blockheight: Long, fulfillSafety: CltvExpiryDelta): Set[UpdateAddHtlc] = { localCommit.spec.htlcs.collect { - case htlc if htlc.direction == IN && blockheight >= (htlc.add.cltvExpiry - fulfillSafety).get => htlc.add + case htlc if htlc.direction == IN && blockheight >= (htlc.add.cltvExpiry - fulfillSafety).toLong => htlc.add } } 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 4ef958b876..9cadd6ab1e 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 @@ -99,7 +99,7 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb with Logging { statement.setBytes(1, channelId.toArray) statement.setLong(2, commitmentNumber) statement.setBytes(3, paymentHash.toArray) - statement.setLong(4, cltvExpiry.get) + statement.setLong(4, cltvExpiry.toLong) statement.executeUpdate() } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala index ec047b1d5c..153f2d0d26 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala @@ -295,7 +295,7 @@ object Graph { val capFactor = 1 - normalize(edgeMaxCapacity.toLong, CAPACITY_CHANNEL_LOW.toLong, CAPACITY_CHANNEL_HIGH.toLong) // Every edge is weighted by its cltv-delta value, normalized - val channelCltvDelta = edge.update.cltvExpiryDelta.delta + val channelCltvDelta = edge.update.cltvExpiryDelta.toInt val cltvFactor = normalize(channelCltvDelta, CLTV_LOW, CLTV_HIGH) // NB 'edgeCost' includes the amount to be sent plus the fees that must be paid to traverse this @param edge diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala index 7889fb18a4..1e7c6f983e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala @@ -118,7 +118,7 @@ object Scripts { OP_IF :: OP_PUSHDATA(revocationPubkey) :: OP_ELSE :: - encodeNumber(toSelfDelay.delta) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: + encodeNumber(toSelfDelay.toInt) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_PUSHDATA(localDelayedPaymentPubkey) :: OP_ENDIF :: OP_CHECKSIG :: Nil @@ -184,7 +184,7 @@ object Scripts { OP_2 :: OP_SWAP :: OP_PUSHDATA(localHtlcPubkey) :: OP_2 :: OP_CHECKMULTISIG :: OP_ELSE :: // To you after timeout. - OP_DROP :: encodeNumber(lockTime.get) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: + OP_DROP :: encodeNumber(lockTime.toLong) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: OP_CHECKSIG :: OP_ENDIF :: OP_ENDIF :: Nil diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 9ed1e9467d..a457deddf1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -229,7 +229,7 @@ object Transactions { version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0x00000000L) :: Nil, txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil, - lockTime = htlc.cltvExpiry.get)) + lockTime = htlc.cltvExpiry.toLong)) } def makeHtlcSuccessTx(commitTx: Transaction, outputsAlreadyUsed: Set[Int], localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, feeratePerKw: Long, htlc: UpdateAddHtlc): HtlcSuccessTx = { @@ -298,7 +298,7 @@ object Transactions { version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0x00000000L) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = htlc.cltvExpiry.get) + lockTime = htlc.cltvExpiry.toLong) val weight = addSigs(ClaimHtlcTimeoutTx(input, tx), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) @@ -347,7 +347,7 @@ object Transactions { // unsigned transaction val tx = Transaction( version = 2, - txIn = TxIn(input.outPoint, ByteVector.empty, toLocalDelay.delta) :: Nil, + txIn = TxIn(input.outPoint, ByteVector.empty, toLocalDelay.toInt) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, lockTime = 0) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala index e3a31f06ef..898605c187 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala @@ -57,8 +57,8 @@ object CommonCodecs { val satoshi: Codec[Satoshi] = uint64overflow.xmapc(l => Satoshi(l))(_.toLong) val millisatoshi: Codec[MilliSatoshi] = uint64overflow.xmapc(l => MilliSatoshi(l))(_.amount) - val cltvExpiry: Codec[CltvExpiry] = uint32.xmapc(CltvExpiry)((_: CltvExpiry).get) - val cltvExpiryDelta: Codec[CltvExpiryDelta] = uint16.xmapc(CltvExpiryDelta)((_: CltvExpiryDelta).delta) + val cltvExpiry: Codec[CltvExpiry] = uint32.xmapc(CltvExpiry)((_: CltvExpiry).toLong) + val cltvExpiryDelta: Codec[CltvExpiryDelta] = uint16.xmapc(CltvExpiryDelta)((_: CltvExpiryDelta).toInt) /** * We impose a minimal encoding on some values (such as varint and truncated int) to ensure that signed hashes can be diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala index c7c0c230a0..779c58a999 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/CltvExpirySpec.scala @@ -26,7 +26,7 @@ class CltvExpirySpec extends FunSuite { test("cltv expiry delta") { val d = CltvExpiryDelta(561) - assert(d.delta === 561) + assert(d.toInt === 561) // add assert(d + 5 === CltvExpiryDelta(566)) @@ -47,6 +47,7 @@ class CltvExpirySpec extends FunSuite { test("cltv expiry") { val e = CltvExpiry(1105) + assert(e.toLong === 1105) // add assert(e + CltvExpiryDelta(561) === CltvExpiry(1666)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 643385ab79..cb88d6cb5f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -1714,7 +1714,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = true)) sender.expectMsg("ok") bob2alice.expectMsgType[UpdateFulfillHtlc] - sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) @@ -1749,7 +1749,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r, commit = false)) sender.expectMsg("ok") bob2alice.expectMsgType[UpdateFulfillHtlc] - sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) @@ -1789,7 +1789,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) alice2bob.expectMsgType[RevokeAndAck] alice2bob.forward(bob) - sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - Bob.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index c5953a6d73..837fc54655 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -419,7 +419,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // We simulate a pending fulfill on that HTLC but not relayed. // When it is close to expiring upstream, we should close the channel. sender.send(commandBuffer, CommandSend(htlc.channelId, htlc.id, CMD_FULFILL_HTLC(htlc.id, r, commit = true))) - sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong)) val ChannelErrorOccured(_, _, _, _, LocalError(err), isFatal) = listener.expectMsgType[ChannelErrorOccured] assert(isFatal) @@ -453,7 +453,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // We simulate a pending failure on that HTLC. // Even if we get close to expiring upstream we shouldn't close the channel, because we have nothing to lose. sender.send(commandBuffer, CommandSend(htlc.channelId, htlc.id, CMD_FAIL_HTLC(htlc.id, Right(IncorrectOrUnknownPaymentDetails(MilliSatoshi(0)))))) - sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).get)) + sender.send(bob, CurrentBlockCount((htlc.cltvExpiry - bob.underlyingActor.nodeParams.fulfillSafetyBeforeTimeoutBlocks).toLong)) bob2blockchain.expectNoMsg(250 millis) alice2blockchain.expectNoMsg(250 millis) 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 2df05bca3e..4f5aa2afaf 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 @@ -437,7 +437,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { // actual test starts here alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 42, 0, aliceCommitTx) - assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay.delta) + assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay.toInt) assert(listener.expectMsgType[PaymentSettlingOnChain].paymentHash == htlca1.paymentHash) alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainDelayedTx), 200, 0, claimMainDelayedTx) alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcTimeoutTx), 201, 0, htlcTimeoutTx) From 6e96a5722e08d547e5b7ed72882f7bf5ed95eeb4 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Thu, 22 Aug 2019 17:10:58 +0200 Subject: [PATCH 4/5] CltvExpiry should extend Ordered[] --- .../main/scala/fr/acinq/eclair/CltvExpiry.scala | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala index 905413cd11..55cc13aadd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala @@ -26,16 +26,12 @@ package fr.acinq.eclair * * @param underlying the absolute cltv expiry value (current block count + some delta). */ -case class CltvExpiry(private val underlying: Long) { +case class CltvExpiry(private val underlying: Long) extends Ordered[CltvExpiry] { // @formatter:off def +(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(underlying + d.toInt) def -(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(underlying - d.toInt) def -(other: CltvExpiry): CltvExpiryDelta = CltvExpiryDelta((underlying - other.underlying).toInt) - def compare(other: CltvExpiry): Int = if (underlying == other.underlying) 0 else if (underlying < other.underlying) -1 else 1 - def <=(that: CltvExpiry): Boolean = compare(that) <= 0 - def >=(that: CltvExpiry): Boolean = compare(that) >= 0 - def <(that: CltvExpiry): Boolean = compare(that) < 0 - def >(that: CltvExpiry): Boolean = compare(that) > 0 + override def compare(other: CltvExpiry): Int = if (underlying == other.underlying) 0 else if (underlying < other.underlying) -1 else 1 def toLong: Long = underlying // @formatter:on } @@ -48,7 +44,7 @@ case class CltvExpiry(private val underlying: Long) { * * @param underlying the cltv expiry delta value. */ -case class CltvExpiryDelta(private val underlying: Int) { +case class CltvExpiryDelta(private val underlying: Int) extends Ordered[CltvExpiryDelta] { /** * Adds the current block height to the given delta to obtain an absolute expiry. @@ -59,10 +55,6 @@ case class CltvExpiryDelta(private val underlying: Int) { def +(other: Int): CltvExpiryDelta = CltvExpiryDelta(underlying + other) def +(other: CltvExpiryDelta): CltvExpiryDelta = CltvExpiryDelta(underlying + other.underlying) def compare(other: CltvExpiryDelta): Int = if (underlying == other.underlying) 0 else if (underlying < other.underlying) -1 else 1 - def <=(that: CltvExpiryDelta): Boolean = compare(that) <= 0 - def >=(that: CltvExpiryDelta): Boolean = compare(that) >= 0 - def <(that: CltvExpiryDelta): Boolean = compare(that) < 0 - def >(that: CltvExpiryDelta): Boolean = compare(that) > 0 def toInt: Int = underlying // @formatter:on From 1575e36d67024122ea0387d4f37f21d8c66cb9f2 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier Date: Thu, 22 Aug 2019 18:01:30 +0200 Subject: [PATCH 5/5] fixup! CltvExpiry should extend Ordered[] --- eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala index 55cc13aadd..3452df377c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/CltvExpiry.scala @@ -31,7 +31,7 @@ case class CltvExpiry(private val underlying: Long) extends Ordered[CltvExpiry] def +(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(underlying + d.toInt) def -(d: CltvExpiryDelta): CltvExpiry = CltvExpiry(underlying - d.toInt) def -(other: CltvExpiry): CltvExpiryDelta = CltvExpiryDelta((underlying - other.underlying).toInt) - override def compare(other: CltvExpiry): Int = if (underlying == other.underlying) 0 else if (underlying < other.underlying) -1 else 1 + override def compare(other: CltvExpiry): Int = underlying.compareTo(other.underlying) def toLong: Long = underlying // @formatter:on } @@ -54,7 +54,7 @@ case class CltvExpiryDelta(private val underlying: Int) extends Ordered[CltvExpi // @formatter:off def +(other: Int): CltvExpiryDelta = CltvExpiryDelta(underlying + other) def +(other: CltvExpiryDelta): CltvExpiryDelta = CltvExpiryDelta(underlying + other.underlying) - def compare(other: CltvExpiryDelta): Int = if (underlying == other.underlying) 0 else if (underlying < other.underlying) -1 else 1 + def compare(other: CltvExpiryDelta): Int = underlying.compareTo(other.underlying) def toInt: Int = underlying // @formatter:on