mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-25 17:13:32 +01:00
Upgrade Eclair to v0.7.0 (#4308)
* Upgrade Eclair to v0.7.0 * Scala 2.12 compatibility * reformat
This commit is contained in:
parent
ce00d3ac36
commit
3dc709386a
9 changed files with 359 additions and 87 deletions
|
@ -285,8 +285,11 @@ case class PaymentRequest(
|
||||||
serialized: String,
|
serialized: String,
|
||||||
description: String,
|
description: String,
|
||||||
paymentHash: Sha256Digest,
|
paymentHash: Sha256Digest,
|
||||||
|
paymentMetadata: String,
|
||||||
expiry: FiniteDuration, //seconds
|
expiry: FiniteDuration, //seconds
|
||||||
amount: Option[MilliSatoshis])
|
minFinalCltvExpiry: Int,
|
||||||
|
amount: Option[MilliSatoshis],
|
||||||
|
features: Features)
|
||||||
|
|
||||||
sealed trait PaymentType
|
sealed trait PaymentType
|
||||||
|
|
||||||
|
@ -295,13 +298,15 @@ object PaymentType extends StringFactory[PaymentType] {
|
||||||
case object Standard extends PaymentType
|
case object Standard extends PaymentType
|
||||||
case object SwapIn extends PaymentType
|
case object SwapIn extends PaymentType
|
||||||
case object SwapOut extends PaymentType
|
case object SwapOut extends PaymentType
|
||||||
|
case object Placeholder extends PaymentType
|
||||||
|
|
||||||
override def fromString(str: String): PaymentType =
|
override def fromString(str: String): PaymentType =
|
||||||
str match {
|
str match {
|
||||||
case "Standard" => Standard
|
case "Standard" => Standard
|
||||||
case "SwapIn" => SwapIn
|
case "SwapIn" => SwapIn
|
||||||
case "SwapOut" => SwapOut
|
case "SwapOut" => SwapOut
|
||||||
case _ => throw new RuntimeException(s"Unknown payment type `$str`")
|
case "placeholder" => Placeholder
|
||||||
|
case _ => throw new RuntimeException(s"Unknown payment type `$str`")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -322,6 +327,7 @@ case class OutgoingPayment(
|
||||||
case class IncomingPayment(
|
case class IncomingPayment(
|
||||||
paymentRequest: PaymentRequest,
|
paymentRequest: PaymentRequest,
|
||||||
paymentPreimage: PaymentPreimage,
|
paymentPreimage: PaymentPreimage,
|
||||||
|
paymentType: PaymentType,
|
||||||
createdAt: Instant, //milliseconds
|
createdAt: Instant, //milliseconds
|
||||||
status: IncomingPaymentStatus)
|
status: IncomingPaymentStatus)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import org.bitcoins.core.protocol.ln.channel._
|
||||||
import org.bitcoins.core.protocol.ln.currency._
|
import org.bitcoins.core.protocol.ln.currency._
|
||||||
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
||||||
import org.bitcoins.core.protocol.ln.node.{Feature, FeatureSupport, NodeId}
|
import org.bitcoins.core.protocol.ln.node.{Feature, FeatureSupport, NodeId}
|
||||||
|
import org.bitcoins.core.protocol.ln.routing.{ChannelRoute, NodeRoute, Route}
|
||||||
import org.bitcoins.core.protocol.script.{
|
import org.bitcoins.core.protocol.script.{
|
||||||
ScriptPubKey,
|
ScriptPubKey,
|
||||||
ScriptSignature,
|
ScriptSignature,
|
||||||
|
@ -805,7 +806,9 @@ object JsonReaders {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val featuresByName: Map[String, Feature] =
|
lazy val featuresByName: Map[String, Feature] =
|
||||||
Feature.knownFeatures.map(f => (f.rfcName, f)).toMap
|
org.bitcoins.core.protocol.ln.node.Features.knownFeatures
|
||||||
|
.map(f => (f.rfcName, f))
|
||||||
|
.toMap
|
||||||
|
|
||||||
implicit val featureReads: Reads[Feature] =
|
implicit val featureReads: Reads[Feature] =
|
||||||
Reads { jsValue =>
|
Reads { jsValue =>
|
||||||
|
@ -854,7 +857,7 @@ object JsonReaders {
|
||||||
for {
|
for {
|
||||||
signature <- (jsValue \ "signature").validate[ECDigitalSignature]
|
signature <- (jsValue \ "signature").validate[ECDigitalSignature]
|
||||||
features <- (jsValue \ "features").validate[Features]
|
features <- (jsValue \ "features").validate[Features]
|
||||||
timestamp <- (jsValue \ "timestamp")
|
timestamp <- (jsValue \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsSeconds)
|
.validate[Instant](instantReadsSeconds)
|
||||||
nodeId <- (jsValue \ "nodeId").validate[NodeId]
|
nodeId <- (jsValue \ "nodeId").validate[NodeId]
|
||||||
rgbColor <- (jsValue \ "rgbColor").validate[String]
|
rgbColor <- (jsValue \ "rgbColor").validate[String]
|
||||||
|
@ -1021,7 +1024,7 @@ object JsonReaders {
|
||||||
signature <- (jsValue \ "signature").validate[ECDigitalSignature]
|
signature <- (jsValue \ "signature").validate[ECDigitalSignature]
|
||||||
chainHash <- (jsValue \ "chainHash").validate[DoubleSha256Digest]
|
chainHash <- (jsValue \ "chainHash").validate[DoubleSha256Digest]
|
||||||
shortChannelId <- (jsValue \ "shortChannelId").validate[ShortChannelId]
|
shortChannelId <- (jsValue \ "shortChannelId").validate[ShortChannelId]
|
||||||
timestamp <- (jsValue \ "timestamp")
|
timestamp <- (jsValue \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsSeconds)
|
.validate[Instant](instantReadsSeconds)
|
||||||
channelFlags <- (jsValue \ "channelFlags").validate[ChannelFlags]
|
channelFlags <- (jsValue \ "channelFlags").validate[ChannelFlags]
|
||||||
cltvExpiryDelta <- (jsValue \ "cltvExpiryDelta").validate[Int]
|
cltvExpiryDelta <- (jsValue \ "cltvExpiryDelta").validate[Int]
|
||||||
|
@ -1080,7 +1083,13 @@ object JsonReaders {
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val paymentReceivedReads: Reads[IncomingPaymentStatus.Received] =
|
implicit val paymentReceivedReads: Reads[IncomingPaymentStatus.Received] =
|
||||||
Json.reads[IncomingPaymentStatus.Received]
|
Reads { js =>
|
||||||
|
for {
|
||||||
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
|
receivedAt <- (js \ "receivedAt" \ "unix")
|
||||||
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
|
} yield IncomingPaymentStatus.Received(amount, receivedAt)
|
||||||
|
}
|
||||||
|
|
||||||
implicit val hopReads: Reads[Hop] =
|
implicit val hopReads: Reads[Hop] =
|
||||||
Json.reads[Hop]
|
Json.reads[Hop]
|
||||||
|
@ -1091,7 +1100,7 @@ object JsonReaders {
|
||||||
preimage <- (js \ "paymentPreimage").validate[PaymentPreimage]
|
preimage <- (js \ "paymentPreimage").validate[PaymentPreimage]
|
||||||
feesPaid <- (js \ "feesPaid").validate[MilliSatoshis]
|
feesPaid <- (js \ "feesPaid").validate[MilliSatoshis]
|
||||||
route <- (js \ "route").validate[Seq[Hop]]
|
route <- (js \ "route").validate[Seq[Hop]]
|
||||||
completed <- (js \ "completedAt")
|
completed <- (js \ "completedAt" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield OutgoingPaymentStatus.Succeeded(paymentPreimage = preimage,
|
} yield OutgoingPaymentStatus.Succeeded(paymentPreimage = preimage,
|
||||||
feesPaid = feesPaid,
|
feesPaid = feesPaid,
|
||||||
|
@ -1152,17 +1161,23 @@ object JsonReaders {
|
||||||
serialized <- (js \ "serialized").validate[String]
|
serialized <- (js \ "serialized").validate[String]
|
||||||
description <- (js \ "serialized").validate[String]
|
description <- (js \ "serialized").validate[String]
|
||||||
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
||||||
|
paymentMetadata <- (js \ "paymentMetadata").validate[String]
|
||||||
expiry <- (js \ "expiry")
|
expiry <- (js \ "expiry")
|
||||||
.validate[FiniteDuration](finiteDurationReadsSeconds)
|
.validate[FiniteDuration](finiteDurationReadsSeconds)
|
||||||
amount <- (js \ "amount").validateOpt[MilliSatoshis]
|
amount <- (js \ "amount").validateOpt[MilliSatoshis]
|
||||||
|
minFinalCltvExpiry <- (js \ "minFinalCltvExpiry").validate[Int]
|
||||||
|
features <- (js \ "features").validate[Features]
|
||||||
} yield PaymentRequest(prefix,
|
} yield PaymentRequest(prefix,
|
||||||
timestamp,
|
timestamp,
|
||||||
nodeId,
|
nodeId,
|
||||||
serialized,
|
serialized,
|
||||||
description,
|
description,
|
||||||
paymentHash,
|
paymentHash,
|
||||||
|
paymentMetadata,
|
||||||
expiry,
|
expiry,
|
||||||
amount)
|
minFinalCltvExpiry,
|
||||||
|
amount,
|
||||||
|
features)
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val paymentSucceededReads: Reads[OutgoingPayment] = Reads { js =>
|
implicit val paymentSucceededReads: Reads[OutgoingPayment] = Reads { js =>
|
||||||
|
@ -1175,7 +1190,7 @@ object JsonReaders {
|
||||||
amount <- (js \ "amount").validate[MilliSatoshis]
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
recipientAmount <- (js \ "recipientAmount").validate[MilliSatoshis]
|
recipientAmount <- (js \ "recipientAmount").validate[MilliSatoshis]
|
||||||
recipientNodeId <- (js \ "recipientNodeId").validate[NodeId]
|
recipientNodeId <- (js \ "recipientNodeId").validate[NodeId]
|
||||||
createdAt <- (js \ "createdAt")
|
createdAt <- (js \ "createdAt" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
paymentRequest <- (js \ "paymentRequest").validateOpt[PaymentRequest]
|
paymentRequest <- (js \ "paymentRequest").validateOpt[PaymentRequest]
|
||||||
status <- (js \ "status").validate[OutgoingPaymentStatus]
|
status <- (js \ "status").validate[OutgoingPaymentStatus]
|
||||||
|
@ -1197,11 +1212,13 @@ object JsonReaders {
|
||||||
for {
|
for {
|
||||||
paymentRequest <- (js \ "paymentRequest").validate[PaymentRequest]
|
paymentRequest <- (js \ "paymentRequest").validate[PaymentRequest]
|
||||||
paymentPreimage <- (js \ "paymentPreimage").validate[PaymentPreimage]
|
paymentPreimage <- (js \ "paymentPreimage").validate[PaymentPreimage]
|
||||||
createdAt <- (js \ "createdAt")
|
paymentType <- (js \ "paymentType").validate[PaymentType]
|
||||||
|
createdAt <- (js \ "createdAt" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
status <- (js \ "status").validate[IncomingPaymentStatus]
|
status <- (js \ "status").validate[IncomingPaymentStatus]
|
||||||
} yield IncomingPayment(paymentRequest,
|
} yield IncomingPayment(paymentRequest,
|
||||||
paymentPreimage,
|
paymentPreimage,
|
||||||
|
paymentType,
|
||||||
createdAt,
|
createdAt,
|
||||||
status)
|
status)
|
||||||
}
|
}
|
||||||
|
@ -1243,7 +1260,7 @@ object JsonReaders {
|
||||||
for {
|
for {
|
||||||
amount <- (js \ "amount").validate[MilliSatoshis]
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield ReceivedPayment.Part(amount, fromChannelId, timestamp)
|
} yield ReceivedPayment.Part(amount, fromChannelId, timestamp)
|
||||||
}
|
}
|
||||||
|
@ -1257,7 +1274,7 @@ object JsonReaders {
|
||||||
amount <- (js \ "amount").validate[MilliSatoshis]
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
feesPaid <- (js \ "feesPaid").validate[MilliSatoshis]
|
feesPaid <- (js \ "feesPaid").validate[MilliSatoshis]
|
||||||
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield SentPayment.Part(id, amount, feesPaid, toChannelId, timestamp)
|
} yield SentPayment.Part(id, amount, feesPaid, toChannelId, timestamp)
|
||||||
}
|
}
|
||||||
|
@ -1271,7 +1288,7 @@ object JsonReaders {
|
||||||
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
||||||
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
||||||
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield RelayedPayment(amountIn,
|
} yield RelayedPayment(amountIn,
|
||||||
amountOut,
|
amountOut,
|
||||||
|
@ -1289,7 +1306,7 @@ object JsonReaders {
|
||||||
txId <- (js \ "txId").validate[DoubleSha256DigestBE]
|
txId <- (js \ "txId").validate[DoubleSha256DigestBE]
|
||||||
fee <- (js \ "fee").validate[Satoshis]
|
fee <- (js \ "fee").validate[Satoshis]
|
||||||
txType <- (js \ "txType").validate[String]
|
txType <- (js \ "txType").validate[String]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield NetworkFeesResult(remoteNodeId,
|
} yield NetworkFeesResult(remoteNodeId,
|
||||||
channelId,
|
channelId,
|
||||||
|
@ -1318,7 +1335,7 @@ object JsonReaders {
|
||||||
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
||||||
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
||||||
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield WebSocketEvent.PaymentRelayed(amountIn,
|
} yield WebSocketEvent.PaymentRelayed(amountIn,
|
||||||
amountOut,
|
amountOut,
|
||||||
|
@ -1333,7 +1350,7 @@ object JsonReaders {
|
||||||
for {
|
for {
|
||||||
amount <- (js \ "amount").validate[MilliSatoshis]
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
fromChannelId <- (js \ "fromChannelId").validate[FundedChannelId]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield WebSocketEvent.PaymentReceived.Part(amount,
|
} yield WebSocketEvent.PaymentReceived.Part(amount,
|
||||||
fromChannelId,
|
fromChannelId,
|
||||||
|
@ -1355,7 +1372,7 @@ object JsonReaders {
|
||||||
id <- (js \ "id").validate[PaymentId]
|
id <- (js \ "id").validate[PaymentId]
|
||||||
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
||||||
failures <- (js \ "failures").validate[Vector[JsObject]]
|
failures <- (js \ "failures").validate[Vector[JsObject]]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield WebSocketEvent.PaymentFailed(id,
|
} yield WebSocketEvent.PaymentFailed(id,
|
||||||
paymentHash,
|
paymentHash,
|
||||||
|
@ -1370,7 +1387,7 @@ object JsonReaders {
|
||||||
amount <- (js \ "amount").validate[MilliSatoshis]
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
feesPaid <- (js \ "feesPaid").validate[MilliSatoshis]
|
feesPaid <- (js \ "feesPaid").validate[MilliSatoshis]
|
||||||
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
toChannelId <- (js \ "toChannelId").validate[FundedChannelId]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield WebSocketEvent.PaymentSent.Part(id,
|
} yield WebSocketEvent.PaymentSent.Part(id,
|
||||||
amount,
|
amount,
|
||||||
|
@ -1398,7 +1415,7 @@ object JsonReaders {
|
||||||
for {
|
for {
|
||||||
amount <- (js \ "amount").validate[MilliSatoshis]
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
paymentHash <- (js \ "paymentHash").validate[Sha256Digest]
|
||||||
timestamp <- (js \ "timestamp")
|
timestamp <- (js \ "timestamp" \ "unix")
|
||||||
.validate[Instant](instantReadsMilliseconds)
|
.validate[Instant](instantReadsMilliseconds)
|
||||||
} yield WebSocketEvent.PaymentSettlingOnchain(amount,
|
} yield WebSocketEvent.PaymentSettlingOnchain(amount,
|
||||||
paymentHash,
|
paymentHash,
|
||||||
|
@ -1475,4 +1492,28 @@ object JsonReaders {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit val routeReads: Reads[Route] = {
|
||||||
|
Reads { js =>
|
||||||
|
for {
|
||||||
|
amount <- (js \ "amount").validate[MilliSatoshis]
|
||||||
|
nodeIds <- (js \ "nodeIds").validateOpt[Vector[NodeId]]
|
||||||
|
shortChannelIds <- (js \ "shortChannelIds")
|
||||||
|
.validateOpt[Vector[ShortChannelId]]
|
||||||
|
} yield {
|
||||||
|
if (shortChannelIds.nonEmpty) {
|
||||||
|
ChannelRoute(amount, shortChannelIds.get)
|
||||||
|
} else {
|
||||||
|
NodeRoute(amount, nodeIds.getOrElse(Vector.empty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val findRouteResultReads: Reads[Vector[Route]] = {
|
||||||
|
Reads { js =>
|
||||||
|
(js \ "routes").validate[JsArray].map(_.value.toVector.map(_.as[Route]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.bitcoins.core.protocol.ln.node
|
package org.bitcoins.core.protocol.ln.node
|
||||||
|
|
||||||
|
import org.bitcoins.core.protocol.ln.node.FeatureSupport.{Mandatory, Optional}
|
||||||
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
||||||
sealed trait FeatureSupport
|
sealed trait FeatureSupport
|
||||||
|
|
||||||
object FeatureSupport {
|
object FeatureSupport {
|
||||||
|
@ -18,111 +21,321 @@ sealed trait Feature {
|
||||||
def mandatory: Int
|
def mandatory: Int
|
||||||
def optional: Int = mandatory + 1
|
def optional: Int = mandatory + 1
|
||||||
|
|
||||||
def supportBit(support: FeatureSupport): Int =
|
def supportBit(support: FeatureSupport): Int = support match {
|
||||||
support match {
|
case Mandatory => mandatory
|
||||||
case FeatureSupport.Mandatory => mandatory
|
case Optional => optional
|
||||||
case FeatureSupport.Optional => optional
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override def toString = rfcName
|
override def toString = rfcName
|
||||||
}
|
}
|
||||||
|
|
||||||
object Feature {
|
/** Feature scope as defined in Bolt 9. */
|
||||||
|
/** Feature that should be advertised in init messages. */
|
||||||
|
trait InitFeature extends Feature
|
||||||
|
|
||||||
case object OptionDataLossProtect extends Feature {
|
/** Feature that should be advertised in node announcements. */
|
||||||
|
trait NodeFeature extends Feature
|
||||||
|
|
||||||
|
/** Feature that should be advertised in invoices. */
|
||||||
|
trait InvoiceFeature extends Feature
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
case class UnknownFeature(bitIndex: Int)
|
||||||
|
|
||||||
|
case class Features[T <: Feature](
|
||||||
|
activated: Map[T, FeatureSupport],
|
||||||
|
unknown: Set[UnknownFeature] = Set.empty) {
|
||||||
|
|
||||||
|
def isEmpty: Boolean = activated.isEmpty && unknown.isEmpty
|
||||||
|
|
||||||
|
def hasFeature(feature: T, support: Option[FeatureSupport] = None): Boolean =
|
||||||
|
support match {
|
||||||
|
case Some(s) => activated.get(feature).contains(s)
|
||||||
|
case None => activated.contains(feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
def hasPluginFeature(feature: UnknownFeature): Boolean =
|
||||||
|
unknown.contains(feature)
|
||||||
|
|
||||||
|
/** NB: this method is not reflexive, see [[Features.areCompatible]] if you want symmetric validation. */
|
||||||
|
def areSupported(remoteFeatures: Features[T]): Boolean = {
|
||||||
|
// we allow unknown odd features (it's ok to be odd)
|
||||||
|
val unknownFeaturesOk = remoteFeatures.unknown.forall(_.bitIndex % 2 == 1)
|
||||||
|
// we verify that we activated every mandatory feature they require
|
||||||
|
val knownFeaturesOk = remoteFeatures.activated.forall {
|
||||||
|
case (_, Optional) => true
|
||||||
|
case (feature, Mandatory) => hasFeature(feature)
|
||||||
|
}
|
||||||
|
unknownFeaturesOk && knownFeaturesOk
|
||||||
|
}
|
||||||
|
|
||||||
|
def initFeatures(): Features[InitFeature] =
|
||||||
|
Features(activated.collect { case (f: InitFeature, s) => (f, s) }, unknown)
|
||||||
|
|
||||||
|
def nodeAnnouncementFeatures(): Features[NodeFeature] =
|
||||||
|
Features(activated.collect { case (f: NodeFeature, s) => (f, s) }, unknown)
|
||||||
|
|
||||||
|
def invoiceFeatures(): Features[InvoiceFeature] = Features(
|
||||||
|
activated.collect { case (f: InvoiceFeature, s) => (f, s) },
|
||||||
|
unknown)
|
||||||
|
|
||||||
|
def unscoped(): Features[Feature] = Features[Feature](
|
||||||
|
activated.collect { case (f, s) => (f: Feature, s) },
|
||||||
|
unknown)
|
||||||
|
|
||||||
|
def toByteVector: ByteVector = {
|
||||||
|
val activatedFeatureBytes = toByteVectorFromIndex(activated.map {
|
||||||
|
case (feature, support) => feature.supportBit(support)
|
||||||
|
}.toSet)
|
||||||
|
val unknownFeatureBytes = toByteVectorFromIndex(unknown.map(_.bitIndex))
|
||||||
|
val maxSize = activatedFeatureBytes.size.max(unknownFeatureBytes.size)
|
||||||
|
activatedFeatureBytes.padLeft(maxSize) | unknownFeatureBytes.padLeft(
|
||||||
|
maxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def toByteVectorFromIndex(indexes: Set[Int]): ByteVector = {
|
||||||
|
if (indexes.isEmpty) return ByteVector.empty
|
||||||
|
// When converting from BitVector to ByteVector, scodec pads right instead of left, so we make sure we pad to bytes *before* setting feature bits.
|
||||||
|
var buf = BitVector.fill(indexes.max + 1)(high = false).bytes.bits
|
||||||
|
indexes.foreach { i => buf = buf.set(i) }
|
||||||
|
buf.reverse.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = {
|
||||||
|
val a = activated
|
||||||
|
.map { case (feature, support) => feature.rfcName + ":" + support }
|
||||||
|
.mkString(",")
|
||||||
|
val u = unknown.map(_.bitIndex).mkString(",")
|
||||||
|
s"$a" + (if (unknown.nonEmpty) s" (unknown=$u)" else "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Features {
|
||||||
|
|
||||||
|
def empty[T <: Feature]: Features[T] =
|
||||||
|
Features[T](Map.empty[T, FeatureSupport])
|
||||||
|
|
||||||
|
def apply[T <: Feature](features: (T, FeatureSupport)*): Features[T] =
|
||||||
|
Features[T](features.toMap)
|
||||||
|
|
||||||
|
def apply(bytes: ByteVector): Features[Feature] = apply(bytes.bits)
|
||||||
|
|
||||||
|
def apply(bits: BitVector): Features[Feature] = {
|
||||||
|
val all = bits.toIndexedSeq.reverse.zipWithIndex.collect {
|
||||||
|
case (true, idx) if knownFeatures.exists(_.optional == idx) =>
|
||||||
|
Right((knownFeatures.find(_.optional == idx).get, Optional))
|
||||||
|
case (true, idx) if knownFeatures.exists(_.mandatory == idx) =>
|
||||||
|
Right((knownFeatures.find(_.mandatory == idx).get, Mandatory))
|
||||||
|
case (true, idx) => Left(UnknownFeature(idx))
|
||||||
|
}
|
||||||
|
Features[Feature](
|
||||||
|
activated = all.collect { case Right((feature, support)) =>
|
||||||
|
feature -> support
|
||||||
|
}.toMap,
|
||||||
|
unknown = all.collect { case Left(inf) => inf }.toSet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case object DataLossProtect
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature {
|
||||||
val rfcName = "option_data_loss_protect"
|
val rfcName = "option_data_loss_protect"
|
||||||
val mandatory = 0
|
val mandatory = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
case object InitialRoutingSync extends Feature {
|
case object InitialRoutingSync extends Feature with InitFeature {
|
||||||
val rfcName = "initial_routing_sync"
|
val rfcName = "initial_routing_sync"
|
||||||
// reserved but not used as per lightningnetwork/lightning-rfc/pull/178
|
// reserved but not used as per lightningnetwork/lightning-rfc/pull/178
|
||||||
val mandatory = 2
|
val mandatory = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
case object OptionUpfrontShutdownScript extends Feature {
|
case object UpfrontShutdownScript
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature {
|
||||||
val rfcName = "option_upfront_shutdown_script"
|
val rfcName = "option_upfront_shutdown_script"
|
||||||
val mandatory = 4
|
val mandatory = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
case object ChannelRangeQueries extends Feature {
|
case object ChannelRangeQueries
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature {
|
||||||
val rfcName = "gossip_queries"
|
val rfcName = "gossip_queries"
|
||||||
val mandatory = 6
|
val mandatory = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
case object VariableLengthOnion extends Feature {
|
case object VariableLengthOnion
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature
|
||||||
|
with InvoiceFeature {
|
||||||
val rfcName = "var_onion_optin"
|
val rfcName = "var_onion_optin"
|
||||||
val mandatory = 8
|
val mandatory = 8
|
||||||
}
|
}
|
||||||
|
|
||||||
case object ChannelRangeQueriesExtended extends Feature {
|
case object ChannelRangeQueriesExtended
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature {
|
||||||
val rfcName = "gossip_queries_ex"
|
val rfcName = "gossip_queries_ex"
|
||||||
val mandatory = 10
|
val mandatory = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
case object StaticRemoteKey extends Feature {
|
case object StaticRemoteKey
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature {
|
||||||
val rfcName = "option_static_remotekey"
|
val rfcName = "option_static_remotekey"
|
||||||
val mandatory = 12
|
val mandatory = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
case object PaymentSecret extends Feature {
|
case object PaymentSecret
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature
|
||||||
|
with InvoiceFeature {
|
||||||
val rfcName = "payment_secret"
|
val rfcName = "payment_secret"
|
||||||
val mandatory = 14
|
val mandatory = 14
|
||||||
}
|
}
|
||||||
|
|
||||||
case object BasicMultiPartPayment extends Feature {
|
case object BasicMultiPartPayment
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature
|
||||||
|
with InvoiceFeature {
|
||||||
val rfcName = "basic_mpp"
|
val rfcName = "basic_mpp"
|
||||||
val mandatory = 16
|
val mandatory = 16
|
||||||
}
|
}
|
||||||
|
|
||||||
case object Wumbo extends Feature {
|
case object Wumbo extends Feature with InitFeature with NodeFeature {
|
||||||
val rfcName = "option_support_large_channel"
|
val rfcName = "option_support_large_channel"
|
||||||
val mandatory = 18
|
val mandatory = 18
|
||||||
}
|
}
|
||||||
|
|
||||||
case object AnchorOutputs extends Feature {
|
case object AnchorOutputs extends Feature with InitFeature with NodeFeature {
|
||||||
val rfcName = "option_anchor_outputs"
|
val rfcName = "option_anchor_outputs"
|
||||||
val mandatory = 20
|
val mandatory = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
case object AnchorOutputsZeroFeeHtlcTx extends Feature {
|
case object AnchorOutputsZeroFeeHtlcTx
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature {
|
||||||
val rfcName = "option_anchors_zero_fee_htlc_tx"
|
val rfcName = "option_anchors_zero_fee_htlc_tx"
|
||||||
val mandatory = 22
|
val mandatory = 22
|
||||||
}
|
}
|
||||||
|
|
||||||
case object ShutdownAnySegwit extends Feature {
|
case object ShutdownAnySegwit
|
||||||
|
extends Feature
|
||||||
|
with InitFeature
|
||||||
|
with NodeFeature {
|
||||||
val rfcName = "option_shutdown_anysegwit"
|
val rfcName = "option_shutdown_anysegwit"
|
||||||
val mandatory = 26
|
val mandatory = 26
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case object DualFunding extends Feature with InitFeature with NodeFeature {
|
||||||
|
val rfcName = "option_dual_fund"
|
||||||
|
val mandatory = 28
|
||||||
|
}
|
||||||
|
|
||||||
|
case object OnionMessages extends Feature with InitFeature with NodeFeature {
|
||||||
|
val rfcName = "option_onion_messages"
|
||||||
|
val mandatory = 38
|
||||||
|
}
|
||||||
|
|
||||||
|
case object ChannelType extends Feature with InitFeature with NodeFeature {
|
||||||
|
val rfcName = "option_channel_type"
|
||||||
|
val mandatory = 44
|
||||||
|
}
|
||||||
|
|
||||||
|
case object PaymentMetadata extends Feature with InvoiceFeature {
|
||||||
|
val rfcName = "option_payment_metadata"
|
||||||
|
val mandatory = 48
|
||||||
|
}
|
||||||
|
|
||||||
|
case object KeySend extends Feature with NodeFeature {
|
||||||
|
val rfcName = "keysend"
|
||||||
|
val mandatory = 54
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: @t-bast: update feature bits once spec-ed (currently reserved here: https://github.com/lightningnetwork/lightning-rfc/issues/605)
|
// TODO: @t-bast: update feature bits once spec-ed (currently reserved here: https://github.com/lightningnetwork/lightning-rfc/issues/605)
|
||||||
// We're not advertising these bits yet in our announcements, clients have to assume support.
|
// We're not advertising these bits yet in our announcements, clients have to assume support.
|
||||||
// This is why we haven't added them yet to `areSupported`.
|
// This is why we haven't added them yet to `areSupported`.
|
||||||
case object TrampolinePayment extends Feature {
|
// The version of trampoline enabled by this feature bit does not match the latest spec PR: once the spec is accepted,
|
||||||
val rfcName = "trampoline_payment"
|
// we will introduce a new version of trampoline that will work in parallel to this legacy one, until we can safely
|
||||||
val mandatory = 50
|
// deprecate it.
|
||||||
}
|
case object TrampolinePaymentPrototype
|
||||||
|
extends Feature
|
||||||
case object KeySend extends Feature {
|
with InitFeature
|
||||||
val rfcName = "keysend"
|
with NodeFeature
|
||||||
val mandatory = 54
|
with InvoiceFeature {
|
||||||
|
val rfcName = "trampoline_payment_prototype"
|
||||||
|
val mandatory = 148
|
||||||
}
|
}
|
||||||
|
|
||||||
val knownFeatures: Set[Feature] = Set(
|
val knownFeatures: Set[Feature] = Set(
|
||||||
OptionDataLossProtect,
|
DataLossProtect,
|
||||||
InitialRoutingSync,
|
InitialRoutingSync,
|
||||||
OptionUpfrontShutdownScript,
|
UpfrontShutdownScript,
|
||||||
ChannelRangeQueries,
|
ChannelRangeQueries,
|
||||||
VariableLengthOnion,
|
VariableLengthOnion,
|
||||||
ChannelRangeQueriesExtended,
|
ChannelRangeQueriesExtended,
|
||||||
PaymentSecret,
|
PaymentSecret,
|
||||||
BasicMultiPartPayment,
|
BasicMultiPartPayment,
|
||||||
Wumbo,
|
Wumbo,
|
||||||
TrampolinePayment,
|
|
||||||
StaticRemoteKey,
|
StaticRemoteKey,
|
||||||
AnchorOutputs,
|
AnchorOutputs,
|
||||||
AnchorOutputsZeroFeeHtlcTx,
|
AnchorOutputsZeroFeeHtlcTx,
|
||||||
ShutdownAnySegwit,
|
ShutdownAnySegwit,
|
||||||
|
DualFunding,
|
||||||
|
OnionMessages,
|
||||||
|
ChannelType,
|
||||||
|
PaymentMetadata,
|
||||||
|
TrampolinePaymentPrototype,
|
||||||
KeySend
|
KeySend
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Features may depend on other features, as specified in Bolt 9.
|
||||||
|
private val featuresDependency = Map(
|
||||||
|
ChannelRangeQueriesExtended -> (ChannelRangeQueries :: Nil),
|
||||||
|
PaymentSecret -> (VariableLengthOnion :: Nil),
|
||||||
|
BasicMultiPartPayment -> (PaymentSecret :: Nil),
|
||||||
|
AnchorOutputs -> (StaticRemoteKey :: Nil),
|
||||||
|
AnchorOutputsZeroFeeHtlcTx -> (StaticRemoteKey :: Nil),
|
||||||
|
DualFunding -> (AnchorOutputsZeroFeeHtlcTx :: Nil),
|
||||||
|
TrampolinePaymentPrototype -> (PaymentSecret :: Nil),
|
||||||
|
KeySend -> (VariableLengthOnion :: Nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
case class FeatureException(message: String)
|
||||||
|
extends IllegalArgumentException(message)
|
||||||
|
|
||||||
|
def validateFeatureGraph[T <: Feature](
|
||||||
|
features: Features[T]): Option[FeatureException] =
|
||||||
|
featuresDependency.collectFirst {
|
||||||
|
case (feature, dependencies)
|
||||||
|
if features.unscoped().hasFeature(feature) && dependencies.exists(d =>
|
||||||
|
!features.unscoped().hasFeature(d)) =>
|
||||||
|
FeatureException(
|
||||||
|
s"$feature is set but is missing a dependency (${dependencies
|
||||||
|
.filter(d => !features.unscoped().hasFeature(d))
|
||||||
|
.mkString(" and ")})")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if both feature sets are compatible. */
|
||||||
|
def areCompatible[T <: Feature](
|
||||||
|
ours: Features[T],
|
||||||
|
theirs: Features[T]): Boolean =
|
||||||
|
ours.areSupported(theirs) && theirs.areSupported(ours)
|
||||||
|
|
||||||
|
/** returns true if both have at least optional support */
|
||||||
|
def canUseFeature[T <: Feature](
|
||||||
|
localFeatures: Features[T],
|
||||||
|
remoteFeatures: Features[T],
|
||||||
|
feature: T): Boolean = {
|
||||||
|
localFeatures.hasFeature(feature) && remoteFeatures.hasFeature(feature)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
package org.bitcoins.core.protocol.ln.routing
|
package org.bitcoins.core.protocol.ln.routing
|
||||||
|
|
||||||
import org.bitcoins.core.protocol.ln.channel.ShortChannelId
|
import org.bitcoins.core.protocol.ln.channel.ShortChannelId
|
||||||
|
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
||||||
import org.bitcoins.core.protocol.ln.node.NodeId
|
import org.bitcoins.core.protocol.ln.node.NodeId
|
||||||
|
|
||||||
/** Represent differet types of LN routes. Supports node and channel routes.
|
/** Represent differet types of LN routes. Supports node and channel routes.
|
||||||
*/
|
*/
|
||||||
sealed trait Route
|
sealed trait Route {
|
||||||
|
val amount: MilliSatoshis
|
||||||
|
}
|
||||||
|
|
||||||
case class NodeRoute(ids: Vector[NodeId]) extends Route
|
case class NodeRoute(
|
||||||
|
override val amount: MilliSatoshis,
|
||||||
|
nodeIds: Vector[NodeId])
|
||||||
|
extends Route
|
||||||
|
|
||||||
case class ChannelRoute(ids: Vector[ShortChannelId]) extends Route
|
case class ChannelRoute(
|
||||||
|
override val amount: MilliSatoshis,
|
||||||
|
shortChannelIds: Vector[ShortChannelId])
|
||||||
|
extends Route
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.bitcoins.core.protocol.ln.channel.{
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.protocol.ln.currency._
|
import org.bitcoins.core.protocol.ln.currency._
|
||||||
import org.bitcoins.core.protocol.ln.node.NodeId
|
import org.bitcoins.core.protocol.ln.node.NodeId
|
||||||
|
import org.bitcoins.core.protocol.ln.routing.NodeRoute
|
||||||
import org.bitcoins.core.protocol.ln.{
|
import org.bitcoins.core.protocol.ln.{
|
||||||
LnHumanReadablePart,
|
LnHumanReadablePart,
|
||||||
LnInvoice,
|
LnInvoice,
|
||||||
|
@ -168,9 +169,9 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
||||||
info.publicAddresses
|
info.publicAddresses
|
||||||
.map(_.getHostString)
|
.map(_.getHostString)
|
||||||
.exists(_.endsWith(".onion")))
|
.exists(_.endsWith(".onion")))
|
||||||
route <- client1.findRoute(invoice, None)
|
routes <- client1.findRoute(invoice, None)
|
||||||
} yield {
|
} yield {
|
||||||
route.ids.size == 4
|
routes.head.asInstanceOf[NodeRoute].nodeIds.size == 4
|
||||||
}).recover {
|
}).recover {
|
||||||
case err: RuntimeException
|
case err: RuntimeException
|
||||||
if err.getMessage.contains("route not found") =>
|
if err.getMessage.contains("route not found") =>
|
||||||
|
@ -189,7 +190,7 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
||||||
.flatMap(_.getInfo)
|
.flatMap(_.getInfo)
|
||||||
.flatMap(info =>
|
.flatMap(info =>
|
||||||
firstClientF.flatMap(_.findRoute(info.nodeId, MilliSatoshis(100))))
|
firstClientF.flatMap(_.findRoute(info.nodeId, MilliSatoshis(100))))
|
||||||
.map(route => route.ids.length == 4)
|
.map(route => route.head.asInstanceOf[NodeRoute].nodeIds.length == 4)
|
||||||
.recover {
|
.recover {
|
||||||
case err: RuntimeException
|
case err: RuntimeException
|
||||||
if err.getMessage.contains("route not found") =>
|
if err.getMessage.contains("route not found") =>
|
||||||
|
@ -416,6 +417,7 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
||||||
maxTries = 60)
|
maxTries = 60)
|
||||||
} yield succeed
|
} yield succeed
|
||||||
}
|
}
|
||||||
|
|
||||||
it should "be able to open and close a channel" in {
|
it should "be able to open and close a channel" in {
|
||||||
|
|
||||||
val changeAddrF = bitcoindRpcClientF.flatMap(_.getNewAddress)
|
val changeAddrF = bitcoindRpcClientF.flatMap(_.getNewAddress)
|
||||||
|
@ -567,7 +569,7 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
||||||
invoice <- otherClient.createInvoice("foo", amt, preimage)
|
invoice <- otherClient.createInvoice("foo", amt, preimage)
|
||||||
route <- client.findRoute(otherClientNodeId, amt)
|
route <- client.findRoute(otherClientNodeId, amt)
|
||||||
result <- client.sendToRoute(invoice,
|
result <- client.sendToRoute(invoice,
|
||||||
route,
|
route.head,
|
||||||
amt,
|
amt,
|
||||||
invoice.lnTags.paymentHash.hash,
|
invoice.lnTags.paymentHash.hash,
|
||||||
finalCltvExpiry = 144,
|
finalCltvExpiry = 144,
|
||||||
|
@ -582,7 +584,7 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
||||||
invoice.lnTags.paymentHash.hash)
|
invoice.lnTags.paymentHash.hash)
|
||||||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client,
|
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client,
|
||||||
result.parentId)
|
result.parentId)
|
||||||
succeeded <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
succeeded <- client.getSentInfo(result.parentId)
|
||||||
} yield {
|
} yield {
|
||||||
assert(otherClientNodeId == invoice.nodeId)
|
assert(otherClientNodeId == invoice.nodeId)
|
||||||
assert(succeeded.nonEmpty)
|
assert(succeeded.nonEmpty)
|
||||||
|
|
|
@ -19,8 +19,8 @@ TaskKeys.downloadEclair := {
|
||||||
Files.createDirectories(binaryDir)
|
Files.createDirectories(binaryDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
val version = "0.6.2"
|
val version = "0.7.0"
|
||||||
val commit = "6817d6f"
|
val commit = "a804905"
|
||||||
|
|
||||||
logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")
|
logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ TaskKeys.downloadEclair := {
|
||||||
.mkString
|
.mkString
|
||||||
|
|
||||||
val expectedHash =
|
val expectedHash =
|
||||||
"e2407173036d9e2176c129f2328018c543c732a96d1f05c0fb35864c15efc9ba"
|
"482a00cc597fd4cc471a1b4035c72a440a9ab336ef5b9006629d2fd717b223b4"
|
||||||
|
|
||||||
if (hash.equalsIgnoreCase(expectedHash)) {
|
if (hash.equalsIgnoreCase(expectedHash)) {
|
||||||
logger.info(s"Download complete and verified, unzipping result")
|
logger.info(s"Download complete and verified, unzipping result")
|
||||||
|
|
|
@ -9,7 +9,7 @@ import org.bitcoins.core.protocol.ln.channel.{
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
||||||
import org.bitcoins.core.protocol.ln.node.{NodeId, NodeUri}
|
import org.bitcoins.core.protocol.ln.node.{NodeId, NodeUri}
|
||||||
import org.bitcoins.core.protocol.ln.routing.{NodeRoute, Route}
|
import org.bitcoins.core.protocol.ln.routing.Route
|
||||||
import org.bitcoins.core.protocol.ln.{LnInvoice, LnParams, PaymentPreimage}
|
import org.bitcoins.core.protocol.ln.{LnInvoice, LnParams, PaymentPreimage}
|
||||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||||
import org.bitcoins.core.protocol.{Address, BitcoinAddress}
|
import org.bitcoins.core.protocol.{Address, BitcoinAddress}
|
||||||
|
@ -72,13 +72,15 @@ trait EclairApi {
|
||||||
|
|
||||||
def close(id: ChannelId, spk: ScriptPubKey): Future[ChannelCommandResult]
|
def close(id: ChannelId, spk: ScriptPubKey): Future[ChannelCommandResult]
|
||||||
|
|
||||||
def findRoute(nodeId: NodeId, amountMsat: MilliSatoshis): Future[NodeRoute]
|
def findRoute(
|
||||||
|
nodeId: NodeId,
|
||||||
|
amountMsat: MilliSatoshis): Future[Vector[Route]]
|
||||||
|
|
||||||
def findRoute(invoice: LnInvoice): Future[NodeRoute]
|
def findRoute(invoice: LnInvoice): Future[Vector[Route]]
|
||||||
|
|
||||||
def findRoute(
|
def findRoute(
|
||||||
invoice: LnInvoice,
|
invoice: LnInvoice,
|
||||||
amountMsat: MilliSatoshis): Future[NodeRoute]
|
amountMsat: MilliSatoshis): Future[Vector[Route]]
|
||||||
|
|
||||||
def forceClose(channelId: ChannelId): Future[ChannelCommandResult]
|
def forceClose(channelId: ChannelId): Future[ChannelCommandResult]
|
||||||
|
|
||||||
|
|
|
@ -143,30 +143,29 @@ class EclairRpcClient(
|
||||||
|
|
||||||
override def findRoute(
|
override def findRoute(
|
||||||
nodeId: NodeId,
|
nodeId: NodeId,
|
||||||
amountMsat: MilliSatoshis): Future[NodeRoute] = {
|
amountMsat: MilliSatoshis): Future[Vector[Route]] = {
|
||||||
eclairCall[Vector[NodeId]](
|
eclairCall[Vector[Route]]("findroutetonode",
|
||||||
"findroutetonode",
|
"nodeId" -> nodeId.toString,
|
||||||
"nodeId" -> nodeId.toString,
|
"amountMsat" -> amountMsat.toBigDecimal.toString)
|
||||||
"amountMsat" -> amountMsat.toBigDecimal.toString).map(NodeRoute.apply)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def findRoute(invoice: LnInvoice): Future[NodeRoute] = {
|
override def findRoute(invoice: LnInvoice): Future[Vector[Route]] = {
|
||||||
findRoute(invoice, None)
|
findRoute(invoice, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def findRoute(
|
override def findRoute(
|
||||||
invoice: LnInvoice,
|
invoice: LnInvoice,
|
||||||
amount: MilliSatoshis): Future[NodeRoute] = {
|
amount: MilliSatoshis): Future[Vector[Route]] = {
|
||||||
findRoute(invoice, Some(amount))
|
findRoute(invoice, Some(amount))
|
||||||
}
|
}
|
||||||
|
|
||||||
def findRoute(
|
def findRoute(
|
||||||
invoice: LnInvoice,
|
invoice: LnInvoice,
|
||||||
amountMsat: Option[MilliSatoshis]): Future[NodeRoute] = {
|
amountMsat: Option[MilliSatoshis]): Future[Vector[Route]] = {
|
||||||
val params = Seq(
|
val params = Seq(
|
||||||
Some("invoice" -> invoice.toString),
|
Some("invoice" -> invoice.toString),
|
||||||
amountMsat.map(x => "amountMsat" -> x.toBigDecimal.toString)).flatten
|
amountMsat.map(x => "amountMsat" -> x.toBigDecimal.toString)).flatten
|
||||||
eclairCall[Vector[NodeId]]("findroute", params: _*).map(NodeRoute.apply)
|
eclairCall[Vector[Route]]("findroute", params: _*)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def forceClose(
|
override def forceClose(
|
||||||
|
@ -377,8 +376,8 @@ class EclairRpcClient(
|
||||||
//register callback that publishes a payment to our actor system's
|
//register callback that publishes a payment to our actor system's
|
||||||
//event stream,
|
//event stream,
|
||||||
receivedInfoF.foreach {
|
receivedInfoF.foreach {
|
||||||
case None |
|
case None | Some(
|
||||||
Some(IncomingPayment(_, _, _, IncomingPaymentStatus.Pending)) =>
|
IncomingPayment(_, _, _, _, IncomingPaymentStatus.Pending)) =>
|
||||||
if (attempts.incrementAndGet() >= maxAttempts) {
|
if (attempts.incrementAndGet() >= maxAttempts) {
|
||||||
// too many tries to get info about a payment
|
// too many tries to get info about a payment
|
||||||
// either Eclair is down or the payment is still in PENDING state for some reason
|
// either Eclair is down or the payment is still in PENDING state for some reason
|
||||||
|
@ -513,8 +512,8 @@ class EclairRpcClient(
|
||||||
parentId: Option[PaymentId],
|
parentId: Option[PaymentId],
|
||||||
externalId: Option[String]): Future[SendToRouteResult] = {
|
externalId: Option[String]): Future[SendToRouteResult] = {
|
||||||
val ids = route match {
|
val ids = route match {
|
||||||
case NodeRoute(ids) => "nodeIds" -> ids.mkString(",")
|
case NodeRoute(_, ids) => "nodeIds" -> ids.mkString(",")
|
||||||
case ChannelRoute(ids) => "shortChannelIds" -> ids.mkString(",")
|
case ChannelRoute(_, ids) => "shortChannelIds" -> ids.mkString(",")
|
||||||
}
|
}
|
||||||
val params = Seq(
|
val params = Seq(
|
||||||
"invoice" -> invoice.toString,
|
"invoice" -> invoice.toString,
|
||||||
|
@ -954,10 +953,10 @@ object EclairRpcClient {
|
||||||
implicit system: ActorSystem) = new EclairRpcClient(instance, binary)
|
implicit system: ActorSystem) = new EclairRpcClient(instance, binary)
|
||||||
|
|
||||||
/** The current commit we support of Eclair */
|
/** The current commit we support of Eclair */
|
||||||
private[bitcoins] val commit = "6817d6f"
|
private[bitcoins] val commit = "a804905"
|
||||||
|
|
||||||
/** The current version we support of Eclair */
|
/** The current version we support of Eclair */
|
||||||
private[bitcoins] val version = "0.6.2"
|
private[bitcoins] val version = "0.7.0"
|
||||||
|
|
||||||
/** The bitcoind version that eclair is officially tested & supported with by ACINQ
|
/** The bitcoind version that eclair is officially tested & supported with by ACINQ
|
||||||
* @see https://github.com/ACINQ/eclair/releases/tag/v0.6.2
|
* @see https://github.com/ACINQ/eclair/releases/tag/v0.6.2
|
||||||
|
|
|
@ -110,16 +110,16 @@ trait EclairRpcTestUtil extends Logging {
|
||||||
"eclair.api.binding-ip" -> "127.0.0.1",
|
"eclair.api.binding-ip" -> "127.0.0.1",
|
||||||
"eclair.api.password" -> "abc123",
|
"eclair.api.password" -> "abc123",
|
||||||
"eclair.api.port" -> apiPort,
|
"eclair.api.port" -> apiPort,
|
||||||
"eclair.mindepth-blocks" -> 2,
|
"eclair.channel.mindepth-blocks" -> 2,
|
||||||
"eclair.max-htlc-value-in-flight-msat" -> 100000000000L,
|
"eclair.channel.max-htlc-value-in-flight-msat" -> 100000000000L,
|
||||||
"eclair.router.broadcast-interval" -> "2 second",
|
"eclair.router.broadcast-interval" -> "2 second",
|
||||||
"eclair.auto-reconnect" -> false,
|
"eclair.auto-reconnect" -> false,
|
||||||
"eclair.to-remote-delay-blocks" -> 144,
|
"eclair.channel.to-remote-delay-blocks" -> 144,
|
||||||
"eclair.db.regtest.url" -> "jdbc:sqlite:regtest/",
|
"eclair.db.regtest.url" -> "jdbc:sqlite:regtest/",
|
||||||
"eclair.max-payment-fee" -> 10, // avoid complaints about too high fees
|
"eclair.max-payment-fee" -> 10, // avoid complaints about too high fees
|
||||||
"eclair.alias" -> "suredbits",
|
"eclair.alias" -> "suredbits",
|
||||||
"eclair.fulfill-safety-before-timeout-blocks" -> 1,
|
"eclair.channel.fulfill-safety-before-timeout-blocks" -> 1,
|
||||||
"eclair.min-final-expiry-delta-blocks" -> 2,
|
"eclair.channel.min-final-expiry-delta-blocks" -> 2,
|
||||||
"eclair.features.keysend" -> "optional"
|
"eclair.features.keysend" -> "optional"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue