Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add support for a ws client & batch processing over ws (gnolang#…
…1498) ## Description Let me start this PR description by explaining _what_ I wanted to accomplish, so you are not discouraged when reading the file changes. I wanted to: - create a WS client from outside the repository (just like we can create http clients) - have the WS client support batch requests - have the TM2 JSON-RPC server support batch requests / responses over WS - **not** have to rewrite core client / server logic and APIs It might seem odd, reading the 3rd point and thinking this is not supported. The truth is actually much more troubling. Our JSON-RPC server (and client!) implementations are not great. HTTP requests are handled and parsed in a completely different flow than WS requests, even though the output of both should be identical (just over different mediums). Lots of logic is coupled, making it hard to extract and simplify. I've broken the WS / HTTP implementation multiple times over the course of this PR, and all of the tests were _passing_, even though I've critically broken the module at the time. The client code for the JSON-RPC server requires a _response type_ (of course, for Amino result parsing) to be given at the moment of calling, which is not amazing considering our WS implementation is async, and doesn't have these response types in the result parsing context (these logic flows are handled by different threads). What I ended up doing: - added support for a WS client - this was a bigger effort than expected; I extracted and simplified the batching logic, but was still blocked by the lack of batch support in WS request handling - added batch support for the TM2 JSON-RPC server - I basically mirrored the HTTP batch request handling (hint that these should be identical flows) - **BREAKING: completely overhauled our JSON-RPC client libraries and actual instances (http / ws)**, for a couple of reasons: - I tried to add support for all previously mentioned items, but it was impossible with the architecture that was in place (`baseRPCClient`). The slightly tweaked API (for creating HTTP / WS clients, and using batches) is much simpler to use, and actually has error handling. - We didn't have nearly enough coverage and good tests for the functionality -- now we have a suite of E2E and unit tests that give confidence. I will start an effort in the near future for refactoring the JSON-RPC server code from the ground up in a subsequent PR, this time with specs and tests at the forefront. ### How to test out the websockets To test out the WS responses, you can use a tool like [websocat](https://github.com/vi/websocat). 1. start a local chain 2. run `websocat ws://127.0.0.1:26657/websocket` (this is the default URL) 3. send a batch request: ```shell [ { "id": 1, "jsonrpc": "2.0", "method": "status", "params": [] }, { "id": 2, "jsonrpc": "2.0", "method": "status", "params": [] } ] ``` ### How to test out the updated client code I created the following snippet for easily testing the functionality updated in this PR: - single HTTP / WS requests - batch HTTP / WS requests ```go package main import ( "context" "fmt" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" ) func main() { // HTTP // fmt.Printf("\n--- HTTP CLIENT ---\n") // Open HTTP connection httpClient, err := client.NewHTTPClient("http://127.0.0.1:26657") if err != nil { panic("unable to start HTTP client") } // Get a single status status, err := httpClient.Status() if err != nil { fmt.Println("Unable to send single status (HTTP)!") panic(err) } fmt.Printf("\n\nHTTP - Single status: %v\n\n", status) // Get a batch of statuses httpBatch := httpClient.NewBatch() // Add 10 status requests for i := 0; i < 10; i++ { if err := httpBatch.Status(); err != nil { fmt.Println("Unable to add status request to HTTP batch!") panic(err) } } // Send the batch results, err := httpBatch.Send(context.Background()) if err != nil { fmt.Println("Unable to send HTTP batch!") panic(err) } for index, resultRaw := range results { result, ok := resultRaw.(*ctypes.ResultStatus) if !ok { panic("Invalid status type in batch response!") } fmt.Printf("\nStatus %d from batch: %v\n", index, result) } // WS // fmt.Printf("\n--- WS CLIENT ---\n") // Open WS connection wsClient, err := client.NewWSClient("ws://127.0.0.1:26657/websocket") if err != nil { panic("unable to start WS client") } // Get a single status status, err = wsClient.Status() if err != nil { fmt.Println("Unable to send single status (WS)!") panic(err) } fmt.Printf("\n\nWS - Single status: %v\n\n", status) // Get a batch of statuses wsBatch := wsClient.NewBatch() // Add 10 status requests for i := 0; i < 10; i++ { if err := wsBatch.Status(); err != nil { fmt.Println("Unable to add status request to WS batch!") panic(err) } } // Send the batch results, err = wsBatch.Send(context.Background()) if err != nil { fmt.Println("Unable to send WS batch!") panic(err) } for index, resultRaw := range results { result, ok := resultRaw.(*ctypes.ResultStatus) if !ok { panic("Invalid status type in batch response!") } fmt.Printf("\nStatus %d from batch: %v\n", index, result) } if err := wsClient.Close(); err != nil { fmt.Println("Unable to gracefully close WS client!") panic(err) } fmt.Printf("\n\nGreat success!\n\n") } ``` cc @dongwon8247 <details><summary>Contributors' checklist...</summary> - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md). </details>
- Loading branch information