@@ -24,7 +24,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw
24
24
import fr .acinq .eclair .payment .OutgoingPacket .Upstream
25
25
import fr .acinq .eclair .router .Announcements
26
26
import fr .acinq .eclair .transactions .CommitmentSpec
27
- import fr .acinq .eclair .transactions .Transactions .{ AnchorOutputsCommitmentFormat , CommitTx , CommitmentFormat , DefaultCommitmentFormat }
27
+ import fr .acinq .eclair .transactions .Transactions ._
28
28
import fr .acinq .eclair .wire .{AcceptChannel , ChannelAnnouncement , ChannelReestablish , ChannelUpdate , ClosingSigned , FailureMessage , FundingCreated , FundingLocked , FundingSigned , Init , OnionRoutingPacket , OpenChannel , Shutdown , UpdateAddHtlc , UpdateFailHtlc , UpdateFailMalformedHtlc , UpdateFulfillHtlc }
29
29
import fr .acinq .eclair .{CltvExpiry , CltvExpiryDelta , Features , MilliSatoshi , ShortChannelId , UInt64 }
30
30
import scodec .bits .{BitVector , ByteVector }
@@ -268,27 +268,111 @@ sealed trait HasCommitments extends Data {
268
268
def commitments : Commitments
269
269
}
270
270
271
- case class ClosingTxProposed (unsignedTx : Transaction , localClosingSigned : ClosingSigned )
271
+ case class ClosingTxProposed (unsignedTx : ClosingTx , localClosingSigned : ClosingSigned )
272
+
273
+ sealed trait CommitPublished {
274
+ /** Commitment tx. */
275
+ def commitTx : Transaction
276
+ /** Map of relevant outpoints that have been spent and the confirmed transaction that spends them. */
277
+ def irrevocablySpent : Map [OutPoint , Transaction ]
272
278
273
- case class LocalCommitPublished (commitTx : Transaction , claimMainDelayedOutputTx : Option [Transaction ], htlcSuccessTxs : List [Transaction ], htlcTimeoutTxs : List [Transaction ], claimHtlcDelayedTxs : List [Transaction ], irrevocablySpent : Map [OutPoint , ByteVector32 ]) {
274
279
def isConfirmed : Boolean = {
275
280
// NB: if multiple transactions end up in the same block, the first confirmation we receive may not be the commit tx.
276
281
// However if the confirmed tx spends from the commit tx, we know that the commit tx is already confirmed and we know
277
282
// the type of closing.
278
- val confirmedTxs = irrevocablySpent.values.toSet
279
- (commitTx :: claimMainDelayedOutputTx.toList ::: htlcSuccessTxs ::: htlcTimeoutTxs ::: claimHtlcDelayedTxs).exists(tx => confirmedTxs.contains(tx.txid))
283
+ irrevocablySpent.values.exists(tx => tx.txid == commitTx.txid) || irrevocablySpent.keys.exists(_.txid == commitTx.txid)
280
284
}
281
285
}
282
- case class RemoteCommitPublished (commitTx : Transaction , claimMainOutputTx : Option [Transaction ], claimHtlcSuccessTxs : List [Transaction ], claimHtlcTimeoutTxs : List [Transaction ], irrevocablySpent : Map [OutPoint , ByteVector32 ]) {
283
- def isConfirmed : Boolean = {
284
- // NB: if multiple transactions end up in the same block, the first confirmation we receive may not be the commit tx.
285
- // However if the confirmed tx spends from the commit tx, we know that the commit tx is already confirmed and we know
286
- // the type of closing.
287
- val confirmedTxs = irrevocablySpent.values.toSet
288
- (commitTx :: claimMainOutputTx.toList ::: claimHtlcSuccessTxs ::: claimHtlcTimeoutTxs).exists(tx => confirmedTxs.contains(tx.txid))
286
+
287
+ /**
288
+ * Details about a force-close where we published our commitment.
289
+ *
290
+ * @param claimMainDelayedOutputTx tx claiming our main output (if we have one).
291
+ * @param htlcTxs txs claiming HTLCs. There will be one entry for each pending HTLC. The value will be
292
+ * None only for incoming HTLCs for which we don't have the preimage (we can't claim them yet).
293
+ * @param claimHtlcDelayedTxs 3rd-stage txs (spending the output of HTLC txs).
294
+ * @param claimAnchorTxs txs spending anchor outputs to bump the feerate of the commitment tx (if applicable).
295
+ */
296
+ case class LocalCommitPublished (commitTx : Transaction , claimMainDelayedOutputTx : Option [ClaimLocalDelayedOutputTx ], htlcTxs : Map [OutPoint , Option [HtlcTx ]], claimHtlcDelayedTxs : List [ClaimLocalDelayedOutputTx ], claimAnchorTxs : List [ClaimAnchorOutputTx ], irrevocablySpent : Map [OutPoint , Transaction ]) extends CommitPublished {
297
+ /**
298
+ * A local commit is considered done when:
299
+ * - all commitment tx outputs that we can spend have been spent and confirmed (even if the spending tx was not ours)
300
+ * - all 3rd stage txs (txs spending htlc txs) have been confirmed
301
+ */
302
+ def isDone : Boolean = {
303
+ val confirmedTxs = irrevocablySpent.values.map(_.txid).toSet
304
+ // is the commitment tx confirmed (we need to check this because we may not have any outputs)?
305
+ val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid)
306
+ // is our main output confirmed (if we have one)?
307
+ val isMainOutputConfirmed = claimMainDelayedOutputTx.forall(tx => irrevocablySpent.contains(tx.input.outPoint))
308
+ // are all htlc outputs from the commitment tx spent (we need to check them all because we may receive preimages later)?
309
+ val allHtlcsSpent = (htlcTxs.keySet -- irrevocablySpent.keys).isEmpty
310
+ // are all outputs from htlc txs spent?
311
+ val unconfirmedHtlcDelayedTxs = claimHtlcDelayedTxs.map(_.input.outPoint)
312
+ // only the txs which parents are already confirmed may get confirmed (note that this eliminates outputs that have been double-spent by a competing tx)
313
+ .filter(input => confirmedTxs.contains(input.txid))
314
+ // has the tx already been confirmed?
315
+ .filterNot(input => irrevocablySpent.contains(input))
316
+ isCommitTxConfirmed && isMainOutputConfirmed && allHtlcsSpent && unconfirmedHtlcDelayedTxs.isEmpty
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Details about a force-close where they published their commitment.
322
+ *
323
+ * @param claimMainOutputTx tx claiming our main output (if we have one).
324
+ * @param claimHtlcTxs txs claiming HTLCs. There will be one entry for each pending HTLC. The value will be None
325
+ * only for incoming HTLCs for which we don't have the preimage (we can't claim them yet).
326
+ * @param claimAnchorTxs txs spending anchor outputs to bump the feerate of the commitment tx (if applicable).
327
+ */
328
+ case class RemoteCommitPublished (commitTx : Transaction , claimMainOutputTx : Option [ClaimRemoteCommitMainOutputTx ], claimHtlcTxs : Map [OutPoint , Option [ClaimHtlcTx ]], claimAnchorTxs : List [ClaimAnchorOutputTx ], irrevocablySpent : Map [OutPoint , Transaction ]) extends CommitPublished {
329
+ /**
330
+ * A remote commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed
331
+ * (even if the spending tx was not ours).
332
+ */
333
+ def isDone : Boolean = {
334
+ val confirmedTxs = irrevocablySpent.values.map(_.txid).toSet
335
+ // is the commitment tx confirmed (we need to check this because we may not have any outputs)?
336
+ val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid)
337
+ // is our main output confirmed (if we have one)?
338
+ val isMainOutputConfirmed = claimMainOutputTx.forall(tx => irrevocablySpent.contains(tx.input.outPoint))
339
+ // are all htlc outputs from the commitment tx spent (we need to check them all because we may receive preimages later)?
340
+ val allHtlcsSpent = (claimHtlcTxs.keySet -- irrevocablySpent.keys).isEmpty
341
+ isCommitTxConfirmed && isMainOutputConfirmed && allHtlcsSpent
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Details about a force-close where they published one of their revoked commitments.
347
+ *
348
+ * @param claimMainOutputTx tx claiming our main output (if we have one).
349
+ * @param mainPenaltyTx penalty tx claiming their main output (if they have one).
350
+ * @param htlcPenaltyTxs penalty txs claiming every HTLC output.
351
+ * @param claimHtlcDelayedPenaltyTxs penalty txs claiming the output of their HTLC txs (if they managed to get them confirmed before our htlcPenaltyTxs).
352
+ */
353
+ case class RevokedCommitPublished (commitTx : Transaction , claimMainOutputTx : Option [ClaimRemoteCommitMainOutputTx ], mainPenaltyTx : Option [MainPenaltyTx ], htlcPenaltyTxs : List [HtlcPenaltyTx ], claimHtlcDelayedPenaltyTxs : List [ClaimHtlcDelayedOutputPenaltyTx ], irrevocablySpent : Map [OutPoint , Transaction ]) extends CommitPublished {
354
+ /**
355
+ * A revoked commit is considered done when all commitment tx outputs that we can spend have been spent and confirmed
356
+ * (even if the spending tx was not ours).
357
+ */
358
+ def isDone : Boolean = {
359
+ val confirmedTxs = irrevocablySpent.values.map(_.txid).toSet
360
+ // is the commitment tx confirmed (we need to check this because we may not have any outputs)?
361
+ val isCommitTxConfirmed = confirmedTxs.contains(commitTx.txid)
362
+ // are there remaining spendable outputs from the commitment tx?
363
+ val unspentCommitTxOutputs = {
364
+ val commitOutputsSpendableByUs = (claimMainOutputTx.toSeq ++ mainPenaltyTx.toSeq ++ htlcPenaltyTxs).map(_.input.outPoint)
365
+ commitOutputsSpendableByUs.toSet -- irrevocablySpent.keys
366
+ }
367
+ // are all outputs from htlc txs spent?
368
+ val unconfirmedHtlcDelayedTxs = claimHtlcDelayedPenaltyTxs.map(_.input.outPoint)
369
+ // only the txs which parents are already confirmed may get confirmed (note that this eliminates outputs that have been double-spent by a competing tx)
370
+ .filter(input => confirmedTxs.contains(input.txid))
371
+ // if one of the tx inputs has been spent, the tx has already been confirmed or a competing tx has been confirmed
372
+ .filterNot(input => irrevocablySpent.contains(input))
373
+ isCommitTxConfirmed && unspentCommitTxOutputs.isEmpty && unconfirmedHtlcDelayedTxs.isEmpty
289
374
}
290
375
}
291
- case class RevokedCommitPublished (commitTx : Transaction , claimMainOutputTx : Option [Transaction ], mainPenaltyTx : Option [Transaction ], htlcPenaltyTxs : List [Transaction ], claimHtlcDelayedPenaltyTxs : List [Transaction ], irrevocablySpent : Map [OutPoint , ByteVector32 ])
292
376
293
377
final case class DATA_WAIT_FOR_OPEN_CHANNEL (initFundee : INPUT_INIT_FUNDEE ) extends Data {
294
378
val channelId : ByteVector32 = initFundee.temporaryChannelId
@@ -352,27 +436,26 @@ final case class DATA_SHUTDOWN(commitments: Commitments,
352
436
final case class DATA_NEGOTIATING (commitments : Commitments ,
353
437
localShutdown : Shutdown , remoteShutdown : Shutdown ,
354
438
closingTxProposed : List [List [ClosingTxProposed ]], // one list for every negotiation (there can be several in case of disconnection)
355
- bestUnpublishedClosingTx_opt : Option [Transaction ]) extends Data with HasCommitments {
439
+ bestUnpublishedClosingTx_opt : Option [ClosingTx ]) extends Data with HasCommitments {
356
440
require(closingTxProposed.nonEmpty, " there must always be a list for the current negotiation" )
357
441
require(! commitments.localParams.isFunder || closingTxProposed.forall(_.nonEmpty), " funder must have at least one closing signature for every negotiation attempt because it initiates the closing" )
358
442
}
359
443
final case class DATA_CLOSING (commitments : Commitments ,
360
444
fundingTx : Option [Transaction ], // this will be non-empty if we are funder and we got in closing while waiting for our own tx to be published
361
445
waitingSinceBlock : Long , // how long since we initiated the closing
362
- mutualCloseProposed : List [Transaction ], // all exchanged closing sigs are flattened, we use this only to keep track of what publishable tx they have
363
- mutualClosePublished : List [Transaction ] = Nil ,
446
+ mutualCloseProposed : List [ClosingTx ], // all exchanged closing sigs are flattened, we use this only to keep track of what publishable tx they have
447
+ mutualClosePublished : List [ClosingTx ] = Nil ,
364
448
localCommitPublished : Option [LocalCommitPublished ] = None ,
365
449
remoteCommitPublished : Option [RemoteCommitPublished ] = None ,
366
450
nextRemoteCommitPublished : Option [RemoteCommitPublished ] = None ,
367
451
futureRemoteCommitPublished : Option [RemoteCommitPublished ] = None ,
368
452
revokedCommitPublished : List [RevokedCommitPublished ] = Nil ) extends Data with HasCommitments {
369
- val spendingTxes = mutualClosePublished ::: localCommitPublished.map(_.commitTx).toList ::: remoteCommitPublished.map(_.commitTx).toList ::: nextRemoteCommitPublished.map(_.commitTx).toList ::: futureRemoteCommitPublished.map(_.commitTx).toList ::: revokedCommitPublished.map(_.commitTx)
370
- require(spendingTxes .nonEmpty, " there must be at least one tx published in this state" )
453
+ 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)
454
+ require(spendingTxs .nonEmpty, " there must be at least one tx published in this state" )
371
455
}
372
456
373
457
final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT (commitments : Commitments , remoteChannelReestablish : ChannelReestablish ) extends Data with HasCommitments
374
458
375
-
376
459
/**
377
460
* @param features current connection features, or last features used if the channel is disconnected. Note that these
378
461
* features are updated at each reconnection and may be different from the ones that were used when the
0 commit comments