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.

275 lines
8.1 KiB

limit number of /subscribe clients and queries per client (#3269) * limit number of /subscribe clients and queries per client Add the following config variables (under [rpc] section): * max_subscription_clients * max_subscriptions_per_client * timeout_broadcast_tx_commit Fixes #2826 new HTTPClient interface for subscriptions finalize HTTPClient events interface remove EventSubscriber fix data race ``` WARNING: DATA RACE Read at 0x00c000a36060 by goroutine 129: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe.func1() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:168 +0x1f0 Previous write at 0x00c000a36060 by goroutine 132: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:191 +0x4e0 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 129 (running) created at: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:164 +0x4b7 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 132 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:119 +0x186 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ================== ``` lite client works (tested manually) godoc comments httpclient: do not close the out channel use TimeoutBroadcastTxCommit no timeout for unsubscribe but 1s Local (5s HTTP) timeout for resubscribe format code change Subscribe#out cap to 1 and replace config vars with RPCConfig TimeoutBroadcastTxCommit can't be greater than rpcserver.WriteTimeout rpc: Context as first parameter to all functions reformat code fixes after my own review fixes after Ethan's review add test stubs fix config.toml * fixes after manual testing - rpc: do not recommend to use BroadcastTxCommit because it's slow and wastes Tendermint resources (pubsub) - rpc: better error in Subscribe and BroadcastTxCommit - HTTPClient: do not resubscribe if err = ErrAlreadySubscribed * fixes after Ismail's review * Update rpc/grpc/grpc_test.go Co-Authored-By: melekes <anton.kalyaev@gmail.com>
6 years ago
limit number of /subscribe clients and queries per client (#3269) * limit number of /subscribe clients and queries per client Add the following config variables (under [rpc] section): * max_subscription_clients * max_subscriptions_per_client * timeout_broadcast_tx_commit Fixes #2826 new HTTPClient interface for subscriptions finalize HTTPClient events interface remove EventSubscriber fix data race ``` WARNING: DATA RACE Read at 0x00c000a36060 by goroutine 129: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe.func1() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:168 +0x1f0 Previous write at 0x00c000a36060 by goroutine 132: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:191 +0x4e0 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 129 (running) created at: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:164 +0x4b7 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 132 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:119 +0x186 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ================== ``` lite client works (tested manually) godoc comments httpclient: do not close the out channel use TimeoutBroadcastTxCommit no timeout for unsubscribe but 1s Local (5s HTTP) timeout for resubscribe format code change Subscribe#out cap to 1 and replace config vars with RPCConfig TimeoutBroadcastTxCommit can't be greater than rpcserver.WriteTimeout rpc: Context as first parameter to all functions reformat code fixes after my own review fixes after Ethan's review add test stubs fix config.toml * fixes after manual testing - rpc: do not recommend to use BroadcastTxCommit because it's slow and wastes Tendermint resources (pubsub) - rpc: better error in Subscribe and BroadcastTxCommit - HTTPClient: do not resubscribe if err = ErrAlreadySubscribed * fixes after Ismail's review * Update rpc/grpc/grpc_test.go Co-Authored-By: melekes <anton.kalyaev@gmail.com>
6 years ago
rpc/lib/client & server: try to conform to JSON-RPC 2.0 spec (#4141) https://www.jsonrpc.org/specification What is done in this PR: JSONRPCClient: validate that Response.ID matches Request.ID I wanted to do the same for the WSClient, but since we're sending events as responses, not notifications, checking IDs would require storing them in memory indefinitely (and we won't be able to remove them upon client unsubscribing because ID is different then). Request.ID is now optional. Notification is a Request without an ID. Previously "" or 0 were considered as notifications Remove #event suffix from ID from an event response (partially fixes #2949) ID must be either string, int or null AND must be equal to request's ID. Now, because we've implemented events as responses, WS clients are tripping when they see Response.ID("0#event") != Request.ID("0"). Implementing events as requests would require a lot of time (~ 2 days to completely rewrite WS client and server) generate unique ID for each request switch to integer IDs instead of "json-client-XYZ" id=0 method=/subscribe id=0 result=... id=1 method=/abci_query id=1 result=... > send events (resulting from /subscribe) as requests+notifications (not responses) this will require a lot of work. probably not worth it * rpc: generate an unique ID for each request in conformance with JSON-RPC spec * WSClient: check for unsolicited responses * fix golangci warnings * save commit * fix errors * remove ID from responses from subscribe Refs #2949 * clients are safe for concurrent access * tm-bench: switch to int ID * fixes after my own review * comment out sentIDs in WSClient see commit body for the reason * remove body.Close it will be closed automatically * stop ws connection outside of write/read routines also, use t.Rate in tm-bench indexer when calculating ID fix gocritic issues * update swagger.yaml * Apply suggestions from code review * fix stylecheck and golint linter warnings * update changelog * update changelog2
5 years ago
limit number of /subscribe clients and queries per client (#3269) * limit number of /subscribe clients and queries per client Add the following config variables (under [rpc] section): * max_subscription_clients * max_subscriptions_per_client * timeout_broadcast_tx_commit Fixes #2826 new HTTPClient interface for subscriptions finalize HTTPClient events interface remove EventSubscriber fix data race ``` WARNING: DATA RACE Read at 0x00c000a36060 by goroutine 129: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe.func1() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:168 +0x1f0 Previous write at 0x00c000a36060 by goroutine 132: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:191 +0x4e0 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 129 (running) created at: github.com/tendermint/tendermint/rpc/client.(*Local).Subscribe() /go/src/github.com/tendermint/tendermint/rpc/client/localclient.go:164 +0x4b7 github.com/tendermint/tendermint/rpc/client.WaitForOneEvent() /go/src/github.com/tendermint/tendermint/rpc/client/helpers.go:64 +0x178 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync.func1() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:139 +0x298 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 Goroutine 132 (running) created at: testing.(*T).Run() /usr/local/go/src/testing/testing.go:878 +0x659 github.com/tendermint/tendermint/rpc/client_test.TestTxEventsSentWithBroadcastTxSync() /go/src/github.com/tendermint/tendermint/rpc/client/event_test.go:119 +0x186 testing.tRunner() /usr/local/go/src/testing/testing.go:827 +0x162 ================== ``` lite client works (tested manually) godoc comments httpclient: do not close the out channel use TimeoutBroadcastTxCommit no timeout for unsubscribe but 1s Local (5s HTTP) timeout for resubscribe format code change Subscribe#out cap to 1 and replace config vars with RPCConfig TimeoutBroadcastTxCommit can't be greater than rpcserver.WriteTimeout rpc: Context as first parameter to all functions reformat code fixes after my own review fixes after Ethan's review add test stubs fix config.toml * fixes after manual testing - rpc: do not recommend to use BroadcastTxCommit because it's slow and wastes Tendermint resources (pubsub) - rpc: better error in Subscribe and BroadcastTxCommit - HTTPClient: do not resubscribe if err = ErrAlreadySubscribed * fixes after Ismail's review * Update rpc/grpc/grpc_test.go Co-Authored-By: melekes <anton.kalyaev@gmail.com>
6 years ago
7 years ago
  1. package proxy
  2. import (
  3. "context"
  4. "github.com/tendermint/tendermint/crypto/merkle"
  5. "github.com/tendermint/tendermint/libs/bytes"
  6. "github.com/tendermint/tendermint/lite"
  7. rpcclient "github.com/tendermint/tendermint/rpc/client"
  8. ctypes "github.com/tendermint/tendermint/rpc/core/types"
  9. rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
  10. )
  11. var _ rpcclient.Client = Wrapper{}
  12. // Wrapper wraps a rpcclient with a Verifier and double-checks any input that is
  13. // provable before passing it along. Allows you to make any rpcclient fully secure.
  14. type Wrapper struct {
  15. rpcclient.Client
  16. cert *lite.DynamicVerifier
  17. prt *merkle.ProofRuntime
  18. }
  19. // SecureClient uses a given Verifier to wrap an connection to an untrusted
  20. // host and return a cryptographically secure rpc client.
  21. //
  22. // If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface
  23. func SecureClient(c rpcclient.Client, cert *lite.DynamicVerifier) Wrapper {
  24. prt := defaultProofRuntime()
  25. wrap := Wrapper{c, cert, prt}
  26. // TODO: no longer possible as no more such interface exposed....
  27. // if we wrap http client, then we can swap out the event switch to filter
  28. // if hc, ok := c.(*rpcclient.HTTP); ok {
  29. // evt := hc.WSEvents.EventSwitch
  30. // hc.WSEvents.EventSwitch = WrappedSwitch{evt, wrap}
  31. // }
  32. return wrap
  33. }
  34. // ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof
  35. func (w Wrapper) ABCIQueryWithOptions(path string, data bytes.HexBytes,
  36. opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
  37. res, err := GetWithProofOptions(w.prt, path, data, opts, w.Client, w.cert)
  38. return res, err
  39. }
  40. // ABCIQuery uses default options for the ABCI query and verifies the returned proof
  41. func (w Wrapper) ABCIQuery(path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) {
  42. return w.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions)
  43. }
  44. // Tx queries for a given tx and verifies the proof if it was requested
  45. func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
  46. res, err := w.Client.Tx(hash, prove)
  47. if !prove || err != nil {
  48. return res, err
  49. }
  50. h := res.Height
  51. sh, err := GetCertifiedCommit(h, w.Client, w.cert)
  52. if err != nil {
  53. return res, err
  54. }
  55. err = res.Proof.Validate(sh.DataHash)
  56. return res, err
  57. }
  58. // BlockchainInfo requests a list of headers and verifies them all...
  59. // Rather expensive.
  60. //
  61. // TODO: optimize this if used for anything needing performance
  62. func (w Wrapper) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
  63. r, err := w.Client.BlockchainInfo(minHeight, maxHeight)
  64. if err != nil {
  65. return nil, err
  66. }
  67. // go and verify every blockmeta in the result....
  68. for _, meta := range r.BlockMetas {
  69. // get a checkpoint to verify from
  70. res, err := w.Commit(&meta.Header.Height)
  71. if err != nil {
  72. return nil, err
  73. }
  74. sh := res.SignedHeader
  75. err = ValidateBlockMeta(meta, sh)
  76. if err != nil {
  77. return nil, err
  78. }
  79. }
  80. return r, nil
  81. }
  82. // Block returns an entire block and verifies all signatures
  83. func (w Wrapper) Block(height *int64) (*ctypes.ResultBlock, error) {
  84. resBlock, err := w.Client.Block(height)
  85. if err != nil {
  86. return nil, err
  87. }
  88. // get a checkpoint to verify from
  89. resCommit, err := w.Commit(height)
  90. if err != nil {
  91. return nil, err
  92. }
  93. sh := resCommit.SignedHeader
  94. err = ValidateBlock(resBlock.Block, sh)
  95. if err != nil {
  96. return nil, err
  97. }
  98. return resBlock, nil
  99. }
  100. // BlockByHash returns an entire block and verifies all signatures
  101. func (w Wrapper) BlockByHash(hash []byte) (*ctypes.ResultBlock, error) {
  102. resBlock, err := w.Client.BlockByHash(hash)
  103. if err != nil {
  104. return nil, err
  105. }
  106. // get a checkpoint to verify from
  107. resCommit, err := w.Commit(&resBlock.Block.Height)
  108. if err != nil {
  109. return nil, err
  110. }
  111. sh := resCommit.SignedHeader
  112. err = ValidateBlock(resBlock.Block, sh)
  113. if err != nil {
  114. return nil, err
  115. }
  116. return resBlock, nil
  117. }
  118. // Commit downloads the Commit and certifies it with the lite.
  119. //
  120. // This is the foundation for all other verification in this module
  121. func (w Wrapper) Commit(height *int64) (*ctypes.ResultCommit, error) {
  122. if height == nil {
  123. resStatus, err := w.Client.Status()
  124. if err != nil {
  125. return nil, err
  126. }
  127. // NOTE: If resStatus.CatchingUp, there is a race
  128. // condition where the validator set for the next height
  129. // isn't available until some time after the blockstore
  130. // has height h on the remote node. This isn't an issue
  131. // once the node has caught up, and a syncing node likely
  132. // won't have this issue esp with the implementation we
  133. // have here, but we may have to address this at some
  134. // point.
  135. height = new(int64)
  136. *height = resStatus.SyncInfo.LatestBlockHeight
  137. }
  138. rpcclient.WaitForHeight(w.Client, *height, nil)
  139. res, err := w.Client.Commit(height)
  140. // if we got it, then verify it
  141. if err == nil {
  142. sh := res.SignedHeader
  143. err = w.cert.Verify(sh)
  144. }
  145. return res, err
  146. }
  147. func (w Wrapper) RegisterOpDecoder(typ string, dec merkle.OpDecoder) {
  148. w.prt.RegisterOpDecoder(typ, dec)
  149. }
  150. // SubscribeWS subscribes for events using the given query and remote address as
  151. // a subscriber, but does not verify responses (UNSAFE)!
  152. func (w Wrapper) SubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) {
  153. out, err := w.Client.Subscribe(context.Background(), ctx.RemoteAddr(), query)
  154. if err != nil {
  155. return nil, err
  156. }
  157. go func() {
  158. for {
  159. select {
  160. case resultEvent := <-out:
  161. // XXX(melekes) We should have a switch here that performs a validation
  162. // depending on the event's type.
  163. ctx.WSConn.TryWriteRPCResponse(
  164. rpctypes.NewRPCSuccessResponse(
  165. ctx.WSConn.Codec(),
  166. ctx.JSONReq.ID,
  167. resultEvent,
  168. ))
  169. case <-w.Client.Quit():
  170. return
  171. }
  172. }
  173. }()
  174. return &ctypes.ResultSubscribe{}, nil
  175. }
  176. // UnsubscribeWS calls original client's Unsubscribe using remote address as a
  177. // subscriber.
  178. func (w Wrapper) UnsubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) {
  179. err := w.Client.Unsubscribe(context.Background(), ctx.RemoteAddr(), query)
  180. if err != nil {
  181. return nil, err
  182. }
  183. return &ctypes.ResultUnsubscribe{}, nil
  184. }
  185. // UnsubscribeAllWS calls original client's UnsubscribeAll using remote address
  186. // as a subscriber.
  187. func (w Wrapper) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) {
  188. err := w.Client.UnsubscribeAll(context.Background(), ctx.RemoteAddr())
  189. if err != nil {
  190. return nil, err
  191. }
  192. return &ctypes.ResultUnsubscribe{}, nil
  193. }
  194. // // WrappedSwitch creates a websocket connection that auto-verifies any info
  195. // // coming through before passing it along.
  196. // //
  197. // // Since the verification takes 1-2 rpc calls, this is obviously only for
  198. // // relatively low-throughput situations that can tolerate a bit extra latency
  199. // type WrappedSwitch struct {
  200. // types.EventSwitch
  201. // client rpcclient.Client
  202. // }
  203. // // FireEvent verifies any block or header returned from the eventswitch
  204. // func (s WrappedSwitch) FireEvent(event string, data events.EventData) {
  205. // tm, ok := data.(types.TMEventData)
  206. // if !ok {
  207. // fmt.Printf("bad type %#v\n", data)
  208. // return
  209. // }
  210. // // check to validate it if possible, and drop if not valid
  211. // switch t := tm.(type) {
  212. // case types.EventDataNewBlockHeader:
  213. // err := verifyHeader(s.client, t.Header)
  214. // if err != nil {
  215. // fmt.Printf("Invalid header: %#v\n", err)
  216. // return
  217. // }
  218. // case types.EventDataNewBlock:
  219. // err := verifyBlock(s.client, t.Block)
  220. // if err != nil {
  221. // fmt.Printf("Invalid block: %#v\n", err)
  222. // return
  223. // }
  224. // // TODO: can we verify tx as well? anything else
  225. // }
  226. // // looks good, we fire it
  227. // s.EventSwitch.FireEvent(event, data)
  228. // }
  229. // func verifyHeader(c rpcclient.Client, head *types.Header) error {
  230. // // get a checkpoint to verify from
  231. // commit, err := c.Commit(&head.Height)
  232. // if err != nil {
  233. // return err
  234. // }
  235. // check := certclient.CommitFromResult(commit)
  236. // return ValidateHeader(head, check)
  237. // }
  238. //
  239. // func verifyBlock(c rpcclient.Client, block *types.Block) error {
  240. // // get a checkpoint to verify from
  241. // commit, err := c.Commit(&block.Height)
  242. // if err != nil {
  243. // return err
  244. // }
  245. // check := certclient.CommitFromResult(commit)
  246. // return ValidateBlock(block, check)
  247. // }