mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-19 18:10:42 +01:00
Features should be a Map (#1715)
We previously used a Set, which means you could theoretically have a feature that is both activated as `optional` and `mandatory`. We change that to be a Map `feature -> support`.
This commit is contained in:
parent
163700a232
commit
844829a9b1
@ -18,7 +18,6 @@ package fr.acinq.eclair
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
|
||||
import fr.acinq.eclair.Features.{BasicMultiPartPayment, PaymentSecret}
|
||||
import scodec.bits.{BitVector, ByteVector}
|
||||
|
||||
/**
|
||||
@ -40,8 +39,8 @@ trait Feature {
|
||||
def optional: Int = mandatory + 1
|
||||
|
||||
def supportBit(support: FeatureSupport): Int = support match {
|
||||
case FeatureSupport.Mandatory => mandatory
|
||||
case FeatureSupport.Optional => optional
|
||||
case Mandatory => mandatory
|
||||
case Optional => optional
|
||||
}
|
||||
|
||||
override def toString = rfcName
|
||||
@ -49,15 +48,13 @@ trait Feature {
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
case class ActivatedFeature(feature: Feature, support: FeatureSupport)
|
||||
|
||||
case class UnknownFeature(bitIndex: Int)
|
||||
|
||||
case class Features(activated: Set[ActivatedFeature], unknown: Set[UnknownFeature] = Set.empty) {
|
||||
case class Features(activated: Map[Feature, FeatureSupport], unknown: Set[UnknownFeature] = Set.empty) {
|
||||
|
||||
def hasFeature(feature: Feature, support: Option[FeatureSupport] = None): Boolean = support match {
|
||||
case Some(s) => activated.contains(ActivatedFeature(feature, s))
|
||||
case None => hasFeature(feature, Some(Optional)) || hasFeature(feature, Some(Mandatory))
|
||||
case Some(s) => activated.get(feature).contains(s)
|
||||
case None => activated.contains(feature)
|
||||
}
|
||||
|
||||
def hasPluginFeature(feature: UnknownFeature): Boolean = unknown.contains(feature)
|
||||
@ -68,14 +65,14 @@ case class Features(activated: Set[ActivatedFeature], unknown: Set[UnknownFeatur
|
||||
val unknownFeaturesOk = remoteFeatures.unknown.forall(_.bitIndex % 2 == 1)
|
||||
// we verify that we activated every mandatory feature they require
|
||||
val knownFeaturesOk = remoteFeatures.activated.forall {
|
||||
case ActivatedFeature(_, Optional) => true
|
||||
case ActivatedFeature(feature, Mandatory) => hasFeature(feature)
|
||||
case (_, Optional) => true
|
||||
case (feature, Mandatory) => hasFeature(feature)
|
||||
}
|
||||
unknownFeaturesOk && knownFeaturesOk
|
||||
}
|
||||
|
||||
def toByteVector: ByteVector = {
|
||||
val activatedFeatureBytes = toByteVectorFromIndex(activated.map { case ActivatedFeature(f, s) => f.supportBit(s) })
|
||||
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)
|
||||
@ -85,14 +82,12 @@ case class Features(activated: Set[ActivatedFeature], unknown: Set[UnknownFeatur
|
||||
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)
|
||||
}
|
||||
indexes.foreach { i => buf = buf.set(i) }
|
||||
buf.reverse.bytes
|
||||
}
|
||||
|
||||
override def toString: String = {
|
||||
val a = activated.map(f => f.feature.rfcName + ":" + f.support).mkString(",")
|
||||
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 "")
|
||||
}
|
||||
@ -100,20 +95,20 @@ case class Features(activated: Set[ActivatedFeature], unknown: Set[UnknownFeatur
|
||||
|
||||
object Features {
|
||||
|
||||
def empty = Features(Set.empty[ActivatedFeature])
|
||||
def empty = Features(Map.empty[Feature, FeatureSupport])
|
||||
|
||||
def apply(features: Set[ActivatedFeature]): Features = Features(activated = features)
|
||||
def apply(features: (Feature, FeatureSupport)*): Features = Features(Map.from(features))
|
||||
|
||||
def apply(bytes: ByteVector): Features = apply(bytes.bits)
|
||||
|
||||
def apply(bits: BitVector): Features = {
|
||||
val all = bits.toIndexedSeq.reverse.zipWithIndex.collect {
|
||||
case (true, idx) if knownFeatures.exists(_.optional == idx) => Right(ActivatedFeature(knownFeatures.find(_.optional == idx).get, Optional))
|
||||
case (true, idx) if knownFeatures.exists(_.mandatory == idx) => Right(ActivatedFeature(knownFeatures.find(_.mandatory == idx).get, Mandatory))
|
||||
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(
|
||||
activated = all.collect { case Right(af) => af }.toSet,
|
||||
activated = all.collect { case Right((feature, support)) => feature -> support }.toMap,
|
||||
unknown = all.collect { case Left(inf) => inf }.toSet
|
||||
)
|
||||
}
|
||||
@ -123,15 +118,16 @@ object Features {
|
||||
knownFeatures.flatMap {
|
||||
feature =>
|
||||
getFeature(config, feature.rfcName) match {
|
||||
case Some(support) => Some(ActivatedFeature(feature, support))
|
||||
case Some(support) => Some(feature -> support)
|
||||
case _ => None
|
||||
}
|
||||
})
|
||||
}.toMap)
|
||||
|
||||
/** tries to extract the given feature name from the config, if successful returns its feature support */
|
||||
private def getFeature(config: Config, name: String): Option[FeatureSupport] = {
|
||||
if (!config.hasPath(s"features.$name")) None
|
||||
else {
|
||||
if (!config.hasPath(s"features.$name")) {
|
||||
None
|
||||
} else {
|
||||
config.getString(s"features.$name") match {
|
||||
case support if support == Mandatory.toString => Some(Mandatory)
|
||||
case support if support == Optional.toString => Some(Optional)
|
||||
|
@ -39,7 +39,7 @@ class FeaturesSpec extends AnyFunSuite {
|
||||
}
|
||||
|
||||
test("'initial_routing_sync', 'data_loss_protect' and 'variable_length_onion' features") {
|
||||
val features = Features(Set(ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(OptionDataLossProtect, Optional), ActivatedFeature(VariableLengthOnion, Mandatory)))
|
||||
val features = Features(InitialRoutingSync -> Optional, OptionDataLossProtect -> Optional, VariableLengthOnion -> Mandatory)
|
||||
assert(features.toByteVector == hex"010a")
|
||||
assert(features.hasFeature(OptionDataLossProtect))
|
||||
assert(features.hasFeature(InitialRoutingSync, None))
|
||||
@ -114,87 +114,87 @@ class FeaturesSpec extends AnyFunSuite {
|
||||
),
|
||||
TestCase(
|
||||
Features.empty,
|
||||
Features(Set(ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(VariableLengthOnion, Optional))),
|
||||
Features(InitialRoutingSync -> Optional, VariableLengthOnion -> Optional),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = true,
|
||||
compatible = true
|
||||
),
|
||||
TestCase(
|
||||
Features.empty,
|
||||
Features(Set.empty, Set(UnknownFeature(101), UnknownFeature(103))),
|
||||
Features(activated = Map.empty, Set(UnknownFeature(101), UnknownFeature(103))),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = true,
|
||||
compatible = true
|
||||
),
|
||||
// Same feature set
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(VariableLengthOnion, Mandatory))),
|
||||
Features(Set(ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(VariableLengthOnion, Mandatory))),
|
||||
Features(InitialRoutingSync -> Optional, VariableLengthOnion -> Mandatory),
|
||||
Features(InitialRoutingSync -> Optional, VariableLengthOnion -> Mandatory),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = true,
|
||||
compatible = true
|
||||
),
|
||||
// Many optional features
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(VariableLengthOnion, Optional), ActivatedFeature(ChannelRangeQueries, Optional), ActivatedFeature(PaymentSecret, Optional))),
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Optional), ActivatedFeature(ChannelRangeQueries, Optional), ActivatedFeature(ChannelRangeQueriesExtended, Optional))),
|
||||
Features(InitialRoutingSync -> Optional, VariableLengthOnion -> Optional, ChannelRangeQueries -> Optional, PaymentSecret -> Optional),
|
||||
Features(VariableLengthOnion -> Optional, ChannelRangeQueries -> Optional, ChannelRangeQueriesExtended -> Optional),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = true,
|
||||
compatible = true
|
||||
),
|
||||
// We support their mandatory features
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Optional))),
|
||||
Features(Set(ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(VariableLengthOnion, Mandatory))),
|
||||
Features(VariableLengthOnion -> Optional),
|
||||
Features(InitialRoutingSync -> Optional, VariableLengthOnion -> Mandatory),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = true,
|
||||
compatible = true
|
||||
),
|
||||
// They support our mandatory features
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Mandatory))),
|
||||
Features(Set(ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(VariableLengthOnion, Optional))),
|
||||
Features(VariableLengthOnion -> Mandatory),
|
||||
Features(InitialRoutingSync -> Optional, VariableLengthOnion -> Optional),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = true,
|
||||
compatible = true
|
||||
),
|
||||
// They have unknown optional features
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Optional))),
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Optional)), Set(UnknownFeature(141))),
|
||||
Features(VariableLengthOnion -> Optional),
|
||||
Features(Map[Feature, FeatureSupport](VariableLengthOnion -> Optional), Set(UnknownFeature(141))),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = true,
|
||||
compatible = true
|
||||
),
|
||||
// They have unknown mandatory features
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Optional))),
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Optional)), Set(UnknownFeature(142))),
|
||||
Features(VariableLengthOnion -> Optional),
|
||||
Features(Map[Feature, FeatureSupport](VariableLengthOnion -> Optional), Set(UnknownFeature(142))),
|
||||
oursSupportTheirs = false,
|
||||
theirsSupportOurs = true,
|
||||
compatible = false
|
||||
),
|
||||
// We don't support one of their mandatory features
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(ChannelRangeQueries, Optional))),
|
||||
Features(Set(ActivatedFeature(ChannelRangeQueries, Mandatory), ActivatedFeature(VariableLengthOnion, Mandatory))),
|
||||
Features(ChannelRangeQueries -> Optional),
|
||||
Features(ChannelRangeQueries -> Mandatory, VariableLengthOnion -> Mandatory),
|
||||
oursSupportTheirs = false,
|
||||
theirsSupportOurs = true,
|
||||
compatible = false
|
||||
),
|
||||
// They don't support one of our mandatory features
|
||||
TestCase(
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Mandatory), ActivatedFeature(PaymentSecret, Mandatory))),
|
||||
Features(Set(ActivatedFeature(VariableLengthOnion, Optional))),
|
||||
Features(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory),
|
||||
Features(VariableLengthOnion -> Optional),
|
||||
oursSupportTheirs = true,
|
||||
theirsSupportOurs = false,
|
||||
compatible = false
|
||||
),
|
||||
// nonreg testing of future features (needs to be updated with every new supported mandatory bit)
|
||||
TestCase(Features.empty, Features(Set.empty, Set(UnknownFeature(22))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
|
||||
TestCase(Features.empty, Features(Set.empty, Set(UnknownFeature(23))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true),
|
||||
TestCase(Features.empty, Features(Set.empty, Set(UnknownFeature(24))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
|
||||
TestCase(Features.empty, Features(Set.empty, Set(UnknownFeature(25))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true)
|
||||
TestCase(Features.empty, Features(Map.empty[Feature, FeatureSupport], Set(UnknownFeature(22))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
|
||||
TestCase(Features.empty, Features(Map.empty[Feature, FeatureSupport], Set(UnknownFeature(23))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true),
|
||||
TestCase(Features.empty, Features(Map.empty[Feature, FeatureSupport], Set(UnknownFeature(24))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
|
||||
TestCase(Features.empty, Features(Map.empty[Feature, FeatureSupport], Set(UnknownFeature(25))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true)
|
||||
)
|
||||
|
||||
for (testCase <- testCases) {
|
||||
@ -207,10 +207,10 @@ class FeaturesSpec extends AnyFunSuite {
|
||||
test("features to bytes") {
|
||||
val testCases = Map(
|
||||
hex"" -> Features.empty,
|
||||
hex"0100" -> Features(Set(ActivatedFeature(VariableLengthOnion, Mandatory))),
|
||||
hex"028a8a" -> Features(Set(ActivatedFeature(OptionDataLossProtect, Optional), ActivatedFeature(InitialRoutingSync, Optional), ActivatedFeature(ChannelRangeQueries, Optional), ActivatedFeature(VariableLengthOnion, Optional), ActivatedFeature(ChannelRangeQueriesExtended, Optional), ActivatedFeature(PaymentSecret, Optional), ActivatedFeature(BasicMultiPartPayment, Optional))),
|
||||
hex"09004200" -> Features(Set(ActivatedFeature(VariableLengthOnion, Optional), ActivatedFeature(PaymentSecret, Mandatory)), Set(UnknownFeature(24), UnknownFeature(27))),
|
||||
hex"52000000" -> Features(Set.empty, Set(UnknownFeature(25), UnknownFeature(28), UnknownFeature(30)))
|
||||
hex"0100" -> Features(VariableLengthOnion -> Mandatory),
|
||||
hex"028a8a" -> Features(OptionDataLossProtect -> Optional, InitialRoutingSync -> Optional, ChannelRangeQueries -> Optional, VariableLengthOnion -> Optional, ChannelRangeQueriesExtended -> Optional, PaymentSecret -> Optional, BasicMultiPartPayment -> Optional),
|
||||
hex"09004200" -> Features(Map[Feature, FeatureSupport](VariableLengthOnion -> Optional, PaymentSecret -> Mandatory), Set(UnknownFeature(24), UnknownFeature(27))),
|
||||
hex"52000000" -> Features(Map.empty[Feature, FeatureSupport], Set(UnknownFeature(25), UnknownFeature(28), UnknownFeature(30)))
|
||||
)
|
||||
|
||||
for ((bin, features) <- testCases) {
|
||||
|
@ -16,9 +16,6 @@
|
||||
|
||||
package fr.acinq.eclair
|
||||
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
import com.typesafe.config.{Config, ConfigFactory}
|
||||
import fr.acinq.bitcoin.Block
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
@ -29,6 +26,8 @@ import fr.acinq.eclair.crypto.keymanager.{LocalChannelKeyManager, LocalNodeKeyMa
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import scodec.bits.{ByteVector, HexStringSyntax}
|
||||
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Try
|
||||
|
||||
@ -145,7 +144,7 @@ class StartupSpec extends AnyFunSuite {
|
||||
|
||||
val nodeParams = makeNodeParamsWithDefaults(perNodeConf.withFallback(defaultConf))
|
||||
val perNodeFeatures = nodeParams.featuresFor(PublicKey(ByteVector.fromValidHex("02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")))
|
||||
assert(perNodeFeatures === Features(Set(ActivatedFeature(VariableLengthOnion, Optional), ActivatedFeature(PaymentSecret, Mandatory), ActivatedFeature(BasicMultiPartPayment, Mandatory))))
|
||||
assert(perNodeFeatures === Features(VariableLengthOnion -> Optional, PaymentSecret -> Mandatory, BasicMultiPartPayment -> Mandatory))
|
||||
}
|
||||
|
||||
test("override feerate mismatch tolerance") {
|
||||
|
@ -153,13 +153,13 @@ object TestConstants {
|
||||
color = Color(1, 2, 3),
|
||||
publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil,
|
||||
features = Features(
|
||||
Set(
|
||||
ActivatedFeature(OptionDataLossProtect, Optional),
|
||||
ActivatedFeature(ChannelRangeQueries, Optional),
|
||||
ActivatedFeature(ChannelRangeQueriesExtended, Optional),
|
||||
ActivatedFeature(VariableLengthOnion, Optional),
|
||||
ActivatedFeature(PaymentSecret, Optional),
|
||||
ActivatedFeature(BasicMultiPartPayment, Optional)
|
||||
Map[Feature, FeatureSupport](
|
||||
OptionDataLossProtect -> Optional,
|
||||
ChannelRangeQueries -> Optional,
|
||||
ChannelRangeQueriesExtended -> Optional,
|
||||
VariableLengthOnion -> Optional,
|
||||
PaymentSecret -> Optional,
|
||||
BasicMultiPartPayment -> Optional
|
||||
),
|
||||
Set(UnknownFeature(TestFeature.optional))
|
||||
),
|
||||
@ -260,14 +260,14 @@ object TestConstants {
|
||||
alias = "bob",
|
||||
color = Color(4, 5, 6),
|
||||
publicAddresses = NodeAddress.fromParts("localhost", 9732).get :: Nil,
|
||||
features = Features(Set(
|
||||
ActivatedFeature(OptionDataLossProtect, Optional),
|
||||
ActivatedFeature(ChannelRangeQueries, Optional),
|
||||
ActivatedFeature(ChannelRangeQueriesExtended, Optional),
|
||||
ActivatedFeature(VariableLengthOnion, Optional),
|
||||
ActivatedFeature(PaymentSecret, Optional),
|
||||
ActivatedFeature(BasicMultiPartPayment, Optional)
|
||||
)),
|
||||
features = Features(
|
||||
OptionDataLossProtect -> Optional,
|
||||
ChannelRangeQueries -> Optional,
|
||||
ChannelRangeQueriesExtended -> Optional,
|
||||
VariableLengthOnion -> Optional,
|
||||
PaymentSecret -> Optional,
|
||||
BasicMultiPartPayment -> Optional
|
||||
),
|
||||
pluginParams = Nil,
|
||||
overrideFeatures = Map.empty,
|
||||
syncWhitelist = Set.empty,
|
||||
|
@ -7,7 +7,7 @@ import fr.acinq.eclair.channel.Helpers.Closing
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.transactions.Transactions
|
||||
import fr.acinq.eclair.wire.{CommitSig, RevokeAndAck, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.{MilliSatoshiLong, TestKitBaseClass}
|
||||
import fr.acinq.eclair.{Feature, FeatureSupport, MilliSatoshiLong, TestKitBaseClass}
|
||||
import org.scalatest.funsuite.AnyFunSuiteLike
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
@ -34,18 +34,18 @@ class ChannelTypesSpec extends TestKitBaseClass with AnyFunSuiteLike with StateT
|
||||
|
||||
test("pick channel version based on local and remote features") {
|
||||
import fr.acinq.eclair.FeatureSupport._
|
||||
import fr.acinq.eclair.Features
|
||||
import fr.acinq.eclair.Features._
|
||||
import fr.acinq.eclair.{ActivatedFeature, Features}
|
||||
|
||||
case class TestCase(localFeatures: Features, remoteFeatures: Features, expectedChannelVersion: ChannelVersion)
|
||||
val testCases = Seq(
|
||||
TestCase(Features.empty, Features.empty, ChannelVersion.STANDARD),
|
||||
TestCase(Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), Features.empty, ChannelVersion.STANDARD),
|
||||
TestCase(Features.empty, Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), ChannelVersion.STANDARD),
|
||||
TestCase(Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), ChannelVersion.STATIC_REMOTEKEY),
|
||||
TestCase(Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), Features(Set(ActivatedFeature(StaticRemoteKey, Mandatory))), ChannelVersion.STATIC_REMOTEKEY),
|
||||
TestCase(Features(Set(ActivatedFeature(StaticRemoteKey, Optional), ActivatedFeature(AnchorOutputs, Optional))), Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), ChannelVersion.STATIC_REMOTEKEY),
|
||||
TestCase(Features(Set(ActivatedFeature(StaticRemoteKey, Mandatory), ActivatedFeature(AnchorOutputs, Optional))), Features(Set(ActivatedFeature(StaticRemoteKey, Optional), ActivatedFeature(AnchorOutputs, Optional))), ChannelVersion.ANCHOR_OUTPUTS)
|
||||
TestCase(Features(StaticRemoteKey -> Optional), Features.empty, ChannelVersion.STANDARD),
|
||||
TestCase(Features.empty, Features(StaticRemoteKey -> Optional), ChannelVersion.STANDARD),
|
||||
TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Optional), ChannelVersion.STATIC_REMOTEKEY),
|
||||
TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Mandatory), ChannelVersion.STATIC_REMOTEKEY),
|
||||
TestCase(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional), ChannelVersion.STATIC_REMOTEKEY),
|
||||
TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), ChannelVersion.ANCHOR_OUTPUTS)
|
||||
)
|
||||
|
||||
for (testCase <- testCases) {
|
||||
|
@ -51,6 +51,8 @@ trait StateTestsBase extends StateTestsHelperMethods with FixtureTestSuite with
|
||||
}
|
||||
|
||||
object StateTestsTags {
|
||||
/** If set, channels will use option_support_large_channel. */
|
||||
val Wumbo = "wumbo"
|
||||
/** If set, channels will use option_static_remotekey. */
|
||||
val StaticRemoteKey = "static_remotekey"
|
||||
/** If set, channels will use option_anchor_outputs. */
|
||||
@ -97,9 +99,19 @@ trait StateTestsHelperMethods extends TestKitBase {
|
||||
SetupFixture(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayerA, relayerB, channelUpdateListener, wallet)
|
||||
}
|
||||
|
||||
def setChannelFeatures(defaultChannelParams: LocalParams, tags: Set[String]): LocalParams = {
|
||||
import com.softwaremill.quicklens._
|
||||
|
||||
defaultChannelParams
|
||||
.modify(_.features.activated).usingIf(tags.contains(StateTestsTags.Wumbo))(_.updated(Features.Wumbo, FeatureSupport.Optional))
|
||||
.modify(_.features.activated).usingIf(tags.contains(StateTestsTags.StaticRemoteKey))(_.updated(Features.StaticRemoteKey, FeatureSupport.Optional))
|
||||
.modify(_.features.activated).usingIf(tags.contains(StateTestsTags.AnchorOutputs))(_.updated(Features.StaticRemoteKey, FeatureSupport.Mandatory).updated(Features.AnchorOutputs, FeatureSupport.Optional))
|
||||
}
|
||||
|
||||
def reachNormal(setup: SetupFixture, tags: Set[String] = Set.empty): Unit = {
|
||||
import com.softwaremill.quicklens._
|
||||
import setup._
|
||||
|
||||
val channelVersion = List(
|
||||
ChannelVersion.STANDARD,
|
||||
if (tags.contains(StateTestsTags.AnchorOutputs)) ChannelVersion.ANCHOR_OUTPUTS else ChannelVersion.ZEROES,
|
||||
@ -107,14 +119,8 @@ trait StateTestsHelperMethods extends TestKitBase {
|
||||
).reduce(_ | _)
|
||||
|
||||
val channelFlags = if (tags.contains(StateTestsTags.ChannelsPublic)) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
|
||||
val aliceParams = Alice.channelParams
|
||||
.modify(_.features.activated).usingIf(channelVersion.hasStaticRemotekey)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Optional)))
|
||||
.modify(_.features.activated).usingIf(channelVersion.hasAnchorOutputs)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Mandatory), ActivatedFeature(Features.AnchorOutputs, FeatureSupport.Optional)))
|
||||
.modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet)))
|
||||
val bobParams = Bob.channelParams
|
||||
.modify(_.features.activated).usingIf(channelVersion.hasStaticRemotekey)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Optional)))
|
||||
.modify(_.features.activated).usingIf(channelVersion.hasAnchorOutputs)(_ ++ Set(ActivatedFeature(Features.StaticRemoteKey, FeatureSupport.Mandatory), ActivatedFeature(Features.AnchorOutputs, FeatureSupport.Optional)))
|
||||
.modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet)))
|
||||
val aliceParams = setChannelFeatures(Alice.channelParams, tags).modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet)))
|
||||
val bobParams = setChannelFeatures(Bob.channelParams, tags).modify(_.walletStaticPaymentBasepoint).setToIf(channelVersion.paysDirectlyToWallet)(Some(Helpers.getWalletPaymentBasepoint(wallet)))
|
||||
val initialFeeratePerKw = if (tags.contains(StateTestsTags.AnchorOutputs)) {
|
||||
FeeEstimator.AnchorOutputMaxCommitFeerate
|
||||
} else {
|
||||
|
@ -18,16 +18,14 @@ package fr.acinq.eclair.channel.states.a
|
||||
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.{Block, Btc, ByteVector32, Satoshi, SatoshiLong}
|
||||
import fr.acinq.eclair.FeatureSupport.Optional
|
||||
import fr.acinq.eclair.Features.Wumbo
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
||||
import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet}
|
||||
import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateTestsBase
|
||||
import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags}
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream}
|
||||
import fr.acinq.eclair.{ActivatedFeature, CltvExpiryDelta, Features, TestConstants, TestKitBaseClass}
|
||||
import fr.acinq.eclair.{CltvExpiryDelta, TestConstants, TestKitBaseClass}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
import scodec.bits.ByteVector
|
||||
@ -53,14 +51,12 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
.modify(_.chainHash).setToIf(test.tags.contains("mainnet"))(Block.LivenetGenesisBlock.hash)
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("high-max-funding-size"))(Btc(100))
|
||||
.modify(_.maxRemoteDustLimit).setToIf(test.tags.contains("high-remote-dust-limit"))(15000 sat)
|
||||
val aliceParams = Alice.channelParams
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
val aliceParams = setChannelFeatures(Alice.channelParams, test.tags)
|
||||
|
||||
val bobNodeParams = Bob.nodeParams
|
||||
.modify(_.chainHash).setToIf(test.tags.contains("mainnet"))(Block.LivenetGenesisBlock.hash)
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("high-max-funding-size"))(Btc(100))
|
||||
val bobParams = Bob.channelParams
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
val bobParams = setChannelFeatures(Bob.channelParams, test.tags)
|
||||
|
||||
val setup = init(aliceNodeParams, bobNodeParams, wallet = noopWallet)
|
||||
|
||||
@ -69,7 +65,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
val aliceInit = Init(aliceParams.features)
|
||||
val bobInit = Init(bobParams.features)
|
||||
within(30 seconds) {
|
||||
val fundingAmount = if (test.tags.contains("wumbo")) Btc(5).toSatoshi else TestConstants.fundingSatoshis
|
||||
val fundingAmount = if (test.tags.contains(StateTestsTags.Wumbo)) Btc(5).toSatoshi else TestConstants.fundingSatoshis
|
||||
alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingAmount, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, None, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Empty, channelVersion)
|
||||
bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelVersion)
|
||||
alice2bob.expectMsgType[OpenChannel]
|
||||
@ -173,7 +169,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
awaitCond(alice.stateName == CLOSED)
|
||||
}
|
||||
|
||||
test("recv AcceptChannel (wumbo size channel)", Tag("wumbo"), Tag("high-max-funding-size")) { f =>
|
||||
test("recv AcceptChannel (wumbo size channel)", Tag(StateTestsTags.Wumbo), Tag("high-max-funding-size")) { f =>
|
||||
import f._
|
||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||
assert(accept.minimumDepth == 13) // with wumbo tag we use fundingSatoshis=5BTC
|
||||
|
@ -18,14 +18,12 @@ package fr.acinq.eclair.channel.states.a
|
||||
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.{Block, Btc, ByteVector32, SatoshiLong}
|
||||
import fr.acinq.eclair.FeatureSupport.Optional
|
||||
import fr.acinq.eclair.Features.Wumbo
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateTestsBase
|
||||
import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags}
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream}
|
||||
import fr.acinq.eclair.{ActivatedFeature, CltvExpiryDelta, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
|
||||
import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
import scodec.bits.ByteVector
|
||||
@ -44,10 +42,8 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
|
||||
import com.softwaremill.quicklens._
|
||||
val aliceParams = Alice.channelParams
|
||||
|
||||
val bobNodeParams = Bob.nodeParams
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("max-funding-satoshis"))(Btc(1))
|
||||
val bobParams = Bob.channelParams
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
val bobNodeParams = Bob.nodeParams.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("max-funding-satoshis"))(Btc(1))
|
||||
val bobParams = setChannelFeatures(Bob.channelParams, test.tags)
|
||||
|
||||
val setup = init(nodeParamsB = bobNodeParams)
|
||||
|
||||
@ -103,7 +99,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
|
||||
awaitCond(bob.stateName == CLOSED)
|
||||
}
|
||||
|
||||
test("recv OpenChannel (fundingSatoshis > max-funding-satoshis)", Tag("wumbo")) { f =>
|
||||
test("recv OpenChannel (fundingSatoshis > max-funding-satoshis)", Tag(StateTestsTags.Wumbo)) { f =>
|
||||
import f._
|
||||
val open = alice2bob.expectMsgType[OpenChannel]
|
||||
val highFundingSat = Bob.nodeParams.maxFundingSatoshis + Btc(1)
|
||||
@ -210,7 +206,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
|
||||
awaitCond(bob.stateName == CLOSED)
|
||||
}
|
||||
|
||||
test("recv OpenChannel (wumbo size)", Tag("wumbo"), Tag("max-funding-satoshis")) { f =>
|
||||
test("recv OpenChannel (wumbo size)", Tag(StateTestsTags.Wumbo), Tag("max-funding-satoshis")) { f =>
|
||||
import f._
|
||||
val open = alice2bob.expectMsgType[OpenChannel]
|
||||
val highFundingSat = Btc(1).toSatoshi
|
||||
|
@ -19,15 +19,13 @@ package fr.acinq.eclair.channel.states.b
|
||||
import akka.actor.ActorRef
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.{Btc, ByteVector32, SatoshiLong}
|
||||
import fr.acinq.eclair.FeatureSupport.Optional
|
||||
import fr.acinq.eclair.Features.Wumbo
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateTestsBase
|
||||
import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags}
|
||||
import fr.acinq.eclair.transactions.Transactions
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{ActivatedFeature, Features, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
|
||||
import fr.acinq.eclair.{TestConstants, TestKitBaseClass, ToMilliSatoshiConversion}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
|
||||
@ -43,18 +41,14 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
import com.softwaremill.quicklens._
|
||||
val aliceNodeParams = Alice.nodeParams
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100))
|
||||
val aliceParams = Alice.channelParams
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
val bobNodeParams = Bob.nodeParams
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100))
|
||||
val bobParams = Bob.channelParams
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
val aliceNodeParams = Alice.nodeParams.modify(_.maxFundingSatoshis).setToIf(test.tags.contains(StateTestsTags.Wumbo))(Btc(100))
|
||||
val aliceParams = setChannelFeatures(Alice.channelParams, test.tags)
|
||||
val bobNodeParams = Bob.nodeParams.modify(_.maxFundingSatoshis).setToIf(test.tags.contains(StateTestsTags.Wumbo))(Btc(100))
|
||||
val bobParams = setChannelFeatures(Bob.channelParams, test.tags)
|
||||
|
||||
val (fundingSatoshis, pushMsat) = if (test.tags.contains("funder_below_reserve")) {
|
||||
(1000100 sat, (1000000 sat).toMilliSatoshi) // toLocal = 100 satoshis
|
||||
} else if (test.tags.contains("wumbo")) {
|
||||
} else if (test.tags.contains(StateTestsTags.Wumbo)) {
|
||||
(Btc(5).toSatoshi, TestConstants.pushMsat)
|
||||
} else {
|
||||
(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
@ -88,7 +82,7 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun
|
||||
assert(watchConfirmed.minDepth === Alice.nodeParams.minDepthBlocks)
|
||||
}
|
||||
|
||||
test("recv FundingCreated (wumbo)", Tag("wumbo")) { f =>
|
||||
test("recv FundingCreated (wumbo)", Tag(StateTestsTags.Wumbo)) { f =>
|
||||
import f._
|
||||
alice2bob.expectMsgType[FundingCreated]
|
||||
alice2bob.forward(bob)
|
||||
|
@ -18,17 +18,15 @@ package fr.acinq.eclair.channel.states.b
|
||||
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.{Btc, ByteVector32, ByteVector64}
|
||||
import fr.acinq.eclair.FeatureSupport.Optional
|
||||
import fr.acinq.eclair.Features.Wumbo
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateTestsBase
|
||||
import fr.acinq.eclair.channel.states.{StateTestsBase, StateTestsTags}
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel}
|
||||
import fr.acinq.eclair.{ActivatedFeature, Features, TestConstants, TestKitBaseClass}
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
import fr.acinq.eclair.{TestConstants, TestKitBaseClass}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
@ -42,16 +40,12 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
import com.softwaremill.quicklens._
|
||||
val aliceNodeParams = Alice.nodeParams
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100))
|
||||
val aliceParams = Alice.channelParams
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
val bobNodeParams = Bob.nodeParams
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("wumbo"))(Btc(100))
|
||||
val bobParams = Bob.channelParams
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
val aliceNodeParams = Alice.nodeParams.modify(_.maxFundingSatoshis).setToIf(test.tags.contains(StateTestsTags.Wumbo))(Btc(100))
|
||||
val aliceParams = setChannelFeatures(Alice.channelParams, test.tags)
|
||||
val bobNodeParams = Bob.nodeParams.modify(_.maxFundingSatoshis).setToIf(test.tags.contains(StateTestsTags.Wumbo))(Btc(100))
|
||||
val bobParams = setChannelFeatures(Bob.channelParams, test.tags)
|
||||
|
||||
val (fundingSatoshis, pushMsat) = if (test.tags.contains("wumbo")) {
|
||||
val (fundingSatoshis, pushMsat) = if (test.tags.contains(StateTestsTags.Wumbo)) {
|
||||
(Btc(5).toSatoshi, TestConstants.pushMsat)
|
||||
} else {
|
||||
(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
@ -86,7 +80,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
assert(watchConfirmed.minDepth === Alice.nodeParams.minDepthBlocks)
|
||||
}
|
||||
|
||||
test("recv FundingSigned with valid signature (wumbo)", Tag("wumbo")) { f =>
|
||||
test("recv FundingSigned with valid signature (wumbo)", Tag(StateTestsTags.Wumbo)) { f =>
|
||||
import f._
|
||||
bob2alice.expectMsgType[FundingSigned]
|
||||
bob2alice.forward(alice)
|
||||
|
@ -25,7 +25,7 @@ import fr.acinq.eclair.db.sqlite.SqliteUtils._
|
||||
import fr.acinq.eclair.router.Announcements
|
||||
import fr.acinq.eclair.router.Router.PublicChannel
|
||||
import fr.acinq.eclair.wire.{Color, NodeAddress, Tor2}
|
||||
import fr.acinq.eclair.{ActivatedFeature, CltvExpiryDelta, Features, MilliSatoshiLong, ShortChannelId, TestConstants, randomBytes32, randomKey}
|
||||
import fr.acinq.eclair.{CltvExpiryDelta, Feature, FeatureSupport, Features, MilliSatoshiLong, ShortChannelId, TestConstants, randomBytes32, randomKey}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
import scala.collection.{SortedMap, mutable}
|
||||
@ -88,8 +88,8 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
|
||||
val db = dbs.network()
|
||||
|
||||
val node_1 = Announcements.makeNodeAnnouncement(randomKey, "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features.empty)
|
||||
val node_2 = Announcements.makeNodeAnnouncement(randomKey, "node-bob", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(Set(ActivatedFeature(VariableLengthOnion, Optional))))
|
||||
val node_3 = Announcements.makeNodeAnnouncement(randomKey, "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(Set(ActivatedFeature(VariableLengthOnion, Optional))))
|
||||
val node_2 = Announcements.makeNodeAnnouncement(randomKey, "node-bob", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional))
|
||||
val node_3 = Announcements.makeNodeAnnouncement(randomKey, "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil, Features(VariableLengthOnion -> Optional))
|
||||
val node_4 = Announcements.makeNodeAnnouncement(randomKey, "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), Tor2("aaaqeayeaudaocaj", 42000) :: Nil, Features.empty)
|
||||
|
||||
assert(db.listNodes().toSet === Set.empty)
|
||||
|
@ -28,8 +28,8 @@ import fr.acinq.eclair.crypto.TransportHandler
|
||||
import fr.acinq.eclair.router.Router._
|
||||
import fr.acinq.eclair.router.RoutingSyncSpec
|
||||
import fr.acinq.eclair.wire._
|
||||
import org.scalatest.{Outcome, ParallelTestExecution}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, ParallelTestExecution}
|
||||
import scodec.bits._
|
||||
|
||||
import java.net.{Inet4Address, InetSocketAddress}
|
||||
@ -165,7 +165,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
|
||||
transport.expectMsgType[TransportHandler.Listener]
|
||||
transport.expectMsgType[wire.Init]
|
||||
// remote activated MPP but forgot payment secret
|
||||
transport.send(peerConnection, Init(Features(Set(ActivatedFeature(BasicMultiPartPayment, Optional), ActivatedFeature(VariableLengthOnion, Optional)))))
|
||||
transport.send(peerConnection, Init(Features(BasicMultiPartPayment -> Optional, VariableLengthOnion -> Optional)))
|
||||
transport.expectMsgType[TransportHandler.ReadAck]
|
||||
probe.expectTerminated(transport.ref)
|
||||
origin.expectMsg(PeerConnection.ConnectionResult.InitializationFailed("basic_mpp is set but is missing a dependency (payment_secret)"))
|
||||
@ -189,7 +189,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi
|
||||
|
||||
test("sync when requested") { f =>
|
||||
import f._
|
||||
val remoteInit = wire.Init(Features(Set(ActivatedFeature(ChannelRangeQueries, Optional))))
|
||||
val remoteInit = wire.Init(Features(ChannelRangeQueries -> Optional))
|
||||
connect(nodeParams, remoteNodeId, switchboard, router, connection, transport, peerConnection, peer, remoteInit, doSync = true)
|
||||
}
|
||||
|
||||
|
@ -55,9 +55,9 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle
|
||||
|
||||
import com.softwaremill.quicklens._
|
||||
val aliceParams = TestConstants.Alice.nodeParams
|
||||
.modify(_.features).setToIf(test.tags.contains("static_remotekey"))(Features(Set(ActivatedFeature(StaticRemoteKey, Optional))))
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Set(ActivatedFeature(Wumbo, Optional))))
|
||||
.modify(_.features).setToIf(test.tags.contains("anchor_outputs"))(Features(Set(ActivatedFeature(StaticRemoteKey, Optional), ActivatedFeature(AnchorOutputs, Optional))))
|
||||
.modify(_.features).setToIf(test.tags.contains("static_remotekey"))(Features(StaticRemoteKey -> Optional))
|
||||
.modify(_.features).setToIf(test.tags.contains("wumbo"))(Features(Wumbo -> Optional))
|
||||
.modify(_.features).setToIf(test.tags.contains("anchor_outputs"))(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional))
|
||||
.modify(_.maxFundingSatoshis).setToIf(test.tags.contains("high-max-funding-satoshis"))(Btc(0.9))
|
||||
.modify(_.autoReconnect).setToIf(test.tags.contains("auto_reconnect"))(true)
|
||||
|
||||
@ -293,7 +293,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle
|
||||
val probe = TestProbe()
|
||||
val fundingAmountBig = Btc(1).toSatoshi
|
||||
system.eventStream.subscribe(probe.ref, classOf[ChannelCreated])
|
||||
connect(remoteNodeId, peer, peerConnection, remoteInit = wire.Init(Features(Set(ActivatedFeature(Wumbo, Optional))))) // Bob supports wumbo
|
||||
connect(remoteNodeId, peer, peerConnection, remoteInit = wire.Init(Features(Wumbo -> Optional))) // Bob supports wumbo
|
||||
|
||||
assert(peer.stateData.channels.isEmpty)
|
||||
probe.send(peer, Peer.OpenChannel(remoteNodeId, fundingAmountBig, 0 msat, None, None, None, None))
|
||||
@ -331,7 +331,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle
|
||||
|
||||
val probe = TestProbe()
|
||||
system.eventStream.subscribe(probe.ref, classOf[ChannelCreated])
|
||||
connect(remoteNodeId, peer, peerConnection, remoteInit = wire.Init(Features(Set(ActivatedFeature(StaticRemoteKey, Optional), ActivatedFeature(AnchorOutputs, Optional)))))
|
||||
connect(remoteNodeId, peer, peerConnection, remoteInit = wire.Init(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional)))
|
||||
|
||||
// We ensure the current network feerate is higher than the default anchor output feerate.
|
||||
val feeEstimator = nodeParams.onChainFeeConf.feeEstimator.asInstanceOf[TestFeeEstimator]
|
||||
@ -347,7 +347,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle
|
||||
import f._
|
||||
|
||||
val probe = TestProbe()
|
||||
connect(remoteNodeId, peer, peerConnection, remoteInit = wire.Init(Features(Set(ActivatedFeature(StaticRemoteKey, Optional))))) // Bob supports option_static_remotekey
|
||||
connect(remoteNodeId, peer, peerConnection, remoteInit = wire.Init(Features(StaticRemoteKey -> Optional))) // Bob supports option_static_remotekey
|
||||
probe.send(peer, Peer.OpenChannel(remoteNodeId, 24000 sat, 0 msat, None, None, None, None))
|
||||
awaitCond(peer.stateData.channels.nonEmpty)
|
||||
peer.stateData.channels.foreach { case (_, channelRef) =>
|
||||
|
@ -32,7 +32,7 @@ import fr.acinq.eclair.payment.receive.MultiPartPaymentFSM.HtlcPart
|
||||
import fr.acinq.eclair.payment.receive.{MultiPartPaymentFSM, PaymentHandler}
|
||||
import fr.acinq.eclair.wire.Onion.FinalTlvPayload
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{ActivatedFeature, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshiLong, NodeParams, ShortChannelId, TestConstants, TestKitBaseClass, randomBytes32, randomKey}
|
||||
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, Features, MilliSatoshiLong, NodeParams, ShortChannelId, TestConstants, TestKitBaseClass, randomBytes32, randomKey}
|
||||
import org.scalatest.Outcome
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
|
||||
@ -44,21 +44,21 @@ import scala.concurrent.duration._
|
||||
|
||||
class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
||||
|
||||
val featuresWithoutMpp = Features(Set(
|
||||
ActivatedFeature(VariableLengthOnion, Optional),
|
||||
ActivatedFeature(PaymentSecret, Optional),
|
||||
))
|
||||
val featuresWithoutMpp = Features(
|
||||
VariableLengthOnion -> Optional,
|
||||
PaymentSecret -> Optional,
|
||||
)
|
||||
|
||||
val featuresWithMpp = Features(Set(
|
||||
ActivatedFeature(VariableLengthOnion, Optional),
|
||||
ActivatedFeature(PaymentSecret, Optional),
|
||||
ActivatedFeature(BasicMultiPartPayment, Optional)
|
||||
))
|
||||
val featuresWithMpp = Features(
|
||||
VariableLengthOnion -> Optional,
|
||||
PaymentSecret -> Optional,
|
||||
BasicMultiPartPayment -> Optional
|
||||
)
|
||||
|
||||
val featuresWithKeySend = Features(Set(
|
||||
ActivatedFeature(VariableLengthOnion, Optional),
|
||||
ActivatedFeature(KeySend, Optional)
|
||||
))
|
||||
val featuresWithKeySend = Features(
|
||||
VariableLengthOnion -> Optional,
|
||||
KeySend -> Optional
|
||||
)
|
||||
|
||||
case class FixtureParam(nodeParams: NodeParams, defaultExpiry: CltvExpiry, register: TestProbe, eventListener: TestProbe, sender: TestProbe) {
|
||||
lazy val handlerWithoutMpp = TestActorRef[PaymentHandler](PaymentHandler.props(nodeParams.copy(features = featuresWithoutMpp), register.ref))
|
||||
|
@ -36,7 +36,7 @@ import fr.acinq.eclair.router.Router._
|
||||
import fr.acinq.eclair.wire.Onion.{FinalLegacyPayload, FinalTlvPayload}
|
||||
import fr.acinq.eclair.wire.OnionTlv.{AmountToForward, OutgoingCltv}
|
||||
import fr.acinq.eclair.wire.{Onion, OnionCodecs, OnionTlv, TrampolineFeeInsufficient, _}
|
||||
import fr.acinq.eclair.{ActivatedFeature, CltvExpiryDelta, Features, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, randomBytes32, randomKey}
|
||||
import fr.acinq.eclair.{CltvExpiryDelta, Feature, FeatureSupport, Features, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, randomBytes32, randomKey}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
import scodec.bits.HexStringSyntax
|
||||
@ -52,16 +52,16 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||
|
||||
case class FixtureParam(nodeParams: NodeParams, initiator: TestActorRef[PaymentInitiator], payFsm: TestProbe, multiPartPayFsm: TestProbe, sender: TestProbe, eventListener: TestProbe)
|
||||
|
||||
val featuresWithoutMpp = Features(Set(
|
||||
ActivatedFeature(VariableLengthOnion, Optional),
|
||||
ActivatedFeature(PaymentSecret, Optional)
|
||||
))
|
||||
val featuresWithoutMpp = Features(
|
||||
VariableLengthOnion -> Optional,
|
||||
PaymentSecret -> Optional
|
||||
)
|
||||
|
||||
val featuresWithMpp = Features(Set(
|
||||
ActivatedFeature(VariableLengthOnion, Optional),
|
||||
ActivatedFeature(PaymentSecret, Optional),
|
||||
ActivatedFeature(BasicMultiPartPayment, Optional),
|
||||
))
|
||||
val featuresWithMpp = Features(
|
||||
VariableLengthOnion -> Optional,
|
||||
PaymentSecret -> Optional,
|
||||
BasicMultiPartPayment -> Optional,
|
||||
)
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
val features = if (test.tags.contains("mpp_disabled")) featuresWithoutMpp else featuresWithMpp
|
||||
|
@ -20,7 +20,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||
import fr.acinq.bitcoin.{Block, BtcDouble, ByteVector32, Crypto, MilliBtcDouble, Protocol, SatoshiLong}
|
||||
import fr.acinq.eclair.Features.{PaymentSecret, _}
|
||||
import fr.acinq.eclair.payment.PaymentRequest._
|
||||
import fr.acinq.eclair.{ActivatedFeature, CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, ShortChannelId, TestConstants, ToMilliSatoshiConversion}
|
||||
import fr.acinq.eclair.{CltvExpiryDelta, FeatureSupport, Features, MilliSatoshiLong, ShortChannelId, TestConstants, ToMilliSatoshiConversion}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import scodec.DecodeResult
|
||||
import scodec.bits._
|
||||
@ -382,7 +382,7 @@ class PaymentRequestSpec extends AnyFunSuite {
|
||||
}
|
||||
|
||||
test("supported payment request features") {
|
||||
val nodeParams = TestConstants.Alice.nodeParams.copy(features = Features(knownFeatures.map(ActivatedFeature(_, FeatureSupport.Optional))))
|
||||
val nodeParams = TestConstants.Alice.nodeParams.copy(features = Features(knownFeatures.map(f => f -> FeatureSupport.Optional).toMap))
|
||||
case class Result(allowMultiPart: Boolean, requirePaymentSecret: Boolean, areSupported: Boolean) // "supported" is based on the "it's okay to be odd" rule"
|
||||
val featureBits = Map(
|
||||
PaymentRequestFeatures(bin" 00000000000000000000") -> Result(allowMultiPart = false, requirePaymentSecret = false, areSupported = true),
|
||||
@ -419,8 +419,8 @@ class PaymentRequestSpec extends AnyFunSuite {
|
||||
(bin" 01000000000000110", hex" 8006"),
|
||||
(bin" 001000000000000000", hex" 8000"),
|
||||
(bin" 101000000000000000", hex"028000"),
|
||||
(bin"0101110000000000110", hex"02e006"),
|
||||
(bin"1001110000000000110", hex"04e006")
|
||||
(bin"0101010000000000110", hex"02a006"),
|
||||
(bin"1000110000000000110", hex"046006")
|
||||
)
|
||||
|
||||
for ((bitmask, featureBytes) <- testCases) {
|
||||
|
@ -415,9 +415,9 @@ class ChannelCodecsSpec extends AnyFunSuite {
|
||||
.replace(""""toRemote"""", """"toRemoteMsat"""")
|
||||
.replace("fundingKeyPath", "channelKeyPath")
|
||||
.replace(""""version":0,""", "")
|
||||
.replace(""""features":{"activated":[{"feature":{},"support":{}},{"feature":{},"support":{}},{"feature":{},"support":{}}],"unknown":[]}""", """"features":"8a"""")
|
||||
.replace(""""features":{"activated":[{"feature":{},"support":{}},{"feature":{},"support":{}}],"unknown":[]}""", """"features":"81"""")
|
||||
.replace(""""features":{"activated":[],"unknown":[]}""", """"features":""""")
|
||||
.replace(""""features":{"activated":{"option_data_loss_protect":{},"initial_routing_sync":{},"gossip_queries":{}},"unknown":[]}""", """"features":"8a"""")
|
||||
.replace(""""features":{"activated":{"option_data_loss_protect":{},"gossip_queries":{}},"unknown":[]}""", """"features":"81"""")
|
||||
.replace(""""features":{"activated":{},"unknown":[]}""", """"features":""""")
|
||||
|
||||
val newjson = Serialization.write(newnormal)(JsonSupport.formats)
|
||||
.replace(""","unknownFields":""""", "")
|
||||
@ -429,9 +429,9 @@ class ChannelCodecsSpec extends AnyFunSuite {
|
||||
.replace(""""toRemote"""", """"toRemoteMsat"""")
|
||||
.replace("fundingKeyPath", "channelKeyPath")
|
||||
.replace(""""version":0,""", "")
|
||||
.replace(""""features":{"activated":[{"feature":{},"support":{}},{"feature":{},"support":{}},{"feature":{},"support":{}}],"unknown":[]}""", """"features":"8a"""")
|
||||
.replace(""""features":{"activated":[{"feature":{},"support":{}},{"feature":{},"support":{}}],"unknown":[]}""", """"features":"81"""")
|
||||
.replace(""""features":{"activated":[],"unknown":[]}""", """"features":""""")
|
||||
.replace(""""features":{"activated":{"option_data_loss_protect":{},"initial_routing_sync":{},"gossip_queries":{}},"unknown":[]}""", """"features":"8a"""")
|
||||
.replace(""""features":{"activated":{"option_data_loss_protect":{},"gossip_queries":{}},"unknown":[]}""", """"features":"81"""")
|
||||
.replace(""""features":{"activated":{},"unknown":[]}""", """"features":""""")
|
||||
|
||||
assert(oldjson === refjson)
|
||||
assert(newjson === refjson)
|
||||
@ -639,6 +639,12 @@ object ChannelCodecsSpec {
|
||||
case x: OutPoint => s"${x.txid}:${x.index}"
|
||||
}))
|
||||
|
||||
class FeatureKeySerializer extends CustomKeySerializer[Feature](_ => ( {
|
||||
null
|
||||
}, {
|
||||
case f: Feature => f.toString
|
||||
}))
|
||||
|
||||
class InputInfoSerializer extends CustomSerializer[InputInfo](_ => ( {
|
||||
null
|
||||
}, {
|
||||
@ -665,6 +671,7 @@ object ChannelCodecsSpec {
|
||||
new InetSocketAddressSerializer +
|
||||
new OutPointSerializer +
|
||||
new OutPointKeySerializer +
|
||||
new FeatureKeySerializer +
|
||||
new ChannelVersionSerializer +
|
||||
new InputInfoSerializer
|
||||
}
|
||||
|
@ -407,17 +407,13 @@ object JsonSupport extends Json4sSupport {
|
||||
CustomTypeHints.outgoingPaymentStatus +
|
||||
CustomTypeHints.paymentEvent).withTypeHintFieldName("type")
|
||||
|
||||
def featuresToJson(features: Features) = JObject(
|
||||
JField("activated", JArray(features.activated.map { a =>
|
||||
JObject(
|
||||
JField("name", JString(a.feature.rfcName)),
|
||||
JField("support", JString(a.support.toString))
|
||||
)
|
||||
def featuresToJson(features: Features): JObject = JObject(
|
||||
JField("activated", JObject(features.activated.map { case (feature, support) =>
|
||||
feature.rfcName -> JString(support.toString)
|
||||
}.toList)),
|
||||
JField("unknown", JArray(features.unknown.map { i =>
|
||||
JObject(
|
||||
JField("featureBit", JInt(i.bitIndex))
|
||||
)
|
||||
JObject(JField("featureBit", JInt(i.bitIndex)))
|
||||
}.toList))
|
||||
)
|
||||
|
||||
}
|
@ -1 +1 @@
|
||||
{"version":"1.0.0-SNAPSHOT-e3f1ec0","nodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","alias":"alice","color":"#000102","features":{"activated":[{"name":"option_data_loss_protect","support":"mandatory"},{"name":"gossip_queries_ex","support":"optional"}],"unknown":[]},"chainHash":"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f","network":"regtest","blockHeight":9999,"publicAddresses":["localhost:9731"],"instanceId":"01234567-0123-4567-89ab-0123456789ab"}
|
||||
{"version":"1.0.0-SNAPSHOT-e3f1ec0","nodeId":"03af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0","alias":"alice","color":"#000102","features":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries_ex":"optional"},"unknown":[]},"chainHash":"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f","network":"regtest","blockHeight":9999,"publicAddresses":["localhost:9731"],"instanceId":"01234567-0123-4567-89ab-0123456789ab"}
|
@ -1 +1 @@
|
||||
{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":[],"unknown":[]}},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"expired"}}
|
||||
{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]}},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"expired"}}
|
@ -1 +1 @@
|
||||
{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":[],"unknown":[]}},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"pending"}}
|
||||
{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]}},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"pending"}}
|
@ -1 +1 @@
|
||||
{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":[],"unknown":[]}},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"received","amount":42,"receivedAt":45}}
|
||||
{"paymentRequest":{"prefix":"lnbc","timestamp":1496314658,"nodeId":"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad","serialized":"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp","description":"1 cup coffee","paymentHash":"0001020304050607080900010203040506070809000102030405060708090102","expiry":60,"amount":250000000,"features":{"activated":{},"unknown":[]}},"paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","paymentType":"Standard","createdAt":42,"status":{"type":"received","amount":42,"receivedAt":45}}
|
@ -177,7 +177,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
|
||||
eclair.getInfo()(any[Timeout]) returns Future.successful(GetInfoResponse(
|
||||
version = "1.0.0-SNAPSHOT-e3f1ec0",
|
||||
color = Color(0.toByte, 1.toByte, 2.toByte).toString,
|
||||
features = Features(Set(ActivatedFeature(OptionDataLossProtect, Mandatory), ActivatedFeature(ChannelRangeQueriesExtended, Optional))),
|
||||
features = Features(OptionDataLossProtect -> Mandatory, ChannelRangeQueriesExtended -> Optional),
|
||||
nodeId = aliceNodeId,
|
||||
alias = "alice",
|
||||
chainHash = ByteVector32(hex"06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"),
|
||||
|
@ -100,7 +100,7 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers {
|
||||
test("Payment Request") {
|
||||
val ref = "lnbcrt50n1p0fm9cdpp5al3wvsfkc6p7fxy89eu8gm4aww9mseu9syrcqtpa4mvx42qelkwqdq9v9ekgxqrrss9qypqsqsp5wl2t45v0hj4lgud0zjxcnjccd29ts0p2kh4vpw75vnhyyzyjtjtqarpvqg33asgh3z5ghfuvhvtf39xtnu9e7aqczpgxa9quwsxkd9rnwmx06pve9awgeewxqh90dqgrhzgsqc09ek6uejr93z8puafm6gsqgrk0hy"
|
||||
val pr = PaymentRequest.read(ref)
|
||||
JsonSupport.serialization.write(pr)(JsonSupport.formats) shouldBe """{"prefix":"lnbcrt","timestamp":1587386125,"nodeId":"03b207771ddba774e318970e9972da2491ff8e54f777ad0528b6526773730248a0","serialized":"lnbcrt50n1p0fm9cdpp5al3wvsfkc6p7fxy89eu8gm4aww9mseu9syrcqtpa4mvx42qelkwqdq9v9ekgxqrrss9qypqsqsp5wl2t45v0hj4lgud0zjxcnjccd29ts0p2kh4vpw75vnhyyzyjtjtqarpvqg33asgh3z5ghfuvhvtf39xtnu9e7aqczpgxa9quwsxkd9rnwmx06pve9awgeewxqh90dqgrhzgsqc09ek6uejr93z8puafm6gsqgrk0hy","description":"asd","paymentHash":"efe2e64136c683e498872e78746ebd738bb867858107802c3daed86aa819fd9c","expiry":3600,"amount":5000,"features":{"activated":[{"name":"var_onion_optin","support":"optional"},{"name":"payment_secret","support":"optional"}],"unknown":[]}}"""
|
||||
JsonSupport.serialization.write(pr)(JsonSupport.formats) shouldBe """{"prefix":"lnbcrt","timestamp":1587386125,"nodeId":"03b207771ddba774e318970e9972da2491ff8e54f777ad0528b6526773730248a0","serialized":"lnbcrt50n1p0fm9cdpp5al3wvsfkc6p7fxy89eu8gm4aww9mseu9syrcqtpa4mvx42qelkwqdq9v9ekgxqrrss9qypqsqsp5wl2t45v0hj4lgud0zjxcnjccd29ts0p2kh4vpw75vnhyyzyjtjtqarpvqg33asgh3z5ghfuvhvtf39xtnu9e7aqczpgxa9quwsxkd9rnwmx06pve9awgeewxqh90dqgrhzgsqc09ek6uejr93z8puafm6gsqgrk0hy","description":"asd","paymentHash":"efe2e64136c683e498872e78746ebd738bb867858107802c3daed86aa819fd9c","expiry":3600,"amount":5000,"features":{"activated":{"var_onion_optin":"optional","payment_secret":"optional"},"unknown":[]}}"""
|
||||
}
|
||||
|
||||
test("type hints") {
|
||||
|
Loading…
Reference in New Issue
Block a user