1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 22:25:26 +01:00

Replace FeatureScope by type hierarchy (#2207)

Instead of defining a separate type `FeatureScope` with its own
hierarchy, that was then mixed in `Feature` using the cake pattern, we
go with a simpler type hierarchy for `Feature`.

This significantly simplifies type declarations (no more `Feature with
FeatureScope`) especially in the tests.
This commit is contained in:
Pierre-Marie Padiou 2022-03-15 16:04:20 +01:00 committed by GitHub
parent f14300e33f
commit 7b5cefaf99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 118 additions and 119 deletions

View file

@ -60,7 +60,7 @@ import scala.collection.immutable.SortedMap
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future, Promise}
case class GetInfoResponse(version: String, nodeId: PublicKey, alias: String, color: String, features: Features[FeatureScope], chainHash: ByteVector32, network: String, blockHeight: Int, publicAddresses: Seq[NodeAddress], onionAddress: Option[NodeAddress], instanceId: String)
case class GetInfoResponse(version: String, nodeId: PublicKey, alias: String, color: String, features: Features[Feature], chainHash: ByteVector32, network: String, blockHeight: Int, publicAddresses: Seq[NodeAddress], onionAddress: Option[NodeAddress], instanceId: String)
case class AuditResponse(sent: Seq[PaymentSent], received: Seq[PaymentReceived], relayed: Seq[PaymentRelayed])

View file

@ -34,10 +34,9 @@ object FeatureSupport {
case object Optional extends FeatureSupport { override def toString: String = "optional" }
}
/** Not a sealed trait, so it can be extended by plugins. */
trait Feature {
this: FeatureScope =>
def rfcName: String
def mandatory: Int
def optional: Int = mandatory + 1
@ -48,24 +47,22 @@ trait Feature {
}
override def toString = rfcName
}
/** Feature scope as defined in Bolt 9. */
sealed trait FeatureScope
/** Feature that should be advertised in init messages. */
trait InitFeature extends FeatureScope
trait InitFeature extends Feature
/** Feature that should be advertised in node announcements. */
trait NodeFeature extends FeatureScope
trait NodeFeature extends Feature
/** Feature that should be advertised in invoices. */
trait InvoiceFeature extends FeatureScope
trait InvoiceFeature extends Feature
// @formatter:on
case class UnknownFeature(bitIndex: Int)
case class Features[T <: FeatureScope](activated: Map[Feature with T, FeatureSupport], unknown: Set[UnknownFeature] = Set.empty) {
case class Features[T <: Feature](activated: Map[T, FeatureSupport], unknown: Set[UnknownFeature] = Set.empty) {
def hasFeature(feature: Feature with T, support: Option[FeatureSupport] = None): Boolean = support match {
def hasFeature(feature: T, support: Option[FeatureSupport] = None): Boolean = support match {
case Some(s) => activated.get(feature).contains(s)
case None => activated.contains(feature)
}
@ -84,13 +81,13 @@ case class Features[T <: FeatureScope](activated: Map[Feature with T, FeatureSup
unknownFeaturesOk && knownFeaturesOk
}
def initFeatures(): Features[InitFeature] = Features[InitFeature](activated.collect { case (f: InitFeature, s) => (f, s) }, unknown)
def initFeatures(): Features[InitFeature] = Features(activated.collect { case (f: InitFeature, s) => (f, s) }, unknown)
def nodeAnnouncementFeatures(): Features[NodeFeature] = Features[NodeFeature](activated.collect { case (f: NodeFeature, s) => (f, s) }, unknown)
def nodeAnnouncementFeatures(): Features[NodeFeature] = Features(activated.collect { case (f: NodeFeature, s) => (f, s) }, unknown)
def invoiceFeatures(): Features[InvoiceFeature] = Features[InvoiceFeature](activated.collect { case (f: InvoiceFeature, s) => (f, s) }, unknown)
def invoiceFeatures(): Features[InvoiceFeature] = Features(activated.collect { case (f: InvoiceFeature, s) => (f, s) }, unknown)
def unscoped(): Features[FeatureScope] = Features[FeatureScope](activated.collect { case (f, s) => (f: Feature with FeatureScope, s) }, unknown)
def unscoped(): Features[Feature] = Features[Feature](activated.collect { case (f, s) => (f: Feature, s) }, unknown)
def toByteVector: ByteVector = {
val activatedFeatureBytes = toByteVectorFromIndex(activated.map { case (feature, support) => feature.supportBit(support) }.toSet)
@ -116,28 +113,28 @@ case class Features[T <: FeatureScope](activated: Map[Feature with T, FeatureSup
object Features {
def empty[T <: FeatureScope]: Features[T] = Features[T](Map.empty[Feature with T, FeatureSupport])
def empty[T <: Feature]: Features[T] = Features[T](Map.empty[T, FeatureSupport])
def apply[T <: FeatureScope](features: (Feature with T, FeatureSupport)*): Features[T] = Features[T](Map.from(features))
def apply[T <: Feature](features: (T, FeatureSupport)*): Features[T] = Features[T](Map.from(features))
def apply(bytes: ByteVector): Features[FeatureScope] = apply(bytes.bits)
def apply(bytes: ByteVector): Features[Feature] = apply(bytes.bits)
def apply(bits: BitVector): Features[FeatureScope] = {
def apply(bits: BitVector): Features[Feature] = {
val all = bits.toIndexedSeq.reverse.zipWithIndex.collect {
case (true, idx) if knownFeatures.exists(_.optional == idx) => Right((knownFeatures.find(_.optional == idx).get, Optional))
case (true, idx) if knownFeatures.exists(_.mandatory == idx) => Right((knownFeatures.find(_.mandatory == idx).get, Mandatory))
case (true, idx) => Left(UnknownFeature(idx))
}
Features[FeatureScope](
Features[Feature](
activated = all.collect { case Right((feature, support)) => feature -> support }.toMap,
unknown = all.collect { case Left(inf) => inf }.toSet
)
}
def fromConfiguration[T <: FeatureScope](config: Config, validFeatures: Set[Feature with T]): Features[T] = Features[T](
def fromConfiguration[T <: Feature](config: Config, validFeatures: Set[T]): Features[T] = Features[T](
config.root().entrySet().asScala.flatMap { entry =>
val featureName = entry.getKey
val feature: Feature with T = validFeatures.find(_.rfcName == featureName).getOrElse(throw new IllegalArgumentException(s"Invalid feature name ($featureName)"))
val feature: T = validFeatures.find(_.rfcName == featureName).getOrElse(throw new IllegalArgumentException(s"Invalid feature name ($featureName)"))
config.getString(featureName) match {
case support if support == Mandatory.toString => Some(feature -> Mandatory)
case support if support == Optional.toString => Some(feature -> Optional)
@ -146,7 +143,7 @@ object Features {
}
}.toMap)
def fromConfiguration(config: Config): Features[FeatureScope] = fromConfiguration[FeatureScope](config, knownFeatures)
def fromConfiguration(config: Config): Features[Feature] = fromConfiguration[Feature](config, knownFeatures)
case object DataLossProtect extends Feature with InitFeature with NodeFeature {
val rfcName = "option_data_loss_protect"
@ -242,7 +239,7 @@ object Features {
val mandatory = 54
}
val knownFeatures: Set[Feature with FeatureScope] = Set(
val knownFeatures: Set[Feature] = Set(
DataLossProtect,
InitialRoutingSync,
UpfrontShutdownScript,
@ -278,16 +275,16 @@ object Features {
case class FeatureException(message: String) extends IllegalArgumentException(message)
def validateFeatureGraph[T <: FeatureScope](features: Features[T]): Option[FeatureException] = featuresDependency.collectFirst {
def validateFeatureGraph[T <: Feature](features: Features[T]): Option[FeatureException] = featuresDependency.collectFirst {
case (feature, dependencies) if features.unscoped().hasFeature(feature) && dependencies.exists(d => !features.unscoped().hasFeature(d)) =>
FeatureException(s"$feature is set but is missing a dependency (${dependencies.filter(d => !features.unscoped().hasFeature(d)).mkString(" and ")})")
}
/** Returns true if both feature sets are compatible. */
def areCompatible[T <: FeatureScope](ours: Features[T], theirs: Features[T]): Boolean = ours.areSupported(theirs) && theirs.areSupported(ours)
def areCompatible[T <: Feature](ours: Features[T], theirs: Features[T]): Boolean = ours.areSupported(theirs) && theirs.areSupported(ours)
/** returns true if both have at least optional support */
def canUseFeature[T <: FeatureScope](localFeatures: Features[T], remoteFeatures: Features[T], feature: Feature with T): Boolean = {
def canUseFeature[T <: Feature](localFeatures: Features[T], remoteFeatures: Features[T], feature: T): Boolean = {
localFeatures.hasFeature(feature) && remoteFeatures.hasFeature(feature)
}

View file

@ -58,7 +58,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
color: Color,
publicAddresses: List[NodeAddress],
torAddress_opt: Option[NodeAddress],
features: Features[FeatureScope],
features: Features[Feature],
private val overrideInitFeatures: Map[PublicKey, Features[InitFeature]],
syncWhitelist: Set[PublicKey],
pluginParams: Seq[PluginParams],
@ -271,7 +271,7 @@ object NodeParams extends Logging {
val nodeAlias = config.getString("node-alias")
require(nodeAlias.getBytes("UTF-8").length <= 32, "invalid alias, too long (max allowed 32 bytes)")
def validateFeatures(features: Features[FeatureScope]): Unit = {
def validateFeatures(features: Features[Feature]): Unit = {
val featuresErr = Features.validateFeatureGraph(features)
require(featuresErr.isEmpty, featuresErr.map(_.message))
require(features.hasFeature(Features.VariableLengthOnion, Some(FeatureSupport.Mandatory)), s"${Features.VariableLengthOnion.rfcName} must be enabled and mandatory")
@ -291,11 +291,11 @@ object NodeParams extends Logging {
require(Features.knownFeatures.map(_.mandatory).intersect(pluginFeatureSet).isEmpty, "Plugin feature bit overlaps with known feature bit")
require(pluginFeatureSet.size == pluginMessageParams.size, "Duplicate plugin feature bits found")
val coreAndPluginFeatures: Features[FeatureScope] = features.copy(unknown = features.unknown ++ pluginMessageParams.map(_.pluginFeature))
val coreAndPluginFeatures: Features[Feature] = features.copy(unknown = features.unknown ++ pluginMessageParams.map(_.pluginFeature))
val overrideInitFeatures: Map[PublicKey, Features[InitFeature]] = config.getConfigList("override-init-features").asScala.map { e =>
val p = PublicKey(ByteVector.fromValidHex(e.getString("nodeid")))
val f = Features.fromConfiguration[InitFeature](e.getConfig("features"), Features.knownFeatures.collect { case f: Feature with InitFeature => f })
val f = Features.fromConfiguration[InitFeature](e.getConfig("features"), Features.knownFeatures.collect { case f: InitFeature => f })
validateFeatures(f.unscoped())
p -> (f.copy(unknown = f.unknown ++ pluginMessageParams.map(_.pluginFeature)): Features[InitFeature])
}.toMap

View file

@ -17,7 +17,7 @@
package fr.acinq.eclair.channel
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat}
import fr.acinq.eclair.{Feature, FeatureScope, FeatureSupport, Features, InitFeature}
import fr.acinq.eclair.{FeatureSupport, Features, InitFeature, Feature}
/**
* Created by t-bast on 24/06/2021.
@ -28,7 +28,7 @@ import fr.acinq.eclair.{Feature, FeatureScope, FeatureSupport, Features, InitFea
* Even if one of these features is later disabled at the connection level, it will still apply to the channel until the
* channel is upgraded or closed.
*/
case class ChannelFeatures(features: Set[Feature with FeatureScope]) {
case class ChannelFeatures(features: Set[Feature]) {
val channelType: SupportedChannelType = {
if (hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) {
@ -45,7 +45,7 @@ case class ChannelFeatures(features: Set[Feature with FeatureScope]) {
val paysDirectlyToWallet: Boolean = channelType.paysDirectlyToWallet
val commitmentFormat: CommitmentFormat = channelType.commitmentFormat
def hasFeature(feature: Feature with FeatureScope): Boolean = features.contains(feature)
def hasFeature(feature: Feature): Boolean = features.contains(feature)
override def toString: String = features.mkString(",")
@ -53,13 +53,13 @@ case class ChannelFeatures(features: Set[Feature with FeatureScope]) {
object ChannelFeatures {
def apply(features: (Feature with FeatureScope)*): ChannelFeatures = ChannelFeatures(Set.from(features))
def apply(features: Feature*): ChannelFeatures = ChannelFeatures(Set.from(features))
/** Enrich the channel type with other permanent features that will be applied to the channel. */
def apply(channelType: ChannelType, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): ChannelFeatures = {
// NB: we don't include features that can be safely activated/deactivated without impacting the channel's operation,
// such as option_dataloss_protect or option_shutdown_anysegwit.
val availableFeatures: Seq[Feature with FeatureScope] = Seq(Features.Wumbo, Features.UpfrontShutdownScript).filter(f => Features.canUseFeature(localFeatures, remoteFeatures, f))
val availableFeatures = Seq(Features.Wumbo, Features.UpfrontShutdownScript).filter(f => Features.canUseFeature(localFeatures, remoteFeatures, f))
val allFeatures = channelType.features.toSeq ++ availableFeatures
ChannelFeatures(allFeatures: _*)
}
@ -69,7 +69,7 @@ object ChannelFeatures {
/** A channel type is a specific set of even feature bits that represent persistent channel features as defined in Bolt 2. */
sealed trait ChannelType {
/** Features representing that channel type. */
def features: Set[Feature with InitFeature]
def features: Set[InitFeature]
}
sealed trait SupportedChannelType extends ChannelType {
@ -84,31 +84,31 @@ object ChannelTypes {
// @formatter:off
case object Standard extends SupportedChannelType {
override def features: Set[Feature with InitFeature] = Set.empty
override def features: Set[InitFeature] = Set.empty
override def paysDirectlyToWallet: Boolean = false
override def commitmentFormat: CommitmentFormat = DefaultCommitmentFormat
override def toString: String = "standard"
}
case object StaticRemoteKey extends SupportedChannelType {
override def features: Set[Feature with InitFeature] = Set(Features.StaticRemoteKey)
override def features: Set[InitFeature] = Set(Features.StaticRemoteKey)
override def paysDirectlyToWallet: Boolean = true
override def commitmentFormat: CommitmentFormat = DefaultCommitmentFormat
override def toString: String = "static_remotekey"
}
case object AnchorOutputs extends SupportedChannelType {
override def features: Set[Feature with InitFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputs)
override def features: Set[InitFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputs)
override def paysDirectlyToWallet: Boolean = false
override def commitmentFormat: CommitmentFormat = UnsafeLegacyAnchorOutputsCommitmentFormat
override def toString: String = "anchor_outputs"
}
case object AnchorOutputsZeroFeeHtlcTx extends SupportedChannelType {
override def features: Set[Feature with InitFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)
override def features: Set[InitFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)
override def paysDirectlyToWallet: Boolean = false
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
override def toString: String = "anchor_outputs_zero_fee_htlc_tx"
}
case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType {
override def features: Set[Feature with InitFeature] = featureBits.activated.keySet
override def features: Set[InitFeature] = featureBits.activated.keySet
override def toString: String = s"0x${featureBits.toByteVector.toHex}"
}
// @formatter:on

View file

@ -225,7 +225,7 @@ object Helpers {
}
def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId): AnnouncementSignatures = {
val features = Features.empty[FeatureScope] // empty features for now
val features = Features.empty[Feature] // empty features for now
val fundingPubKey = nodeParams.channelKeyManager.fundingPublicKey(commitments.localParams.fundingKeyPath)
val witness = Announcements.generateChannelAnnouncementWitness(
nodeParams.chainHash,

View file

@ -28,7 +28,7 @@ import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes
import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.wire.protocol
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{FSMDiagnosticActorLogging, FeatureScope, Features, InitFeature, Logs, TimestampMilli, TimestampSecond}
import fr.acinq.eclair.{FSMDiagnosticActorLogging, Feature, Features, InitFeature, Logs, TimestampMilli, TimestampSecond}
import scodec.Attempt
import scodec.bits.ByteVector

View file

@ -35,7 +35,7 @@ import fr.acinq.eclair.transactions.DirectedHtlc
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.protocol.MessageOnionCodecs.blindedRouteCodec
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64, UnknownFeature}
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, FeatureSupport, Feature, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64, UnknownFeature}
import org.json4s
import org.json4s.JsonAST._
import org.json4s.jackson.Serialization

View file

@ -18,7 +18,7 @@ package fr.acinq.eclair.payment
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, Block, ByteVector32, ByteVector64, Crypto}
import fr.acinq.eclair.{CltvExpiryDelta, FeatureScope, FeatureSupport, Features, InvoiceFeature, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TimestampSecond, randomBytes32}
import fr.acinq.eclair.{CltvExpiryDelta, Feature, FeatureSupport, Features, InvoiceFeature, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TimestampSecond, randomBytes32}
import scodec.bits.{BitVector, ByteOrdering, ByteVector}
import scodec.codecs.{list, ubyte}
import scodec.{Codec, Err}
@ -299,7 +299,7 @@ object Bolt11Invoice {
* This returns a bitvector with the minimum size necessary to encode the features, left padded to have a length (in
* bits) that is a multiple of 5.
*/
def features2bits[T <: FeatureScope](features: Features[T]): BitVector = leftPaddedBits(features.toByteVector.bits)
def features2bits[T <: Feature](features: Features[T]): BitVector = leftPaddedBits(features.toByteVector.bits)
private def leftPaddedBits(bits: BitVector): BitVector = {
var highest = -1
@ -365,7 +365,7 @@ object Bolt11Invoice {
/**
* Features supported or required for receiving this payment.
*/
case class InvoiceFeatures(features: Features[FeatureScope]) extends TaggedField
case class InvoiceFeatures(features: Features[Feature]) extends TaggedField
object Codecs {
@ -408,7 +408,7 @@ object Bolt11Invoice {
.typecase(2, dataCodec(bits).as[UnknownTag2])
.typecase(3, dataCodec(listOfN(extraHopsLengthCodec, extraHopCodec)).as[RoutingInfo])
.typecase(4, dataCodec(bits).as[UnknownTag4])
.typecase(5, dataCodec(bits).xmap[Features[FeatureScope]](Features(_), features2bits).as[InvoiceFeatures])
.typecase(5, dataCodec(bits).xmap[Features[Feature]](Features(_), features2bits).as[InvoiceFeatures])
.typecase(6, dataCodec(bits).as[Expiry])
.typecase(7, dataCodec(bits).as[UnknownTag7])
.typecase(8, dataCodec(bits).as[UnknownTag8])

View file

@ -31,7 +31,7 @@ import fr.acinq.eclair.wire.protocol.CommonCodecs._
import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._
import fr.acinq.eclair.wire.protocol.QueryChannelRangeTlv.queryFlagsCodec
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiryDelta, FeatureScope, Features, InitFeature}
import fr.acinq.eclair.{CltvExpiryDelta, Feature, Features, InitFeature}
import scodec._
import scodec.codecs._
@ -98,7 +98,7 @@ object EclairInternalsSerializer {
("channelQueryChunkSize" | int32) ::
("pathFindingExperimentConf" | pathFindingExperimentConfCodec)).as[RouterConf]
val overrideFeaturesListCodec: Codec[List[(PublicKey, Features[FeatureScope])]] = listOfN(uint16, publicKey ~ variableSizeBytes(uint16, featuresCodec))
val overrideFeaturesListCodec: Codec[List[(PublicKey, Features[Feature])]] = listOfN(uint16, publicKey ~ variableSizeBytes(uint16, featuresCodec))
val peerConnectionConfCodec: Codec[PeerConnection.Conf] = (
("authTimeout" | finiteDurationCodec) ::

View file

@ -19,7 +19,7 @@ package fr.acinq.eclair.router
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, sha256, verifySignature}
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, LexicographicalOrdering}
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiryDelta, FeatureScope, Features, MilliSatoshi, NodeFeature, ShortChannelId, TimestampSecond, TimestampSecondLong, serializationResult}
import fr.acinq.eclair.{CltvExpiryDelta, Feature, Features, MilliSatoshi, NodeFeature, ShortChannelId, TimestampSecond, TimestampSecondLong, serializationResult}
import scodec.bits.ByteVector
import shapeless.HNil
@ -28,16 +28,16 @@ import shapeless.HNil
*/
object Announcements {
def channelAnnouncementWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, bitcoinKey2: PublicKey, features: Features[FeatureScope], tlvStream: TlvStream[ChannelAnnouncementTlv]): ByteVector =
def channelAnnouncementWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, bitcoinKey2: PublicKey, features: Features[Feature], tlvStream: TlvStream[ChannelAnnouncementTlv]): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.channelAnnouncementWitnessCodec.encode(features :: chainHash :: shortChannelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: tlvStream :: HNil))))
def nodeAnnouncementWitnessEncode(timestamp: TimestampSecond, nodeId: PublicKey, rgbColor: Color, alias: String, features: Features[FeatureScope], addresses: List[NodeAddress], tlvStream: TlvStream[NodeAnnouncementTlv]): ByteVector =
def nodeAnnouncementWitnessEncode(timestamp: TimestampSecond, nodeId: PublicKey, rgbColor: Color, alias: String, features: Features[Feature], addresses: List[NodeAddress], tlvStream: TlvStream[NodeAnnouncementTlv]): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.nodeAnnouncementWitnessCodec.encode(features :: timestamp :: nodeId :: rgbColor :: alias :: addresses :: tlvStream :: HNil))))
def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: TimestampSecond, channelFlags: ChannelUpdate.ChannelFlags, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: Option[MilliSatoshi], tlvStream: TlvStream[ChannelUpdateTlv]): ByteVector =
sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: channelFlags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: htlcMaximumMsat :: tlvStream :: HNil))))
def generateChannelAnnouncementWitness(chainHash: ByteVector32, shortChannelId: ShortChannelId, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, features: Features[FeatureScope]): ByteVector =
def generateChannelAnnouncementWitness(chainHash: ByteVector32, shortChannelId: ShortChannelId, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, features: Features[Feature]): ByteVector =
if (isNode1(localNodeId, remoteNodeId)) {
channelAnnouncementWitnessEncode(chainHash, shortChannelId, localNodeId, remoteNodeId, localFundingKey, remoteFundingKey, features, TlvStream.empty)
} else {

View file

@ -23,7 +23,7 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.{BlockHeight, Feature, FeatureScope, Features, channel}
import fr.acinq.eclair.{BlockHeight, Features, Feature, channel}
import scodec.bits.BitVector
private[channel] object ChannelTypes0 {
@ -196,8 +196,8 @@ private[channel] object ChannelTypes0 {
ChannelConfig()
}
val isWumboChannel = commitInput.txOut.amount > Satoshi(16777215)
val baseChannelFeatures: Set[Feature with FeatureScope] = if (isWumboChannel) Set(Features.Wumbo) else Set.empty
val commitmentFeatures: Set[Feature with FeatureScope] = if (channelVersion.hasAnchorOutputs) {
val baseChannelFeatures: Set[Feature] = if (isWumboChannel) Set(Features.Wumbo) else Set.empty
val commitmentFeatures: Set[Feature] = if (channelVersion.hasAnchorOutputs) {
Set(Features.StaticRemoteKey, Features.AnchorOutputs)
} else if (channelVersion.hasStaticRemotekey) {
Set(Features.StaticRemoteKey)

View file

@ -18,7 +18,7 @@ package fr.acinq.eclair.wire.protocol
import fr.acinq.eclair.wire.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.wire.protocol.CommonCodecs._
import fr.acinq.eclair.{FeatureScope, Features, InitFeature, KamonExt, NodeFeature}
import fr.acinq.eclair.{Feature, Features, InitFeature, KamonExt, NodeFeature}
import scodec.bits.{BitVector, ByteVector}
import scodec.codecs._
import scodec.{Attempt, Codec}
@ -29,7 +29,7 @@ import shapeless._
*/
object LightningMessageCodecs {
val featuresCodec: Codec[Features[FeatureScope]] = varsizebinarydata.xmap[Features[FeatureScope]](
val featuresCodec: Codec[Features[Feature]] = varsizebinarydata.xmap[Features[Feature]](
{ bytes => Features(bytes) },
{ features => features.toByteVector }
)

View file

@ -21,7 +21,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.{ChannelFlags, ChannelType}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, FeatureScope, Features, InitFeature, MilliSatoshi, NodeFeature, ShortChannelId, TimestampSecond, UInt64}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, NodeFeature, ShortChannelId, TimestampSecond, UInt64}
import scodec.bits.ByteVector
import java.net.{Inet4Address, Inet6Address, InetAddress, InetSocketAddress}
@ -200,7 +200,7 @@ case class ChannelAnnouncement(nodeSignature1: ByteVector64,
nodeSignature2: ByteVector64,
bitcoinSignature1: ByteVector64,
bitcoinSignature2: ByteVector64,
features: Features[FeatureScope],
features: Features[Feature],
chainHash: ByteVector32,
shortChannelId: ShortChannelId,
nodeId1: PublicKey,
@ -263,7 +263,7 @@ case class Tor3(tor3: String, port: Int) extends OnionAddress { override def soc
// @formatter:on
case class NodeAnnouncement(signature: ByteVector64,
features: Features[FeatureScope],
features: Features[Feature],
timestamp: TimestampSecond,
nodeId: PublicKey,
rgbColor: Color,

View file

@ -109,7 +109,7 @@ class FeaturesSpec extends AnyFunSuite {
}
test("features compatibility") {
case class TestCase(ours: Features[FeatureScope], theirs: Features[FeatureScope], oursSupportTheirs: Boolean, theirsSupportOurs: Boolean, compatible: Boolean)
case class TestCase(ours: Features[Feature], theirs: Features[Feature], oursSupportTheirs: Boolean, theirsSupportOurs: Boolean, compatible: Boolean)
val testCases = Seq(
// Empty features
TestCase(
@ -168,7 +168,7 @@ class FeaturesSpec extends AnyFunSuite {
// They have unknown optional features
TestCase(
Features(VariableLengthOnion -> Optional),
Features[FeatureScope](Map[Feature with FeatureScope, FeatureSupport](VariableLengthOnion -> Optional), Set(UnknownFeature(141))),
Features(Map(VariableLengthOnion -> Optional), unknown = Set(UnknownFeature(141))),
oursSupportTheirs = true,
theirsSupportOurs = true,
compatible = true
@ -176,7 +176,7 @@ class FeaturesSpec extends AnyFunSuite {
// They have unknown mandatory features
TestCase(
Features(VariableLengthOnion -> Optional),
Features[FeatureScope](Map[Feature with FeatureScope, FeatureSupport](VariableLengthOnion -> Optional), Set(UnknownFeature(142))),
Features(Map(VariableLengthOnion -> Optional), unknown = Set(UnknownFeature(142))),
oursSupportTheirs = false,
theirsSupportOurs = true,
compatible = false
@ -198,10 +198,10 @@ class FeaturesSpec extends AnyFunSuite {
compatible = false
),
// nonreg testing of future features (needs to be updated with every new supported mandatory bit)
TestCase(Features.empty, Features[FeatureScope](Map.empty[Feature with FeatureScope, FeatureSupport], Set(UnknownFeature(24))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
TestCase(Features.empty, Features[FeatureScope](Map.empty[Feature with FeatureScope, FeatureSupport], Set(UnknownFeature(25))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true),
TestCase(Features.empty, Features[FeatureScope](Map.empty[Feature with FeatureScope, FeatureSupport], Set(UnknownFeature(28))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
TestCase(Features.empty, Features[FeatureScope](Map.empty[Feature with FeatureScope, FeatureSupport], Set(UnknownFeature(29))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true),
TestCase(Features.empty, Features(Map.empty, unknown = Set(UnknownFeature(24))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
TestCase(Features.empty, Features(Map.empty, unknown = Set(UnknownFeature(25))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true),
TestCase(Features.empty, Features(Map.empty, unknown = Set(UnknownFeature(28))), oursSupportTheirs = false, theirsSupportOurs = true, compatible = false),
TestCase(Features.empty, Features(Map.empty, unknown = Set(UnknownFeature(29))), oursSupportTheirs = true, theirsSupportOurs = true, compatible = true),
)
for (testCase <- testCases) {
@ -212,20 +212,20 @@ class FeaturesSpec extends AnyFunSuite {
}
test("filter features based on their usage") {
val features = Features[FeatureScope](
Map[Feature with FeatureScope, FeatureSupport](DataLossProtect -> Optional, InitialRoutingSync -> Optional, VariableLengthOnion -> Mandatory, PaymentMetadata -> Optional),
val features = Features(
Map(DataLossProtect -> Optional, InitialRoutingSync -> Optional, VariableLengthOnion -> Mandatory, PaymentMetadata -> Optional),
Set(UnknownFeature(753), UnknownFeature(852), UnknownFeature(65303))
)
assert(features.initFeatures() === Features[InitFeature](
Map[Feature with InitFeature, FeatureSupport](DataLossProtect -> Optional, InitialRoutingSync -> Optional, VariableLengthOnion -> Mandatory),
assert(features.initFeatures() === Features(
Map(DataLossProtect -> Optional, InitialRoutingSync -> Optional, VariableLengthOnion -> Mandatory),
Set(UnknownFeature(753), UnknownFeature(852), UnknownFeature(65303))
))
assert(features.nodeAnnouncementFeatures() === Features[NodeFeature](
Map[Feature with NodeFeature, FeatureSupport](DataLossProtect -> Optional, VariableLengthOnion -> Mandatory),
assert(features.nodeAnnouncementFeatures() === Features(
Map(DataLossProtect -> Optional, VariableLengthOnion -> Mandatory),
Set(UnknownFeature(753), UnknownFeature(852), UnknownFeature(65303))
))
assert(features.invoiceFeatures() === Features[InvoiceFeature](
Map[Feature with InvoiceFeature, FeatureSupport](VariableLengthOnion -> Mandatory, PaymentMetadata -> Optional),
assert(features.invoiceFeatures() === Features(
Map(VariableLengthOnion -> Mandatory, PaymentMetadata -> Optional),
Set(UnknownFeature(753), UnknownFeature(852), UnknownFeature(65303))
))
}
@ -235,8 +235,8 @@ class FeaturesSpec extends AnyFunSuite {
hex"" -> Features.empty,
hex"0100" -> Features(VariableLengthOnion -> Mandatory),
hex"028a8a" -> Features(DataLossProtect -> Optional, InitialRoutingSync -> Optional, ChannelRangeQueries -> Optional, VariableLengthOnion -> Optional, ChannelRangeQueriesExtended -> Optional, PaymentSecret -> Optional, BasicMultiPartPayment -> Optional),
hex"09004200" -> Features[FeatureScope](Map[Feature with FeatureScope, FeatureSupport](VariableLengthOnion -> Optional, PaymentSecret -> Mandatory, ShutdownAnySegwit -> Optional), Set(UnknownFeature(24))),
hex"52000000" -> Features[FeatureScope](Map.empty[Feature with FeatureScope, FeatureSupport], Set(UnknownFeature(25), UnknownFeature(28), UnknownFeature(30)))
hex"09004200" -> Features(Map(VariableLengthOnion -> Optional, PaymentSecret -> Mandatory, ShutdownAnySegwit -> Optional), Set(UnknownFeature(24))),
hex"52000000" -> Features(Map.empty[Feature, FeatureSupport], Set(UnknownFeature(25), UnknownFeature(28), UnknownFeature(30)))
)
for ((bin, features) <- testCases) {

View file

@ -84,8 +84,8 @@ object TestConstants {
color = Color(1, 2, 3),
publicAddresses = NodeAddress.fromParts("localhost", 9731).get :: Nil,
torAddress_opt = None,
features = Features[FeatureScope](
Map[Feature with FeatureScope, FeatureSupport](
features = Features(
Map(
DataLossProtect -> Optional,
ChannelRangeQueries -> Optional,
ChannelRangeQueriesExtended -> Optional,
@ -94,7 +94,7 @@ object TestConstants {
BasicMultiPartPayment -> Optional,
PaymentMetadata -> Optional,
),
Set(UnknownFeature(TestFeature.optional))
unknown = Set(UnknownFeature(TestFeature.optional))
),
pluginParams = List(pluginParams),
overrideInitFeatures = Map.empty,

View file

@ -20,7 +20,7 @@ import fr.acinq.eclair.FeatureSupport._
import fr.acinq.eclair.Features._
import fr.acinq.eclair.channel.states.ChannelStateTestsHelperMethods
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.{Features, InitFeature, TestKitBaseClass}
import fr.acinq.eclair.{Features, InitFeature, NodeFeature, TestKitBaseClass}
import org.scalatest.funsuite.AnyFunSuiteLike
class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStateTestsHelperMethods {
@ -80,29 +80,31 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha
}
test("create channel type from features") {
case class TestCase(features: Features[InitFeature], expectedChannelType: ChannelType)
val validChannelTypes = Seq(
Features.empty[InitFeature] -> ChannelTypes.Standard,
Features[InitFeature](StaticRemoteKey -> Mandatory) -> ChannelTypes.StaticRemoteKey,
Features[InitFeature](StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory) -> ChannelTypes.AnchorOutputs,
Features[InitFeature](StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory) -> ChannelTypes.AnchorOutputsZeroFeeHtlcTx,
TestCase(Features.empty[InitFeature], ChannelTypes.Standard),
TestCase(Features(StaticRemoteKey -> Mandatory), ChannelTypes.StaticRemoteKey),
TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory), ChannelTypes.AnchorOutputs),
TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx),
)
for ((features, expected) <- validChannelTypes) {
assert(ChannelTypes.fromFeatures(features) === expected)
for (testCase <- validChannelTypes) {
assert(ChannelTypes.fromFeatures(testCase.features) === testCase.expectedChannelType)
}
val invalidChannelTypes = Seq(
Features[InitFeature](Wumbo -> Optional),
Features[InitFeature](StaticRemoteKey -> Optional),
Features[InitFeature](StaticRemoteKey -> Mandatory, Wumbo -> Optional),
Features[InitFeature](StaticRemoteKey -> Optional, AnchorOutputs -> Optional),
Features[InitFeature](StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional),
Features[InitFeature](StaticRemoteKey -> Optional, AnchorOutputs -> Mandatory),
Features[InitFeature](StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional),
Features[InitFeature](StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Mandatory),
Features[InitFeature](StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional),
Features[InitFeature](StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory),
Features[InitFeature](StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Mandatory),
Features[InitFeature](StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory, Wumbo -> Optional),
val invalidChannelTypes: Seq[Features[InitFeature]] = Seq(
Features(Wumbo -> Optional),
Features(StaticRemoteKey -> Optional),
Features(StaticRemoteKey -> Mandatory, Wumbo -> Optional),
Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional),
Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional),
Features(StaticRemoteKey -> Optional, AnchorOutputs -> Mandatory),
Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional),
Features(StaticRemoteKey -> Optional, AnchorOutputsZeroFeeHtlcTx -> Mandatory),
Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional),
Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory),
Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Mandatory),
Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory, Wumbo -> Optional),
)
for (features <- invalidChannelTypes) {
assert(ChannelTypes.fromFeatures(features) === ChannelTypes.UnsupportedChannelType(features))

View file

@ -353,7 +353,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle
}
// They want to use a channel type we don't support yet.
{
val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(UnsupportedChannelType(Features[InitFeature](Map[Feature with InitFeature, FeatureSupport](StaticRemoteKey -> Mandatory), Set(UnknownFeature(22)))))))
val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(UnsupportedChannelType(Features(Map(StaticRemoteKey -> Mandatory), unknown = Set(UnknownFeature(22)))))))
peerConnection.send(peer, open)
peerConnection.expectMsg(Error(open.temporaryChannelId, "invalid channel_type=0x401000, expected channel_type=standard"))
}

View file

@ -21,7 +21,7 @@ import fr.acinq.bitcoin.{Block, BtcDouble, ByteVector32, Crypto, MilliBtcDouble,
import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional}
import fr.acinq.eclair.Features.{PaymentMetadata, PaymentSecret, _}
import fr.acinq.eclair.payment.Bolt11Invoice._
import fr.acinq.eclair.{CltvExpiryDelta, Feature, FeatureScope, FeatureSupport, Features, InvoiceFeature, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, TimestampSecond, TimestampSecondLong, ToMilliSatoshiConversion, UnknownFeature, randomBytes32, randomKey}
import fr.acinq.eclair.{CltvExpiryDelta, FeatureSupport, Features, Feature, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, TimestampSecond, TimestampSecondLong, ToMilliSatoshiConversion, UnknownFeature, randomBytes32}
import org.scalatest.funsuite.AnyFunSuite
import scodec.DecodeResult
import scodec.bits._
@ -54,7 +54,7 @@ class Bolt11InvoiceSpec extends AnyFunSuite {
timestamp: TimestampSecond = TimestampSecond.now(),
paymentSecret: ByteVector32 = randomBytes32(),
paymentMetadata: Option[ByteVector] = None,
features: Features[FeatureScope] = defaultFeatures.unscoped()): Bolt11Invoice = {
features: Features[Feature] = defaultFeatures.unscoped()): Bolt11Invoice = {
require(features.hasFeature(Features.PaymentSecret, Some(FeatureSupport.Mandatory)), "invoices must require a payment secret")
val prefix = prefixes(chainHash)
val tags = {
@ -624,11 +624,11 @@ class Bolt11InvoiceSpec extends AnyFunSuite {
test("no unknown feature in invoice"){
assert(TestConstants.Alice.nodeParams.features.invoiceFeatures().unknown.nonEmpty)
val invoice = Bolt11Invoice(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, Left("Some invoice"), CltvExpiryDelta(18), features = TestConstants.Alice.nodeParams.features.invoiceFeatures())
assert(invoice.features === Features[InvoiceFeature](Map[Feature with InvoiceFeature, FeatureSupport](PaymentSecret -> Mandatory, BasicMultiPartPayment -> Optional, PaymentMetadata -> Optional, VariableLengthOnion -> Mandatory)))
assert(invoice.features === Features(PaymentSecret -> Mandatory, BasicMultiPartPayment -> Optional, PaymentMetadata -> Optional, VariableLengthOnion -> Mandatory))
assert(Bolt11Invoice.fromString(invoice.toString) === invoice)
}
test("Invoices can't have high features"){
assertThrows[Exception](createInvoiceUnsafe(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, Left("Some invoice"), CltvExpiryDelta(18), features = Features[FeatureScope](Map[Feature with FeatureScope, FeatureSupport](VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), Set(UnknownFeature(424242)))))
assertThrows[Exception](createInvoiceUnsafe(Block.LivenetGenesisBlock.hash, Some(123 msat), ByteVector32.One, priv, Left("Some invoice"), CltvExpiryDelta(18), features = Features[Feature](Map[Feature, FeatureSupport](VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), Set(UnknownFeature(424242)))))
}
}

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.protocol.PaymentOnion.FinalTlvPayload
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, FeatureScope, Features, MilliSatoshiLong, NodeParams, ShortChannelId, TestConstants, TestKitBaseClass, TimestampMilliLong, randomBytes32, randomKey}
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, Features, MilliSatoshiLong, NodeParams, ShortChannelId, TestConstants, TestKitBaseClass, TimestampMilliLong, randomBytes32, randomKey}
import org.scalatest.Outcome
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import scodec.bits.HexStringSyntax
@ -46,18 +46,18 @@ import scala.concurrent.duration._
class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
val featuresWithoutMpp = Features[FeatureScope](
val featuresWithoutMpp = Features[Feature](
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
)
val featuresWithMpp = Features[FeatureScope](
val featuresWithMpp = Features[Feature](
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
BasicMultiPartPayment -> Optional
)
val featuresWithKeySend = Features[FeatureScope](
val featuresWithKeySend = Features[Feature](
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
KeySend -> Optional

View file

@ -37,10 +37,10 @@ import fr.acinq.eclair.router.Router._
import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv.{AmountToForward, KeySend, OutgoingCltv}
import fr.acinq.eclair.wire.protocol.PaymentOnion.FinalTlvPayload
import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{CltvExpiryDelta, Feature, FeatureSupport, Features, InvoiceFeature, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, TimestampSecond, UnknownFeature, randomBytes32, randomKey}
import fr.acinq.eclair.{CltvExpiryDelta, Features, InvoiceFeature, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, TimestampSecond, UnknownFeature, randomBytes32, randomKey}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
import scodec.bits.{BinStringSyntax, ByteVector, HexStringSyntax}
import scodec.bits.{ByteVector, HexStringSyntax}
import java.util.UUID
import scala.concurrent.duration._
@ -53,18 +53,18 @@ 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[InvoiceFeature] = Features[InvoiceFeature](
val featuresWithoutMpp: Features[InvoiceFeature] = Features(
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory
)
val featuresWithMpp: Features[InvoiceFeature] = Features[InvoiceFeature](
val featuresWithMpp: Features[InvoiceFeature] = Features(
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
BasicMultiPartPayment -> Optional,
)
val featuresWithTrampoline: Features[InvoiceFeature] = Features[InvoiceFeature](
val featuresWithTrampoline: Features[InvoiceFeature] = Features(
VariableLengthOnion -> Mandatory,
PaymentSecret -> Mandatory,
BasicMultiPartPayment -> Optional,
@ -128,7 +128,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
Bolt11Invoice.Description("Some invoice"),
Bolt11Invoice.PaymentSecret(randomBytes32()),
Bolt11Invoice.Expiry(3600),
Bolt11Invoice.InvoiceFeatures(Features[InvoiceFeature](Map[Feature with InvoiceFeature, FeatureSupport](VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), Set(UnknownFeature(42))).unscoped())
Bolt11Invoice.InvoiceFeatures(Features(Map(VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), unknown = Set(UnknownFeature(42))))
)
val invoice = Bolt11Invoice("lnbc", Some(finalAmount), TimestampSecond.now(), randomKey().publicKey, taggedFields, ByteVector.empty)
val req = SendPaymentToNode(finalAmount + 100.msat, invoice, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams)

View file

@ -54,7 +54,7 @@ class AnnouncementsSpec extends AnyFunSuite {
}
test("create valid signed node announcement") {
val features = Features[FeatureScope](
val features = Features(
Features.DataLossProtect -> FeatureSupport.Optional,
Features.InitialRoutingSync -> FeatureSupport.Optional,
Features.ChannelRangeQueries -> FeatureSupport.Optional,

View file

@ -271,7 +271,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
val commit_sig = CommitSig(randomBytes32(), randomBytes64(), randomBytes64() :: randomBytes64() :: randomBytes64() :: Nil)
val revoke_and_ack = RevokeAndAck(randomBytes32(), scalar(0), point(1))
val channel_announcement = ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(bin(7, 9)), Block.RegtestGenesisBlock.hash, ShortChannelId(1), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey)
val node_announcement = NodeAnnouncement(randomBytes64(), Features[FeatureScope](DataLossProtect -> Optional), 1 unixsec, randomKey().publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil)
val node_announcement = NodeAnnouncement(randomBytes64(), Features(DataLossProtect -> Optional), 1 unixsec, randomKey().publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil)
val channel_update = ChannelUpdate(randomBytes64(), Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(3), 4 msat, 5 msat, 6, None)
val announcement_signatures = AnnouncementSignatures(randomBytes32(), ShortChannelId(42), randomBytes64(), randomBytes64())
val gossip_timestamp_filter = GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, 100000 unixsec, 1500)