Skip to content

Commit 49bee72

Browse files
authored
Extract CommitParams to individual commitments (#3118)
We introduce a new channel codec version where: - we extract per-commitment parameters (commitment format and params such as `dust_limit` which we may want to dynamically update in the future) - we clean-up the splice iterations on the `Commitments` structure - we remove the funding transaction from the confirmed local funding status (once confirmed, we don't need it anymore: it is wasting DB space for no good reason, we can get it from the blockchain) - we add a `maxClosingFeerate_opt` to the closing state, to allow limiting the feerate used in RBF attempts for safe transactions - we add the commitment format to interactive-tx parameters - we make channel type feature non permanent We add a mechanism to prevent the node from starting-up, unless an override is provided. This provides an opportunity to iterate on codecs while knowing that node operators won't be affected if we make changes that are backwards-incompatible. This is meant to be temporary.
1 parent b651e5b commit 49bee72

File tree

73 files changed

+2334
-1373
lines changed

Some content is hidden

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

73 files changed

+2334
-1373
lines changed

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,15 @@ object FeatureSupport {
3636

3737
/** Not a sealed trait, so it can be extended by plugins. */
3838
trait Feature {
39-
4039
def rfcName: String
4140
def mandatory: Int
4241
def optional: Int = mandatory + 1
43-
4442
def supportBit(support: FeatureSupport): Int = support match {
4543
case Mandatory => mandatory
4644
case Optional => optional
4745
}
4846

49-
override def toString = rfcName
47+
override def toString: String = rfcName
5048
}
5149

5250
/** Feature scope as defined in Bolt 9. */
@@ -68,11 +66,9 @@ trait Bolt12Feature extends InvoiceFeature
6866
*/
6967
trait PermanentChannelFeature extends InitFeature // <- not in the spec
7068
/**
71-
* Permanent channel feature negotiated in the channel type. Those features take precedence over permanent channel
72-
* features negotiated in init messages. For example, if the channel type is option_static_remotekey, then even if
73-
* the option_anchor_outputs feature is supported by both peers, it won't apply to the channel.
69+
* Features that can be included in the [[fr.acinq.eclair.wire.protocol.ChannelTlv.ChannelTypeTlv]].
7470
*/
75-
trait ChannelTypeFeature extends PermanentChannelFeature
71+
trait ChannelTypeFeature extends InitFeature
7672
// @formatter:on
7773

7874
case class UnknownFeature(bitIndex: Int)
@@ -275,6 +271,7 @@ object Features {
275271
val rfcName = "option_attribution_data"
276272
val mandatory = 36
277273
}
274+
278275
case object OnionMessages extends Feature with InitFeature with NodeFeature {
279276
val rfcName = "option_onion_messages"
280277
val mandatory = 38
@@ -290,7 +287,7 @@ object Features {
290287
val mandatory = 44
291288
}
292289

293-
case object ScidAlias extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
290+
case object ScidAlias extends Feature with InitFeature with NodeFeature with ChannelTypeFeature with PermanentChannelFeature {
294291
val rfcName = "option_scid_alias"
295292
val mandatory = 46
296293
}
@@ -300,7 +297,7 @@ object Features {
300297
val mandatory = 48
301298
}
302299

303-
case object ZeroConf extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
300+
case object ZeroConf extends Feature with InitFeature with NodeFeature with ChannelTypeFeature with PermanentChannelFeature {
304301
val rfcName = "option_zeroconf"
305302
val mandatory = 50
306303
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ trait SpendFromChannelAddress {
4141
inputTx <- appKit.wallet.getTransaction(outPoint.txid)
4242
channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig.standard, fundingKeyPath)
4343
localFundingKey = channelKeys.fundingKey(fundingTxIndex)
44-
inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), ByteVector.empty)
44+
inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt))
4545
// classify as splice, doesn't really matter
4646
tx = Transactions.SpliceTx(inputInfo, unsignedTx)
4747
localSig = tx.sign(localFundingKey, remoteFundingPubkey, extraUtxos = Map.empty)

eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ object CheckBalance {
176176
case d: DATA_SHUTDOWN => this.copy(shutdown = this.shutdown.addChannelBalance(d.commitments))
177177
// If one of our closing transactions is in the mempool or recently confirmed, and thus included in our on-chain
178178
// balance, we ignore this channel in our off-chain balance to avoid counting it twice.
179-
case d: DATA_NEGOTIATING if recentlySpentInputs.contains(d.commitments.latest.commitInput.outPoint) => this
180-
case d: DATA_NEGOTIATING_SIMPLE if recentlySpentInputs.contains(d.commitments.latest.commitInput.outPoint) => this
179+
case d: DATA_NEGOTIATING if recentlySpentInputs.contains(d.commitments.latest.fundingInput) => this
180+
case d: DATA_NEGOTIATING_SIMPLE if recentlySpentInputs.contains(d.commitments.latest.fundingInput) => this
181181
// Otherwise, that means the closing transactions aren't in the mempool yet, so we include our off-chain balance.
182182
case d: DATA_NEGOTIATING => this.copy(negotiating = this.negotiating.addChannelBalance(d.commitments))
183183
case d: DATA_NEGOTIATING_SIMPLE => this.copy(negotiating = this.negotiating.addChannelBalance(d.commitments))

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

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import akka.actor.{ActorRef, PossiblyHarmful, typed}
2020
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2121
import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxId, TxOut}
2222
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
23-
import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx
2423
import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._
2524
import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession}
2625
import fr.acinq.eclair.io.Peer
@@ -430,6 +429,7 @@ case class RevokedCommitPublished(commitTx: Transaction, localOutput_opt: Option
430429
case class ShortIdAliases(localAlias: Alias, remoteAlias_opt: Option[Alias])
431430

432431
sealed trait LocalFundingStatus {
432+
/** While the transaction is unconfirmed, we keep the funding transaction (if available) to allow rebroadcasting. */
433433
def signedTx_opt: Option[Transaction]
434434
/** We store local signatures for the purpose of retransmitting if the funding/splicing flow is interrupted. */
435435
def localSigs_opt: Option[TxSignatures]
@@ -458,8 +458,8 @@ object LocalFundingStatus {
458458
case class ZeroconfPublishedFundingTx(tx: Transaction, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends UnconfirmedFundingTx with Locked {
459459
override val signedTx_opt: Option[Transaction] = Some(tx)
460460
}
461-
case class ConfirmedFundingTx(tx: Transaction, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus with Locked {
462-
override val signedTx_opt: Option[Transaction] = Some(tx)
461+
case class ConfirmedFundingTx(txOut: TxOut, shortChannelId: RealShortChannelId, localSigs_opt: Option[TxSignatures], liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends LocalFundingStatus with Locked {
462+
override val signedTx_opt: Option[Transaction] = None
463463
}
464464
}
465465

@@ -567,23 +567,34 @@ final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_CHANNEL_INI
567567
val channelId: ByteVector32 = initFunder.temporaryChannelId
568568
}
569569
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(channelParams: ChannelParams,
570+
channelType: SupportedChannelType,
571+
localCommitParams: CommitParams,
572+
remoteCommitParams: CommitParams,
570573
fundingAmount: Satoshi,
571574
pushAmount: MilliSatoshi,
572575
commitTxFeerate: FeeratePerKw,
573576
remoteFundingPubKey: PublicKey,
574577
remoteFirstPerCommitmentPoint: PublicKey,
575578
replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData {
576579
val channelId: ByteVector32 = channelParams.channelId
580+
val commitmentFormat: CommitmentFormat = channelType.commitmentFormat
577581
}
578582
final case class DATA_WAIT_FOR_FUNDING_CREATED(channelParams: ChannelParams,
583+
channelType: SupportedChannelType,
584+
localCommitParams: CommitParams,
585+
remoteCommitParams: CommitParams,
579586
fundingAmount: Satoshi,
580587
pushAmount: MilliSatoshi,
581588
commitTxFeerate: FeeratePerKw,
582589
remoteFundingPubKey: PublicKey,
583590
remoteFirstPerCommitmentPoint: PublicKey) extends TransientChannelData {
584591
val channelId: ByteVector32 = channelParams.channelId
592+
val commitmentFormat: CommitmentFormat = channelType.commitmentFormat
585593
}
586594
final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams,
595+
channelType: SupportedChannelType,
596+
localCommitParams: CommitParams,
597+
remoteCommitParams: CommitParams,
587598
remoteFundingPubKey: PublicKey,
588599
fundingTx: Transaction,
589600
fundingTxFee: Satoshi,
@@ -593,6 +604,7 @@ final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelParams: ChannelParams,
593604
lastSent: FundingCreated,
594605
replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData {
595606
val channelId: ByteVector32 = channelParams.channelId
607+
val commitmentFormat: CommitmentFormat = channelType.commitmentFormat
596608
}
597609
final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments,
598610
waitingSince: BlockHeight, // how long have we been waiting for the funding tx to confirm
@@ -610,6 +622,8 @@ final case class DATA_WAIT_FOR_ACCEPT_DUAL_FUNDED_CHANNEL(init: INPUT_INIT_CHANN
610622
}
611623
final case class DATA_WAIT_FOR_DUAL_FUNDING_CREATED(channelId: ByteVector32,
612624
channelParams: ChannelParams,
625+
localCommitParams: CommitParams,
626+
remoteCommitParams: CommitParams,
613627
secondRemotePerCommitmentPoint: PublicKey,
614628
localPushAmount: MilliSatoshi,
615629
remotePushAmount: MilliSatoshi,
@@ -620,29 +634,28 @@ final case class DATA_WAIT_FOR_DUAL_FUNDING_SIGNED(channelParams: ChannelParams,
620634
secondRemotePerCommitmentPoint: PublicKey,
621635
localPushAmount: MilliSatoshi,
622636
remotePushAmount: MilliSatoshi,
623-
signingSession: InteractiveTxSigningSession.WaitingForSigs,
624-
remoteChannelData_opt: Option[ByteVector]) extends ChannelDataWithoutCommitments
637+
signingSession: InteractiveTxSigningSession.WaitingForSigs) extends ChannelDataWithoutCommitments
625638
final case class DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments: Commitments,
626639
localPushAmount: MilliSatoshi,
627640
remotePushAmount: MilliSatoshi,
628641
waitingSince: BlockHeight, // how long have we been waiting for a funding tx to confirm
629642
lastChecked: BlockHeight, // last time we checked if the channel was double-spent
630643
status: DualFundingStatus,
631644
deferred: Option[ChannelReady]) extends ChannelDataWithCommitments {
632-
def allFundingTxs: Seq[DualFundedUnconfirmedFundingTx] = commitments.active.map(_.localFundingStatus).collect { case fundingTx: DualFundedUnconfirmedFundingTx => fundingTx }
633-
def latestFundingTx: DualFundedUnconfirmedFundingTx = commitments.latest.localFundingStatus.asInstanceOf[DualFundedUnconfirmedFundingTx]
634-
def previousFundingTxs: Seq[DualFundedUnconfirmedFundingTx] = allFundingTxs diff Seq(latestFundingTx)
645+
def allFundingTxs: Seq[LocalFundingStatus.DualFundedUnconfirmedFundingTx] = commitments.active.map(_.localFundingStatus).collect { case fundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx => fundingTx }
646+
def latestFundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx = commitments.latest.localFundingStatus.asInstanceOf[LocalFundingStatus.DualFundedUnconfirmedFundingTx]
647+
def previousFundingTxs: Seq[LocalFundingStatus.DualFundedUnconfirmedFundingTx] = allFundingTxs diff Seq(latestFundingTx)
635648
}
636649
final case class DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments: Commitments, aliases: ShortIdAliases) extends ChannelDataWithCommitments
637650

638651
final case class DATA_NORMAL(commitments: Commitments,
639652
aliases: ShortIdAliases,
640653
lastAnnouncement_opt: Option[ChannelAnnouncement],
641654
channelUpdate: ChannelUpdate,
655+
spliceStatus: SpliceStatus,
642656
localShutdown: Option[Shutdown],
643657
remoteShutdown: Option[Shutdown],
644-
closeStatus_opt: Option[CloseStatus],
645-
spliceStatus: SpliceStatus) extends ChannelDataWithCommitments {
658+
closeStatus_opt: Option[CloseStatus]) extends ChannelDataWithCommitments {
646659
val lastAnnouncedCommitment_opt: Option[AnnouncedCommitment] = lastAnnouncement_opt.flatMap(ann => commitments.resolveCommitment(ann.shortChannelId).map(c => AnnouncedCommitment(c, ann)))
647660
val lastAnnouncedFundingTxId_opt: Option[TxId] = lastAnnouncedCommitment_opt.map(_.fundingTxId)
648661
val isNegotiatingQuiescence: Boolean = spliceStatus.isNegotiatingQuiescence
@@ -675,24 +688,21 @@ final case class DATA_CLOSING(commitments: Commitments,
675688
remoteCommitPublished: Option[RemoteCommitPublished] = None,
676689
nextRemoteCommitPublished: Option[RemoteCommitPublished] = None,
677690
futureRemoteCommitPublished: Option[RemoteCommitPublished] = None,
678-
revokedCommitPublished: List[RevokedCommitPublished] = Nil) extends ChannelDataWithCommitments {
691+
revokedCommitPublished: List[RevokedCommitPublished] = Nil,
692+
maxClosingFeerate_opt: Option[FeeratePerKw] = None) extends ChannelDataWithCommitments {
679693
val spendingTxs: List[Transaction] = mutualClosePublished.map(_.tx) ::: localCommitPublished.map(_.commitTx).toList ::: remoteCommitPublished.map(_.commitTx).toList ::: nextRemoteCommitPublished.map(_.commitTx).toList ::: futureRemoteCommitPublished.map(_.commitTx).toList ::: revokedCommitPublished.map(_.commitTx)
680694
require(spendingTxs.nonEmpty, "there must be at least one tx published in this state")
681695
}
682696

683697
final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends ChannelDataWithCommitments
684698

699+
/** Local params that apply for the channel's lifetime. */
685700
case class LocalChannelParams(nodeId: PublicKey,
686701
fundingKeyPath: DeterministicWallet.KeyPath,
687-
dustLimit: Satoshi,
688-
maxHtlcValueInFlightMsat: UInt64,
689702
// Channel reserve applied to the remote peer, if we're not using [[Features.DualFunding]] (in
690703
// which case the reserve is set to 1%). If the channel is spliced, this initial value will be
691704
// ignored in favor of a 1% reserve of the resulting capacity.
692705
initialRequestedChannelReserve_opt: Option[Satoshi],
693-
htlcMinimum: MilliSatoshi,
694-
toRemoteDelay: CltvExpiryDelta,
695-
maxAcceptedHtlcs: Int,
696706
isChannelOpener: Boolean,
697707
paysCommitTxFees: Boolean,
698708
upfrontShutdownScript_opt: Option[ByteVector],
@@ -704,18 +714,12 @@ case class LocalChannelParams(nodeId: PublicKey,
704714
// The node responsible for the commit tx fees is also the node paying the mutual close fees.
705715
// The other node's balance may be empty, which wouldn't allow them to pay the closing fees.
706716
val paysClosingFees: Boolean = paysCommitTxFees
707-
708-
val proposedCommitParams: ProposedCommitParams = ProposedCommitParams(dustLimit, htlcMinimum, maxHtlcValueInFlightMsat, maxAcceptedHtlcs, toRemoteDelay)
709717
}
710718

719+
/** Remote params that apply for the channel's lifetime. */
711720
case class RemoteChannelParams(nodeId: PublicKey,
712-
dustLimit: Satoshi,
713-
maxHtlcValueInFlightMsat: UInt64,
714721
// See comment in LocalChannelParams for details.
715722
initialRequestedChannelReserve_opt: Option[Satoshi],
716-
htlcMinimum: MilliSatoshi,
717-
toRemoteDelay: CltvExpiryDelta,
718-
maxAcceptedHtlcs: Int,
719723
revocationBasepoint: PublicKey,
720724
paymentBasepoint: PublicKey,
721725
delayedPaymentBasepoint: PublicKey,

0 commit comments

Comments
 (0)