Skip to content

Commit 075b3b9

Browse files
committed
Use official splice messages
We replace our experimental version of `splice_init`, `splice_ack` and `splice_locked` by their official version. We also change the TLV fields added to `commit_sig`, `tx_add_input`, and `tx_signatures` to match the spec version. We only allow connecting to peers who support the official splicing feature.
1 parent 0242acd commit 075b3b9

File tree

14 files changed

+154
-72
lines changed

14 files changed

+154
-72
lines changed

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ sealed class Feature {
172172
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
173173
}
174174

175+
@Serializable
176+
object Splicing : Feature() {
177+
override val rfcName get() = "option_splice"
178+
override val mandatory get() = 62
179+
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
180+
}
181+
175182
// The following features have not been standardised, hence the high feature bits to avoid conflicts.
176183

177184
/** This feature bit should be activated when a node accepts having their channel reserve set to 0. */
@@ -263,13 +270,6 @@ sealed class Feature {
263270
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
264271
}
265272

266-
@Serializable
267-
object ExperimentalSplice : Feature() {
268-
override val rfcName get() = "splice_experimental"
269-
override val mandatory get() = 154
270-
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init)
271-
}
272-
273273
@Serializable
274274
object OnTheFlyFunding : Feature() {
275275
override val rfcName get() = "on_the_fly_funding"
@@ -356,6 +356,7 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
356356
Feature.PaymentMetadata,
357357
Feature.TrampolinePayment,
358358
Feature.SimpleClose,
359+
Feature.Splicing,
359360
Feature.ExperimentalTrampolinePayment,
360361
Feature.ZeroReserveChannels,
361362
Feature.ZeroConfChannels,
@@ -367,7 +368,6 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
367368
Feature.TrustedSwapInProvider,
368369
Feature.ChannelBackupClient,
369370
Feature.ChannelBackupProvider,
370-
Feature.ExperimentalSplice,
371371
Feature.OnTheFlyFunding,
372372
Feature.FundingFeeCredit
373373
)
@@ -403,7 +403,6 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
403403
Feature.SimpleClose to listOf(Feature.ShutdownAnySegwit),
404404
Feature.TrampolinePayment to listOf(Feature.PaymentSecret),
405405
Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret),
406-
Feature.OnTheFlyFunding to listOf(Feature.ExperimentalSplice),
407406
Feature.FundingFeeCredit to listOf(Feature.OnTheFlyFunding)
408407
)
409408

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ data class NodeParams(
177177
require(features.hasFeature(Feature.PaymentSecret, FeatureSupport.Mandatory)) { "${Feature.PaymentSecret.rfcName} should be mandatory" }
178178
require(features.hasFeature(Feature.ChannelType, FeatureSupport.Mandatory)) { "${Feature.ChannelType.rfcName} should be mandatory" }
179179
require(features.hasFeature(Feature.DualFunding, FeatureSupport.Mandatory)) { "${Feature.DualFunding.rfcName} should be mandatory" }
180+
require(features.hasFeature(Feature.Splicing, FeatureSupport.Mandatory)) { "${Feature.Splicing.rfcName} should be mandatory" }
180181
require(features.hasFeature(Feature.RouteBlinding)) { "${Feature.RouteBlinding.rfcName} should be supported" }
181182
require(features.hasFeature(Feature.ShutdownAnySegwit, FeatureSupport.Mandatory)) { "${Feature.ShutdownAnySegwit.rfcName} should be mandatory" }
182183
require(features.hasFeature(Feature.SimpleClose, FeatureSupport.Mandatory)) { "${Feature.SimpleClose.rfcName} should be mandatory" }
@@ -212,10 +213,10 @@ data class NodeParams(
212213
Feature.ChannelType to FeatureSupport.Mandatory,
213214
Feature.PaymentMetadata to FeatureSupport.Optional,
214215
Feature.SimpleClose to FeatureSupport.Mandatory,
216+
Feature.Splicing to FeatureSupport.Mandatory,
215217
Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional,
216218
Feature.ZeroReserveChannels to FeatureSupport.Optional,
217219
Feature.WakeUpNotificationClient to FeatureSupport.Optional,
218-
Feature.ExperimentalSplice to FeatureSupport.Optional,
219220
Feature.OnTheFlyFunding to FeatureSupport.Optional,
220221
Feature.FundingFeeCredit to FeatureSupport.Optional,
221222
),

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

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ data class PublishableTxs(val commitTx: CommitTx, val htlcTxsAndSigs: List<HtlcT
9999
/** The local commitment maps to a commitment transaction that we can sign and broadcast if necessary. */
100100
data class LocalCommit(val index: Long, val spec: CommitmentSpec, val publishableTxs: PublishableTxs) {
101101
companion object {
102-
fun fromCommitSig(keyManager: KeyManager.ChannelKeys, params: ChannelParams, fundingTxIndex: Long,
103-
remoteFundingPubKey: PublicKey, commitInput: Transactions.InputInfo, commit: CommitSig,
104-
localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, log: MDCLogger): Either<ChannelException, LocalCommit> {
102+
fun fromCommitSig(
103+
keyManager: KeyManager.ChannelKeys, params: ChannelParams, fundingTxIndex: Long,
104+
remoteFundingPubKey: PublicKey, commitInput: Transactions.InputInfo, commit: CommitSig,
105+
localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, log: MDCLogger
106+
): Either<ChannelException, LocalCommit> {
105107
val (localCommitTx, sortedHtlcTxs) = Commitments.makeLocalTxs(
106108
keyManager,
107109
commitTxNumber = localCommitIndex,
@@ -497,14 +499,24 @@ data class Commitment(
497499
if (spec.htlcs.isEmpty()) {
498500
val alternativeSigs = Commitments.alternativeFeerates.map { feerate ->
499501
val alternativeSpec = spec.copy(feerate = feerate)
500-
val (alternativeRemoteCommitTx, _) = Commitments.makeRemoteTxs(channelKeys, commitTxNumber = remoteCommit.index + 1, params.localParams, params.remoteParams, fundingTxIndex = fundingTxIndex, remoteFundingPubKey = remoteFundingPubkey, commitInput, remotePerCommitmentPoint = remoteNextPerCommitmentPoint, alternativeSpec)
502+
val (alternativeRemoteCommitTx, _) = Commitments.makeRemoteTxs(
503+
channelKeys,
504+
commitTxNumber = remoteCommit.index + 1,
505+
params.localParams,
506+
params.remoteParams,
507+
fundingTxIndex = fundingTxIndex,
508+
remoteFundingPubKey = remoteFundingPubkey,
509+
commitInput,
510+
remotePerCommitmentPoint = remoteNextPerCommitmentPoint,
511+
alternativeSpec
512+
)
501513
val alternativeSig = Transactions.sign(alternativeRemoteCommitTx, channelKeys.fundingKey(fundingTxIndex))
502514
CommitSigTlv.AlternativeFeerateSig(feerate, alternativeSig)
503515
}
504516
add(CommitSigTlv.AlternativeFeerateSigs(alternativeSigs))
505517
}
506518
if (batchSize > 1) {
507-
add(CommitSigTlv.Batch(batchSize))
519+
add(CommitSigTlv.FundingTx(fundingTxId))
508520
}
509521
}
510522
val commitSig = CommitSig(params.channelId, sig, htlcSigs.toList(), TlvStream(tlvs))
@@ -792,10 +804,12 @@ data class Commitments(
792804
listOf(commits)
793805
}
794806
}
795-
796-
// Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments.
797-
val active1 = active.zip(sigs).map {
798-
when (val commitment1 = it.first.receiveCommit(channelKeys, params, changes, it.second, log)) {
807+
val active1 = active.withIndex().map { (i, c) ->
808+
// If the funding_txid isn't provided, we assume that signatures are sent in order (most recent first).
809+
// This ensures that the case where we have a batch of a single element (no pending splice) works correctly.
810+
// This also ensures that we get a signature failure when our set of funding txs doesn't match with our peer.
811+
val commit = sigs.find { it.fundingTxId == c.fundingTxId } ?: sigs[i]
812+
when (val commitment1 = c.receiveCommit(channelKeys, params, changes, commit, log)) {
799813
is Either.Left -> return Either.Left(commitment1.value)
800814
is Either.Right -> commitment1.value
801815
}
@@ -1032,7 +1046,17 @@ data class Commitments(
10321046
}.flatMap { remoteCommit ->
10331047
alternativeFeerates.map { feerate ->
10341048
val alternativeSpec = remoteCommit.spec.copy(feerate = feerate)
1035-
val (alternativeRemoteCommitTx, _) = makeRemoteTxs(channelKeys, remoteCommit.index, commitments.params.localParams, commitments.params.remoteParams, commitments.latest.fundingTxIndex, commitments.latest.remoteFundingPubkey, commitments.latest.commitInput, remoteCommit.remotePerCommitmentPoint, alternativeSpec)
1049+
val (alternativeRemoteCommitTx, _) = makeRemoteTxs(
1050+
channelKeys,
1051+
remoteCommit.index,
1052+
commitments.params.localParams,
1053+
commitments.params.remoteParams,
1054+
commitments.latest.fundingTxIndex,
1055+
commitments.latest.remoteFundingPubkey,
1056+
commitments.latest.commitInput,
1057+
remoteCommit.remotePerCommitmentPoint,
1058+
alternativeSpec
1059+
)
10361060
RemoteCommit(remoteCommit.index, alternativeSpec, alternativeRemoteCommitTx.tx.txid, remoteCommit.remotePerCommitmentPoint)
10371061
}
10381062
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ data class Normal(
892892
// If we already have a signed commitment transaction containing their signature, we must have previously received that commit_sig.
893893
val commitTx = commitments.latest.localCommit.publishableTxs.commitTx.tx
894894
return commitments.params.channelFeatures.hasFeature(Feature.DualFunding) &&
895-
commit.batchSize == 1 &&
895+
commit.fundingTxId == null &&
896896
commitTx.txIn.first().witness.stack.contains(Scripts.der(commit.signature, SigHash.SIGHASH_ALL))
897897
}
898898

modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ data class PeerConnection(val id: Long, val output: Channel<LightningMessage>, v
117117

118118
fun send(msg: LightningMessage) {
119119
when (msg) {
120-
is CommitSigBatch -> msg.messages.map { sendInternal(it) }
120+
is CommitSigBatch -> {
121+
sendInternal(StartBatch(msg.channelId, msg.batchSize))
122+
msg.messages.map { sendInternal(it) }
123+
}
121124
else -> sendInternal(msg)
122125
}
123126
}
@@ -492,12 +495,12 @@ class Peer(
492495
try {
493496
while (isActive) {
494497
val msg = receiveMessage()
495-
when {
496-
msg is CommitSig && msg.batchSize > 1 -> {
497-
val others = (1 until msg.batchSize).mapNotNull { receiveMessage() as CommitSig }
498-
input.send(MessageReceived(peerConnection.id, CommitSigs.fromSigs(listOf(msg) + others)))
498+
when (msg) {
499+
is StartBatch -> {
500+
val sigs = (0 until msg.batchSize).mapNotNull { receiveMessage() as CommitSig }
501+
input.send(MessageReceived(peerConnection.id, CommitSigs.fromSigs(sigs)))
499502
}
500-
msg is LightningMessage -> input.send(MessageReceived(peerConnection.id, msg))
503+
is LightningMessage -> input.send(MessageReceived(peerConnection.id, msg))
501504
else -> {}
502505
}
503506
}

modules/core/src/commonMain/kotlin/fr/acinq/lightning/json/JsonSerializers.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
JsonSerializers.ChannelReadyTlvSerializer::class,
9191
JsonSerializers.CommitSigTlvAlternativeFeerateSigSerializer::class,
9292
JsonSerializers.CommitSigTlvAlternativeFeerateSigsSerializer::class,
93-
JsonSerializers.CommitSigTlvBatchSerializer::class,
93+
JsonSerializers.CommitSigTlvFundingTxSerializer::class,
9494
JsonSerializers.CommitSigTlvSerializer::class,
9595
JsonSerializers.UUIDSerializer::class,
9696
JsonSerializers.ClosingSerializer::class,
@@ -203,7 +203,7 @@ object JsonSerializers {
203203
polymorphic(Tlv::class) {
204204
subclass(ChannelReadyTlv.ShortChannelIdTlv::class, ChannelReadyTlvShortChannelIdTlvSerializer)
205205
subclass(CommitSigTlv.AlternativeFeerateSigs::class, CommitSigTlvAlternativeFeerateSigsSerializer)
206-
subclass(CommitSigTlv.Batch::class, CommitSigTlvBatchSerializer)
206+
subclass(CommitSigTlv.FundingTx::class, CommitSigTlvFundingTxSerializer)
207207
subclass(ShutdownTlv.ChannelData::class, ShutdownTlvChannelDataSerializer)
208208
subclass(UpdateAddHtlcTlv.PathKey::class, UpdateAddHtlcTlvPathKeySerializer)
209209
}
@@ -536,8 +536,8 @@ object JsonSerializers {
536536
@Serializer(forClass = CommitSigTlv.AlternativeFeerateSigs::class)
537537
object CommitSigTlvAlternativeFeerateSigsSerializer
538538

539-
@Serializer(forClass = CommitSigTlv.Batch::class)
540-
object CommitSigTlvBatchSerializer
539+
@Serializer(forClass = CommitSigTlv.FundingTx::class)
540+
object CommitSigTlvFundingTxSerializer
541541

542542
@Serializer(forClass = CommitSigTlv::class)
543543
object CommitSigTlvSerializer

modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,27 @@ sealed class ChannelReadyTlv : Tlv {
114114
}
115115
}
116116

117+
sealed class StartBatchTlv : Tlv
118+
117119
sealed class CommitSigTlv : Tlv {
120+
/**
121+
* While a splice is ongoing and not locked, we have multiple valid commitments.
122+
* We send one [CommitSig] message for each valid commitment.
123+
*
124+
* @param txId the funding transaction spent by this commitment.
125+
*/
126+
data class FundingTx(val txId: TxId) : CommitSigTlv() {
127+
override val tag: Long get() = FundingTx.tag
128+
override fun write(out: Output) {
129+
LightningCodecs.writeTxHash(TxHash(txId), out)
130+
}
131+
132+
companion object : TlvValueReader<FundingTx> {
133+
const val tag: Long = 0
134+
override fun read(input: Input): FundingTx = FundingTx(txId = TxId(LightningCodecs.txHash(input)))
135+
}
136+
}
137+
118138
data class AlternativeFeerateSig(val feerate: FeeratePerKw, val sig: ByteVector64)
119139

120140
/**
@@ -145,16 +165,6 @@ sealed class CommitSigTlv : Tlv {
145165
}
146166
}
147167
}
148-
149-
data class Batch(val size: Int) : CommitSigTlv() {
150-
override val tag: Long get() = Batch.tag
151-
override fun write(out: Output) = LightningCodecs.writeTU16(size, out)
152-
153-
companion object : TlvValueReader<Batch> {
154-
const val tag: Long = 0x47010005
155-
override fun read(input: Input): Batch = Batch(size = LightningCodecs.tu16(input))
156-
}
157-
}
158168
}
159169

160170
sealed class RevokeAndAckTlv : Tlv

modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/InteractiveTxTlv.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ sealed class TxAddInputTlv : Tlv {
1818
override fun write(out: Output) = LightningCodecs.writeTxHash(TxHash(txId), out)
1919

2020
companion object : TlvValueReader<SharedInputTxId> {
21-
const val tag: Long = 1105
21+
const val tag: Long = 0
2222
override fun read(input: Input): SharedInputTxId = SharedInputTxId(TxId(LightningCodecs.txHash(input)))
2323
}
2424
}
@@ -97,7 +97,7 @@ sealed class TxSignaturesTlv : Tlv {
9797
override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out)
9898

9999
companion object : TlvValueReader<PreviousFundingTxSig> {
100-
const val tag: Long = 601
100+
const val tag: Long = 0
101101
override fun read(input: Input): PreviousFundingTxSig = PreviousFundingTxSig(LightningCodecs.bytes(input, 64).toByteVector64())
102102
}
103103
}

modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ interface LightningMessage {
6767
TxAbort.type -> TxAbort.read(stream)
6868
CommitSig.type -> CommitSig.read(stream)
6969
RevokeAndAck.type -> RevokeAndAck.read(stream)
70+
StartBatch.type -> StartBatch.read(stream)
7071
UpdateAddHtlc.type -> UpdateAddHtlc.read(stream)
7172
UpdateFailHtlc.type -> UpdateFailHtlc.read(stream)
7273
UpdateFailMalformedHtlc.type -> UpdateFailMalformedHtlc.read(stream)
@@ -985,7 +986,7 @@ data class SpliceInit(
985986
}
986987

987988
companion object : LightningMessageReader<SpliceInit> {
988-
const val type: Long = 37000
989+
const val type: Long = 80
989990

990991
@Suppress("UNCHECKED_CAST")
991992
private val readers = mapOf(
@@ -1033,7 +1034,7 @@ data class SpliceAck(
10331034
}
10341035

10351036
companion object : LightningMessageReader<SpliceAck> {
1036-
const val type: Long = 37002
1037+
const val type: Long = 81
10371038

10381039
@Suppress("UNCHECKED_CAST")
10391040
private val readers = mapOf(
@@ -1065,7 +1066,7 @@ data class SpliceLocked(
10651066
}
10661067

10671068
companion object : LightningMessageReader<SpliceLocked> {
1068-
const val type: Long = 37004
1069+
const val type: Long = 77
10691070

10701071
private val readers = emptyMap<Long, TlvValueReader<ChannelTlv>>()
10711072

@@ -1077,6 +1078,28 @@ data class SpliceLocked(
10771078
}
10781079
}
10791080

1081+
data class StartBatch(override val channelId: ByteVector32, val batchSize: Int, val tlvStream: TlvStream<StartBatchTlv> = TlvStream.empty()): ChannelMessage, HasChannelId {
1082+
override val type: Long get() = StartBatch.type
1083+
1084+
override fun write(out: Output) {
1085+
LightningCodecs.writeBytes(channelId, out)
1086+
LightningCodecs.writeU16(batchSize, out)
1087+
TlvStreamSerializer(false, readers).write(tlvStream, out)
1088+
}
1089+
1090+
companion object : LightningMessageReader<StartBatch> {
1091+
const val type: Long = 127
1092+
1093+
val readers: Map<Long, TlvValueReader<StartBatchTlv>> = mapOf()
1094+
1095+
override fun read(input: Input): StartBatch = StartBatch(
1096+
channelId = ByteVector32(LightningCodecs.bytes(input, 32)),
1097+
batchSize = LightningCodecs.u16(input),
1098+
tlvStream = TlvStreamSerializer(false, readers).read(input)
1099+
)
1100+
}
1101+
}
1102+
10801103
data class UpdateAddHtlc(
10811104
override val channelId: ByteVector32,
10821105
val id: Long,
@@ -1242,7 +1265,7 @@ data class CommitSig(
12421265
override val type: Long get() = CommitSig.type
12431266

12441267
val alternativeFeerateSigs: List<CommitSigTlv.AlternativeFeerateSig> = tlvStream.get<CommitSigTlv.AlternativeFeerateSigs>()?.sigs ?: listOf()
1245-
val batchSize: Int = tlvStream.get<CommitSigTlv.Batch>()?.size ?: 1
1268+
val fundingTxId: TxId? = tlvStream.get<CommitSigTlv.FundingTx>()?.txId
12461269

12471270
override fun write(out: Output) {
12481271
LightningCodecs.writeBytes(channelId, out)
@@ -1257,8 +1280,8 @@ data class CommitSig(
12571280

12581281
@Suppress("UNCHECKED_CAST")
12591282
val readers = mapOf(
1283+
CommitSigTlv.FundingTx.tag to CommitSigTlv.FundingTx.Companion as TlvValueReader<CommitSigTlv>,
12601284
CommitSigTlv.AlternativeFeerateSigs.tag to CommitSigTlv.AlternativeFeerateSigs.Companion as TlvValueReader<CommitSigTlv>,
1261-
CommitSigTlv.Batch.tag to CommitSigTlv.Batch.Companion as TlvValueReader<CommitSigTlv>,
12621285
)
12631286

12641287
override fun read(input: Input): CommitSig {

modules/core/src/commonTest/kotlin/fr/acinq/lightning/channel/states/SpliceTestsCommon.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ class SpliceTestsCommon : LightningTestSuite() {
707707
val (bob6, actionsBob6) = bob5.process(ChannelCommand.Commitment.Sign)
708708
assertEquals(actionsBob6.size, 3)
709709
val commitSigBob = actionsBob6.findOutgoingMessage<CommitSig>()
710-
assertEquals(commitSigBob.batchSize, 1)
710+
assertNull(commitSigBob.fundingTxId)
711711
actionsBob6.has<ChannelAction.Storage.StoreHtlcInfos>()
712712
actionsBob6.has<ChannelAction.Storage.StoreState>()
713713
val (alice6, actionsAlice6) = alice5.process(ChannelCommand.MessageReceived(revokeAndAckBob))

0 commit comments

Comments
 (0)