github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_rest_api_test.go (about)

     1  package itest
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/tls"
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"regexp"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/decred/dcrlnd/lnrpc"
    19  	"github.com/decred/dcrlnd/lnrpc/autopilotrpc"
    20  	"github.com/decred/dcrlnd/lnrpc/chainrpc"
    21  	"github.com/decred/dcrlnd/lnrpc/routerrpc"
    22  	"github.com/decred/dcrlnd/lnrpc/verrpc"
    23  	"github.com/decred/dcrlnd/lnrpc/walletrpc"
    24  	"github.com/decred/dcrlnd/lntest"
    25  	"github.com/golang/protobuf/jsonpb"
    26  	"github.com/golang/protobuf/proto"
    27  	"github.com/gorilla/websocket"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	"matheusd.com/testctx"
    31  )
    32  
    33  var (
    34  	insecureTransport = &http.Transport{
    35  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    36  	}
    37  	restClient = &http.Client{
    38  		Transport: insecureTransport,
    39  	}
    40  	jsonMarshaler = &jsonpb.Marshaler{
    41  		EmitDefaults: true,
    42  		OrigName:     true,
    43  		Indent:       "    ",
    44  	}
    45  	urlEnc          = base64.URLEncoding
    46  	webSocketDialer = &websocket.Dialer{
    47  		HandshakeTimeout: time.Second,
    48  		TLSClientConfig:  insecureTransport.TLSClientConfig,
    49  	}
    50  	resultPattern = regexp.MustCompile("{\"result\":(.*)}")
    51  	closeMsg      = websocket.FormatCloseMessage(
    52  		websocket.CloseNormalClosure, "done",
    53  	)
    54  
    55  	pingInterval = time.Millisecond * 200
    56  	pongWait     = time.Millisecond * 50
    57  )
    58  
    59  // testRestAPI tests that the most important features of the REST API work
    60  // correctly.
    61  func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) {
    62  	testCases := []struct {
    63  		name string
    64  		run  func(*testing.T, *lntest.HarnessNode, *lntest.HarnessNode)
    65  	}{{
    66  		name: "simple GET",
    67  		run: func(t *testing.T, a, b *lntest.HarnessNode) {
    68  			// Check that the parsing into the response proto
    69  			// message works.
    70  			resp := &lnrpc.GetInfoResponse{}
    71  			err := invokeGET(a, "/v1/getinfo", resp)
    72  			require.Nil(t, err, "getinfo")
    73  			assert.Equal(t, "#3399ff", resp.Color, "node color")
    74  
    75  			// Make sure we get the correct field names (snake
    76  			// case).
    77  			_, resp2, err := makeRequest(
    78  				a, "/v1/getinfo", "GET", nil, nil,
    79  			)
    80  			require.Nil(t, err, "getinfo")
    81  			assert.Contains(
    82  				t, string(resp2), "best_header_timestamp",
    83  				"getinfo",
    84  			)
    85  		},
    86  	}, {
    87  		name: "simple POST and GET with query param",
    88  		run: func(t *testing.T, a, b *lntest.HarnessNode) {
    89  			// Add an invoice, testing POST in the process.
    90  			req := &lnrpc.Invoice{Value: 1234, IgnoreMaxInboundAmt: true}
    91  			resp := &lnrpc.AddInvoiceResponse{}
    92  			err := invokePOST(a, "/v1/invoices", req, resp)
    93  			require.Nil(t, err, "add invoice")
    94  			assert.Equal(t, 32, len(resp.RHash), "invoice rhash")
    95  
    96  			// Make sure we can call a GET endpoint with a hex
    97  			// encoded URL part.
    98  			url := fmt.Sprintf("/v1/invoice/%x", resp.RHash)
    99  			resp2 := &lnrpc.Invoice{}
   100  			err = invokeGET(a, url, resp2)
   101  			require.Nil(t, err, "query invoice")
   102  			assert.Equal(t, int64(1234), resp2.Value, "invoice amt")
   103  		},
   104  	}, {
   105  		name: "GET with base64 encoded byte slice in path",
   106  		run: func(t *testing.T, a, b *lntest.HarnessNode) {
   107  			url := "/v2/router/mc/probability/%s/%s/%d"
   108  			url = fmt.Sprintf(
   109  				url, urlEnc.EncodeToString(a.PubKey[:]),
   110  				urlEnc.EncodeToString(b.PubKey[:]), 1234,
   111  			)
   112  			resp := &routerrpc.QueryProbabilityResponse{}
   113  			err := invokeGET(a, url, resp)
   114  			require.Nil(t, err, "query probability")
   115  			assert.Greater(t, resp.Probability, 0.5, "probability")
   116  		},
   117  	}, {
   118  		name: "GET with map type query param",
   119  		run: func(t *testing.T, a, b *lntest.HarnessNode) {
   120  			// Get a new wallet address from Alice.
   121  			ctxb := context.Background()
   122  			newAddrReq := &lnrpc.NewAddressRequest{
   123  				Type: lnrpc.AddressType_PUBKEY_HASH,
   124  			}
   125  			addrRes, err := a.NewAddress(ctxb, newAddrReq)
   126  			require.Nil(t, err, "get address")
   127  
   128  			// Create the full URL with the map query param.
   129  			//
   130  			// NOTE(decred): estimatefee is unimplemented so this
   131  			// test is disabled for the moment.
   132  			_ = addrRes
   133  			/*
   134  				url := "/v1/transactions/fee?target_conf=%d&" +
   135  					"AddrToAmount[%s]=%d"
   136  				url = fmt.Sprintf(url, 2, addrRes.Address, 50000)
   137  				resp := &lnrpc.EstimateFeeResponse{}
   138  				err = invokeGET(a, url, resp)
   139  				require.Nil(t, err, "estimate fee")
   140  				assert.Greater(t, resp.FeeAtoms, int64(253), "fee")
   141  			*/
   142  		},
   143  	}, {
   144  		name: "sub RPC servers REST support",
   145  		run: func(t *testing.T, a, b *lntest.HarnessNode) {
   146  			// Query autopilot status.
   147  			res1 := &autopilotrpc.StatusResponse{}
   148  			err := invokeGET(a, "/v2/autopilot/status", res1)
   149  			require.Nil(t, err, "autopilot status")
   150  			assert.Equal(t, false, res1.Active, "autopilot status")
   151  
   152  			// Query the version RPC.
   153  			res2 := &verrpc.Version{}
   154  			err = invokeGET(a, "/v2/versioner/version", res2)
   155  			require.Nil(t, err, "version")
   156  			assert.Greater(
   157  				t, res2.AppMinor, uint32(0), "lnd minor version",
   158  			)
   159  
   160  			// Request a new external address from the wallet kit.
   161  			req1 := &walletrpc.AddrRequest{}
   162  			res3 := &walletrpc.AddrResponse{}
   163  			err = invokePOST(
   164  				a, "/v2/wallet/address/next", req1, res3,
   165  			)
   166  			require.Nil(t, err, "address")
   167  			assert.NotEmpty(t, res3.Addr, "address")
   168  		},
   169  	}, {
   170  		name: "CORS headers",
   171  		run: func(t *testing.T, a, b *lntest.HarnessNode) {
   172  			// Alice allows all origins. Make sure we get the same
   173  			// value back in the CORS header that we send in the
   174  			// Origin header.
   175  			reqHeaders := make(http.Header)
   176  			reqHeaders.Add("Origin", "https://foo.bar:9999")
   177  			resHeaders, body, err := makeRequest(
   178  				a, "/v1/getinfo", "OPTIONS", nil, reqHeaders,
   179  			)
   180  			require.Nil(t, err, "getinfo")
   181  			assert.Equal(
   182  				t, "https://foo.bar:9999",
   183  				resHeaders.Get("Access-Control-Allow-Origin"),
   184  				"CORS header",
   185  			)
   186  			assert.Equal(t, 0, len(body))
   187  
   188  			// Make sure that we don't get a value set for Bob which
   189  			// doesn't allow any CORS origin.
   190  			resHeaders, body, err = makeRequest(
   191  				b, "/v1/getinfo", "OPTIONS", nil, reqHeaders,
   192  			)
   193  			require.Nil(t, err, "getinfo")
   194  			assert.Equal(
   195  				t, "",
   196  				resHeaders.Get("Access-Control-Allow-Origin"),
   197  				"CORS header",
   198  			)
   199  			assert.Equal(t, 0, len(body))
   200  		},
   201  	}}
   202  	wsTestCases := []struct {
   203  		name string
   204  		run  func(ht *harnessTest, net *lntest.NetworkHarness)
   205  	}{{
   206  		name: "websocket subscription",
   207  		run:  wsTestCaseSubscription,
   208  	}, {
   209  		name: "websocket subscription with macaroon in protocol",
   210  		run:  wsTestCaseSubscriptionMacaroon,
   211  	}, {
   212  		name: "websocket bi-directional subscription",
   213  		run:  wsTestCaseBiDirectionalSubscription,
   214  	}, {
   215  		name: "websocket ping and pong timeout",
   216  		run:  wsTestPingPongTimeout,
   217  	}}
   218  
   219  	// Make sure Alice allows all CORS origins. Bob will keep the default.
   220  	// We also make sure the ping/pong messages are sent very often, so we
   221  	// can test them without waiting half a minute.
   222  	net.Alice.Cfg.ExtraArgs = append(
   223  		net.Alice.Cfg.ExtraArgs, "--restcors=\"*\"",
   224  		fmt.Sprintf("--ws-ping-interval=%s", pingInterval),
   225  		fmt.Sprintf("--ws-pong-wait=%s", pongWait),
   226  	)
   227  	err := net.RestartNode(net.Alice, nil)
   228  	if err != nil {
   229  		ht.t.Fatalf("Could not restart Alice to set CORS config: %v",
   230  			err)
   231  	}
   232  
   233  	for _, tc := range testCases {
   234  		tc := tc
   235  		ht.t.Run(tc.name, func(t *testing.T) {
   236  			tc.run(t, net.Alice, net.Bob)
   237  
   238  			assertCleanState(ht, net)
   239  		})
   240  	}
   241  
   242  	for _, tc := range wsTestCases {
   243  		tc := tc
   244  		ht.t.Run(tc.name, func(t *testing.T) {
   245  			ht := &harnessTest{
   246  				t: t, testCase: ht.testCase, lndHarness: net,
   247  			}
   248  			tc.run(ht, net)
   249  
   250  			assertCleanState(ht, net)
   251  		})
   252  	}
   253  }
   254  
   255  func wsTestCaseSubscription(ht *harnessTest, net *lntest.NetworkHarness) {
   256  	// Find out the current best block so we can subscribe to the next one.
   257  	hash, height, err := net.Miner.Node.GetBestBlock(testctx.New(ht.t))
   258  	require.Nil(ht.t, err, "get best block")
   259  
   260  	// Create a new subscription to get block epoch events.
   261  	req := &chainrpc.BlockEpoch{
   262  		Hash:   hash.CloneBytes(),
   263  		Height: uint32(height),
   264  	}
   265  	url := "/v2/chainnotifier/register/blocks"
   266  	c, err := openWebSocket(net.Alice, url, "POST", req, nil)
   267  	require.Nil(ht.t, err, "websocket")
   268  	defer func() {
   269  		err := c.WriteMessage(websocket.CloseMessage, closeMsg)
   270  		require.NoError(ht.t, err)
   271  		_ = c.Close()
   272  	}()
   273  
   274  	msgChan := make(chan *chainrpc.BlockEpoch)
   275  	errChan := make(chan error)
   276  	timeout := time.After(defaultTimeout)
   277  
   278  	// We want to read exactly one message.
   279  	go func() {
   280  		defer close(msgChan)
   281  
   282  		_, msg, err := c.ReadMessage()
   283  		if err != nil {
   284  			errChan <- err
   285  			return
   286  		}
   287  
   288  		// The chunked/streamed responses come wrapped in either a
   289  		// {"result":{}} or {"error":{}} wrapper which we'll get rid of
   290  		// here.
   291  		msgStr := string(msg)
   292  		if !strings.Contains(msgStr, "\"result\":") {
   293  			errChan <- fmt.Errorf("invalid msg: %s", msgStr)
   294  			return
   295  		}
   296  		msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
   297  
   298  		// Make sure we can parse the unwrapped message into the
   299  		// expected proto message.
   300  		protoMsg := &chainrpc.BlockEpoch{}
   301  		err = jsonpb.UnmarshalString(msgStr, protoMsg)
   302  		if err != nil {
   303  			errChan <- err
   304  			return
   305  		}
   306  
   307  		select {
   308  		case msgChan <- protoMsg:
   309  		case <-timeout:
   310  		}
   311  	}()
   312  
   313  	// Mine a block and make sure we get a message for it.
   314  	blockHashes, err := net.Miner.Node.Generate(testctx.New(ht.t), 1)
   315  	require.Nil(ht.t, err, "generate blocks")
   316  	assert.Equal(ht.t, 1, len(blockHashes), "num blocks")
   317  	select {
   318  	case msg := <-msgChan:
   319  		assert.Equal(
   320  			ht.t, blockHashes[0].CloneBytes(), msg.Hash,
   321  			"block hash",
   322  		)
   323  
   324  	case err := <-errChan:
   325  		ht.t.Fatalf("Received error from WS: %v", err)
   326  
   327  	case <-timeout:
   328  		ht.t.Fatalf("Timeout before message was received")
   329  	}
   330  }
   331  
   332  func wsTestCaseSubscriptionMacaroon(ht *harnessTest,
   333  	net *lntest.NetworkHarness) {
   334  
   335  	// Find out the current best block so we can subscribe to the next one.
   336  	hash, height, err := net.Miner.Node.GetBestBlock(testctx.New(ht.t))
   337  	require.Nil(ht.t, err, "get best block")
   338  
   339  	// Create a new subscription to get block epoch events.
   340  	req := &chainrpc.BlockEpoch{
   341  		Hash:   hash.CloneBytes(),
   342  		Height: uint32(height),
   343  	}
   344  	url := "/v2/chainnotifier/register/blocks"
   345  
   346  	// This time we send the macaroon in the special header
   347  	// Sec-Websocket-Protocol which is the only header field available to
   348  	// browsers when opening a WebSocket.
   349  	mac, err := net.Alice.ReadMacaroon(
   350  		net.Alice.AdminMacPath(), defaultTimeout,
   351  	)
   352  	require.NoError(ht.t, err, "read admin mac")
   353  	macBytes, err := mac.MarshalBinary()
   354  	require.NoError(ht.t, err, "marshal admin mac")
   355  
   356  	customHeader := make(http.Header)
   357  	customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf(
   358  		"Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes),
   359  	))
   360  	c, err := openWebSocket(net.Alice, url, "POST", req, customHeader)
   361  	require.Nil(ht.t, err, "websocket")
   362  	defer func() {
   363  		err := c.WriteMessage(websocket.CloseMessage, closeMsg)
   364  		require.NoError(ht.t, err)
   365  		_ = c.Close()
   366  	}()
   367  
   368  	msgChan := make(chan *chainrpc.BlockEpoch)
   369  	errChan := make(chan error)
   370  	timeout := time.After(defaultTimeout)
   371  
   372  	// We want to read exactly one message.
   373  	go func() {
   374  		defer close(msgChan)
   375  
   376  		_, msg, err := c.ReadMessage()
   377  		if err != nil {
   378  			errChan <- err
   379  			return
   380  		}
   381  
   382  		// The chunked/streamed responses come wrapped in either a
   383  		// {"result":{}} or {"error":{}} wrapper which we'll get rid of
   384  		// here.
   385  		msgStr := string(msg)
   386  		if !strings.Contains(msgStr, "\"result\":") {
   387  			errChan <- fmt.Errorf("invalid msg: %s", msgStr)
   388  			return
   389  		}
   390  		msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
   391  
   392  		// Make sure we can parse the unwrapped message into the
   393  		// expected proto message.
   394  		protoMsg := &chainrpc.BlockEpoch{}
   395  		err = jsonpb.UnmarshalString(msgStr, protoMsg)
   396  		if err != nil {
   397  			errChan <- err
   398  			return
   399  		}
   400  
   401  		select {
   402  		case msgChan <- protoMsg:
   403  		case <-timeout:
   404  		}
   405  	}()
   406  
   407  	// Mine a block and make sure we get a message for it.
   408  	blockHashes, err := net.Miner.Node.Generate(testctx.New(ht.t), 1)
   409  	require.Nil(ht.t, err, "generate blocks")
   410  	assert.Equal(ht.t, 1, len(blockHashes), "num blocks")
   411  	select {
   412  	case msg := <-msgChan:
   413  		assert.Equal(
   414  			ht.t, blockHashes[0].CloneBytes(), msg.Hash,
   415  			"block hash",
   416  		)
   417  
   418  	case err := <-errChan:
   419  		ht.t.Fatalf("Received error from WS: %v", err)
   420  
   421  	case <-timeout:
   422  		ht.t.Fatalf("Timeout before message was received")
   423  	}
   424  }
   425  
   426  func wsTestCaseBiDirectionalSubscription(ht *harnessTest,
   427  	net *lntest.NetworkHarness) {
   428  
   429  	initialRequest := &lnrpc.ChannelAcceptResponse{}
   430  	url := "/v1/channels/acceptor"
   431  
   432  	// This time we send the macaroon in the special header
   433  	// Sec-Websocket-Protocol which is the only header field available to
   434  	// browsers when opening a WebSocket.
   435  	mac, err := net.Alice.ReadMacaroon(
   436  		net.Alice.AdminMacPath(), defaultTimeout,
   437  	)
   438  	require.NoError(ht.t, err, "read admin mac")
   439  	macBytes, err := mac.MarshalBinary()
   440  	require.NoError(ht.t, err, "marshal admin mac")
   441  
   442  	customHeader := make(http.Header)
   443  	customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf(
   444  		"Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes),
   445  	))
   446  	conn, err := openWebSocket(
   447  		net.Alice, url, "POST", initialRequest, customHeader,
   448  	)
   449  	require.Nil(ht.t, err, "websocket")
   450  	defer func() {
   451  		err := conn.WriteMessage(websocket.CloseMessage, closeMsg)
   452  		_ = conn.Close()
   453  		require.NoError(ht.t, err)
   454  	}()
   455  
   456  	// Buffer the message channel to make sure we're always blocking on
   457  	// conn.ReadMessage() to allow the ping/pong mechanism to work.
   458  	msgChan := make(chan *lnrpc.ChannelAcceptResponse, 1)
   459  	errChan := make(chan error)
   460  	done := make(chan struct{})
   461  	timeout := time.After(defaultTimeout)
   462  
   463  	// We want to read messages over and over again. We just accept any
   464  	// channels that are opened.
   465  	defer close(done)
   466  	go func() {
   467  		for {
   468  			_, msg, err := conn.ReadMessage()
   469  			if err != nil {
   470  				select {
   471  				case errChan <- err:
   472  				case <-done:
   473  				}
   474  				return
   475  			}
   476  
   477  			// The chunked/streamed responses come wrapped in either
   478  			// a {"result":{}} or {"error":{}} wrapper which we'll
   479  			// get rid of here.
   480  			msgStr := string(msg)
   481  			if !strings.Contains(msgStr, "\"result\":") {
   482  				select {
   483  				case errChan <- fmt.Errorf("invalid msg: %s",
   484  					msgStr):
   485  				case <-done:
   486  				}
   487  				return
   488  			}
   489  			msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
   490  
   491  			// Make sure we can parse the unwrapped message into the
   492  			// expected proto message.
   493  			protoMsg := &lnrpc.ChannelAcceptRequest{}
   494  			err = jsonpb.UnmarshalString(msgStr, protoMsg)
   495  			if err != nil {
   496  				select {
   497  				case errChan <- err:
   498  				case <-done:
   499  				}
   500  				return
   501  			}
   502  
   503  			// Send the response that we accept the channel.
   504  			res := &lnrpc.ChannelAcceptResponse{
   505  				Accept:        true,
   506  				PendingChanId: protoMsg.PendingChanId,
   507  			}
   508  			resMsg, err := jsonMarshaler.MarshalToString(res)
   509  			if err != nil {
   510  				select {
   511  				case errChan <- err:
   512  				case <-done:
   513  				}
   514  				return
   515  			}
   516  			err = conn.WriteMessage(
   517  				websocket.TextMessage, []byte(resMsg),
   518  			)
   519  			if err != nil {
   520  				select {
   521  				case errChan <- err:
   522  				case <-done:
   523  				}
   524  				return
   525  			}
   526  
   527  			// Also send the message on our message channel to make
   528  			// sure we count it as successful.
   529  			msgChan <- res
   530  
   531  			// Are we done or should there be more messages?
   532  			select {
   533  			case <-done:
   534  				return
   535  			default:
   536  			}
   537  		}
   538  	}()
   539  
   540  	// Before we start opening channels, make sure the two nodes are
   541  	// connected.
   542  	net.EnsureConnected(ht.t, net.Alice, net.Bob)
   543  
   544  	// Open 3 channels to make sure multiple requests and responses can be
   545  	// sent over the web socket.
   546  	const numChannels = 3
   547  	for i := 0; i < numChannels; i++ {
   548  		cp := openChannelAndAssert(
   549  			ht, net, net.Bob, net.Alice,
   550  			lntest.OpenChannelParams{Amt: 500000},
   551  		)
   552  		defer closeChannelAndAssert(ht, net, net.Alice,
   553  			cp, false)
   554  
   555  		select {
   556  		case <-msgChan:
   557  		case err := <-errChan:
   558  			ht.t.Fatalf("Received error from WS: %v", err)
   559  
   560  		case <-timeout:
   561  			ht.t.Fatalf("Timeout before message was received")
   562  		}
   563  	}
   564  }
   565  
   566  func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) {
   567  	initialRequest := &lnrpc.InvoiceSubscription{
   568  		AddIndex: 1, SettleIndex: 1,
   569  	}
   570  	url := "/v1/invoices/subscribe"
   571  
   572  	// This time we send the macaroon in the special header
   573  	// Sec-Websocket-Protocol which is the only header field available to
   574  	// browsers when opening a WebSocket.
   575  	mac, err := net.Alice.ReadMacaroon(
   576  		net.Alice.AdminMacPath(), defaultTimeout,
   577  	)
   578  	require.NoError(ht.t, err, "read admin mac")
   579  	macBytes, err := mac.MarshalBinary()
   580  	require.NoError(ht.t, err, "marshal admin mac")
   581  
   582  	customHeader := make(http.Header)
   583  	customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf(
   584  		"Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes),
   585  	))
   586  	conn, err := openWebSocket(
   587  		net.Alice, url, "GET", initialRequest, customHeader,
   588  	)
   589  	require.Nil(ht.t, err, "websocket")
   590  	defer func() {
   591  		err := conn.WriteMessage(websocket.CloseMessage, closeMsg)
   592  		_ = conn.Close()
   593  		require.NoError(ht.t, err)
   594  	}()
   595  
   596  	// We want to be able to read invoices for a long time, making sure we
   597  	// can continue to read even after we've gone through several ping/pong
   598  	// cycles.
   599  	invoices := make(chan *lnrpc.Invoice, 1)
   600  	errChan := make(chan error)
   601  	done := make(chan struct{})
   602  	timeout := time.After(defaultTimeout)
   603  
   604  	defer close(done)
   605  	go func() {
   606  		for {
   607  			_, msg, err := conn.ReadMessage()
   608  			if err != nil {
   609  				select {
   610  				case errChan <- err:
   611  				case <-done:
   612  				}
   613  				return
   614  			}
   615  
   616  			// The chunked/streamed responses come wrapped in either
   617  			// a {"result":{}} or {"error":{}} wrapper which we'll
   618  			// get rid of here.
   619  			msgStr := string(msg)
   620  			if !strings.Contains(msgStr, "\"result\":") {
   621  				select {
   622  				case errChan <- fmt.Errorf("invalid msg: %s",
   623  					msgStr):
   624  				case <-done:
   625  				}
   626  				return
   627  			}
   628  			msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
   629  
   630  			// Make sure we can parse the unwrapped message into the
   631  			// expected proto message.
   632  			protoMsg := &lnrpc.Invoice{}
   633  			err = jsonpb.UnmarshalString(msgStr, protoMsg)
   634  			if err != nil {
   635  				select {
   636  				case errChan <- err:
   637  				case <-done:
   638  				}
   639  				return
   640  			}
   641  
   642  			invoices <- protoMsg
   643  
   644  			// Make sure we exit the loop once we've sent through
   645  			// all expected test messages.
   646  			select {
   647  			case <-done:
   648  				return
   649  			default:
   650  			}
   651  		}
   652  	}()
   653  
   654  	// The SubscribeInvoices call returns immediately after the gRPC/REST
   655  	// connection is established. But it can happen that the goroutine in
   656  	// lnd that actually registers the subscriber in the invoice backend
   657  	// didn't get any CPU time just yet. So we can run into the situation
   658  	// where we add our first invoice _before_ the subscription client is
   659  	// registered. If that happens, we'll never get notified about the
   660  	// invoice in question. So all we really can do is wait a bit here to
   661  	// make sure the subscription is registered correctly.
   662  	time.Sleep(500 * time.Millisecond)
   663  
   664  	// Let's create five invoices and wait for them to arrive. We'll wait
   665  	// for at least one ping/pong cycle between each invoice.
   666  	ctxb := context.Background()
   667  	const numInvoices = 5
   668  	const value = 123
   669  	const memo = "websocket"
   670  	for i := 0; i < numInvoices; i++ {
   671  		_, err := net.Alice.AddInvoice(ctxb, &lnrpc.Invoice{
   672  			Value: value,
   673  			Memo:  memo,
   674  
   675  			IgnoreMaxInboundAmt: true,
   676  		})
   677  		require.NoError(ht.t, err)
   678  
   679  		select {
   680  		case streamMsg := <-invoices:
   681  			require.Equal(ht.t, int64(value), streamMsg.Value)
   682  			require.Equal(ht.t, memo, streamMsg.Memo)
   683  
   684  		case err := <-errChan:
   685  			require.Fail(ht.t, "Error reading invoice: %v", err)
   686  
   687  		case <-timeout:
   688  			require.Fail(ht.t, "No invoice msg received in time")
   689  		}
   690  
   691  		// Let's wait for at least a whole ping/pong cycle to happen, so
   692  		// we can be sure the read/write deadlines are set correctly.
   693  		// We double the pong wait just to add some extra margin.
   694  		time.Sleep(pingInterval + 2*pongWait)
   695  	}
   696  }
   697  
   698  // invokeGET calls the given URL with the GET method and appropriate macaroon
   699  // header fields then tries to unmarshal the response into the given response
   700  // proto message.
   701  func invokeGET(node *lntest.HarnessNode, url string, resp proto.Message) error {
   702  	_, rawResp, err := makeRequest(node, url, "GET", nil, nil)
   703  	if err != nil {
   704  		return err
   705  	}
   706  
   707  	return jsonpb.Unmarshal(bytes.NewReader(rawResp), resp)
   708  }
   709  
   710  // invokePOST calls the given URL with the POST method, request body and
   711  // appropriate macaroon header fields then tries to unmarshal the response into
   712  // the given response proto message.
   713  func invokePOST(node *lntest.HarnessNode, url string, req,
   714  	resp proto.Message) error {
   715  
   716  	// Marshal the request to JSON using the jsonpb marshaler to get correct
   717  	// field names.
   718  	var buf bytes.Buffer
   719  	if err := jsonMarshaler.Marshal(&buf, req); err != nil {
   720  		return err
   721  	}
   722  
   723  	_, rawResp, err := makeRequest(node, url, "POST", &buf, nil)
   724  	if err != nil {
   725  		return err
   726  	}
   727  
   728  	return jsonpb.Unmarshal(bytes.NewReader(rawResp), resp)
   729  }
   730  
   731  // makeRequest calls the given URL with the given method, request body and
   732  // appropriate macaroon header fields and returns the raw response body.
   733  func makeRequest(node *lntest.HarnessNode, url, method string,
   734  	request io.Reader, additionalHeaders http.Header) (http.Header, []byte,
   735  	error) {
   736  
   737  	// Assemble the full URL from the node's listening address then create
   738  	// the request so we can set the macaroon on it.
   739  	fullURL := fmt.Sprintf("https://%s%s", node.Cfg.RESTAddr(), url)
   740  	req, err := http.NewRequest(method, fullURL, request)
   741  	if err != nil {
   742  		return nil, nil, err
   743  	}
   744  	if err := addAdminMacaroon(node, req.Header); err != nil {
   745  		return nil, nil, err
   746  	}
   747  	for key, values := range additionalHeaders {
   748  		for _, value := range values {
   749  			req.Header.Add(key, value)
   750  		}
   751  	}
   752  
   753  	// Do the actual call with the completed request object now.
   754  	resp, err := restClient.Do(req)
   755  	if err != nil {
   756  		return nil, nil, err
   757  	}
   758  	defer func() { _ = resp.Body.Close() }()
   759  
   760  	data, err := ioutil.ReadAll(resp.Body)
   761  	return resp.Header, data, err
   762  }
   763  
   764  // openWebSocket opens a new WebSocket connection to the given URL with the
   765  // appropriate macaroon headers and sends the request message over the socket.
   766  func openWebSocket(node *lntest.HarnessNode, url, method string,
   767  	req proto.Message, customHeader http.Header) (*websocket.Conn, error) {
   768  
   769  	// Prepare our macaroon headers and assemble the full URL from the
   770  	// node's listening address. WebSockets always work over GET so we need
   771  	// to append the target request method as a query parameter.
   772  	header := customHeader
   773  	if header == nil {
   774  		header = make(http.Header)
   775  		if err := addAdminMacaroon(node, header); err != nil {
   776  			return nil, err
   777  		}
   778  	}
   779  	fullURL := fmt.Sprintf(
   780  		"wss://%s%s?method=%s", node.Cfg.RESTAddr(), url, method,
   781  	)
   782  	conn, resp, err := webSocketDialer.Dial(fullURL, header)
   783  	if err != nil {
   784  		return nil, err
   785  	}
   786  	defer func() { _ = resp.Body.Close() }()
   787  
   788  	// Send the given request message as the first message on the socket.
   789  	reqMsg, err := jsonMarshaler.MarshalToString(req)
   790  	if err != nil {
   791  		return nil, err
   792  	}
   793  	err = conn.WriteMessage(websocket.TextMessage, []byte(reqMsg))
   794  	if err != nil {
   795  		return nil, err
   796  	}
   797  
   798  	return conn, nil
   799  }
   800  
   801  // addAdminMacaroon reads the admin macaroon from the node and appends it to
   802  // the HTTP header fields.
   803  func addAdminMacaroon(node *lntest.HarnessNode, header http.Header) error {
   804  	mac, err := node.ReadMacaroon(node.AdminMacPath(), defaultTimeout)
   805  	if err != nil {
   806  		return err
   807  	}
   808  	macBytes, err := mac.MarshalBinary()
   809  	if err != nil {
   810  		return err
   811  	}
   812  
   813  	header.Set("Grpc-Metadata-Macaroon", hex.EncodeToString(macBytes))
   814  
   815  	return nil
   816  }