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:
parent
f14300e33f
commit
7b5cefaf99
22 changed files with 118 additions and 119 deletions
|
@ -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])
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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) ::
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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)))))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue