Skip to content

Commit 7fae0fa

Browse files
authored
Implement option_simple_close (#597)
Implement `option_simple_close` as defined in lightning/bolts#1205 This is a pre-requisite for taproot channels that is already supported by `eclair`.
1 parent feda82c commit 7fae0fa

File tree

63 files changed

+1775
-2412
lines changed

Some content is hidden

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

63 files changed

+1775
-2412
lines changed

modules/core/src/commonMain/kotlin/fr/acinq/lightning/Features.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ sealed class Feature {
147147
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Invoice)
148148
}
149149

150-
// The following features have not been standardised, hence the high feature bits to avoid conflicts.
151-
152150
// We historically used the following feature bit in our invoices.
153151
// However, the spec assigned the same feature bit to `option_scid_alias` (https://github.com/lightning/bolts/pull/910).
154152
// We're moving this feature bit to 148, but we have to keep supporting it until enough wallet users have migrated, then we can remove it.
@@ -160,6 +158,15 @@ sealed class Feature {
160158
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
161159
}
162160

161+
@Serializable
162+
object SimpleClose : Feature() {
163+
override val rfcName get() = "option_simple_close"
164+
override val mandatory get() = 60
165+
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
166+
}
167+
168+
// The following features have not been standardised, hence the high feature bits to avoid conflicts.
169+
163170
/** This feature bit should be activated when a node accepts having their channel reserve set to 0. */
164171
@Serializable
165172
object ZeroReserveChannels : Feature() {
@@ -340,6 +347,7 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
340347
Feature.ChannelType,
341348
Feature.PaymentMetadata,
342349
Feature.TrampolinePayment,
350+
Feature.SimpleClose,
343351
Feature.ExperimentalTrampolinePayment,
344352
Feature.ZeroReserveChannels,
345353
Feature.ZeroConfChannels,
@@ -384,6 +392,7 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
384392
Feature.PaymentSecret to listOf(Feature.VariableLengthOnion),
385393
Feature.BasicMultiPartPayment to listOf(Feature.PaymentSecret),
386394
Feature.AnchorOutputs to listOf(Feature.StaticRemoteKey),
395+
Feature.SimpleClose to listOf(Feature.ShutdownAnySegwit),
387396
Feature.TrampolinePayment to listOf(Feature.PaymentSecret),
388397
Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret),
389398
Feature.OnTheFlyFunding to listOf(Feature.ExperimentalSplice),

modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ data class NodeParams(
174174
require(features.hasFeature(Feature.ChannelType, FeatureSupport.Mandatory)) { "${Feature.ChannelType.rfcName} should be mandatory" }
175175
require(features.hasFeature(Feature.DualFunding, FeatureSupport.Mandatory)) { "${Feature.DualFunding.rfcName} should be mandatory" }
176176
require(features.hasFeature(Feature.RouteBlinding)) { "${Feature.RouteBlinding.rfcName} should be supported" }
177+
require(features.hasFeature(Feature.ShutdownAnySegwit, FeatureSupport.Mandatory)) { "${Feature.ShutdownAnySegwit.rfcName} should be mandatory" }
178+
require(features.hasFeature(Feature.SimpleClose, FeatureSupport.Mandatory)) { "${Feature.SimpleClose.rfcName} should be mandatory" }
177179
require(!features.hasFeature(Feature.ZeroConfChannels)) { "${Feature.ZeroConfChannels.rfcName} has been deprecated: use the zeroConfPeers whitelist instead" }
178180
require(!features.hasFeature(Feature.TrustedSwapInClient)) { "${Feature.TrustedSwapInClient.rfcName} has been deprecated" }
179181
require(!features.hasFeature(Feature.TrustedSwapInProvider)) { "${Feature.TrustedSwapInProvider.rfcName} has been deprecated" }
@@ -205,6 +207,7 @@ data class NodeParams(
205207
Feature.Quiescence to FeatureSupport.Mandatory,
206208
Feature.ChannelType to FeatureSupport.Mandatory,
207209
Feature.PaymentMetadata to FeatureSupport.Optional,
210+
Feature.SimpleClose to FeatureSupport.Mandatory,
208211
Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional,
209212
Feature.ZeroReserveChannels to FeatureSupport.Optional,
210213
Feature.WakeUpNotificationClient to FeatureSupport.Optional,

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelCommand.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import fr.acinq.lightning.MilliSatoshi
66
import fr.acinq.lightning.blockchain.WatchTriggered
77
import fr.acinq.lightning.blockchain.electrum.WalletState
88
import fr.acinq.lightning.blockchain.fee.FeeratePerKw
9-
import fr.acinq.lightning.channel.states.ClosingFeerates
109
import fr.acinq.lightning.channel.states.PersistedChannelState
1110
import fr.acinq.lightning.crypto.KeyManager
1211
import fr.acinq.lightning.utils.UUID
@@ -105,7 +104,7 @@ sealed class ChannelCommand {
105104
}
106105

107106
sealed class Close : ChannelCommand() {
108-
data class MutualClose(val scriptPubKey: ByteVector?, val feerates: ClosingFeerates?) : Close(), ForbiddenDuringSplice, ForbiddenDuringQuiescence
107+
data class MutualClose(val replyTo: CompletableDeferred<ChannelCloseResponse>, val scriptPubKey: ByteVector?, val feerate: FeeratePerKw) : Close(), ForbiddenDuringSplice, ForbiddenDuringQuiescence
109108
data object ForceClose : Close()
110109
}
111110

@@ -145,4 +144,19 @@ sealed class ChannelFundingResponse {
145144
data class UnexpectedMessage(val msg: LightningMessage) : Failure()
146145
data object Disconnected : Failure()
147146
}
147+
}
148+
149+
sealed class ChannelCloseResponse {
150+
/** This response doesn't fully guarantee that the closing transaction will confirm: it can be RBF-ed if necessary. */
151+
data class Success(val closingTxId: TxId, val closingFee: Satoshi) : ChannelCloseResponse()
152+
153+
sealed class Failure : ChannelCloseResponse() {
154+
data class ChannelNotOpenedYet(val state: String) : Failure()
155+
data object ChannelOffline : Failure()
156+
data object ClosingAlreadyInProgress : Failure()
157+
data class ClosingUpdated(val updatedFeerate: FeeratePerKw, val updatedScript: ByteVector?) : Failure()
158+
data class InvalidClosingAddress(val script: ByteVector) : Failure()
159+
data class RbfFeerateTooLow(val proposed: FeeratePerKw, val expected: FeeratePerKw) : Failure()
160+
data class Unknown(val reason: ChannelException) : Failure()
161+
}
148162
}

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelData.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import fr.acinq.lightning.logging.LoggingContext
1313
import fr.acinq.lightning.transactions.Scripts
1414
import fr.acinq.lightning.transactions.Transactions.TransactionWithInputInfo.*
1515
import fr.acinq.lightning.utils.toMilliSatoshi
16-
import fr.acinq.lightning.wire.ClosingSigned
1716

1817
/**
1918
* Details about a force-close where we published our commitment.
@@ -370,10 +369,6 @@ data class LocalParams(
370369
features = nodeParams.features.initFeatures()
371370
)
372371

373-
// The node responsible for the commit tx fees is also the node paying the mutual close fees.
374-
// The other node's balance may be empty, which wouldn't allow them to pay the closing fees.
375-
val paysClosingFees: Boolean = paysCommitTxFees
376-
377372
fun channelKeys(keyManager: KeyManager) = keyManager.channelKeys(fundingKeyPath)
378373
}
379374

@@ -397,8 +392,6 @@ data class RemoteParams(
397392
*/
398393
data class ChannelFlags(val announceChannel: Boolean, val nonInitiatorPaysCommitFees: Boolean)
399394

400-
data class ClosingTxProposed(val unsignedTx: ClosingTx, val localClosingSigned: ClosingSigned)
401-
402395
/**
403396
* @param miningFee fee paid to miners for the underlying on-chain transaction.
404397
* @param serviceFee fee paid to our peer for any service provided with the on-chain transaction.

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelException.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package fr.acinq.lightning.channel
22

3-
import fr.acinq.bitcoin.BlockHash
4-
import fr.acinq.bitcoin.ByteVector32
5-
import fr.acinq.bitcoin.Satoshi
6-
import fr.acinq.bitcoin.TxId
3+
import fr.acinq.bitcoin.*
74
import fr.acinq.lightning.CltvExpiry
85
import fr.acinq.lightning.CltvExpiryDelta
96
import fr.acinq.lightning.MilliSatoshi
@@ -62,8 +59,10 @@ data class FeerateTooSmall (override val channelId: Byte
6259
data class FeerateTooDifferent (override val channelId: ByteVector32, val localFeeratePerKw: FeeratePerKw, val remoteFeeratePerKw: FeeratePerKw) : ChannelException(channelId, "local/remote feerates are too different: remoteFeeratePerKw=${remoteFeeratePerKw.toLong()} localFeeratePerKw=${localFeeratePerKw.toLong()}")
6360
data class InvalidCommitmentSignature (override val channelId: ByteVector32, val txId: TxId) : ChannelException(channelId, "invalid commitment signature: txId=$txId")
6461
data class InvalidHtlcSignature (override val channelId: ByteVector32, val txId: TxId) : ChannelException(channelId, "invalid htlc signature: txId=$txId")
62+
data class CannotGenerateClosingTx (override val channelId: ByteVector32) : ChannelException(channelId, "failed to generate closing transaction: all outputs are trimmed")
63+
data class MissingCloseSignature (override val channelId: ByteVector32) : ChannelException(channelId, "closing_complete is missing a signature for a closing transaction including our output")
6564
data class InvalidCloseSignature (override val channelId: ByteVector32, val txId: TxId) : ChannelException(channelId, "invalid close signature: txId=$txId")
66-
data class InvalidCloseAmountBelowDust (override val channelId: ByteVector32, val txId: TxId) : ChannelException(channelId, "invalid closing tx: some outputs are below dust: txId=$txId")
65+
data class InvalidCloseeScript (override val channelId: ByteVector32, val received: ByteVector, val expected: ByteVector) : ChannelException(channelId, "invalid closee script used in closing_complete: our latest script is $expected, you're using $received")
6766
data class CommitSigCountMismatch (override val channelId: ByteVector32, val expected: Int, val actual: Int) : ChannelException(channelId, "commit sig count mismatch: expected=$expected actual=$actual")
6867
data class HtlcSigCountMismatch (override val channelId: ByteVector32, val expected: Int, val actual: Int) : ChannelException(channelId, "htlc sig count mismatch: expected=$expected actual: $actual")
6968
data class ForcedLocalCommit (override val channelId: ByteVector32) : ChannelException(channelId, "forced local commit")

0 commit comments

Comments
 (0)