1
0
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:
Bastien Teinturier 2021-03-04 08:40:56 +01:00 committed by GitHub
parent 163700a232
commit 844829a9b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 180 additions and 196 deletions

View File

@ -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)

View File

@ -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) {

View File

@ -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") {

View File

@ -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,

View File

@ -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) {

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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) =>

View File

@ -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))

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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))
)
}

View File

@ -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"}

View File

@ -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"}}

View File

@ -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"}}

View File

@ -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}}

View File

@ -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"),

View File

@ -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") {