Skip to content

Commit

Permalink
Add paysCommitTxFees flag to LocalParams (#2845)
Browse files Browse the repository at this point in the history
The channel initiator traditionally 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`
- `paysCommitTxFees`

We always set `paysCommitTxFees` to the same value as `isChannelOpener`.
Custom feature bits may override that behavior if necessary.

Note that backwards compatibility is preserved since our previous `bool8`
codec encodes `true` as `0xff` and `false` as `0x00`.
  • Loading branch information
t-bast authored Jun 12, 2024
1 parent 40f13f4 commit f0e3985
Show file tree
Hide file tree
Showing 57 changed files with 242 additions and 209 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ final case class DATA_NEGOTIATING(commitments: Commitments,
closingTxProposed: List[List[ClosingTxProposed]], // one list for every negotiation (there can be several in case of disconnection)
bestUnpublishedClosingTx_opt: Option[ClosingTx]) extends ChannelDataWithCommitments {
require(closingTxProposed.nonEmpty, "there must always be a list for the current negotiation")
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")
require(!commitments.params.localParams.paysClosingFees || closingTxProposed.forall(_.nonEmpty), "initiator must have at least one closing signature for every negotiation attempt because it initiates the closing")
}
final case class DATA_CLOSING(commitments: Commitments,
waitingSince: BlockHeight, // how long since we initiated the closing
Expand Down Expand Up @@ -632,10 +632,15 @@ case class LocalParams(nodeId: PublicKey,
htlcMinimum: MilliSatoshi,
toSelfDelay: CltvExpiryDelta,
maxAcceptedHtlcs: Int,
isInitiator: Boolean,
isChannelOpener: Boolean,
paysCommitTxFees: Boolean,
upfrontShutdownScript_opt: Option[ByteVector],
walletStaticPaymentBasepoint: Option[PublicKey],
initFeatures: Features[InitFeature])
initFeatures: Features[InitFeature]) {
// The node responsible for the commit tx fees is also the node paying the mutual close fees.
// The other node's balance may be empty, which wouldn't allow them to pay the closing fees.
val paysClosingFees: Boolean = paysCommitTxFees
}

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

/** Information about what triggered the opening of the channel */
sealed trait ChannelOrigin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import fr.acinq.eclair.{BlockHeight, Features, ShortChannelId}

trait ChannelEvent

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

// This trait can be used by non-standard channels to inject themselves into Register actor and thus make them usable for routing
trait AbstractChannelRestored extends ChannelEvent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ case class Commitment(fundingTxIndex: Long,
val remoteCommit1 = nextRemoteCommit_opt.map(_.commit).getOrElse(remoteCommit)
val reduced = CommitmentSpec.reduce(remoteCommit1.spec, changes.remoteChanges.acked, changes.localChanges.proposed)
val balanceNoFees = (reduced.toRemote - localChannelReserve(params)).max(0 msat)
if (localParams.isInitiator) {
if (localParams.paysCommitTxFees) {
// The initiator always pays the on-chain fees, so we must subtract that from the amount we can send.
val commitFees = commitTxTotalCostMsat(remoteParams.dustLimit, reduced, commitmentFormat)
// the initiator needs to keep a "funder fee buffer" (see explanation above)
Expand All @@ -347,7 +347,7 @@ case class Commitment(fundingTxIndex: Long,
import params._
val reduced = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed)
val balanceNoFees = (reduced.toRemote - remoteChannelReserve(params)).max(0 msat)
if (localParams.isInitiator) {
if (localParams.paysCommitTxFees) {
// The non-initiator doesn't pay on-chain fees so we don't take those into account when receiving.
balanceNoFees
} else {
Expand Down Expand Up @@ -456,12 +456,12 @@ case class Commitment(fundingTxIndex: Long,
val funderFeeBuffer = commitTxTotalCostMsat(params.remoteParams.dustLimit, reduced.copy(commitTxFeerate = reduced.commitTxFeerate * 2), params.commitmentFormat) + htlcOutputFee(reduced.commitTxFeerate * 2, params.commitmentFormat)
// NB: increasing the feerate can actually remove htlcs from the commit tx (if they fall below the trim threshold)
// which may result in a lower commit tx fee; this is why we take the max of the two.
val missingForSender = reduced.toRemote - localChannelReserve(params) - (if (params.localParams.isInitiator) fees.max(funderFeeBuffer.truncateToSatoshi) else 0.sat)
val missingForReceiver = reduced.toLocal - remoteChannelReserve(params) - (if (params.localParams.isInitiator) 0.sat else fees)
val missingForSender = reduced.toRemote - localChannelReserve(params) - (if (params.localParams.paysCommitTxFees) fees.max(funderFeeBuffer.truncateToSatoshi) else 0.sat)
val missingForReceiver = reduced.toLocal - remoteChannelReserve(params) - (if (params.localParams.paysCommitTxFees) 0.sat else fees)
if (missingForSender < 0.msat) {
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = localChannelReserve(params), fees = if (params.localParams.isInitiator) fees else 0.sat))
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = localChannelReserve(params), fees = if (params.localParams.paysCommitTxFees) fees else 0.sat))
} else if (missingForReceiver < 0.msat) {
if (params.localParams.isInitiator) {
if (params.localParams.paysCommitTxFees) {
// 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
} else if (reduced.toLocal > fees && reduced.htlcs.size < 5 && fundingTxIndex > 0) {
// Receiver is the channel initiator; we usually don't want to let them dip into their channel reserve, because
Expand Down Expand Up @@ -527,15 +527,15 @@ case class Commitment(fundingTxIndex: Long,
val fees = commitTxTotalCost(params.localParams.dustLimit, reduced, params.commitmentFormat)
// NB: we don't enforce the funderFeeReserve (see sendAdd) because it would confuse a remote initiator that doesn't have this mitigation in place
// We could enforce it once we're confident a large portion of the network implements it.
val missingForSender = reduced.toRemote - remoteChannelReserve(params) - (if (params.localParams.isInitiator) 0.sat else fees)
val missingForSender = reduced.toRemote - remoteChannelReserve(params) - (if (params.localParams.paysCommitTxFees) 0.sat else fees)
// Note that Bolt 2 requires to also meet our channel reserve requirement, but we're more lenient than that because
// 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
// HTLC, which means our balance will increase and meet the channel reserve again.
val missingForReceiver = reduced.toLocal - (if (params.localParams.isInitiator) fees else 0.sat)
val missingForReceiver = reduced.toLocal - (if (params.localParams.paysCommitTxFees) fees else 0.sat)
if (missingForSender < 0.sat) {
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = remoteChannelReserve(params), fees = if (params.localParams.isInitiator) 0.sat else fees))
return Left(InsufficientFunds(params.channelId, amount = amount, missing = -missingForSender.truncateToSatoshi, reserve = remoteChannelReserve(params), fees = if (params.localParams.paysCommitTxFees) 0.sat else fees))
} else if (missingForReceiver < 0.sat) {
if (params.localParams.isInitiator) {
if (params.localParams.paysCommitTxFees) {
return Left(CannotAffordFees(params.channelId, missing = -missingForReceiver.truncateToSatoshi, reserve = localChannelReserve(params), fees = fees))
} else {
// 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
Expand Down Expand Up @@ -699,8 +699,8 @@ object Commitment {
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint)
val localPaymentBasepoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
val outputs = makeCommitTxOutputs(localParams.isInitiator, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteFundingPubKey, spec, channelFeatures.commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isInitiator, outputs)
val outputs = makeCommitTxOutputs(localParams.paysCommitTxFees, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteFundingPubKey, spec, channelFeatures.commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isChannelOpener, outputs)
val htlcTxs = makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat)
(commitTx, htlcTxs)
}
Expand Down Expand Up @@ -728,8 +728,8 @@ object Commitment {
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val outputs = makeCommitTxOutputs(!localParams.isInitiator, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteFundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isInitiator, outputs)
val outputs = makeCommitTxOutputs(!localParams.paysCommitTxFees, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteFundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat)
val commitTx = makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isChannelOpener, outputs)
val htlcTxs = makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat)
(commitTx, htlcTxs)
}
Expand Down Expand Up @@ -960,7 +960,7 @@ case class Commitments(params: ChannelParams,
}

def sendFee(cmd: CMD_UPDATE_FEE, feeConf: OnChainFeeConf): Either[ChannelException, (Commitments, UpdateFee)] = {
if (!params.localParams.isInitiator) {
if (!params.localParams.paysCommitTxFees) {
Left(NonInitiatorCannotSendUpdateFee(channelId))
} else {
val fee = UpdateFee(channelId, cmd.feeratePerKw)
Expand All @@ -976,7 +976,7 @@ case class Commitments(params: ChannelParams,
}

def receiveFee(fee: UpdateFee, feerates: FeeratesPerKw, feeConf: OnChainFeeConf)(implicit log: LoggingAdapter): Either[ChannelException, Commitments] = {
if (params.localParams.isInitiator) {
if (params.localParams.paysCommitTxFees) {
Left(NonInitiatorCannotSendUpdateFee(channelId))
} else if (fee.feeratePerKw < FeeratePerKw.MinimumFeeratePerKw) {
Left(FeerateTooSmall(channelId, remoteFeeratePerKw = fee.feeratePerKw))
Expand Down
Loading

0 comments on commit f0e3985

Please sign in to comment.