lnd_rest_api_test.go 21 KB

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