Skip to content

Commit

Permalink
Ignore commit_sig for aborted splice (#2709)
Browse files Browse the repository at this point in the history
After exchanging `tx_complete`, we validate the splice transaction before
sending our `commit_sig`. If we consider the transaction invalid, we send
`tx_abort`. But if our peer thinks the transaction is valid, they will send
their `commit_sig`, which we must ignore until they've acked our `tx_abort`.
  • Loading branch information
t-bast authored Jul 10, 2023
1 parent abf1dd3 commit cf46b64
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
case s: SpliceStatus.SpliceInProgress =>
log.debug("received their commit_sig, deferring message")
stay() using d.copy(spliceStatus = s.copy(remoteCommitSig = Some(commit)))
case SpliceStatus.SpliceAborted =>
log.warning("received commit_sig after sending tx_abort, they probably sent it before receiving our tx_abort, ignoring...")
stay()
case SpliceStatus.SpliceWaitingForSigs(signingSession) =>
signingSession.receiveCommitSig(nodeParams, d.commitments.params, commit) match {
case Left(f) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,54 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
}

test("recv TxAbort (after CommitSig)") { f =>
import f._

val sender = TestProbe()
alice ! CMD_SPLICE(sender.ref, spliceIn_opt = Some(SpliceIn(50_000 sat)), spliceOut_opt = None)
alice2bob.expectMsgType[SpliceInit]
alice2bob.forward(bob)
bob2alice.expectMsgType[SpliceAck]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddInput]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxComplete]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddInput]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxComplete]
bob2alice.forward(alice)
val output1 = alice2bob.expectMsgType[TxAddOutput]
alice2bob.forward(bob)
bob2alice.expectMsgType[TxComplete]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxAddOutput]
// We forward a duplicate of the first output, which will make bob abort after receiving tx_complete.
alice2bob.forward(bob, output1.copy(serialId = UInt64(100)))
bob2alice.expectMsgType[TxComplete]
bob2alice.forward(alice)
alice2bob.expectMsgType[TxComplete]
alice2bob.forward(bob)
val commitSigAlice = alice2bob.expectMsgType[CommitSig]
val txAbortBob = bob2alice.expectMsgType[TxAbort]
sender.expectMsgType[RES_SPLICE]

// Bob ignores Alice's commit_sig.
alice2bob.forward(bob, commitSigAlice)
bob2alice.expectNoMessage(100 millis)
bob2blockchain.expectNoMessage(100 millis)

// Alice acks Bob's tx_abort.
bob2alice.forward(alice, txAbortBob)
alice2bob.expectMsgType[TxAbort]
alice2bob.forward(bob)

awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].spliceStatus == SpliceStatus.NoSplice)
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.active.size == 1)
}

test("recv WatchFundingConfirmedTriggered on splice tx", Tag(NoMaxHtlcValueInFlight)) { f =>
import f._

Expand Down Expand Up @@ -1370,15 +1418,23 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
bob.stop()

alice2blockchain.expectNoMessage(100 millis)
bob2blockchain.expectNoMessage(100 millis)

val alice2 = TestFSMRef(new Channel(aliceNodeParams, wallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
alice2 ! INPUT_RESTORED(aliceData)

alice2blockchain.expectMsgType[SetChannelId]
alice2blockchain.expectWatchFundingConfirmed(fundingTx2.txid)
alice2blockchain.expectWatchFundingConfirmed(fundingTx1.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx0.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.expectNoMessage(100 millis)
}

test("put back watches after restart (inactive)", Tag(ChannelStateTestsTags.ZeroConf), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f =>
Expand Down Expand Up @@ -1421,15 +1477,23 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
bob.stop()

alice2blockchain.expectNoMessage(100 millis)
bob2blockchain.expectNoMessage(100 millis)

val alice2 = TestFSMRef(new Channel(aliceNodeParams, wallet, bobNodeParams.nodeId, alice2blockchain.ref, TestProbe().ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer)
alice2 ! INPUT_RESTORED(aliceData)

alice2blockchain.expectMsgType[SetChannelId]
alice2blockchain.expectWatchPublished(fundingTx2.txid)
alice2blockchain.expectWatchFundingConfirmed(fundingTx1.txid)
alice2blockchain.expectWatchFundingSpent(fundingTx0.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.expectNoMessage(100 millis)
}

}

0 comments on commit cf46b64

Please sign in to comment.