From f4ce2125ec32aaa38e529dc84a7c60f11898f708 Mon Sep 17 00:00:00 2001 From: Richard Myers Date: Thu, 15 Jun 2023 13:50:54 +0200 Subject: [PATCH] Updated with suggested initial fixes - only use quiescence if both nodes signal the feature - reduce timeout to 1 min - use message type 2 for `stfu` - Use feature bits 34/35 for option_quiesce a per spec --- eclair-core/src/main/resources/reference.conf | 2 +- .../src/main/scala/fr/acinq/eclair/Features.scala | 4 +--- .../scala/fr/acinq/eclair/channel/Commitments.scala | 3 ++- .../scala/fr/acinq/eclair/channel/fsm/Channel.scala | 10 +++++----- .../eclair/wire/protocol/LightningMessageCodecs.scala | 2 +- .../eclair/wire/protocol/LightningMessageTypes.scala | 3 ++- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index f62bfadec5..313d736290 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -155,7 +155,7 @@ eclair { channel-opener-whitelist = [] // a list of public keys; we will ignore rate limits on pending channels from these peers } - quiescence-timeout = 2 minutes // maximum time we will wait for quiescence to complete before disconnecting + quiescence-timeout = 1 minutes // maximum time we will wait for quiescence to complete before disconnecting } balance-check-interval = 1 hour diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index 667a039804..718ee26cb2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -300,11 +300,9 @@ object Features { val mandatory = 154 } - // TODO: @remyers reserve feature bits here: currently reserved here: https://github.com/lightning/bolts/issues/605 - // TODO: @remyers option_quiesce implementation, to be replaced once quiescence is spec-ed case object QuiescePrototype extends Feature with InitFeature { val rfcName = "option_quiesce_prototype" - val mandatory = 158 + val mandatory = 34 } val knownFeatures: Set[Feature] = Set( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index a98fd6a793..9a67314fa4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -145,6 +145,7 @@ case class ChannelParams(channelId: ByteVector32, else Right(remoteScriptPubKey) } + def useQuiescence: Boolean = localParams.initFeatures.hasFeature(Features.QuiescePrototype) && remoteParams.initFeatures.hasFeature(Features.QuiescePrototype) } object ChannelParams { @@ -797,7 +798,7 @@ case class Commitments(params: ChannelParams, // @formatter:off // HTLCs and pending changes are the same for all active commitments, so we don't need to loop through all of them. - def isIdle: Boolean = active.head.isIdle(changes, params.remoteParams.initFeatures.hasFeature(QuiescePrototype)) + def isIdle: Boolean = active.head.isIdle(changes, params.localParams.initFeatures.hasFeature(QuiescePrototype) && params.remoteParams.initFeatures.hasFeature(QuiescePrototype)) def hasNoPendingHtlcsOrFeeUpdate: Boolean = active.head.hasNoPendingHtlcsOrFeeUpdate(changes) def hasPendingOrProposedHtlcs: Boolean = active.head.hasPendingOrProposedHtlcs(changes) def timedOutOutgoingHtlcs(currentHeight: BlockHeight): Set[UpdateAddHtlc] = active.head.timedOutOutgoingHtlcs(currentHeight) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index 0417e3a94c..f88a3e454c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -383,7 +383,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case _ => handleCommandError(error, c) } - case Event(msg: ForbiddenMessageDuringSplice, d: DATA_NORMAL) if d.commitments.params.remoteParams.initFeatures.hasFeature(QuiescePrototype) && d.spliceStatus != SpliceStatus.NoSplice && !d.spliceStatus.isInstanceOf[SpliceStatus.InitiatorQuiescent] => + case Event(msg: ForbiddenMessageDuringSplice, d: DATA_NORMAL) if d.commitments.params.useQuiescence && d.spliceStatus != SpliceStatus.NoSplice && !d.spliceStatus.isInstanceOf[SpliceStatus.InitiatorQuiescent] => val error = ForbiddenDuringSplice(d.channelId, msg.getClass.getSimpleName) msg match { case fulfill: UpdateFulfillHtlc => @@ -809,7 +809,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case Event(cmd: CMD_SPLICE, d: DATA_NORMAL) => if (d.commitments.params.remoteParams.initFeatures.hasFeature(SplicePrototype)) { - if (d.commitments.params.remoteParams.initFeatures.hasFeature(QuiescePrototype) && d.spliceStatus == SpliceStatus.NoSplice) { + if (d.commitments.params.useQuiescence && d.spliceStatus == SpliceStatus.NoSplice) { startSingleTimer(QuiescenceTimeout.toString, QuiescenceTimeout(peer), nodeParams.channelConf.revocationTimeout) if (d.commitments.changes.localChanges.all.isEmpty) { stay() using d.copy(spliceStatus = SpliceStatus.InitiatorQuiescent(cmd)) sending Stfu(d.channelId, 1) @@ -826,7 +826,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with } case Event(msg: Stfu, d: DATA_NORMAL) => - if (d.commitments.params.remoteParams.initFeatures.hasFeature(QuiescePrototype)) { + if (d.commitments.params.useQuiescence) { d.spliceStatus match { case SpliceStatus.NoSplice => startSingleTimer(QuiescenceTimeout.toString, QuiescenceTimeout(peer), nodeParams.channelConf.quiescenceTimeout) @@ -856,7 +856,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with stay() } } else { - log.warning("ignoring stfu because peer doesn't support quiescence") + log.warning("ignoring stfu because both peers do not advertise quiescence") stay() } @@ -2644,7 +2644,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with } private def replayQuiescenceSettlements(d: DATA_NORMAL): Unit = - if (d.commitments.params.remoteParams.initFeatures.hasFeature(QuiescePrototype)) { + if (d.commitments.params.useQuiescence) { PendingCommandsDb.getSettlementCommands(nodeParams.db.pendingCommands, d.channelId).foreach(self ! _) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index 595ea3a6e3..70e1350f6c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -435,6 +435,7 @@ object LightningMessageCodecs { val lightningMessageCodec = discriminated[LightningMessage].by(uint16) .typecase(1, warningCodec) + .typecase(2, stfuCodec) .typecase(16, initCodec) .typecase(17, errorCodec) .typecase(18, pingCodec) @@ -481,7 +482,6 @@ object LightningMessageCodecs { .typecase(37000, spliceInitCodec) .typecase(37002, spliceAckCodec) .typecase(37004, spliceLockedCodec) - .typecase(37006, stfuCodec) // // diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 72889ff8ee..b09ddf3e3a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -278,6 +278,8 @@ case class ChannelReady(channelId: ByteVector32, val alias_opt: Option[Alias] = tlvStream.get[ShortChannelIdTlv].map(_.alias) } +case class Stfu(channelId: ByteVector32, initiator: Byte) extends LightningMessage + case class SpliceInit(channelId: ByteVector32, fundingContribution: Satoshi, feerate: FeeratePerKw, @@ -562,7 +564,6 @@ case class GossipTimestampFilter(chainHash: ByteVector32, firstTimestamp: Timest case class OnionMessage(blindingKey: PublicKey, onionRoutingPacket: OnionRoutingPacket, tlvStream: TlvStream[OnionMessageTlv] = TlvStream.empty) extends LightningMessage -case class Stfu(channelId: ByteVector32, initiator: Byte) extends LightningMessage // NB: blank lines to minimize merge conflicts //