test: add integration tests for forced channel closure

This commit adds a basic test to exercise the scenario of forced
channel closure between to nodes with an existing channel open. The
test ensures that the latest commitment transaction it broadcast to the
chain and additionally that all time-locked outputs are sweeped with a
single transaction as soon as the inputs are mature enough.
This commit is contained in:
Olaoluwa Osuntokun 2016-09-13 19:00:47 -07:00
parent d23bf8b633
commit 4c01e42670
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
1 changed files with 128 additions and 7 deletions

View File

@ -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