diff --git a/.travis.yml b/.travis.yml index 9f42b385..61855879 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ env: matrix: - RACE=true - ITEST=true + - NEUTRINO_ITEST=true - COVER=true sudo: required @@ -28,9 +29,12 @@ script: # Run unit tests with race condition detector. - 'if [ "$RACE" = true ]; then make travis-race ; fi' - # Run integration tests. + # Run btcd integration tests. - 'if [ "$ITEST" = true ]; then make travis-itest; fi' + # Run neutrino integration tests. + - 'if [ "$NEUTRINO_ITEST" = true ]; then make travis-itest backend=neutrino; fi' + # Run unit tests and generate coverage report. - 'if [ "$COVER" = true ]; then make travis-cover; fi' diff --git a/Makefile b/Makefile index a21ab026..93d901ee 100644 --- a/Makefile +++ b/Makefile @@ -115,7 +115,7 @@ scratch: build check: unit itest itest-only: - @$(call print, "Running integration tests.") + @$(call print, "Running integration tests with ${backend} backend.") $(ITEST) itest: btcd build-itest itest-only @@ -149,7 +149,7 @@ travis-itest: lint itest # ============= flakehunter: build-itest - @$(call print, "Flake hunting integration tests.") + @$(call print, "Flake hunting ${backend} integration tests.") while [ $$? -eq 0 ]; do $(ITEST); done flake-unit: diff --git a/lnd_test.go b/lnd_test.go index bf48c1bc..99adba60 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -68,6 +68,12 @@ func newHarnessTest(t *testing.T) *harnessTest { return &harnessTest{t, nil} } +// Skipf calls the underlying testing.T's Skip method, causing the current test +// to be skipped. +func (h *harnessTest) Skipf(format string, args ...interface{}) { + h.t.Skipf(format, args...) +} + // Fatalf causes the current active test case to fail with a fatal error. All // integration tests should mark test failures solely with this method due to // the error stack traces it produces. @@ -713,7 +719,7 @@ func getChanInfo(ctx context.Context, node *lntest.HarnessNode) ( } if len(channelInfo.Channels) != 1 { return nil, fmt.Errorf("node should only have a single "+ - "channel, instead he has %v", len(channelInfo.Channels)) + "channel, instead it has %v", len(channelInfo.Channels)) } return channelInfo.Channels[0], nil @@ -972,8 +978,8 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) } -// testUnconfirmedChannelFunding tests that unconfirmed outputs that pay to us -// can be used to fund channels. +// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can +// be used to fund channels. func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() @@ -989,13 +995,34 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { } defer shutdownAndAssert(net, t, carol) - // We'll send her some funds that should not confirm. + // We'll send her some confirmed funds. ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoinsUnconfirmed(ctxt, 2*chanAmt, carol) + err = net.SendCoins(ctxt, 2*chanAmt, carol) if err != nil { t.Fatalf("unable to send coins to carol: %v", err) } + // Now let Carol send some funds to herself, making a unconfirmed + // change output. + addrReq := &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.NewAddress(ctxt, addrReq) + if err != nil { + t.Fatalf("unable to get new address: %v", err) + } + + sendReq := &lnrpc.SendCoinsRequest{ + Addr: resp.Address, + Amount: int64(chanAmt) / 5, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = carol.SendCoins(ctxt, sendReq) + if err != nil { + t.Fatalf("unable to send coins: %v", err) + } + // Make sure the unconfirmed tx is seen in the mempool. _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) if err != nil { @@ -1760,10 +1787,16 @@ func assertMinerBlockHeightDelta(t *harnessTest, // channel where the funding tx gets reorged out, the channel will no // longer be present in the node's routing table. func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { + // Skip test for neutrino, as we cannot disconnect the miner at will. + // TODO(halseth): remove when either can disconnect at will, or restart + // node with connection to new miner. + if net.BackendCfg.Name() == "neutrino" { + t.Skipf("skipping reorg test for neutrino backend") + } + var ( ctxb = context.Background() temp = "temp" - perm = "perm" ) // Set up a new miner that we can use to cause a reorg. @@ -1901,9 +1934,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { // Now we disconnect Alice's chain backend from the original miner, and // connect the two miners together. Since the temporary miner knows // about a longer chain, both miners should sync to that chain. - err = net.Miner.Node.Node( - btcjson.NRemove, net.BackendCfg.P2PAddr(), &perm, - ) + err = net.BackendCfg.DisconnectMiner() if err != nil { t.Fatalf("unable to remove node: %v", err) } @@ -1934,9 +1965,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to remove node: %v", err) } - err = net.Miner.Node.Node( - btcjson.NConnect, net.BackendCfg.P2PAddr(), &perm, - ) + err = net.BackendCfg.ConnectMiner() if err != nil { t.Fatalf("unable to remove node: %v", err) } @@ -13316,13 +13345,6 @@ var testsCases = []*testCase{ func TestLightningNetworkDaemon(t *testing.T) { ht := newHarnessTest(t) - // Start a btcd chain backend. - chainBackend, cleanUp, err := lntest.NewBtcdBackend() - if err != nil { - ht.Fatalf("unable to start btcd: %v", err) - } - defer cleanUp() - // Declare the network harness here to gain access to its // 'OnTxAccepted' call back. var lndHarness *lntest.NetworkHarness @@ -13343,7 +13365,6 @@ func TestLightningNetworkDaemon(t *testing.T) { "--debuglevel=debug", "--logdir=" + minerLogDir, "--trickleinterval=100ms", - "--connect=" + chainBackend.P2PAddr(), } handlers := &rpcclient.NotificationHandlers{ OnTxAccepted: func(hash *chainhash.Hash, amt btcutil.Amount) { @@ -13373,6 +13394,13 @@ func TestLightningNetworkDaemon(t *testing.T) { } }() + // Start a chain backend. + chainBackend, cleanUp, err := lntest.NewBackend(miner.P2PAddress()) + if err != nil { + ht.Fatalf("unable to start backend: %v", err) + } + defer cleanUp() + if err := miner.SetUp(true, 50); err != nil { ht.Fatalf("unable to set up mining node: %v", err) } diff --git a/lntest/btcd.go b/lntest/btcd.go index 48e4d71f..7187befd 100644 --- a/lntest/btcd.go +++ b/lntest/btcd.go @@ -1,3 +1,5 @@ +// +build btcd + package lntest import ( @@ -5,6 +7,7 @@ import ( "fmt" "os" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" @@ -13,14 +16,23 @@ import ( // logDir is the name of the temporary log directory. const logDir = "./.backendlogs" +// perm is used to signal we want to establish a permanent connection using the +// btcd Node API. +// +// NOTE: Cannot be const, since the node API expects a reference. +var perm = "perm" + // BtcdBackendConfig is an implementation of the BackendConfig interface // backed by a btcd node. type BtcdBackendConfig struct { // rpcConfig houses the connection config to the backing btcd instance. rpcConfig rpcclient.ConnConfig - // p2pAddress is the p2p address of the btcd instance. - p2pAddress string + // harness is the backing btcd instance. + harness *rpctest.Harness + + // minerAddr is the p2p address of the miner to connect to. + minerAddr string } // GenArgs returns the arguments needed to be passed to LND at startup for @@ -37,21 +49,32 @@ func (b BtcdBackendConfig) GenArgs() []string { return args } -// P2PAddr returns the address of this node to be used when connection over the -// Bitcoin P2P network. -func (b BtcdBackendConfig) P2PAddr() string { - return b.p2pAddress +// ConnectMiner is called to establish a connection to the test miner. +func (b BtcdBackendConfig) ConnectMiner() error { + return b.harness.Node.Node(btcjson.NConnect, b.minerAddr, &perm) } -// NewBtcdBackend starts a new rpctest.Harness and returns a BtcdBackendConfig -// for that node. -func NewBtcdBackend() (*BtcdBackendConfig, func(), error) { +// DisconnectMiner is called to disconnect the miner. +func (b BtcdBackendConfig) DisconnectMiner() error { + return b.harness.Node.Node(btcjson.NRemove, b.minerAddr, &perm) +} + +// Name returns the name of the backend type. +func (b BtcdBackendConfig) Name() string { + return "btcd" +} + +// NewBackend starts a new rpctest.Harness and returns a BtcdBackendConfig for +// that node. miner should be set to the P2P address of the miner to connect +// to. +func NewBackend(miner string) (*BtcdBackendConfig, func(), error) { args := []string{ "--rejectnonstd", "--txindex", "--trickleinterval=100ms", "--debuglevel=debug", "--logdir=" + logDir, + "--connect=" + miner, } netParams := &chaincfg.SimNetParams chainBackend, err := rpctest.New(netParams, nil, args) @@ -64,8 +87,9 @@ func NewBtcdBackend() (*BtcdBackendConfig, func(), error) { } bd := &BtcdBackendConfig{ - rpcConfig: chainBackend.RPCConfig(), - p2pAddress: chainBackend.P2PAddress(), + rpcConfig: chainBackend.RPCConfig(), + harness: chainBackend, + minerAddr: miner, } cleanUp := func() { diff --git a/lntest/harness.go b/lntest/harness.go index 4b3048e6..f2a486c8 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -1314,6 +1314,12 @@ func (n *NetworkHarness) sendCoins(ctx context.Context, amt btcutil.Amount, // Now, wait for ListUnspent to show the unconfirmed transaction // containing the correct pkscript. err = WaitNoError(func() error { + // Since neutrino doesn't support unconfirmed outputs, skip + // this check. + if target.cfg.BackendCfg.Name() == "neutrino" { + return nil + } + req := &lnrpc.ListUnspentRequest{} resp, err := target.ListUnspent(ctx, req) if err != nil { diff --git a/lntest/neutrino.go b/lntest/neutrino.go new file mode 100644 index 00000000..d127c0b5 --- /dev/null +++ b/lntest/neutrino.go @@ -0,0 +1,45 @@ +// +build neutrino + +package lntest + +import "fmt" + +// NeutrinoBackendConfig is an implementation of the BackendConfig interface +// backed by a neutrino node. +type NeutrinoBackendConfig struct { + minerAddr string +} + +// GenArgs returns the arguments needed to be passed to LND at startup for +// using this node as a chain backend. +func (b NeutrinoBackendConfig) GenArgs() []string { + var args []string + args = append(args, "--bitcoin.node=neutrino") + args = append(args, "--neutrino.connect="+b.minerAddr) + return args +} + +// ConnectMiner is called to establish a connection to the test miner. +func (b NeutrinoBackendConfig) ConnectMiner() error { + return fmt.Errorf("unimplemented") +} + +// DisconnectMiner is called to disconnect the miner. +func (b NeutrinoBackendConfig) DisconnectMiner() error { + return fmt.Errorf("unimplemented") +} + +// Name returns the name of the backend type. +func (b NeutrinoBackendConfig) Name() string { + return "neutrino" +} + +// NewBackend starts and returns a NeutrinoBackendConfig for the node. +func NewBackend(miner string) (*NeutrinoBackendConfig, func(), error) { + bd := &NeutrinoBackendConfig{ + minerAddr: miner, + } + + cleanUp := func() {} + return bd, cleanUp, nil +} diff --git a/lntest/node.go b/lntest/node.go index 3afbd5d0..751d629a 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -99,9 +99,14 @@ type BackendConfig interface { // for using this node as a chain backend. GenArgs() []string - // P2PAddr returns the address of this node to be used when connection - // over the Bitcoin P2P network. - P2PAddr() string + // ConnectMiner is called to establish a connection to the test miner. + ConnectMiner() error + + // DisconnectMiner is called to bitconneeeect the miner. + DisconnectMiner() error + + // Name returns the name of the backend type. + Name() string } type nodeConfig struct { diff --git a/make/testing_flags.mk b/make/testing_flags.mk index 177dcd98..7de1cded 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -35,7 +35,7 @@ endif ifneq ($(timeout),) TEST_FLAGS += -test.timeout=$(timeout) else -TEST_FLAGS += -test.timeout=30m +TEST_FLAGS += -test.timeout=40m endif # UNIT_TARGTED is undefined iff a specific package and/or unit test case is @@ -57,4 +57,12 @@ endif # Construct the integration test command with the added build flags. ITEST_TAGS := $(DEV_TAGS) rpctest chainrpc walletrpc signrpc invoicesrpc autopilotrpc routerrpc + +# Default to btcd backend if not set. +ifneq ($(backend),) +ITEST_TAGS += ${backend} +else +ITEST_TAGS += btcd +endif + ITEST := rm output*.log; date; $(GOTEST) -tags="$(ITEST_TAGS)" $(TEST_FLAGS) -logoutput diff --git a/rpcserver.go b/rpcserver.go index a98845df..4fd091b1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2099,6 +2099,8 @@ func (r *rpcServer) WalletBalance(ctx context.Context, } // Get confirmed balance, from txs that have >= 1 confirmations. + // TODO(halseth): get both unconfirmed and confirmed balance in one + // call, as this is racy. confirmedBal, err := r.server.cc.wallet.ConfirmedBalance(1) if err != nil { return nil, err @@ -2107,7 +2109,8 @@ func (r *rpcServer) WalletBalance(ctx context.Context, // Get unconfirmed balance, from txs with 0 confirmations. unconfirmedBal := totalBal - confirmedBal - rpcsLog.Debugf("[walletbalance] Total balance=%v", totalBal) + rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+ + "unconfirmed=%v)", totalBal, confirmedBal, unconfirmedBal) return &lnrpc.WalletBalanceResponse{ TotalBalance: int64(totalBal),