Skip to content

Commit f58209a

Browse files
committed
Add payCommitTxFees flag to LocalParams
The channel initiator traditionnally pays the commit tx fees, but we may want to override that when providing services to wallet users. We thus split the current `isInitiator` flag into two flags: - `isChannelOpener` - `payCommitTxFees` We always set `payCommitTxFees` to the same value as `isChannelOpener`. Custom feature bits may override that behavior if necessary. Note that backwards compatibity is preserved since our previous `bool8` codec encodes `true` as `0xff` and `false` as `0x00`.
1 parent b73a009 commit f58209a

File tree

57 files changed

+242
-209
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+242
-209
lines changed

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ final case class DATA_NEGOTIATING(commitments: Commitments,
601601
closingTxProposed: List[List[ClosingTxProposed]], // one list for every negotiation (there can be several in case of disconnection)
602602
bestUnpublishedClosingTx_opt: Option[ClosingTx]) extends ChannelDataWithCommitments {
603603
require(closingTxProposed.nonEmpty, "there must always be a list for the current negotiation")
604-
require(!commitments.params.localParams.isInitiator || closingTxProposed.forall(_.nonEmpty), "initiator must have at least one closing signature for every negotiation attempt because it initiates the closing")
604+
require(!commitments.params.localParams.payClosingFees || closingTxProposed.forall(_.nonEmpty), "initiator must have at least one closing signature for every negotiation attempt because it initiates the closing")
605605
}
606606
final case class DATA_CLOSING(commitments: Commitments,
607607
waitingSince: BlockHeight, // how long since we initiated the closing
@@ -632,10 +632,15 @@ case class LocalParams(nodeId: PublicKey,
632632
htlcMinimum: MilliSatoshi,
633633
toSelfDelay: CltvExpiryDelta,
634634
maxAcceptedHtlcs: Int,
635-
isInitiator: Boolean,
635+
isChannelOpener: Boolean,
636+
payCommitTxFees: Boolean,
636637
upfrontShutdownScript_opt: Option[ByteVector],
637638
walletStaticPaymentBasepoint: Option[PublicKey],
638-
initFeatures: Features[InitFeature])
639+
initFeatures: Features[InitFeature]) {
640+
// The node responsible for the commit tx fees is also the node paying the mutual close fees.
641+
// The other node's balance may be empty, which wouldn't allow them to pay the closing fees.
642+
val payClosingFees: Boolean = payCommitTxFees
643+
}
639644

640645
/**
641646
* @param initFeatures see [[LocalParams.initFeatures]]
@@ -657,10 +662,6 @@ case class RemoteParams(nodeId: PublicKey,
657662
case class ChannelFlags(announceChannel: Boolean) {
658663
override def toString: String = s"ChannelFlags(announceChannel=$announceChannel)"
659664
}
660-
object ChannelFlags {
661-
val Private: ChannelFlags = ChannelFlags(announceChannel = false)
662-
val Public: ChannelFlags = ChannelFlags(announceChannel = true)
663-
}
664665

665666
/** Information about what triggered the opening of the channel */
666667
sealed trait ChannelOrigin

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import fr.acinq.eclair.{BlockHeight, Features, ShortChannelId}
3131

3232
trait ChannelEvent
3333

34-
case class ChannelCreated(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isInitiator: Boolean, temporaryChannelId: ByteVector32, commitTxFeerate: FeeratePerKw, fundingTxFeerate: Option[FeeratePerKw]) extends ChannelEvent
34+
case class ChannelCreated(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isOpener: Boolean, temporaryChannelId: ByteVector32, commitTxFeerate: FeeratePerKw, fundingTxFeerate: Option[FeeratePerKw]) extends ChannelEvent
3535

3636
// This trait can be used by non-standard channels to inject themselves into Register actor and thus make them usable for routing
3737
trait AbstractChannelRestored extends ChannelEvent {

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ case class Commitment(fundingTxIndex: Long,
320320
val remoteCommit1 = nextRemoteCommit_opt.map(_.commit).getOrElse(remoteCommit)
321321
val reduced = CommitmentSpec.reduce(remoteCommit1.spec, changes.remoteChanges.acked, changes.localChanges.proposed)
322322
val balanceNoFees = (reduced.toRemote - localChannelReserve(params)).max(0 msat)
323-
if (localParams.isInitiator) {
323+
if (localParams.payCommitTxFees) {
324324
// The initiator always pays the on-chain fees, so we must subtract that from the amount we can send.
325325
val commitFees = commitTxTotalCostMsat(remoteParams.dustLimit, reduced, commitmentFormat)
326326
// the initiator needs to keep a "funder fee buffer" (see explanation above)
@@ -347,7 +347,7 @@ case class Commitment(fundingTxIndex: Long,
347347
import params._
348348
val reduced = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed)
349349
val balanceNoFees = (reduced.toRemote - remoteChannelReserve(params)).max(0 msat)
350-
if (localParams.isInitiator) {
350+
if (localParams.payCommitTxFees) {
351351
// The non-initiator doesn't pay on-chain fees so we don't take those into account when receiving.
352352
balanceNoFees
353353
} else {
@@ -456,12 +456,12 @@ case class Commitment(fundingTxIndex: Long,
456456
val funderFeeBuffer = commitTxTotalCostMsat(params.remoteParams.dustLimit, reduced.copy(commitTxFeerate = reduced.commitTxFeerate * 2), params.commitmentFormat) + htlcOutputFee(reduced.commitTxFeerate * 2, params.commitmentFormat)
457457
// NB: increasing the feerate can actually remove htlcs from the commit tx (if they fall below the trim threshold)
458458
// which may result in a lower commit tx fee; this is why we take the max of the two.
459-
val missingForSender = reduced.toRemote - localChannelReserve(params) - (if (params.localParams.isInitiator) fees.max(funderFeeBuffer.truncateToSatoshi) else 0.sat)
460-
val missingForReceiver = reduced.toLocal - remoteChannelReserve(params) - (if (params.localParams.isInitiator) 0.sat else fees)
459+
val missingForSender = reduced.toRemote - localChannelReserve(params) - (if (params.localParams.payCommitTxFees) fees.max(funderFeeBuffer.truncateToSatoshi) else 0.sat)
460+
val missingForReceiver = reduced.toLocal - remoteChannelReserve(params) - (if (params.localParams.payCommitTxFees) 0.sat else fees)
461461
if (missingForSender < 0.msat) {
462-
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = localChannelReserve(params), fees = if (params.localParams.isInitiator) fees else 0.sat))
462+
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = localChannelReserve(params), fees = if (params.localParams.payCommitTxFees) fees else 0.sat))
463463
} else if (missingForReceiver < 0.msat) {
464-
if (params.localParams.isInitiator) {
464+
if (params.localParams.payCommitTxFees) {
465465
// receiver is not the channel initiator; it is ok if it can't maintain its channel_reserve for now, as long as its balance is increasing, which is the case if it is receiving a payment
466466
} else if (reduced.toLocal > fees && reduced.htlcs.size < 5 && fundingTxIndex > 0) {
467467
// Receiver is the channel initiator; we usually don't want to let them dip into their channel reserve, because
@@ -527,15 +527,15 @@ case class Commitment(fundingTxIndex: Long,
527527
val fees = commitTxTotalCost(params.localParams.dustLimit, reduced, params.commitmentFormat)
528528
// NB: we don't enforce the funderFeeReserve (see sendAdd) because it would confuse a remote initiator that doesn't have this mitigation in place
529529
// We could enforce it once we're confident a large portion of the network implements it.
530-
val missingForSender = reduced.toRemote - remoteChannelReserve(params) - (if (params.localParams.isInitiator) 0.sat else fees)
530+
val missingForSender = reduced.toRemote - remoteChannelReserve(params) - (if (params.localParams.payCommitTxFees) 0.sat else fees)
531531
// Note that Bolt 2 requires to also meet our channel reserve requirement, but we're more lenient than that because
532532
// as long as we're able to pay the commit tx fee, it's ok if we dip into our channel reserve: we're receiving an
533533
// HTLC, which means our balance will increase and meet the channel reserve again.
534-
val missingForReceiver = reduced.toLocal - (if (params.localParams.isInitiator) fees else 0.sat)
534+
val missingForReceiver = reduced.toLocal - (if (params.localParams.payCommitTxFees) fees else 0.sat)
535535
if (missingForSender < 0.sat) {
536-
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = remoteChannelReserve(params), fees = if (params.localParams.isInitiator) 0.sat else fees))
536+
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = remoteChannelReserve(params), fees = if (params.localParams.payCommitTxFees) 0.sat else fees))
537537
} else if (missingForReceiver < 0.sat) {
538-
if (params.localParams.isInitiator) {
538+
if (params.localParams.payCommitTxFees) {
539539
return Left(CannotAffordFees(params.channelId, missing = -missingForReceiver.truncateToSatoshi, reserve = localChannelReserve(params), fees = fees))
540540
} else {
541541
// receiver is not the channel initiator; it is ok if it can't maintain its channel_reserve for now, as long as its balance is increasing, which is the case if it is receiving a payment
@@ -699,8 +699,8 @@ object Commitment {
699699
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
700700
val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint)
701701
val localPaymentBasepoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
702-
val outputs = makeCommitTxOutputs(localParams.isInitiator, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteFundingPubKey, spec, channelFeatures.commitmentFormat)
703-
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isInitiator, outputs)
702+
val outputs = makeCommitTxOutputs(localParams.payCommitTxFees, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteFundingPubKey, spec, channelFeatures.commitmentFormat)
703+
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isChannelOpener, outputs)
704704
val htlcTxs = makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat)
705705
(commitTx, htlcTxs)
706706
}
@@ -728,8 +728,8 @@ object Commitment {
728728
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
729729
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
730730
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
731-
val outputs = makeCommitTxOutputs(!localParams.isInitiator, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteFundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat)
732-
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isInitiator, outputs)
731+
val outputs = makeCommitTxOutputs(!localParams.payCommitTxFees, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteFundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat)
732+
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isChannelOpener, outputs)
733733
val htlcTxs = makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat)
734734
(commitTx, htlcTxs)
735735
}
@@ -960,7 +960,7 @@ case class Commitments(params: ChannelParams,
960960
}
961961

962962
def sendFee(cmd: CMD_UPDATE_FEE, feeConf: OnChainFeeConf): Either[ChannelException, (Commitments, UpdateFee)] = {
963-
if (!params.localParams.isInitiator) {
963+
if (!params.localParams.payCommitTxFees) {
964964
Left(NonInitiatorCannotSendUpdateFee(channelId))
965965
} else {
966966
val fee = UpdateFee(channelId, cmd.feeratePerKw)
@@ -976,7 +976,7 @@ case class Commitments(params: ChannelParams,
976976
}
977977

978978
def receiveFee(fee: UpdateFee, feerates: FeeratesPerKw, feeConf: OnChainFeeConf)(implicit log: LoggingAdapter): Either[ChannelException, Commitments] = {
979-
if (params.localParams.isInitiator) {
979+
if (params.localParams.payCommitTxFees) {
980980
Left(NonInitiatorCannotSendUpdateFee(channelId))
981981
} else if (fee.feeratePerKw < FeeratePerKw.MinimumFeeratePerKw) {
982982
Left(FeerateTooSmall(channelId, remoteFeeratePerKw = fee.feeratePerKw))

0 commit comments

Comments
 (0)