Skip to content

Commit

Permalink
Set child splices as hints in watch-funding-spent
Browse files Browse the repository at this point in the history
We start watching funding outputs once the corresponding funding transaction
has been confirmed with 3 confirmations. We may have already spent that
transaction with a child splice transaction by that time: providing hints
with the corresponding txids to the `ZmqWatcher` improves the performance
when that happens.
  • Loading branch information
t-bast committed Sep 4, 2023
1 parent 8d42052 commit 62a188a
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,7 @@ case class Commitments(params: ChannelParams,
def updateLocalFundingStatus(fundingTxId: ByteVector32, status: LocalFundingStatus)(implicit log: LoggingAdapter): Either[Commitments, (Commitments, Commitment)] =
updateFundingStatus(fundingTxId, _ => {
case c if c.fundingTxId == fundingTxId =>
log.info(s"setting localFundingStatus=${status.getClass.getSimpleName} for fundingTxId=$fundingTxId fundingTxIndex=${c.fundingTxIndex}")
log.info(s"setting localFundingStatus=${status.getClass.getSimpleName} for fundingTxId=${c.fundingTxId} fundingTxIndex=${c.fundingTxIndex}")
c.copy(localFundingStatus = status)
case c => c
})
Expand All @@ -1162,7 +1162,7 @@ case class Commitments(params: ChannelParams,
updateFundingStatus(fundingTxId, fundingTxIndex => {
// all funding older than this one are considered locked
case c if c.fundingTxId == fundingTxId || c.fundingTxIndex < fundingTxIndex =>
log.info(s"setting remoteFundingStatus=${RemoteFundingStatus.Locked.getClass.getSimpleName} for fundingTxId=$fundingTxId fundingTxIndex=${c.fundingTxIndex}")
log.info(s"setting remoteFundingStatus=${RemoteFundingStatus.Locked.getClass.getSimpleName} for fundingTxId=${c.fundingTxId} fundingTxIndex=${c.fundingTxIndex}")
c.copy(remoteFundingStatus = RemoteFundingStatus.Locked)
case c => c
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
// in all other cases we need to be ready for any type of closing
watchFundingSpent(commitment, closing.spendingTxs.map(_.txid).toSet)
case _ =>
watchFundingSpent(commitment)
// Children splice transactions may already spend that confirmed funding transaction.
val spliceSpendingTxs = data.commitments.all.collect { case c if c.fundingTxIndex == commitment.fundingTxIndex + 1 => c.fundingTxId }
watchFundingSpent(commitment, additionalKnownSpendingTxs = spliceSpendingTxs.toSet)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ trait CommonFundingHandlers extends CommonHandlers {
context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, w.tx))
d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus).map {
case (commitments1, commitment) =>
// first of all, we watch the funding tx that is now confirmed
watchFundingSpent(commitment)
// First of all, we watch the funding tx that is now confirmed.
// Children splice transactions may already spend that confirmed funding transaction.
val spliceSpendingTxs = commitments1.all.collect { case c if c.fundingTxIndex == commitment.fundingTxIndex + 1 => c.fundingTxId }
watchFundingSpent(commitment, additionalKnownSpendingTxs = spliceSpendingTxs.toSet)
// in the dual-funding case we can forget all other transactions, they have been double spent by the tx that just confirmed
rollbackDualFundingTxs(d.commitments.active // note how we use the unpruned original commitments
.filter(c => c.fundingTxIndex == commitment.fundingTxIndex && c.fundingTxId != commitment.fundingTxId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
import f._

val (fundingTx1, fundingTx2) = setup2Splices(f)
val commitAlice1 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active(1).localCommit.commitTxAndRemoteSig.commitTx.tx
val commitAlice2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active(0).localCommit.commitTxAndRemoteSig.commitTx.tx
val commitBob1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active(1).localCommit.commitTxAndRemoteSig.commitTx.tx
val commitBob2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active(0).localCommit.commitTxAndRemoteSig.commitTx.tx

// splice 1 confirms
alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1)
Expand All @@ -432,8 +436,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1)
bob2alice.expectMsgTypeHaving[SpliceLocked](_.fundingTxid == fundingTx1.txid)
bob2alice.forward(alice)
alice2blockchain.expectWatchFundingSpent(fundingTx1.txid)
bob2blockchain.expectWatchFundingSpent(fundingTx1.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx1.txid, Some(Set(fundingTx2.txid, commitAlice1.txid, commitBob1.txid)))
bob2blockchain.expectWatchFundingSpent(fundingTx1.txid, Some(Set(fundingTx2.txid, commitAlice1.txid, commitBob1.txid)))
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.map(_.fundingTxIndex) == Seq(2, 1))
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.inactive.map(_.fundingTxIndex) == Seq.empty)

Expand All @@ -444,8 +448,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx2)
bob2alice.expectMsgTypeHaving[SpliceLocked](_.fundingTxid == fundingTx2.txid)
bob2alice.forward(alice)
alice2blockchain.expectWatchFundingSpent(fundingTx2.txid)
bob2blockchain.expectWatchFundingSpent(fundingTx2.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx2.txid, Some(Set(commitAlice2.txid, commitBob2.txid)))
bob2blockchain.expectWatchFundingSpent(fundingTx2.txid, Some(Set(commitAlice2.txid, commitBob2.txid)))
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.map(_.fundingTxIndex) == Seq(2))
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.inactive.map(_.fundingTxIndex) == Seq.empty)
}
Expand Down Expand Up @@ -478,6 +482,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
import f._

val (fundingTx1, fundingTx2) = setup2Splices(f)
val commitAlice2 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx
val commitBob2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.head.localCommit.commitTxAndRemoteSig.commitTx.tx

// splice 2 confirms
alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx2)
Expand All @@ -486,8 +492,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx2)
bob2alice.expectMsgTypeHaving[SpliceLocked](_.fundingTxid == fundingTx2.txid)
bob2alice.forward(alice)
alice2blockchain.expectWatchFundingSpent(fundingTx2.txid)
bob2blockchain.expectWatchFundingSpent(fundingTx2.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx2.txid, Some(Set(commitAlice2.txid, commitBob2.txid)))
bob2blockchain.expectWatchFundingSpent(fundingTx2.txid, Some(Set(commitAlice2.txid, commitBob2.txid)))
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.map(_.fundingTxIndex) == Seq(2))
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.inactive.map(_.fundingTxIndex) == Seq.empty)

Expand Down Expand Up @@ -1403,7 +1409,8 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
test("put back watches after restart") { f =>
import f._

val fundingTx0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
val commitments0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest
val fundingTx0 = commitments0.localFundingStatus.signedTx_opt.get
val (fundingTx1, fundingTx2) = setup2Splices(f)

val (aliceNodeParams, bobNodeParams) = (alice.underlyingActor.nodeParams, bob.underlyingActor.nodeParams)
Expand All @@ -1423,22 +1430,23 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectMsgType[SetChannelId]
alice2blockchain.expectWatchFundingConfirmed(fundingTx2.txid)
alice2blockchain.expectWatchFundingConfirmed(fundingTx1.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx0.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx0.txid, Some(Set(fundingTx1.txid, commitments0.localCommit.commitTxAndRemoteSig.commitTx.tx.txid, commitments0.remoteCommit.txid)))
alice2blockchain.expectNoMessage(100 millis)

val bob2 = TestFSMRef(new Channel(bobNodeParams, wallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
bob2 ! INPUT_RESTORED(bobData)
bob2blockchain.expectMsgType[SetChannelId]
bob2blockchain.expectWatchFundingConfirmed(fundingTx2.txid)
bob2blockchain.expectWatchFundingConfirmed(fundingTx1.txid)
bob2blockchain.expectWatchFundingSpent(fundingTx0.txid)
bob2blockchain.expectWatchFundingSpent(fundingTx0.txid, Some(Set(fundingTx1.txid, commitments0.localCommit.commitTxAndRemoteSig.commitTx.tx.txid, commitments0.remoteCommit.txid)))
bob2blockchain.expectNoMessage(100 millis)
}

test("put back watches after restart (inactive)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
import f._

val fundingTx0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localFundingStatus.signedTx_opt.get
val commitments0 = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest
val fundingTx0 = commitments0.localFundingStatus.signedTx_opt.get

alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx0)
bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx0)
Expand Down Expand Up @@ -1482,15 +1490,15 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2blockchain.expectMsgType[SetChannelId]
alice2blockchain.expectWatchPublished(fundingTx2.txid)
alice2blockchain.expectWatchFundingConfirmed(fundingTx1.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx0.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx0.txid, Some(Set(fundingTx1.txid, commitments0.localCommit.commitTxAndRemoteSig.commitTx.tx.txid, commitments0.remoteCommit.txid)))
alice2blockchain.expectNoMessage(100 millis)

val bob2 = TestFSMRef(new Channel(bobNodeParams, wallet, aliceNodeParams.nodeId, bob2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(bob2blockchain)), bobPeer)
bob2 ! INPUT_RESTORED(bobData)
bob2blockchain.expectMsgType[SetChannelId]
bob2blockchain.expectWatchPublished(fundingTx2.txid)
bob2blockchain.expectWatchFundingConfirmed(fundingTx1.txid)
bob2blockchain.expectWatchFundingSpent(fundingTx0.txid)
bob2blockchain.expectWatchFundingSpent(fundingTx0.txid, Some(Set(fundingTx1.txid, commitments0.localCommit.commitTxAndRemoteSig.commitTx.tx.txid, commitments0.remoteCommit.txid)))
bob2blockchain.expectNoMessage(100 millis)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ case class PimpTestProbe(probe: TestProbe) extends Assertions {
msg
}

def expectWatchFundingSpent(txid: ByteVector32): WatchFundingSpent =
expectMsgTypeHaving[WatchFundingSpent](w => assert(w.txId == txid, "txid"))
def expectWatchFundingSpent(txid: ByteVector32, hints_opt: Option[Set[ByteVector32]] = None): WatchFundingSpent =
expectMsgTypeHaving[WatchFundingSpent](w => {
assert(w.txId == txid, "txid")
hints_opt.foreach(hints => assert(hints == w.hints))
})

def expectWatchFundingConfirmed(txid: ByteVector32): WatchFundingConfirmed =
expectMsgTypeHaving[WatchFundingConfirmed](w => assert(w.txId == txid, "txid"))
Expand Down

0 comments on commit 62a188a

Please sign in to comment.