diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 46aab0d0..f4e0d96a 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -842,8 +842,9 @@ func (c *ChannelArbitrator) stateStep( // With the close transaction in hand, broadcast the // transaction to the network, thereby entering the post // channel resolution state. - log.Infof("Broadcasting force close transaction, "+ - "ChannelPoint(%v): %v", c.cfg.ChanPoint, + log.Infof("Broadcasting force close transaction %v, "+ + "ChannelPoint(%v): %v", closeTx.TxHash(), + c.cfg.ChanPoint, newLogClosure(func() string { return spew.Sdump(closeTx) })) diff --git a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go index 37634a28..16ac3987 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_chain_claim_test.go @@ -96,11 +96,18 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, // At this point, Bob decides that he wants to exit the channel // immediately, so he force closes his commitment transaction. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - bobForceClose := closeChannelAndAssert(ctxt, t, net, bob, - aliceChanPoint, true) + bobForceClose := closeChannelAndAssertType(ctxt, t, net, bob, + aliceChanPoint, c == commitTypeAnchors, true) - // Alice will sweep her output immediately. - _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + // Alice will sweep her commitment output immediately. If there are + // anchors, Alice will also sweep hers. + expectedTxes := 1 + if c == commitTypeAnchors { + expectedTxes = 2 + } + _, err = waitForNTxsInMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { t.Fatalf("unable to find alice's sweep tx in miner mempool: %v", err) @@ -135,8 +142,11 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, t.Fatalf("unable to generate blocks") } - // Carol's commitment transaction should now be in the mempool. - txids, err := waitForNTxsInMempool(net.Miner.Node, 1, minerMempoolTimeout) + // Carol's commitment transaction should now be in the mempool. If there + // is an anchor, Carol will sweep that too. + _, err = waitForNTxsInMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { t.Fatalf("transactions not found in mempool: %v", err) } @@ -149,53 +159,47 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, Index: bobChanPoint.OutputIndex, } - // The tx should be spending from the funding transaction, - commitHash := txids[0] - tx1, err := net.Miner.Node.GetRawTransaction(commitHash) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - if tx1.MsgTx().TxIn[0].PreviousOutPoint != carolFundingPoint { - t.Fatalf("commit transaction not spending fundingtx: %v", - spew.Sdump(tx1)) - } + // Look up the closing transaction. It should be spending from the + // funding transaction, + closingTx := getSpendingTxInMempool( + t, net.Miner.Node, minerMempoolTimeout, carolFundingPoint, + ) + closingTxid := closingTx.TxHash() - // Mine a block that should confirm the commit tx. - block := mineBlocks(t, net, 1, 1)[0] - if len(block.Transactions) != 2 { - t.Fatalf("expected 2 transactions in block, got %v", - len(block.Transactions)) + // Mine a block that should confirm the commit tx, the anchor if present + // and the coinbase. + block := mineBlocks(t, net, 1, expectedTxes)[0] + if len(block.Transactions) != expectedTxes+1 { + t.Fatalf("expected %v transactions in block, got %v", + expectedTxes+1, len(block.Transactions)) } - assertTxInBlock(t, block, commitHash) + assertTxInBlock(t, block, &closingTxid) // Restart bob again. if err := restartBob(); err != nil { t.Fatalf("unable to restart bob: %v", err) } - // After the force close transacion is mined, Carol should broadcast - // her second level HTLC transacion. Bob will broadcast a sweep tx to - // sweep his output in the channel with Carol. He can do this - // immediately, as the output is not timelocked since Carol was the one - // force closing. - commitSpends, err := waitForNTxsInMempool(net.Miner.Node, 2, - minerMempoolTimeout) + // After the force close transacion is mined, Carol should broadcast her + // second level HTLC transacion. Bob will broadcast a sweep tx to sweep + // his output in the channel with Carol. He can do this immediately, as + // the output is not timelocked since Carol was the one force closing. + // If there are anchors on the commitment, Bob will also sweep his + // anchor. + expectedTxes = 2 + if c == commitTypeAnchors { + expectedTxes = 3 + } + txes, err := getNTxsFromMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { t.Fatalf("transactions not found in mempool: %v", err) } // Both Carol's second level transaction and Bob's sweep should be // spending from the commitment transaction. - for _, txid := range commitSpends { - tx, err := net.Miner.Node.GetRawTransaction(txid) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - - if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("tx did not spend from commitment tx") - } - } + assertAllTxesSpendFrom(t, txes, closingTxid) // At this point we suspend Alice to make sure she'll handle the // on-chain settle after a restart. @@ -205,14 +209,11 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest, } // Mine a block to confirm the two transactions (+ the coinbase). - block = mineBlocks(t, net, 1, 2)[0] - if len(block.Transactions) != 3 { + block = mineBlocks(t, net, 1, expectedTxes)[0] + if len(block.Transactions) != expectedTxes+1 { t.Fatalf("expected 3 transactions in block, got %v", len(block.Transactions)) } - for _, txid := range commitSpends { - assertTxInBlock(t, block, txid) - } // Keep track of the second level tx maturity. carolSecondLevelCSV := uint32(defaultCSV) diff --git a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go index 98b66bda..1a7feed4 100644 --- a/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_local_timeout_test.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" @@ -106,24 +107,33 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, t.Fatalf("unable to generate blocks: %v", err) } - // Bob's force close transaction should now be found in the mempool. + // Bob's force close transaction should now be found in the mempool. If + // there are anchors, we also expect Bob's anchor sweep. + expectedTxes := 1 + if c == commitTypeAnchors { + expectedTxes = 2 + } + bobFundingTxid, err := lnd.GetChanPointFundingTxid(bobChanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } - closeTxid, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + _, err = waitForNTxsInMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { t.Fatalf("unable to find closing txid: %v", err) } - assertSpendingTxInMempool( + closeTx := getSpendingTxInMempool( t, net.Miner.Node, minerMempoolTimeout, wire.OutPoint{ Hash: *bobFundingTxid, Index: bobChanPoint.OutputIndex, }, ) + closeTxid := closeTx.TxHash() // Mine a block to confirm the closing transaction. - mineBlocks(t, net, 1, 1) + mineBlocks(t, net, 1, expectedTxes) // At this point, Bob should have canceled backwards the dust HTLC // that we sent earlier. This means Alice should now only have a single @@ -143,20 +153,42 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, // With the closing transaction confirmed, we should expect Bob's HTLC // timeout transaction to be broadcast due to the expiry being reached. - htlcTimeout, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + // If there are anchors, we also expect Carol's anchor sweep now. + txes, err := getNTxsFromMempool(net.Miner.Node, expectedTxes, minerMempoolTimeout) if err != nil { t.Fatalf("unable to find bob's htlc timeout tx: %v", err) } + // Lookup the timeout transaction that is expected to spend from the + // closing tx. We distinguish it from a possibly anchor sweep by value. + var htlcTimeout *chainhash.Hash + for _, tx := range txes { + prevOp := tx.TxIn[0].PreviousOutPoint + if prevOp.Hash != closeTxid { + t.Fatalf("tx not spending from closing tx") + } + + // Assume that the timeout tx doesn't spend an output of exactly + // the size of the anchor. + if closeTx.TxOut[prevOp.Index].Value != anchorSize { + hash := tx.TxHash() + htlcTimeout = &hash + } + } + if htlcTimeout == nil { + t.Fatalf("htlc timeout tx not found in mempool") + } + // We'll mine the remaining blocks in order to generate the sweep // transaction of Bob's commitment output. - mineBlocks(t, net, defaultCSV, 1) - assertSpendingTxInMempool( - t, net.Miner.Node, minerMempoolTimeout, wire.OutPoint{ - Hash: *closeTxid, - Index: 1, - }, - ) + mineBlocks(t, net, defaultCSV, expectedTxes) + + // Check that the sweep spends from the mined commitment. + txes, err = getNTxsFromMempool(net.Miner.Node, 1, minerMempoolTimeout) + if err != nil { + t.Fatalf("sweep not found: %v", err) + } + assertAllTxesSpendFrom(t, txes, closeTxid) // Bob's pending channel report should show that he has a commitment // output awaiting sweeping, and also that there's an outgoing HTLC @@ -248,6 +280,10 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest, t.Fatalf(predErr.Error()) } + // Coop close channel, expect no anchors. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, alice, aliceChanPoint, false) + closeChannelAndAssertType( + ctxt, t, net, alice, aliceChanPoint, false, + false, + ) } diff --git a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go index e11b2ed7..d7335311 100644 --- a/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_receiver_chain_claim_test.go @@ -7,7 +7,6 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd" @@ -126,8 +125,15 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, } // At this point, Carol should broadcast her active commitment - // transaction in order to go to the chain and sweep her HTLC. - txids, err := waitForNTxsInMempool(net.Miner.Node, 1, minerMempoolTimeout) + // transaction in order to go to the chain and sweep her HTLC. If there + // are anchors, Carol also sweeps hers. + expectedTxes := 1 + if c == commitTypeAnchors { + expectedTxes = 2 + } + txes, err := getNTxsFromMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { t.Fatalf("expected transaction not found in mempool: %v", err) } @@ -144,20 +150,13 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, // The commitment transaction should be spending from the funding // transaction. - commitHash := txids[0] - tx, err := net.Miner.Node.GetRawTransaction(commitHash) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - commitTx := tx.MsgTx() - - if commitTx.TxIn[0].PreviousOutPoint != carolFundingPoint { - t.Fatalf("commit transaction not spending from expected "+ - "outpoint: %v", spew.Sdump(commitTx)) - } + closingTx := getSpendingTxInMempool( + t, net.Miner.Node, minerMempoolTimeout, carolFundingPoint, + ) + closingTxid := closingTx.TxHash() // Confirm the commitment. - mineBlocks(t, net, 1, 1) + mineBlocks(t, net, 1, expectedTxes) // Restart bob again. if err := restartBob(); err != nil { @@ -167,30 +166,21 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, // After the force close transaction is mined, Carol should broadcast // her second level HTLC transaction. Bob will broadcast a sweep tx to // sweep his output in the channel with Carol. When Bob notices Carol's - // second level transaction in the mempool, he will extract the - // preimage and settle the HTLC back off-chain. - secondLevelHashes, err := waitForNTxsInMempool(net.Miner.Node, 2, - minerMempoolTimeout) + // second level transaction in the mempool, he will extract the preimage + // and settle the HTLC back off-chain. Bob will also sweep his anchor, + // if present. + expectedTxes = 2 + if c == commitTypeAnchors { + expectedTxes = 3 + } + txes, err = getNTxsFromMempool(net.Miner.Node, + expectedTxes, minerMempoolTimeout) if err != nil { t.Fatalf("transactions not found in mempool: %v", err) } - // Carol's second level transaction should be spending from - // the commitment transaction. - var secondLevelHash *chainhash.Hash - for _, txid := range secondLevelHashes { - tx, err := net.Miner.Node.GetRawTransaction(txid) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - - if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash == *commitHash { - secondLevelHash = txid - } - } - if secondLevelHash == nil { - t.Fatalf("Carol's second level tx not found") - } + // All transactions should be spending from the commitment transaction. + assertAllTxesSpendFrom(t, txes, closingTxid) // We'll now mine an additional block which should confirm both the // second layer transactions. @@ -314,5 +304,8 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest, // We'll close out the channel between Alice and Bob, then shutdown // carol to conclude the test. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, alice, aliceChanPoint, false) + closeChannelAndAssertType( + ctxt, t, net, alice, aliceChanPoint, + false, false, + ) } diff --git a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go index 257b7b97..c73a066b 100644 --- a/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go +++ b/lntest/itest/lnd_multi-hop_htlc_remote_chain_claim_test.go @@ -97,8 +97,8 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest // immediately force close the channel by broadcast her commitment // transaction. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - aliceForceClose := closeChannelAndAssert(ctxt, t, net, alice, - aliceChanPoint, true) + aliceForceClose := closeChannelAndAssertType(ctxt, t, net, alice, + aliceChanPoint, c == commitTypeAnchors, true) // Wait for the channel to be marked pending force close. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) @@ -114,8 +114,13 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest t.Fatalf("unable to generate blocks: %v", err) } - // Alice should now sweep her funds. - _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + // Alice should now sweep her funds. If there are anchors, Alice should + // also sweep hers. + expectedTxes := 1 + if c == commitTypeAnchors { + expectedTxes = 2 + } + _, err = waitForNTxsInMempool(net.Miner.Node, expectedTxes, minerMempoolTimeout) if err != nil { t.Fatalf("unable to find sweeping tx in mempool: %v", err) } @@ -149,10 +154,13 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest t.Fatalf("unable to generate blocks") } - // Carol's commitment transaction should now be in the mempool. - txids, err := waitForNTxsInMempool(net.Miner.Node, 1, minerMempoolTimeout) + // Carol's commitment transaction should now be in the mempool. If there + // are anchors, Carol also sweeps her anchor. + _, err = waitForNTxsInMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { - t.Fatalf("transactions not found in mempool: %v", err) + t.Fatalf("unable to find carol's txes: %v", err) } bobFundingTxid, err := lnd.GetChanPointFundingTxid(bobChanPoint) if err != nil { @@ -163,63 +171,51 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest Index: bobChanPoint.OutputIndex, } - // The transaction should be spending from the funding transaction - commitHash := txids[0] - tx1, err := net.Miner.Node.GetRawTransaction(commitHash) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - if tx1.MsgTx().TxIn[0].PreviousOutPoint != carolFundingPoint { - t.Fatalf("commit transaction not spending fundingtx: %v", - spew.Sdump(tx1)) - } + // The closing transaction should be spending from the funding + // transaction. + closingTx := getSpendingTxInMempool( + t, net.Miner.Node, minerMempoolTimeout, carolFundingPoint, + ) + closingTxid := closingTx.TxHash() - // Mine a block, which should contain the commitment. - block := mineBlocks(t, net, 1, 1)[0] - if len(block.Transactions) != 2 { - t.Fatalf("expected 2 transactions in block, got %v", - len(block.Transactions)) + // Mine a block, which should contain: the commitment, possibly an + // anchor sweep and the coinbase tx. + block := mineBlocks(t, net, 1, expectedTxes)[0] + if len(block.Transactions) != expectedTxes+1 { + t.Fatalf("expected %v transactions in block, got %v", + expectedTxes, len(block.Transactions)) } - assertTxInBlock(t, block, commitHash) + assertTxInBlock(t, block, &closingTxid) // Restart bob again. if err := restartBob(); err != nil { t.Fatalf("unable to restart bob: %v", err) } - // After the force close transacion is mined, Carol should broadcast - // her second level HTLC transacion. Bob will broadcast a sweep tx to - // sweep his output in the channel with Carol. He can do this - // immediately, as the output is not timelocked since Carol was the one - // force closing. - commitSpends, err := waitForNTxsInMempool(net.Miner.Node, 2, + // After the force close transacion is mined, Carol should broadcast her + // second level HTLC transacion. Bob will broadcast a sweep tx to sweep + // his output in the channel with Carol. He can do this immediately, as + // the output is not timelocked since Carol was the one force closing. + // If there are anchors, Bob should also sweep his. + expectedTxes = 2 + if c == commitTypeAnchors { + expectedTxes = 3 + } + txes, err := getNTxsFromMempool(net.Miner.Node, expectedTxes, minerMempoolTimeout) if err != nil { t.Fatalf("transactions not found in mempool: %v", err) } - // Both Carol's second level transaction and Bob's sweep should be - // spending from the commitment transaction. - for _, txid := range commitSpends { - tx, err := net.Miner.Node.GetRawTransaction(txid) - if err != nil { - t.Fatalf("unable to get txn: %v", err) - } - - if tx.MsgTx().TxIn[0].PreviousOutPoint.Hash != *commitHash { - t.Fatalf("tx did not spend from commitment tx") - } - } + // All transactions should be pending from the commitment transaction. + assertAllTxesSpendFrom(t, txes, closingTxid) // Mine a block to confirm the two transactions (+ coinbase). - block = mineBlocks(t, net, 1, 2)[0] - if len(block.Transactions) != 3 { + block = mineBlocks(t, net, 1, expectedTxes)[0] + if len(block.Transactions) != expectedTxes+1 { t.Fatalf("expected 3 transactions in block, got %v", len(block.Transactions)) } - for _, txid := range commitSpends { - assertTxInBlock(t, block, txid) - } // Keep track of the second level tx maturity. carolSecondLevelCSV := uint32(defaultCSV) diff --git a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go index b60cfda4..d51d2c3d 100644 --- a/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_local_force_close_on_chain_htlc_timeout_test.go @@ -82,7 +82,9 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // force close the Bob -> Carol channel. This should trigger contract // resolution mode for both of them. ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, bob, bobChanPoint, true) + closeChannelAndAssertType( + ctxt, t, net, bob, bobChanPoint, c == commitTypeAnchors, true, + ) // At this point, Bob should have a pending force close channel as he // just went to chain. @@ -116,8 +118,16 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, t.Fatalf(predErr.Error()) } - // We'll mine defaultCSV blocks in order to generate the sweep transaction - // of Bob's funding output. + // We'll mine defaultCSV blocks in order to generate the sweep + // transaction of Bob's funding output. If there are anchors, mine + // Carol's anchor sweep too. + if c == commitTypeAnchors { + _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + if err != nil { + t.Fatalf("unable to find carol's anchor sweep tx: %v", err) + } + } + if _, err := net.Miner.Node.Generate(defaultCSV); err != nil { t.Fatalf("unable to generate blocks: %v", err) } @@ -275,6 +285,9 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, t.Fatalf(predErr.Error()) } + // Coop close, no anchors. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, alice, aliceChanPoint, false) + closeChannelAndAssertType( + ctxt, t, net, alice, aliceChanPoint, false, false, + ) } diff --git a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go b/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go index 2f70fe3c..ebff9187 100644 --- a/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go +++ b/lntest/itest/lnd_multi-hop_remote_force_close_on_chain_htlc_timeout_test.go @@ -81,9 +81,13 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // At this point, we'll now instruct Carol to force close the // transaction. This will let us exercise that Bob is able to sweep the - // expired HTLC on Carol's version of the commitment transaction. + // expired HTLC on Carol's version of the commitment transaction. If + // Carol has an anchor, it will be swept too. ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, bobChanPoint, true) + closeChannelAndAssertType( + ctxt, t, net, carol, bobChanPoint, c == commitTypeAnchors, + true, + ) // At this point, Bob should have a pending force close channel as // Carol has gone directly to chain. @@ -110,11 +114,18 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, t.Fatalf(predErr.Error()) } - // Bob can sweep his output immediately. - _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + // Bob can sweep his output immediately. If there is an anchor, Bob will + // sweep that as well. + expectedTxes := 1 + if c == commitTypeAnchors { + expectedTxes = 2 + } + + _, err = waitForNTxsInMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { - t.Fatalf("unable to find bob's funding output sweep tx: %v", - err) + t.Fatalf("failed to find txes in miner mempool: %v", err) } // Next, we'll mine enough blocks for the HTLC to expire. At this @@ -232,7 +243,10 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness, // We'll close out the test by closing the channel from Alice to Bob, // and then shutting down the new node we created as its no longer - // needed. + // needed. Coop close, no anchors. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, alice, aliceChanPoint, false) + closeChannelAndAssertType( + ctxt, t, net, alice, aliceChanPoint, false, + false, + ) } diff --git a/lntest/itest/lnd_multi-hop_test.go b/lntest/itest/lnd_multi-hop_test.go index 2d4a9e5f..ec73e187 100644 --- a/lntest/itest/lnd_multi-hop_test.go +++ b/lntest/itest/lnd_multi-hop_test.go @@ -7,6 +7,8 @@ import ( "fmt" "testing" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -65,6 +67,7 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) { commitTypes := []commitType{ commitTypeLegacy, + commitTypeAnchors, } for _, commitType := range commitTypes { @@ -204,16 +207,19 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, t.Fatalf("unable to connect peers: %v", err) } - ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, alice) - if err != nil { - t.Fatalf("unable to send coins to Alice: %v", err) - } + // Make sure there are enough utxos for anchoring. + for i := 0; i < 2; i++ { + ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, alice) + if err != nil { + t.Fatalf("unable to send coins to Alice: %v", err) + } - ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, bob) - if err != nil { - t.Fatalf("unable to send coins to Bob: %v", err) + ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, bob) + if err != nil { + t.Fatalf("unable to send coins to Bob: %v", err) + } } // We'll start the test by creating a channel between Alice and Bob, @@ -255,6 +261,18 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, t.Fatalf("unable to connect bob to carol: %v", err) } + // Make sure Carol has enough utxos for anchoring. Because the anchor by + // itself often doesn't meet the dust limit, a utxo from the wallet + // needs to be attached as an additional input. This can still lead to a + // positively-yielding transaction. + for i := 0; i < 2; i++ { + ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to Alice: %v", err) + } + } + // We'll then create a channel from Bob to Carol. After this channel is // open, our topology looks like: A -> B -> C. ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) @@ -282,3 +300,16 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness, return aliceChanPoint, bobChanPoint, carol } + +// assertAllTxesSpendFrom asserts that all txes in the list spend from the given +// tx. +func assertAllTxesSpendFrom(t *harnessTest, txes []*wire.MsgTx, + prevTxid chainhash.Hash) { + + for _, tx := range txes { + if tx.TxIn[0].PreviousOutPoint.Hash != prevTxid { + t.Fatalf("tx %v did not spend from %v", + tx.TxHash(), prevTxid) + } + } +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index cefc0aca..61645517 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -43,6 +43,7 @@ import ( "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" ) @@ -59,6 +60,7 @@ const ( channelOpenTimeout = lntest.ChannelOpenTimeout channelCloseTimeout = lntest.ChannelCloseTimeout itestLndBinary = "../../lnd-itest" + anchorSize = 330 ) // harnessTest wraps a regular testing.T providing enhanced error detection @@ -266,6 +268,13 @@ func closeChannelAndAssert(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, node *lntest.HarnessNode, fundingChanPoint *lnrpc.ChannelPoint, force bool) *chainhash.Hash { + return closeChannelAndAssertType(ctx, t, net, node, fundingChanPoint, false, force) +} + +func closeChannelAndAssertType(ctx context.Context, t *harnessTest, + net *lntest.NetworkHarness, node *lntest.HarnessNode, + fundingChanPoint *lnrpc.ChannelPoint, anchors, force bool) *chainhash.Hash { + // Fetch the current channel policy. If the channel is currently // enabled, we will register for graph notifications before closing to // assert that the node sends out a disabling update as a result of the @@ -299,7 +308,9 @@ func closeChannelAndAssert(ctx context.Context, t *harnessTest, ) } - return assertChannelClosed(ctx, t, net, node, fundingChanPoint, closeUpdates) + return assertChannelClosed( + ctx, t, net, node, fundingChanPoint, anchors, closeUpdates, + ) } // closeReorgedChannelAndAssert attempts to close a channel identified by the @@ -320,14 +331,16 @@ func closeReorgedChannelAndAssert(ctx context.Context, t *harnessTest, t.Fatalf("unable to close channel: %v", err) } - return assertChannelClosed(ctx, t, net, node, fundingChanPoint, closeUpdates) + return assertChannelClosed( + ctx, t, net, node, fundingChanPoint, false, closeUpdates, + ) } // assertChannelClosed asserts that the channel is properly cleaned up after // initiating a cooperative or local close. func assertChannelClosed(ctx context.Context, t *harnessTest, net *lntest.NetworkHarness, node *lntest.HarnessNode, - fundingChanPoint *lnrpc.ChannelPoint, + fundingChanPoint *lnrpc.ChannelPoint, anchors bool, closeUpdates lnrpc.Lightning_CloseChannelClient) *chainhash.Hash { txid, err := lnd.GetChanPointFundingTxid(fundingChanPoint) @@ -379,8 +392,13 @@ func assertChannelClosed(ctx context.Context, t *harnessTest, // We'll now, generate a single block, wait for the final close status // update, then ensure that the closing transaction was included in the - // block. - block := mineBlocks(t, net, 1, 1)[0] + // block. If there are anchors, we also expect an anchor sweep. + expectedTxes := 1 + if anchors { + expectedTxes = 2 + } + + block := mineBlocks(t, net, 1, expectedTxes)[0] closingTxid, err := net.WaitForChannelClose(ctx, closeUpdates) if err != nil { @@ -600,22 +618,6 @@ func shutdownAndAssert(net *lntest.NetworkHarness, t *harnessTest, } } -// calcStaticFee calculates appropriate fees for commitment transactions. This -// function provides a simple way to allow test balance assertions to take fee -// calculations into account. -// -// TODO(bvu): Refactor when dynamic fee estimation is added. -// TODO(conner) remove code duplication -func calcStaticFee(numHTLCs int) btcutil.Amount { - const ( - commitWeight = btcutil.Amount(724) - htlcWeight = 172 - feePerKw = btcutil.Amount(50 * 1000 / 4) - ) - return feePerKw * (commitWeight + - btcutil.Amount(htlcWeight*numHTLCs)) / 1000 -} - // completePaymentRequests sends payments from a lightning node to complete all // payment requests. If the awaitResponse parameter is true, this function // does not return until all payments successfully complete without errors. @@ -998,6 +1000,66 @@ func (c commitType) Args() []string { return nil } +// calcStaticFee calculates appropriate fees for commitment transactions. This +// function provides a simple way to allow test balance assertions to take fee +// calculations into account. +func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount { + const htlcWeight = input.HTLCWeight + var ( + feePerKw = chainfee.SatPerKVByte(50000).FeePerKWeight() + commitWeight = input.CommitWeight + anchors = btcutil.Amount(0) + ) + + // The anchor commitment type is slightly heavier, and we must also add + // the value of the two anchors to the resulting fee the initiator + // pays. + if c == commitTypeAnchors { + commitWeight = input.AnchorCommitWeight + anchors = 2 * anchorSize + } + + return feePerKw.FeeForWeight(int64(commitWeight+htlcWeight*numHTLCs)) + + anchors +} + +// channelCommitType retrieves the active channel commitment type for the given +// chan point. +func channelCommitType(node *lntest.HarnessNode, + chanPoint *lnrpc.ChannelPoint) (commitType, error) { + + ctxb := context.Background() + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + + req := &lnrpc.ListChannelsRequest{} + channels, err := node.ListChannels(ctxt, req) + if err != nil { + return 0, fmt.Errorf("listchannels failed: %v", err) + } + + for _, c := range channels.Channels { + if c.ChannelPoint == txStr(chanPoint) { + switch c.CommitmentType { + + // If the anchor output size is non-zero, we are + // dealing with the anchor type. + case lnrpc.CommitmentType_ANCHORS: + return commitTypeAnchors, nil + + // StaticRemoteKey means it is tweakless, + case lnrpc.CommitmentType_STATIC_REMOTE_KEY: + return commitTypeTweakless, nil + + // Otherwise legacy. + default: + return commitTypeLegacy, nil + } + } + } + + return 0, fmt.Errorf("channel point %v not found", chanPoint) +} + // basicChannelFundingTest is a sub-test of the main testBasicChannelFunding // test. Given two nodes: Alice and Bob, it'll assert proper channel creation, // then return a function closure that should be called to assert proper @@ -1038,6 +1100,12 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness, "channel: %v", err) } + cType, err := channelCommitType(alice, chanPoint) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to get channel "+ + "type: %v", err) + } + // With the channel open, ensure that the amount specified above has // properly been pushed to Bob. balReq := &lnrpc.ChannelBalanceRequest{} @@ -1054,7 +1122,7 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness, "balance: %v", err) } - expBalanceAlice := chanAmt - pushAmt - calcStaticFee(0) + expBalanceAlice := chanAmt - pushAmt - cType.calcStaticFee(0) aliceBalance := btcutil.Amount(aliceBal.Balance) if aliceBalance != expBalanceAlice { return nil, nil, nil, fmt.Errorf("alice's balance is "+ @@ -1104,6 +1172,7 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { allTypes := []commitType{ commitTypeLegacy, commitTypeTweakless, + commitTypeAnchors, } test: @@ -1156,27 +1225,58 @@ test: t.Fatalf("failed funding flow: %v", err) } - carolTweakless := carolCommitType == commitTypeTweakless + // Both nodes should report the same commitment + // type. + chansCommitType := carolChannel.CommitmentType + if daveChannel.CommitmentType != chansCommitType { + t.Fatalf("commit types don't match, "+ + "carol got %v, dave got %v", + carolChannel.CommitmentType, + daveChannel.CommitmentType, + ) + } - daveTweakless := daveCommitType == commitTypeTweakless + // Now check that the commitment type reported + // by both nodes is what we expect. It will be + // the minimum of the two nodes' preference, in + // the order Legacy, Tweakless, Anchors. + expType := carolCommitType - tweaklessSignalled := carolTweakless && daveTweakless - tweaklessChans := (carolChannel.StaticRemoteKey && - daveChannel.StaticRemoteKey) + switch daveCommitType { + + // Dave supports anchors, type will be what + // Carol supports. + case commitTypeAnchors: + + // Dave only supports tweakless, channel will + // be downgraded to this type if Carol supports + // anchors. + case commitTypeTweakless: + if expType == commitTypeAnchors { + expType = commitTypeTweakless + } + + // Dave only supoprts legacy type, channel will + // be downgraded to this type. + case commitTypeLegacy: + expType = commitTypeLegacy + + default: + t.Fatalf("invalid commit type %v", + daveCommitType) + } + + // Check that the signalled type matches what we + // expect. switch { - // If both sides signalled a tweakless channel, and the - // resulting channel doesn't reflect this, then this - // is a failed case. - case tweaklessSignalled && !tweaklessChans: - t.Fatalf("expected tweakless channnel, got " + - "non-tweaked channel") + case expType == commitTypeAnchors && chansCommitType == lnrpc.CommitmentType_ANCHORS: + case expType == commitTypeTweakless && chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY: + case expType == commitTypeLegacy && chansCommitType == lnrpc.CommitmentType_LEGACY: - // If both sides didn't signal a tweakless - // channel, and the resulting channel is - // tweakless, and this is also a failed case. - case !tweaklessSignalled && tweaklessChans: - t.Fatalf("expected non-tweaked channel, got " + - "tweakless channel") + default: + t.Fatalf("expected nodes to signal "+ + "commit type %v, instead got "+ + "%v", expType, chansCommitType) } // As we've concluded this sub-test case we'll @@ -1275,6 +1375,11 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("error while waiting for channel open: %v", err) } + cType, err := channelCommitType(net.Alice, chanPoint) + if err != nil { + t.Fatalf("unable to get channel type: %v", err) + } + // With the channel open, we'll check the balances on each side of the // channel as a sanity check to ensure things worked out as intended. balReq := &lnrpc.ChannelBalanceRequest{} @@ -1288,9 +1393,9 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { if err != nil { t.Fatalf("unable to get alice's balance: %v", err) } - if carolBal.Balance != int64(chanAmt-pushAmt-calcStaticFee(0)) { + if carolBal.Balance != int64(chanAmt-pushAmt-cType.calcStaticFee(0)) { t.Fatalf("carol's balance is incorrect: expected %v got %v", - chanAmt-pushAmt-calcStaticFee(0), carolBal) + chanAmt-pushAmt-cType.calcStaticFee(0), carolBal) } if aliceBal.Balance != int64(pushAmt) { t.Fatalf("alice's balance is incorrect: expected %v got %v", @@ -2708,9 +2813,14 @@ func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) { "timeout: %v", err) } + cType, err := channelCommitType(net.Alice, chanPoint) + if err != nil { + t.Fatalf("unable to get channel type: %v", err) + } + // As this is a single funder channel, Alice's balance should be // exactly 0.5 BTC since now state transitions have taken place yet. - checkChannelBalance(net.Alice, amount-calcStaticFee(0)) + checkChannelBalance(net.Alice, amount-cType.calcStaticFee(0)) // Ensure Bob currently has no available balance within the channel. checkChannelBalance(net.Bob, 0) @@ -2987,6 +3097,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { // outputs can be swept. commitTypes := []commitType{ commitTypeLegacy, + commitTypeAnchors, } for _, channelType := range commitTypes { @@ -3026,7 +3137,19 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { err) } - channelForceClosureTest(net, ht, alice, carol) + // Also give Carol some coins to allow her to sweep her + // anchor. + err = net.SendCoins( + ctxt, btcutil.SatoshiPerBitcoin, carol, + ) + if err != nil { + t.Fatalf("unable to send coins to Alice: %v", + err) + } + + channelForceClosureTest( + net, ht, alice, carol, channelType, + ) }) if !success { return @@ -3035,7 +3158,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) { } func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, - alice, carol *lntest.HarnessNode) { + alice, carol *lntest.HarnessNode, channelType commitType) { ctxb := context.Background() @@ -3211,8 +3334,16 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, } // Mine a block which should confirm the commitment transaction - // broadcast as a result of the force closure. - _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + // broadcast as a result of the force closure. If there are anchors, we + // also expect the anchor sweep tx to be in the mempool. + expectedTxes := 1 + if channelType == commitTypeAnchors { + expectedTxes = 2 + } + + _, err = waitForNTxsInMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { t.Fatalf("failed to find commitment in miner mempool: %v", err) } @@ -3223,52 +3354,52 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, // Now that the commitment has been confirmed, the channel should be // marked as force closed. - err = wait.Predicate(func() bool { + err = wait.NoError(func() error { ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) pendingChanResp, err := alice.PendingChannels( ctxt, pendingChansRequest, ) if err != nil { - predErr = fmt.Errorf("unable to query for pending "+ + return fmt.Errorf("unable to query for pending "+ "channels: %v", err) - return false } - predErr = checkNumForceClosedChannels(pendingChanResp, 1) - if predErr != nil { - return false + err = checkNumForceClosedChannels(pendingChanResp, 1) + if err != nil { + return err } - forceClose, predErr := findForceClosedChannel( - pendingChanResp, &op, - ) - if predErr != nil { - return false + forceClose, err := findForceClosedChannel(pendingChanResp, &op) + if err != nil { + return err } // Now that the channel has been force closed, it should now // have the height and number of blocks to confirm populated. - predErr = checkCommitmentMaturity( + err = checkCommitmentMaturity( forceClose, commCsvMaturityHeight, int32(defaultCSV), ) - if predErr != nil { - return false + if err != nil { + return err } // None of our outputs have been swept, so they should all be in - // limbo. + // limbo. For anchors, we expect the anchor amount to be + // recovered. if forceClose.LimboBalance == 0 { - predErr = errors.New("all funds should still be in " + + return errors.New("all funds should still be in " + "limbo") - return false } - if forceClose.RecoveredBalance != 0 { - predErr = errors.New("no funds should yet be shown " + + expectedRecoveredBalance := int64(0) + if channelType == commitTypeAnchors { + expectedRecoveredBalance = anchorSize + } + if forceClose.RecoveredBalance != expectedRecoveredBalance { + return errors.New("no funds should yet be shown " + "as recovered") - return false } - return true + return nil }, 15*time.Second) if err != nil { t.Fatalf(predErr.Error()) @@ -3283,8 +3414,11 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, } // Carol's sweep tx should be in the mempool already, as her output is - // not timelocked. - _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) + // not timelocked. If there are anchors, we also expect Carol's anchor + // sweep now. + _, err = waitForNTxsInMempool( + net.Miner.Node, expectedTxes, minerMempoolTimeout, + ) if err != nil { t.Fatalf("failed to find Carol's sweep in miner mempool: %v", err) @@ -3346,7 +3480,11 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest, return errors.New("all funds should still be in " + "limbo") } - if forceClose.RecoveredBalance != 0 { + expectedRecoveredBalance := int64(0) + if channelType == commitTypeAnchors { + expectedRecoveredBalance = anchorSize + } + if forceClose.RecoveredBalance != expectedRecoveredBalance { return errors.New("no funds should yet be shown " + "as recovered") } @@ -6855,6 +6993,27 @@ func waitForNTxsInMempool(miner *rpcclient.Client, n int, } } +// getNTxsFromMempool polls until finding the desired number of transactions in +// the provided miner's mempool and returns the full transactions to the caller. +func getNTxsFromMempool(miner *rpcclient.Client, n int, + timeout time.Duration) ([]*wire.MsgTx, error) { + + txids, err := waitForNTxsInMempool(miner, n, timeout) + if err != nil { + return nil, err + } + + var txes []*wire.MsgTx + for _, txid := range txids { + tx, err := miner.GetRawTransaction(txid) + if err != nil { + return nil, err + } + txes = append(txes, tx.MsgTx()) + } + return txes, nil +} + // testFailingChannel tests that we will fail the channel by force closing ii // in the case where a counterparty tries to settle an HTLC with the wrong // preimage. @@ -9156,7 +9315,12 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("channel not seen by alice before timeout: %v", err) } - commitFee := calcStaticFee(0) + cType, err := channelCommitType(net.Alice, chanPointAlice) + if err != nil { + t.Fatalf("unable to get channel type: %v", err) + } + + commitFee := cType.calcStaticFee(0) assertBaseBalance := func() { balReq := &lnrpc.ChannelBalanceRequest{} ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) @@ -10573,7 +10737,16 @@ func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error { } func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, - timeout time.Duration, chanPoint wire.OutPoint) { + timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash { + + tx := getSpendingTxInMempool(t, miner, timeout, chanPoint) + return tx.TxHash() +} + +// getSpendingTxInMempool waits for a transaction spending the given outpoint to +// appear in the mempool and returns that tx in full. +func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, + timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx { breakTimeout := time.After(timeout) ticker := time.NewTicker(50 * time.Millisecond) @@ -10599,9 +10772,10 @@ func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, t.Fatalf("unable to fetch tx: %v", err) } - for _, txIn := range tx.MsgTx().TxIn { + msgTx := tx.MsgTx() + for _, txIn := range msgTx.TxIn { if txIn.PreviousOutPoint == chanPoint { - return + return msgTx } } } diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 229ecf3e..1e47dad2 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -138,10 +138,6 @@ func createSweepTx(inputs []input.Input, outputPkScript []byte, txFee := feePerKw.FeeForWeight(txWeight) - log.Infof("Creating sweep transaction for %v inputs (%s) "+ - "using %v sat/kw, tx_fee=%v", len(inputs), - inputTypeSummary(inputs), int64(feePerKw), txFee) - // Sum up the total value contained in the inputs. var totalSum btcutil.Amount for _, o := range inputs { @@ -211,6 +207,10 @@ func createSweepTx(inputs []input.Input, outputPkScript []byte, } } + log.Infof("Creating sweep transaction %v for %v inputs (%s) "+ + "using %v sat/kw, tx_fee=%v", sweepTx.TxHash(), len(inputs), + inputTypeSummary(inputs), int64(feePerKw), txFee) + return sweepTx, nil } @@ -254,27 +254,19 @@ func getWeightEstimate(inputs []input.Input) ([]input.Input, int64) { // inputSummary returns a string containing a human readable summary about the // witness types of a list of inputs. func inputTypeSummary(inputs []input.Input) string { - // Count each input by the string representation of its witness type. - // We also keep track of the keys so we can later sort by them to get - // a stable output. - counts := make(map[string]uint32) - keys := make([]string, 0, len(inputs)) - for _, i := range inputs { - key := i.WitnessType().String() - _, ok := counts[key] - if !ok { - counts[key] = 0 - keys = append(keys, key) - } - counts[key]++ - } - sort.Strings(keys) + // Sort inputs by witness type. + sortedInputs := make([]input.Input, len(inputs)) + copy(sortedInputs, inputs) + sort.Slice(sortedInputs, func(i, j int) bool { + return sortedInputs[i].WitnessType().String() < + sortedInputs[j].WitnessType().String() + }) - // Return a nice string representation of the counts by comma joining a - // slice. var parts []string - for _, witnessType := range keys { - part := fmt.Sprintf("%d %s", counts[witnessType], witnessType) + for _, i := range sortedInputs { + part := fmt.Sprintf("%v (%v)", + *i.OutPoint(), i.WitnessType()) + parts = append(parts, part) } return strings.Join(parts, ", ") diff --git a/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index 726347ec..91641e5e 100644 --- a/sweep/txgenerator_test.go +++ b/sweep/txgenerator_test.go @@ -3,6 +3,7 @@ package sweep import ( "testing" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" ) @@ -15,9 +16,10 @@ var ( input.WitnessKeyHash, } expectedWeight = int64(1462) - expectedSummary = "1 CommitmentTimeLock, 1 " + - "HtlcAcceptedSuccessSecondLevel, 1 HtlcOfferedRemoteTimeout, " + - "1 WitnessKeyHash" + expectedSummary = "0000000000000000000000000000000000000000000000000000000000000000:10 (CommitmentTimeLock), " + + "0000000000000000000000000000000000000000000000000000000000000001:11 (HtlcAcceptedSuccessSecondLevel), " + + "0000000000000000000000000000000000000000000000000000000000000002:12 (HtlcOfferedRemoteTimeout), " + + "0000000000000000000000000000000000000000000000000000000000000003:13 (WitnessKeyHash)" ) // TestWeightEstimate tests that the estimated weight and number of CSVs/CLTVs @@ -27,9 +29,12 @@ func TestWeightEstimate(t *testing.T) { t.Parallel() var inputs []input.Input - for _, witnessType := range witnessTypes { + for i, witnessType := range witnessTypes { inputs = append(inputs, input.NewBaseInput( - &wire.OutPoint{}, witnessType, + &wire.OutPoint{ + Hash: chainhash.Hash{byte(i)}, + Index: uint32(i) + 10, + }, witnessType, &input.SignDescriptor{}, 0, )) }