Skip to content

Commit

Permalink
Clean up check balance tests
Browse files Browse the repository at this point in the history
Remove unnecessary parts that belonged to the NormalStateSpec test.
  • Loading branch information
t-bast committed Jul 15, 2021
1 parent 7e77560 commit 51b0190
Showing 1 changed file with 30 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package fr.acinq.eclair.balance

import akka.pattern.pipe
import akka.testkit.TestProbe
import fr.acinq.bitcoin.{ByteVector32, SatoshiLong, ScriptFlags, Transaction}
import fr.acinq.bitcoin.{ByteVector32, SatoshiLong}
import fr.acinq.eclair.balance.CheckBalance.{ClosingBalance, OffChainBalance, PossiblyPublishedMainAndHtlcBalance, PossiblyPublishedMainBalance}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{apply => _, _}
import fr.acinq.eclair.blockchain.bitcoind.rpc.ExtendedBitcoinClient
Expand Down Expand Up @@ -38,45 +38,26 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
}
}

test("recv WatchFundingSpentTriggered (their commit w/ htlc)") { f =>
test("take published remote commit tx into account") { f =>
import f._

val (ra1, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)
// We add 3 htlcs Alice -> Bob (one of them below dust) and 2 htlcs Bob -> Alice
addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)
val (ra2, htlca2) = addHtlc(100000000 msat, alice, bob, alice2bob, bob2alice)
val (ra3, htlca3) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice)
val (_, htlca3) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice)
val (rb1, htlcb1) = addHtlc(50000000 msat, bob, alice, bob2alice, alice2bob)
val (rb2, htlcb2) = addHtlc(55000000 msat, bob, alice, bob2alice, alice2bob)
val (_, htlcb2) = addHtlc(55000000 msat, bob, alice, bob2alice, alice2bob)
crossSign(alice, bob, alice2bob, bob2alice)
fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob)
fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice)

// at this point here is the situation from alice pov and what she should do when bob publishes his commit tx:
// balances :
// alice's balance : 449 999 990 => nothing to do
// bob's balance : 95 000 000 => nothing to do
// htlcs :
// alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend
// alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend
// alice -> bob : 10 (dust) => won't appear in the commitment tx
// bob -> alice : 50 000 000 (alice has the preimage) => spend immediately using the preimage
// bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout
// And fulfill one htlc in each direction without signing a new commit tx
fulfillHtlc(htlca2.id, ra2, bob, alice, bob2alice, alice2bob)
fulfillHtlc(htlcb1.id, rb1, alice, bob, alice2bob, bob2alice)

// bob publishes his current commit tx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
alice ! WatchFundingSpentTriggered(bobCommitTx)

// in response to that, alice publishes its claim txs
val claimTxs = for (_ <- 0 until 4) yield alice2blockchain.expectMsgType[PublishRawTx].tx
// in addition to its main output, alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage
val amountClaimed = (for (claimHtlcTx <- claimTxs) yield {
assert(claimHtlcTx.txIn.size == 1)
assert(claimHtlcTx.txOut.size == 1)
Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
claimHtlcTx.txOut.head.amount
}).sum
// at best we have a little less than 450 000 + 250 000 + 100 000 + 50 000 = 850 000 (because fees)
assert(amountClaimed === 814880.sat)

val commitments = alice.stateData.asInstanceOf[DATA_CLOSING].commitments
val remoteCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get
Expand All @@ -97,52 +78,33 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
))
}

test("recv WatchFundingSpentTriggered (their *next* commit w/ htlc)") { f =>
test("take published next remote commit tx into account") { f =>
import f._

val (ra1, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)
// We add 3 htlcs Alice -> Bob (one of them below dust) and 2 htlcs Bob -> Alice
addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)
val (ra2, htlca2) = addHtlc(100000000 msat, alice, bob, alice2bob, bob2alice)
val (ra3, htlca3) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice)
val (_, htlca3) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice)
val (rb1, htlcb1) = addHtlc(50000000 msat, bob, alice, bob2alice, alice2bob)
val (rb2, htlcb2) = addHtlc(55000000 msat, bob, alice, bob2alice, alice2bob)
val (_, htlcb2) = addHtlc(55000000 msat, bob, alice, bob2alice, alice2bob)
crossSign(alice, bob, alice2bob, bob2alice)
fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob)
fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice)
// alice sign but we intercept bob's revocation
// And fulfill one htlc in each direction
fulfillHtlc(htlca2.id, ra2, bob, alice, bob2alice, alice2bob)
fulfillHtlc(htlcb1.id, rb1, alice, bob, alice2bob, bob2alice)
// alice signs but we intercept bob's revocation
alice ! CMD_SIGN()
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[RevokeAndAck]

// as far as alice knows, bob currently has two valid unrevoked commitment transactions

// at this point here is the situation from bob's pov with the latest sig received from alice,
// and what alice should do when bob publishes his commit tx:
// balances :
// alice's balance : 499 999 990 => nothing to do
// bob's balance : 95 000 000 => nothing to do
// htlcs :
// alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend
// alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend
// alice -> bob : 10 (dust) => won't appear in the commitment tx
// bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout

// bob publishes his current commit tx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
assert(bobCommitTx.txOut.size == 5) // two main outputs and 3 pending htlcs
alice ! WatchFundingSpentTriggered(bobCommitTx)

// in response to that, alice publishes its claim txs
val claimTxs = for (_ <- 0 until 3) yield alice2blockchain.expectMsgType[PublishRawTx].tx
// in addition to its main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage
val amountClaimed = (for (claimHtlcTx <- claimTxs) yield {
assert(claimHtlcTx.txIn.size == 1)
assert(claimHtlcTx.txOut.size == 1)
Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
claimHtlcTx.txOut.head.amount
}).sum
// at best we have a little less than 500 000 + 250 000 + 100 000 = 850 000 (because fees)
assert(amountClaimed === 822310.sat)

val commitments = alice.stateData.asInstanceOf[DATA_CLOSING].commitments
val remoteCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get
Expand All @@ -163,43 +125,29 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
))
}

test("recv Error") { f =>
test("take published local commit tx into account") { f =>
import f._
val (ra1, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)

// We add 3 htlcs Alice -> Bob (one of them below dust) and 2 htlcs Bob -> Alice
val (_, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)
val (ra2, htlca2) = addHtlc(100000000 msat, alice, bob, alice2bob, bob2alice)
val (ra3, htlca3) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice)
val (_, htlca3) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice)
val (rb1, htlcb1) = addHtlc(50000000 msat, bob, alice, bob2alice, alice2bob)
val (rb2, htlcb2) = addHtlc(55000000 msat, bob, alice, bob2alice, alice2bob)
addHtlc(55000000 msat, bob, alice, bob2alice, alice2bob)
crossSign(alice, bob, alice2bob, bob2alice)
// And fulfill one htlc in each direction without signing a new commit tx
fulfillHtlc(htlca2.id, ra2, bob, alice, bob2alice, alice2bob)
fulfillHtlc(htlcb1.id, rb1, alice, bob, alice2bob, bob2alice)

// at this point here is the situation from alice pov and what she should do when she publishes his commit tx:
// balances :
// alice's balance : 449 999 990 => nothing to do
// bob's balance : 95 000 000 => nothing to do
// htlcs :
// alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend using 2nd stage htlc-timeout
// alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend using 2nd stage htlc-timeout
// alice -> bob : 10 (dust) => won't appear in the commitment tx
// bob -> alice : 50 000 000 (alice has the preimage) => spend immediately using the preimage using htlc-success
// bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout

// an error occurs and alice publishes her commit tx
// alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
alice ! Error(ByteVector32.Zeroes, "oops")
assert(alice2blockchain.expectMsgType[PublishRawTx].tx === aliceCommitTx)
assert(alice2blockchain.expectMsgType[PublishRawTx].tx.txid === aliceCommitTx.txid)
assert(aliceCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
awaitCond(alice.stateName == CLOSING)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined)
val commitments = alice.stateData.asInstanceOf[DATA_CLOSING].commitments
val localCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get
assert(localCommitPublished.commitTx == aliceCommitTx)
assert(localCommitPublished.htlcTxs.size === 4)
assert(getHtlcSuccessTxs(localCommitPublished).length === 1)
assert(getHtlcTimeoutTxs(localCommitPublished).length === 2)
assert(localCommitPublished.claimHtlcDelayedTxs.isEmpty)

val knownPreimages = Set((commitments.channelId, htlcb1.id))
assert(CheckBalance.computeLocalCloseBalance(commitments, LocalClose(commitments.localCommit, localCommitPublished), knownPreimages) ===
PossiblyPublishedMainAndHtlcBalance(
Expand All @@ -208,38 +156,27 @@ class CheckBalanceSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
htlcsUnpublished = htlca1.amountMsat.truncateToSatoshi + htlca3.amountMsat.truncateToSatoshi + htlcb1.amountMsat.truncateToSatoshi
))

// alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the htlc
// so we expect 4 transactions:
// - 1 tx to claim the main delayed output
// - 3 txs for each htlc
// NB: 3rd-stage txs will only be published once the htlc txs confirm
val claimMain = alice2blockchain.expectMsgType[PublishRawTx].tx
alice2blockchain.expectMsgType[PublishRawTx] // claim-main
val htlcTx1 = alice2blockchain.expectMsgType[PublishRawTx].tx
val htlcTx2 = alice2blockchain.expectMsgType[PublishRawTx].tx
val htlcTx3 = alice2blockchain.expectMsgType[PublishRawTx].tx
// the main delayed output and htlc txs spend the commitment transaction
Seq(claimMain, htlcTx1, htlcTx2, htlcTx3).foreach(tx => Transaction.correctlySpends(tx, aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))

assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === aliceCommitTx.txid)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === claimMain.txid) // main-delayed
alice2blockchain.expectMsgType[WatchTxConfirmed] // commit tx
alice2blockchain.expectMsgType[WatchTxConfirmed] // main-delayed
alice2blockchain.expectMsgType[WatchOutputSpent] // htlc 1
alice2blockchain.expectMsgType[WatchOutputSpent] // htlc 2
alice2blockchain.expectMsgType[WatchOutputSpent] // htlc 3
alice2blockchain.expectMsgType[WatchOutputSpent] // htlc 4
alice2blockchain.expectNoMsg(1 second)

// 3rd-stage txs are published when htlc txs confirm
val claimHtlcDelayedTxs = Seq(htlcTx1, htlcTx2, htlcTx3).map { htlcTimeoutTx =>
alice ! WatchOutputSpentTriggered(htlcTimeoutTx)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === htlcTimeoutTx.txid)
alice ! WatchTxConfirmedTriggered(2701, 3, htlcTimeoutTx)
val claimHtlcDelayedTx = alice2blockchain.expectMsgType[PublishRawTx].tx
Transaction.correctlySpends(claimHtlcDelayedTx, htlcTimeoutTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId === claimHtlcDelayedTx.txid)
claimHtlcDelayedTx
}
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get.claimHtlcDelayedTxs.length == 3)
alice2blockchain.expectNoMessage(1 second)

assert(CheckBalance.computeLocalCloseBalance(commitments, LocalClose(commitments.localCommit, alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get), knownPreimages) ===
PossiblyPublishedMainAndHtlcBalance(
Expand Down

0 comments on commit 51b0190

Please sign in to comment.