lnd: add basic integration test using the networkHarness
This commit adds an initial integration test to the project which exercises some basic channel opening/closing functionality between two lnd nodes.
This commit is contained in:
parent
2032723efc
commit
b21c8eccf0
187
lnd_test.go
Normal file
187
lnd_test.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/roasbeef/btcd/rpctest"
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
"github.com/roasbeef/btcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lndTestCase func(net *networkHarness, t *testing.T)
|
||||||
|
|
||||||
|
func assertTxInBlock(block *btcutil.Block, txid *wire.ShaHash, t *testing.T) {
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
if bytes.Equal(txid[:], tx.Sha()[:]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("funding tx was not included in block")
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBasicChannelFunding performs a test excercising expected behavior from a
|
||||||
|
// basic funding workflow. The test creates a new channel between Alice and
|
||||||
|
// Bob, then immediately closes the channel after asserting some expected post
|
||||||
|
// conditions. Finally, the chain itelf is checked to ensure the closing
|
||||||
|
// transaction was mined.
|
||||||
|
func testBasicChannelFunding(net *networkHarness, t *testing.T) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// First establish a channel between Alice and Bob.
|
||||||
|
openReq := &lnrpc.OpenChannelRequest{
|
||||||
|
// TODO(roasbeef): should pass actual id instead, will fail if
|
||||||
|
// more connections added for Alice.
|
||||||
|
TargetPeerId: 1,
|
||||||
|
LocalFundingAmount: btcutil.SatoshiPerBitcoin / 2,
|
||||||
|
RemoteFundingAmount: 0,
|
||||||
|
NumConfs: 1,
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
respStream, err := net.AliceClient.OpenChannel(ctxb, openReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to open channel between alice and bob: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mine a block, the funding txid should be included, and both nodes should
|
||||||
|
// be aware of the channel.
|
||||||
|
// TODO(roasbeef): replace sleep with something more robust
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
blockHash, err := net.Miner.Node.Generate(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate block: %v", err)
|
||||||
|
}
|
||||||
|
resp, err := respStream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to read rpc resp: %v", err)
|
||||||
|
}
|
||||||
|
block, err := net.Miner.Node.GetBlock(blockHash[0])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get block: %v", err)
|
||||||
|
}
|
||||||
|
if len(block.Transactions()) < 2 {
|
||||||
|
t.Fatalf("funding transaction not included")
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingTxID, _ := wire.NewShaHash(resp.ChannelPoint.FundingTxid)
|
||||||
|
fundingTxIDStr := fundingTxID.String()
|
||||||
|
assertTxInBlock(block, fundingTxID, t)
|
||||||
|
|
||||||
|
// TODO(roasbeef): remove and use "listchannels" command after
|
||||||
|
// implemented.
|
||||||
|
req := &lnrpc.ListPeersRequest{}
|
||||||
|
alicePeerInfo, err := net.AliceClient.ListPeers(ctxb, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to list alice peers: %v", err)
|
||||||
|
}
|
||||||
|
bobPeerInfo, err := net.BobClient.ListPeers(ctxb, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to list bob peers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The channel should be listed in the peer information returned by
|
||||||
|
// both peers.
|
||||||
|
aliceTxID := alicePeerInfo.Peers[0].Channels[0].ChannelPoint
|
||||||
|
bobTxID := bobPeerInfo.Peers[0].Channels[0].ChannelPoint
|
||||||
|
if !strings.Contains(bobTxID, fundingTxIDStr) {
|
||||||
|
t.Fatalf("alice's channel not found")
|
||||||
|
}
|
||||||
|
if !strings.Contains(aliceTxID, fundingTxIDStr) {
|
||||||
|
t.Fatalf("bob's channel not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiate a close from Alice's side. After mining a block, the closing
|
||||||
|
// transaction should be included, and both nodes should have forgotten
|
||||||
|
// about the channel.
|
||||||
|
closeReq := &lnrpc.CloseChannelRequest{
|
||||||
|
ChannelPoint: resp.ChannelPoint,
|
||||||
|
}
|
||||||
|
closeRespStream, err := net.AliceClient.CloseChannel(ctxb, closeReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to close channel: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 1)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
closeResp, err := closeRespStream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to read rpc resp: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
closingTxID, _ := wire.NewShaHash(closeResp.ClosingTxid)
|
||||||
|
assertTxInBlock(block, closingTxID, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lndTestCases = map[string]lndTestCase{
|
||||||
|
"basic funding flow": testBasicChannelFunding,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// If one of the integration tests caused a panic within the main
|
||||||
|
// goroutine, then tear down all the harnesses in order to avoid
|
||||||
|
// any leaked processes.
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Println("recovering from test panic: ", r)
|
||||||
|
if err := btcdHarness.TearDown(); err != nil {
|
||||||
|
fmt.Println("unable to tear btcd harnesses: ", err)
|
||||||
|
}
|
||||||
|
if err := lightningNetwork.TearDownAll(); err != nil {
|
||||||
|
fmt.Println("unable to tear lnd harnesses: ", err)
|
||||||
|
}
|
||||||
|
t.Fatalf("test %v panicked: %s", currentTest, debug.Stack())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// First create an intance of the btcd's rpctest.Harness. This will be
|
||||||
|
// used to fund the wallets of the nodes within the test network and to
|
||||||
|
// drive blockchain related events within the network.
|
||||||
|
btcdHarness, err = rpctest.New(harnessNetParams, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create mining node: %v", err)
|
||||||
|
}
|
||||||
|
defer btcdHarness.TearDown()
|
||||||
|
if err = btcdHarness.SetUp(true, 50); err != nil {
|
||||||
|
t.Fatalf("unable to set up mining node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the btcd harness created, create an instance of the lightning
|
||||||
|
// network harness as it depends on the btcd harness to script network
|
||||||
|
// activity.
|
||||||
|
lightningNetwork, err = newNetworkHarness(btcdHarness, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create lightning network harness: %v", err)
|
||||||
|
}
|
||||||
|
defer lightningNetwork.TearDownAll()
|
||||||
|
if err = lightningNetwork.SetUp(); err != nil {
|
||||||
|
t.Fatalf("unable to set up test lightning network: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Running %v integration tests", len(lndTestCases))
|
||||||
|
for testName, lnTest := range lndTestCases {
|
||||||
|
t.Logf("Executing test %v", testName)
|
||||||
|
|
||||||
|
currentTest = testName
|
||||||
|
lnTest(lightningNetwork, t)
|
||||||
|
}
|
||||||
|
}
|
@ -230,6 +230,9 @@ type networkHarness struct {
|
|||||||
// running instance of btcd's rpctest harness. Any extra command line flags
|
// running instance of btcd's rpctest harness. Any extra command line flags
|
||||||
// which should be passed to create lnd instance should be formatted properly
|
// which should be passed to create lnd instance should be formatted properly
|
||||||
// in the lndArgs slice (--arg=value).
|
// in the lndArgs slice (--arg=value).
|
||||||
|
// TODO(roasbeef): add option to use golang's build library to a binary of the
|
||||||
|
// current repo. This'll save developers from having to manually `go install`
|
||||||
|
// within the repo each time before changes.
|
||||||
func newNetworkHarness(r *rpctest.Harness, lndArgs []string) (*networkHarness, error) {
|
func newNetworkHarness(r *rpctest.Harness, lndArgs []string) (*networkHarness, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user