diff --git a/lnd_test.go b/lnd_test.go index 24b4d2e3..ea5fce5e 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -5,6 +5,7 @@ import ( "fmt" "runtime/debug" "testing" + "time" "golang.org/x/net/context" @@ -34,8 +35,8 @@ func assertTxInBlock(block *btcutil.Block, txid *wire.ShaHash, t *testing.T) { func testBasicChannelFunding(net *networkHarness, t *testing.T) { ctxb := context.Background() - // First establish a channel ween with a capacity of 0.5 BTC between - // Alice and Bob. + // First establish a channel with a capacity of 0.5 BTC between Alice + // and Bob. chanAmt := btcutil.Amount(btcutil.SatoshiPerBitcoin / 2) chanOpenUpdate, err := net.OpenChannel(ctxb, net.Alice, net.Bob, chanAmt, 1) if err != nil { @@ -99,17 +100,137 @@ func testBasicChannelFunding(net *networkHarness, t *testing.T) { assertTxInBlock(block, closingTxid, t) } +// testChannelForceClosure performs a test to excerise the behavior of "force" +// closing a channel or unilterally broadcating the latest local commitment +// state on-chain. The test creates a new channel between Alice and Bob, then +// force closes the channel after some cursory assertions. Within the test, two +// transactions should be broadcast on-chain, the commitment transaction itself +// (which closes the channel), and the sweep transaction a few blocks later +// once the output(s) become mature. +// +// TODO(roabeef): also add an unsettled HTLC before force closing. +func testChannelForceClosure(net *networkHarness, t *testing.T) { + ctxb := context.Background() + + // First establish a channel ween with a capacity of 100k satoshis + // between Alice and Bob. + numFundingConfs := uint32(1) + chanAmt := btcutil.Amount(10e4) + chanOpenUpdate, err := net.OpenChannel(ctxb, net.Alice, net.Bob, + chanAmt, numFundingConfs) + if err != nil { + t.Fatalf("unable to open channel: %v", err) + } + if _, err := net.Miner.Node.Generate(numFundingConfs); err != nil { + t.Fatalf("unable to mine block: %v", err) + } + chanPoint, err := net.WaitForChannelOpen(chanOpenUpdate) + if err != nil { + t.Fatalf("error while waiting for channel to open: %v", err) + } + + // Now that the channel is open, immediately execute a force closure of + // the channel. This will also assert that the commitment transaction + // was immediately broadcast in order to fufill the force closure + // request. + closeUpdate, err := net.CloseChannel(ctxb, net.Alice, chanPoint, true) + if err != nil { + t.Fatalf("unable to execute force channel closure: %v", err) + } + + // Mine a block which should confirm the commitment transaction + // broadcast as a result of the force closure. + if _, err := net.Miner.Node.Generate(1); err != nil { + t.Fatalf("unable to generate block: %v", err) + } + closingTxID, err := net.WaitForChannelClose(closeUpdate) + if err != nil { + t.Fatalf("error while waiting for channel close: %v", err) + } + + // Currently within the codebase, the default CSV is 4 relative blocks. + // So generate exactly 4 new blocks. + // TODO(roasbeef): should check default value in config here instead, + // or make delay a param + const defaultCSV = 4 + if _, err := net.Miner.Node.Generate(defaultCSV); err != nil { + t.Fatalf("unable to mine blocks: %v", err) + } + + // At this point, the sweeping transaction should now be broadcast. So + // we fetch the node's mempool to ensure it has been properly + // broadcasted. + var sweepingTXID *wire.ShaHash + var mempool []*wire.ShaHash +mempoolPoll: + for { + select { + case <-time.After(time.Second * 5): + t.Fatalf("sweep tx not found in mempool") + default: + mempool, err = net.Miner.Node.GetRawMempool() + if err != nil { + t.Fatalf("unable to fetch node's mempool: %v", err) + } + if len(mempool) == 0 { + continue + } + break mempoolPoll + } + } + + // There should be exactly one transaction within the mempool at this + // point. + // TODO(roasbeef): assertion may not necessarily hold with concurrent + // test executions + if len(mempool) != 1 { + t.Fatalf("node's mempool is wrong size, expected 1 got %v", + len(mempool)) + } + sweepingTXID = mempool[0] + + // Fetch the sweep transaction, all input it's spending should be from + // the commitment transaction which was broadcasted on-chain. + sweepTx, err := net.Miner.Node.GetRawTransaction(sweepingTXID) + if err != nil { + t.Fatalf("unable to fetch sweep tx: %v", err) + } + for _, txIn := range sweepTx.MsgTx().TxIn { + if !closingTxID.IsEqual(&txIn.PreviousOutPoint.Hash) { + t.Fatalf("sweep transaction not spending from commit "+ + "tx %v, instead spending %v", + closingTxID, txIn.PreviousOutPoint) + } + } + + // Finally, we mine an additional block which should include the sweep + // transaction as the input scripts and the sequence locks on the + // inputs should be properly met. + blockHash, err := net.Miner.Node.Generate(1) + if err != nil { + t.Fatalf("unable to generate block: %v", err) + } + block, err := net.Miner.Node.GetBlock(blockHash[0]) + if err != nil { + t.Fatalf("unable to get block: %v", err) + } + assertTxInBlock(block, sweepTx.Sha(), t) +} + var lndTestCases = map[string]lndTestCase{ - "basic funding flow": testBasicChannelFunding, + "basic funding flow": testBasicChannelFunding, + "channel force closure": testChannelForceClosure, } // TestLightningNetworkDaemon performs a series of integration tests amongst a // programatically driven network of lnd nodes. func TestLightningNetworkDaemon(t *testing.T) { - var btcdHarness *rpctest.Harness - var lightningNetwork *networkHarness - var currentTest string - var err error + var ( + btcdHarness *rpctest.Harness + lightningNetwork *networkHarness + currentTest string + err error + ) defer func() { // If one of the integration tests caused a panic within the main