You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
3.2 KiB

rpc: add support for batched requests/responses (#3534) Continues from #3280 in building support for batched requests/responses in the JSON RPC (as per issue #3213). * Add JSON RPC batching for client and server As per #3213, this adds support for [JSON RPC batch requests and responses](https://www.jsonrpc.org/specification#batch). * Add additional checks to ensure client responses are the same as results * Fix case where a notification is sent and no response is expected * Add test to check that JSON RPC notifications in a batch are left out in responses * Update CHANGELOG_PENDING.md * Update PR number now that PR has been created * Make errors start with lowercase letter * Refactor batch functionality to be standalone This refactors the batching functionality to rather act in a standalone way. In light of supporting concurrent goroutines making use of the same client, it would make sense to have batching functionality where one could create a batch of requests per goroutine and send that batch without interfering with a batch from another goroutine. * Add examples for simple and batch HTTP client usage * Check errors from writer and remove nolinter directives * Make error strings start with lowercase letter * Refactor examples to make them testable * Use safer deferred shutdown for example Tendermint test node * Recompose rpcClient interface from pre-existing interface components * Rename WaitGroup for brevity * Replace empty ID string with request ID * Remove extraneous test case * Convert first letter of errors.Wrap() messages to lowercase * Remove extraneous function parameter * Make variable declaration terse * Reorder WaitGroup.Done call to help prevent race conditions in the face of failure * Swap mutex to value representation and remove initialization * Restore empty JSONRPC string ID in response to prevent nil * Make JSONRPCBufferedRequest private * Revert PR hard link in CHANGELOG_PENDING * Add client ID for JSONRPCClient This adds code to automatically generate a randomized client ID for the JSONRPCClient, and adds a check of the IDs in the responses (if one was set in the requests). * Extract response ID validation into separate function * Remove extraneous comments * Reorder fields to indicate clearly which are protected by the mutex * Refactor for loop to remove indexing * Restructure and combine loop * Flatten conditional block for better readability * Make multi-variable declaration slightly more readable * Change for loop style * Compress error check statements * Make function description more generic to show that we support different protocols * Preallocate memory for request and result objects
6 years ago
  1. package client_test
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/tendermint/tendermint/abci/example/kvstore"
  6. "github.com/tendermint/tendermint/rpc/client"
  7. ctypes "github.com/tendermint/tendermint/rpc/core/types"
  8. rpctest "github.com/tendermint/tendermint/rpc/test"
  9. )
  10. func ExampleHTTP_simple() {
  11. // Start a tendermint node (and kvstore) in the background to test against
  12. app := kvstore.NewKVStoreApplication()
  13. node := rpctest.StartTendermint(app, rpctest.SuppressStdout, rpctest.RecreateConfig)
  14. defer rpctest.StopTendermint(node)
  15. // Create our RPC client
  16. rpcAddr := rpctest.GetConfig().RPC.ListenAddress
  17. c := client.NewHTTP(rpcAddr, "/websocket")
  18. // Create a transaction
  19. k := []byte("name")
  20. v := []byte("satoshi")
  21. tx := append(k, append([]byte("="), v...)...)
  22. // Broadcast the transaction and wait for it to commit (rather use
  23. // c.BroadcastTxSync though in production)
  24. bres, err := c.BroadcastTxCommit(tx)
  25. if err != nil {
  26. panic(err)
  27. }
  28. if bres.CheckTx.IsErr() || bres.DeliverTx.IsErr() {
  29. panic("BroadcastTxCommit transaction failed")
  30. }
  31. // Now try to fetch the value for the key
  32. qres, err := c.ABCIQuery("/key", k)
  33. if err != nil {
  34. panic(err)
  35. }
  36. if qres.Response.IsErr() {
  37. panic("ABCIQuery failed")
  38. }
  39. if !bytes.Equal(qres.Response.Key, k) {
  40. panic("returned key does not match queried key")
  41. }
  42. if !bytes.Equal(qres.Response.Value, v) {
  43. panic("returned value does not match sent value")
  44. }
  45. fmt.Println("Sent tx :", string(tx))
  46. fmt.Println("Queried for :", string(qres.Response.Key))
  47. fmt.Println("Got value :", string(qres.Response.Value))
  48. // Output:
  49. // Sent tx : name=satoshi
  50. // Queried for : name
  51. // Got value : satoshi
  52. }
  53. func ExampleHTTP_batching() {
  54. // Start a tendermint node (and kvstore) in the background to test against
  55. app := kvstore.NewKVStoreApplication()
  56. node := rpctest.StartTendermint(app, rpctest.SuppressStdout, rpctest.RecreateConfig)
  57. defer rpctest.StopTendermint(node)
  58. // Create our RPC client
  59. rpcAddr := rpctest.GetConfig().RPC.ListenAddress
  60. c := client.NewHTTP(rpcAddr, "/websocket")
  61. // Create our two transactions
  62. k1 := []byte("firstName")
  63. v1 := []byte("satoshi")
  64. tx1 := append(k1, append([]byte("="), v1...)...)
  65. k2 := []byte("lastName")
  66. v2 := []byte("nakamoto")
  67. tx2 := append(k2, append([]byte("="), v2...)...)
  68. txs := [][]byte{tx1, tx2}
  69. // Create a new batch
  70. batch := c.NewBatch()
  71. // Queue up our transactions
  72. for _, tx := range txs {
  73. if _, err := batch.BroadcastTxCommit(tx); err != nil {
  74. panic(err)
  75. }
  76. }
  77. // Send the batch of 2 transactions
  78. if _, err := batch.Send(); err != nil {
  79. panic(err)
  80. }
  81. // Now let's query for the original results as a batch
  82. keys := [][]byte{k1, k2}
  83. for _, key := range keys {
  84. if _, err := batch.ABCIQuery("/key", key); err != nil {
  85. panic(err)
  86. }
  87. }
  88. // Send the 2 queries and keep the results
  89. results, err := batch.Send()
  90. if err != nil {
  91. panic(err)
  92. }
  93. // Each result in the returned list is the deserialized result of each
  94. // respective ABCIQuery response
  95. for _, result := range results {
  96. qr, ok := result.(*ctypes.ResultABCIQuery)
  97. if !ok {
  98. panic("invalid result type from ABCIQuery request")
  99. }
  100. fmt.Println(string(qr.Response.Key), "=", string(qr.Response.Value))
  101. }
  102. // Output:
  103. // firstName = satoshi
  104. // lastName = nakamoto
  105. }