itest: add test for bi-directional WebSocket
This commit is contained in:
parent
066472fd3e
commit
13aed071bd
@ -198,6 +198,9 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) {
|
|||||||
}, {
|
}, {
|
||||||
name: "websocket subscription with macaroon in protocol",
|
name: "websocket subscription with macaroon in protocol",
|
||||||
run: wsTestCaseSubscriptionMacaroon,
|
run: wsTestCaseSubscriptionMacaroon,
|
||||||
|
}, {
|
||||||
|
name: "websocket bi-directional subscription",
|
||||||
|
run: wsTestCaseBiDirectionalSubscription,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// Make sure Alice allows all CORS origins. Bob will keep the default.
|
// Make sure Alice allows all CORS origins. Bob will keep the default.
|
||||||
@ -399,6 +402,129 @@ func wsTestCaseSubscriptionMacaroon(ht *harnessTest,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wsTestCaseBiDirectionalSubscription(ht *harnessTest,
|
||||||
|
net *lntest.NetworkHarness) {
|
||||||
|
|
||||||
|
initialRequest := &lnrpc.ChannelAcceptResponse{}
|
||||||
|
url := "/v1/channels/acceptor"
|
||||||
|
|
||||||
|
// This time we send the macaroon in the special header
|
||||||
|
// Sec-Websocket-Protocol which is the only header field available to
|
||||||
|
// browsers when opening a WebSocket.
|
||||||
|
mac, err := net.Alice.ReadMacaroon(
|
||||||
|
net.Alice.AdminMacPath(), defaultTimeout,
|
||||||
|
)
|
||||||
|
require.NoError(ht.t, err, "read admin mac")
|
||||||
|
macBytes, err := mac.MarshalBinary()
|
||||||
|
require.NoError(ht.t, err, "marshal admin mac")
|
||||||
|
|
||||||
|
customHeader := make(http.Header)
|
||||||
|
customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf(
|
||||||
|
"Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes),
|
||||||
|
))
|
||||||
|
conn, err := openWebSocket(
|
||||||
|
net.Alice, url, "POST", initialRequest, customHeader,
|
||||||
|
)
|
||||||
|
require.Nil(ht.t, err, "websocket")
|
||||||
|
defer func() {
|
||||||
|
err := conn.WriteMessage(websocket.CloseMessage, closeMsg)
|
||||||
|
require.NoError(ht.t, err)
|
||||||
|
_ = conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
msgChan := make(chan *lnrpc.ChannelAcceptResponse)
|
||||||
|
errChan := make(chan error)
|
||||||
|
done := make(chan struct{})
|
||||||
|
timeout := time.After(defaultTimeout)
|
||||||
|
|
||||||
|
// We want to read messages over and over again. We just accept any
|
||||||
|
// channels that are opened.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
_, msg, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chunked/streamed responses come wrapped in either
|
||||||
|
// a {"result":{}} or {"error":{}} wrapper which we'll
|
||||||
|
// get rid of here.
|
||||||
|
msgStr := string(msg)
|
||||||
|
if !strings.Contains(msgStr, "\"result\":") {
|
||||||
|
errChan <- fmt.Errorf("invalid msg: %s", msgStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
|
||||||
|
|
||||||
|
// Make sure we can parse the unwrapped message into the
|
||||||
|
// expected proto message.
|
||||||
|
protoMsg := &lnrpc.ChannelAcceptRequest{}
|
||||||
|
err = jsonpb.UnmarshalString(msgStr, protoMsg)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the response that we accept the channel.
|
||||||
|
res := &lnrpc.ChannelAcceptResponse{
|
||||||
|
Accept: true,
|
||||||
|
PendingChanId: protoMsg.PendingChanId,
|
||||||
|
}
|
||||||
|
resMsg, err := jsonMarshaler.MarshalToString(res)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.WriteMessage(
|
||||||
|
websocket.TextMessage, []byte(resMsg),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also send the message on our message channel to make
|
||||||
|
// sure we count it as successful.
|
||||||
|
msgChan <- res
|
||||||
|
|
||||||
|
// Are we done or should there be more messages?
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Before we start opening channels, make sure the two nodes are
|
||||||
|
// connected.
|
||||||
|
err = net.EnsureConnected(context.Background(), net.Alice, net.Bob)
|
||||||
|
require.NoError(ht.t, err)
|
||||||
|
|
||||||
|
// Open 3 channels to make sure multiple requests and responses can be
|
||||||
|
// sent over the web socket.
|
||||||
|
const numChannels = 3
|
||||||
|
for i := 0; i < numChannels; i++ {
|
||||||
|
openChannelAndAssert(
|
||||||
|
context.Background(), ht, net, net.Bob, net.Alice,
|
||||||
|
lntest.OpenChannelParams{
|
||||||
|
Amt: 500000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-msgChan:
|
||||||
|
case err := <-errChan:
|
||||||
|
ht.t.Fatalf("Received error from WS: %v", err)
|
||||||
|
|
||||||
|
case <-timeout:
|
||||||
|
ht.t.Fatalf("Timeout before message was received")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}
|
||||||
|
|
||||||
// invokeGET calls the given URL with the GET method and appropriate macaroon
|
// invokeGET calls the given URL with the GET method and appropriate macaroon
|
||||||
// header fields then tries to unmarshal the response into the given response
|
// header fields then tries to unmarshal the response into the given response
|
||||||
// proto message.
|
// proto message.
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<time> [ERR] BTCN: unable to get filter for hash=<hex>, retrying: unable to fetch cfilter
|
<time> [ERR] BTCN: unable to get filter for hash=<hex>, retrying: unable to fetch cfilter
|
||||||
<time> [ERR] BTCN: Unable to process block connected (height=<height>, hash=<hex>): out of order block <hex>: expected PrevBlock <hex>, got <hex>
|
<time> [ERR] BTCN: Unable to process block connected (height=<height>, hash=<hex>): out of order block <hex>: expected PrevBlock <hex>, got <hex>
|
||||||
<time> [ERR] BTCN: Unknown connid=<id>
|
<time> [ERR] BTCN: Unknown connid=<id>
|
||||||
|
<time> [ERR] CHAC: Received an error: rpc error: code = Canceled desc = context canceled, shutting down
|
||||||
<time> [ERR] CHFT: Close channel <chan_point> unknown to store
|
<time> [ERR] CHFT: Close channel <chan_point> unknown to store
|
||||||
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to advance state: channel not found
|
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to advance state: channel not found
|
||||||
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to broadcast close tx: Transaction rejected: output already spent
|
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to broadcast close tx: Transaction rejected: output already spent
|
||||||
@ -182,6 +183,7 @@
|
|||||||
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission action. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
|
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission action. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
|
||||||
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission entity. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
|
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission entity. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
|
||||||
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: permission list cannot be empty. specify at least one action/entity pair. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
|
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: permission list cannot be empty. specify at least one action/entity pair. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
|
||||||
|
<time> [ERR] RPCS: [/lnrpc.Lightning/ChannelAcceptor]: rpc error: code = Canceled desc = context canceled
|
||||||
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot close channel with state: ChanStatusRestored
|
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot close channel with state: ChanStatusRestored
|
||||||
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot co-op close frozen channel as initiator until height=3059, (current_height=3055)
|
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot co-op close frozen channel as initiator until height=3059, (current_height=3055)
|
||||||
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot co-op close frozen channel as initiator until height=<height>, (current_height=<height>)
|
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot co-op close frozen channel as initiator until height=<height>, (current_height=<height>)
|
||||||
|
Loading…
Reference in New Issue
Block a user