Skip to content

Commit a3daca2

Browse files
committed
Address PR comments
* ChannelType contains a set of features * TLV doesn't expose the feature bits directly * An unsupported channel type class is explicitly added
1 parent 33e1466 commit a3daca2

File tree

17 files changed

+145
-121
lines changed

17 files changed

+145
-121
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ trait Eclair {
9090

9191
def disconnect(nodeId: PublicKey)(implicit timeout: Timeout): Future[String]
9292

93-
def open(nodeId: PublicKey, fundingAmount: Satoshi, pushAmount_opt: Option[MilliSatoshi], channelType_opt: Option[ChannelType], fundingFeeratePerByte_opt: Option[FeeratePerByte], flags_opt: Option[Int], openTimeout_opt: Option[Timeout])(implicit timeout: Timeout): Future[ChannelOpenResponse]
93+
def open(nodeId: PublicKey, fundingAmount: Satoshi, pushAmount_opt: Option[MilliSatoshi], channelType_opt: Option[SupportedChannelType], fundingFeeratePerByte_opt: Option[FeeratePerByte], flags_opt: Option[Int], openTimeout_opt: Option[Timeout])(implicit timeout: Timeout): Future[ChannelOpenResponse]
9494

9595
def close(channels: List[ApiTypes.ChannelIdentifier], scriptPubKey_opt: Option[ByteVector])(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_CLOSE]]]]
9696

@@ -177,7 +177,7 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
177177
(appKit.switchboard ? Peer.Disconnect(nodeId)).mapTo[String]
178178
}
179179

180-
override def open(nodeId: PublicKey, fundingAmount: Satoshi, pushAmount_opt: Option[MilliSatoshi], channelType_opt: Option[ChannelType], fundingFeeratePerByte_opt: Option[FeeratePerByte], flags_opt: Option[Int], openTimeout_opt: Option[Timeout])(implicit timeout: Timeout): Future[ChannelOpenResponse] = {
180+
override def open(nodeId: PublicKey, fundingAmount: Satoshi, pushAmount_opt: Option[MilliSatoshi], channelType_opt: Option[SupportedChannelType], fundingFeeratePerByte_opt: Option[FeeratePerByte], flags_opt: Option[Int], openTimeout_opt: Option[Timeout])(implicit timeout: Timeout): Future[ChannelOpenResponse] = {
181181
// we want the open timeout to expire *before* the default ask timeout, otherwise user won't get a generic response
182182
val openTimeout = openTimeout_opt.getOrElse(Timeout(10 seconds))
183183
(appKit.switchboard ? Peer.OpenChannel(

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package fr.acinq.eclair.blockchain.fee
1919
import fr.acinq.bitcoin.Crypto.PublicKey
2020
import fr.acinq.bitcoin.Satoshi
2121
import fr.acinq.eclair.blockchain.CurrentFeerates
22-
import fr.acinq.eclair.channel.{ChannelType, ChannelTypes}
22+
import fr.acinq.eclair.channel.{ChannelType, ChannelTypes, SupportedChannelType}
2323

2424
trait FeeEstimator {
2525
// @formatter:off
@@ -37,7 +37,7 @@ case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMax
3737
* @param proposedFeerate fee rate proposed (new proposal through update_fee or previous proposal used in our current commit tx)
3838
* @return true if the difference between proposed and reference fee rates is too high.
3939
*/
40-
def isFeeDiffTooHigh(channelType: ChannelType, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
40+
def isFeeDiffTooHigh(channelType: SupportedChannelType, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
4141
channelType match {
4242
case ChannelTypes.Standard =>
4343
proposedFeerate < networkFeerate * ratioLow || networkFeerate * ratioHigh < proposedFeerate

eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
222222
htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
223223
firstPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0),
224224
channelFlags = channelFlags,
225-
tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScript(localShutdownScript), ChannelTlv.ChannelType(channelType.features)))
225+
tlvStream = TlvStream(
226+
ChannelTlv.UpfrontShutdownScriptTlv(localShutdownScript),
227+
ChannelTlv.ChannelTypeTlv(channelType)
228+
))
226229
goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder, open) sending open
227230

228231
case Event(inputFundee@INPUT_INIT_FUNDEE(_, localParams, remote, _, _, _), Nothing) if !localParams.isFunder =>
@@ -338,7 +341,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
338341

339342
when(WAIT_FOR_OPEN_CHANNEL)(handleExceptions {
340343
case Event(open: OpenChannel, d@DATA_WAIT_FOR_OPEN_CHANNEL(INPUT_INIT_FUNDEE(_, localParams, _, remoteInit, channelConfig, channelType))) =>
341-
Helpers.validateParamsFundee(nodeParams, localParams.initFeatures, channelType, open, remoteNodeId, remoteInit.features) match {
344+
Helpers.validateParamsFundee(nodeParams, channelType, localParams.initFeatures, open, remoteNodeId, remoteInit.features) match {
342345
case Left(t) => handleLocalError(t, d, Some(open))
343346
case Right((channelFeatures, remoteShutdownScript)) =>
344347
context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isFunder = false, open.temporaryChannelId, open.feeratePerKw, None))
@@ -362,7 +365,10 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
362365
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
363366
htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
364367
firstPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0),
365-
tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScript(localShutdownScript), ChannelTlv.ChannelType(channelType.features)))
368+
tlvStream = TlvStream(
369+
ChannelTlv.UpfrontShutdownScriptTlv(localShutdownScript),
370+
ChannelTlv.ChannelTypeTlv(channelType)
371+
))
366372
val remoteParams = RemoteParams(
367373
nodeId = remoteNodeId,
368374
dustLimit = open.dustLimitSatoshis,
@@ -887,7 +893,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
887893
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey, _), d: DATA_NORMAL) =>
888894
d.commitments.getRemoteShutdownScript(remoteScriptPubKey) match {
889895
case Left(e) =>
890-
log.warning("they sent an invalid closing script")
896+
log.warning(s"they sent an invalid closing script: ${e.getMessage}")
891897
context.system.scheduler.scheduleOnce(2 second, peer, Peer.Disconnect(remoteNodeId))
892898
stay() sending Warning(d.channelId, "invalid closing script")
893899
case Right(remoteShutdownScript) =>

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ case class INPUT_INIT_FUNDER(temporaryChannelId: ByteVector32,
8787
remoteInit: Init,
8888
channelFlags: Byte,
8989
channelConfig: ChannelConfig,
90-
channelType: ChannelType)
90+
channelType: SupportedChannelType)
9191
case class INPUT_INIT_FUNDEE(temporaryChannelId: ByteVector32,
9292
localParams: LocalParams,
9393
remote: ActorRef,
9494
remoteInit: Init,
9595
channelConfig: ChannelConfig,
96-
channelType: ChannelType)
96+
channelType: SupportedChannelType)
9797
case object INPUT_CLOSE_COMPLETE_TIMEOUT // when requesting a mutual close, we wait for as much as this timeout, then unilateral close
9898
case object INPUT_DISCONNECTED
9999
case class INPUT_RECONNECTED(remote: ActorRef, localInit: Init, remoteInit: Init)

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import fr.acinq.bitcoin.Crypto.PrivateKey
2020
import fr.acinq.bitcoin.{ByteVector32, Satoshi, Transaction}
2121
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
2222
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, Error, UpdateAddHtlc}
23-
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, UInt64}
23+
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshi, UInt64}
2424

2525
/**
2626
* Created by PM on 11/04/2017.
@@ -40,7 +40,7 @@ case class InvalidChainHash (override val channelId: Byte
4040
case class InvalidFundingAmount (override val channelId: ByteVector32, fundingAmount: Satoshi, min: Satoshi, max: Satoshi) extends ChannelException(channelId, s"invalid funding_satoshis=$fundingAmount (min=$min max=$max)")
4141
case class InvalidPushAmount (override val channelId: ByteVector32, pushAmount: MilliSatoshi, max: MilliSatoshi) extends ChannelException(channelId, s"invalid pushAmount=$pushAmount (max=$max)")
4242
case class InvalidMaxAcceptedHtlcs (override val channelId: ByteVector32, maxAcceptedHtlcs: Int, max: Int) extends ChannelException(channelId, s"invalid max_accepted_htlcs=$maxAcceptedHtlcs (max=$max)")
43-
case class InvalidChannelType (override val channelId: ByteVector32, ourChannelType: Features, theirChannelType: Features) extends ChannelException(channelId, s"invalid channel_type=0x${theirChannelType.toByteVector.toHex}, expected 0x${ourChannelType.toByteVector.toHex}")
43+
case class InvalidChannelType (override val channelId: ByteVector32, ourChannelType: ChannelType, theirChannelType: ChannelType) extends ChannelException(channelId, s"invalid channel_type=$theirChannelType, expected channel_type=$ourChannelType")
4444
case class DustLimitTooSmall (override val channelId: ByteVector32, dustLimit: Satoshi, min: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is too small (min=$min)")
4545
case class DustLimitTooLarge (override val channelId: ByteVector32, dustLimit: Satoshi, max: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is too large (max=$max)")
4646
case class DustLimitAboveOurChannelReserve (override val channelId: ByteVector32, dustLimit: Satoshi, channelReserve: Satoshi) extends ChannelException(channelId, s"dustLimit=$dustLimit is above our channelReserve=$channelReserve")

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ case class ChannelFeatures(activated: Set[Feature]) {
4040
}
4141
}
4242

43-
val channelType: ChannelType = {
43+
val channelType: SupportedChannelType = {
4444
if (hasFeature(AnchorOutputs)) {
4545
ChannelTypes.AnchorOutputs
4646
} else if (hasFeature(StaticRemoteKey)) {
@@ -67,52 +67,57 @@ object ChannelFeatures {
6767
// NB: we don't include features that can be safely activated/deactivated without impacting the channel's operation,
6868
// such as option_dataloss_protect or option_shutdown_anysegwit.
6969
val availableFeatures: Seq[Feature] = Seq(Wumbo, OptionUpfrontShutdownScript).filter(f => Features.canUseFeature(localFeatures, remoteFeatures, f))
70-
val allFeatures = channelType.features.activated.keys.toSeq ++ availableFeatures
70+
val allFeatures = channelType.features.toSeq ++ availableFeatures
7171
ChannelFeatures(allFeatures: _*)
7272
}
7373

7474
}
7575

7676
/** A channel type is a specific set of even feature bits that represent persistent channel features as defined in Bolt 2. */
7777
sealed trait ChannelType {
78-
// @formatter:off
7978
/** Features representing that channel type. */
80-
def features: Features
79+
def features: Set[Feature]
80+
}
81+
82+
sealed trait SupportedChannelType extends ChannelType {
8183
/** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */
8284
def paysDirectlyToWallet: Boolean
83-
// @formatter:on
8485
}
8586

8687
object ChannelTypes {
8788

8889
// @formatter:off
89-
case object Standard extends ChannelType {
90-
override def features: Features = Features.empty
90+
case object Standard extends SupportedChannelType {
91+
override def features: Set[Feature] = Set.empty
9192
override def paysDirectlyToWallet: Boolean = false
9293
override def toString: String = "standard"
9394
}
94-
case object StaticRemoteKey extends ChannelType {
95-
override def features: Features = Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory)
95+
case object StaticRemoteKey extends SupportedChannelType {
96+
override def features: Set[Feature] = Set(Features.StaticRemoteKey)
9697
override def paysDirectlyToWallet: Boolean = true
9798
override def toString: String = "static_remotekey"
9899
}
99-
case object AnchorOutputs extends ChannelType {
100-
override def features: Features = Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory)
100+
case object AnchorOutputs extends SupportedChannelType {
101+
override def features: Set[Feature] = Set(Features.StaticRemoteKey, Features.AnchorOutputs)
101102
override def paysDirectlyToWallet: Boolean = false
102103
override def toString: String = "anchor_outputs"
103104
}
105+
case class UnsupportedChannelType(featureBits: Features) extends ChannelType {
106+
override def features: Set[Feature] = featureBits.activated.keySet
107+
override def toString: String = s"0x${featureBits.toByteVector.toHex}"
108+
}
104109
// @formatter:on
105110

106111
// NB: Bolt 2: features must exactly match in order to identify a channel type.
107-
def fromFeatures(features: Features): Option[ChannelType] = features match {
108-
case f if f == AnchorOutputs.features => Some(AnchorOutputs)
109-
case f if f == StaticRemoteKey.features => Some(StaticRemoteKey)
110-
case f if f == Standard.features => Some(Standard)
111-
case _ => None
112+
def fromFeatures(features: Features): ChannelType = features match {
113+
case f if f == Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory, Features.AnchorOutputs -> FeatureSupport.Mandatory) => AnchorOutputs
114+
case f if f == Features(Features.StaticRemoteKey -> FeatureSupport.Mandatory) => StaticRemoteKey
115+
case f if f == Features.empty => Standard
116+
case _ => UnsupportedChannelType(features)
112117
}
113118

114119
/** Pick the channel type based on local and remote feature bits. */
115-
def pickChannelType(localFeatures: Features, remoteFeatures: Features): ChannelType = {
120+
def pickChannelType(localFeatures: Features, remoteFeatures: Features): SupportedChannelType = {
116121
if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputs)) {
117122
AnchorOutputs
118123
} else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.StaticRemoteKey)) {

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ case class Commitments(channelId: ByteVector32,
199199

200200
val commitmentFormat: CommitmentFormat = channelFeatures.commitmentFormat
201201

202-
val channelType: ChannelType = channelFeatures.channelType
202+
val channelType: SupportedChannelType = channelFeatures.channelType
203203

204204
val localNodeId: PublicKey = localParams.nodeId
205205

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,15 @@ object Helpers {
9999
/**
100100
* Called by the fundee
101101
*/
102-
def validateParamsFundee(nodeParams: NodeParams, initFeatures: Features, channelType: ChannelType, open: OpenChannel, remoteNodeId: PublicKey, remoteFeatures: Features): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = {
102+
def validateParamsFundee(nodeParams: NodeParams, channelType: SupportedChannelType, localFeatures: Features, open: OpenChannel, remoteNodeId: PublicKey, remoteFeatures: Features): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = {
103103
// BOLT #2: if the chain_hash value, within the open_channel, message is set to a hash of a chain that is unknown to the receiver:
104104
// MUST reject the channel.
105105
if (nodeParams.chainHash != open.chainHash) return Left(InvalidChainHash(open.temporaryChannelId, local = nodeParams.chainHash, remote = open.chainHash))
106106

107107
if (open.fundingSatoshis < nodeParams.minFundingSatoshis || open.fundingSatoshis > nodeParams.maxFundingSatoshis) return Left(InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, nodeParams.maxFundingSatoshis))
108108

109109
// BOLT #2: Channel funding limits
110-
if (open.fundingSatoshis >= Channel.MAX_FUNDING && !initFeatures.hasFeature(Features.Wumbo)) return Left(InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, Channel.MAX_FUNDING))
110+
if (open.fundingSatoshis >= Channel.MAX_FUNDING && !localFeatures.hasFeature(Features.Wumbo)) return Left(InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, Channel.MAX_FUNDING))
111111

112112
// BOLT #2: The receiving node MUST fail the channel if: push_msat is greater than funding_satoshis * 1000.
113113
if (open.pushMsat > open.fundingSatoshis) return Left(InvalidPushAmount(open.temporaryChannelId, open.pushMsat, open.fundingSatoshis.toMilliSatoshi))
@@ -147,22 +147,22 @@ object Helpers {
147147
val reserveToFundingRatio = open.channelReserveSatoshis.toLong.toDouble / Math.max(open.fundingSatoshis.toLong, 1)
148148
if (reserveToFundingRatio > nodeParams.maxReserveToFundingRatio) return Left(ChannelReserveTooHigh(open.temporaryChannelId, open.channelReserveSatoshis, reserveToFundingRatio, nodeParams.maxReserveToFundingRatio))
149149

150-
val channelFeatures = ChannelFeatures(channelType, initFeatures, remoteFeatures)
151-
extractShutdownScript(open.temporaryChannelId, initFeatures, remoteFeatures, open.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
150+
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures)
151+
extractShutdownScript(open.temporaryChannelId, localFeatures, remoteFeatures, open.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
152152
}
153153

154154
/**
155155
* Called by the funder
156156
*/
157-
def validateParamsFunder(nodeParams: NodeParams, channelType: ChannelType, initFeatures: Features, remoteFeatures: Features, open: OpenChannel, accept: AcceptChannel): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = {
157+
def validateParamsFunder(nodeParams: NodeParams, channelType: SupportedChannelType, localFeatures: Features, remoteFeatures: Features, open: OpenChannel, accept: AcceptChannel): Either[ChannelException, (ChannelFeatures, Option[ByteVector])] = {
158158
accept.channelType_opt match {
159-
case None if channelType != ChannelTypes.pickChannelType(initFeatures, remoteFeatures) =>
159+
case None if channelType != ChannelTypes.pickChannelType(localFeatures, remoteFeatures) =>
160160
// If we have overridden the default channel type, but they didn't support explicit channel type negotiation,
161161
// we need to abort because they expect a different channel type than what we offered.
162-
return Left(InvalidChannelType(open.temporaryChannelId, channelType.features, ChannelTypes.pickChannelType(initFeatures, remoteFeatures).features))
162+
return Left(InvalidChannelType(open.temporaryChannelId, channelType, ChannelTypes.pickChannelType(localFeatures, remoteFeatures)))
163163
case Some(theirChannelType) if accept.channelType_opt != open.channelType_opt =>
164164
// if channel_type is set, and channel_type was set in open_channel, and they are not equal types: MUST reject the channel.
165-
return Left(InvalidChannelType(open.temporaryChannelId, channelType.features, theirChannelType))
165+
return Left(InvalidChannelType(open.temporaryChannelId, channelType, theirChannelType))
166166
case _ => // we agree on channel type
167167
}
168168

@@ -192,8 +192,8 @@ object Helpers {
192192
val reserveToFundingRatio = accept.channelReserveSatoshis.toLong.toDouble / Math.max(open.fundingSatoshis.toLong, 1)
193193
if (reserveToFundingRatio > nodeParams.maxReserveToFundingRatio) return Left(ChannelReserveTooHigh(open.temporaryChannelId, accept.channelReserveSatoshis, reserveToFundingRatio, nodeParams.maxReserveToFundingRatio))
194194

195-
val channelFeatures = ChannelFeatures(channelType, initFeatures, remoteFeatures)
196-
extractShutdownScript(accept.temporaryChannelId, initFeatures, remoteFeatures, accept.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
195+
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures)
196+
extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
197197
}
198198

199199
/**

0 commit comments

Comments
 (0)