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>
This commit is contained in:
Anton Kaliaev
2019-03-11 22:45:58 +04:00
committed by GitHub
parent 15f621141d
commit d741c7b478
36 changed files with 657 additions and 350 deletions

View File

@ -10,24 +10,23 @@ import (
"github.com/stretchr/testify/assert"
amino "github.com/tendermint/go-amino"
cmn "github.com/tendermint/tendermint/libs/common"
types "github.com/tendermint/tendermint/rpc/lib/types"
)
func TestParseJSONMap(t *testing.T) {
assert := assert.New(t)
input := []byte(`{"value":"1234","height":22}`)
// naive is float,string
var p1 map[string]interface{}
err := json.Unmarshal(input, &p1)
if assert.Nil(err) {
if assert.Nil(t, err) {
h, ok := p1["height"].(float64)
if assert.True(ok, "%#v", p1["height"]) {
assert.EqualValues(22, h)
if assert.True(t, ok, "%#v", p1["height"]) {
assert.EqualValues(t, 22, h)
}
v, ok := p1["value"].(string)
if assert.True(ok, "%#v", p1["value"]) {
assert.EqualValues("1234", v)
if assert.True(t, ok, "%#v", p1["value"]) {
assert.EqualValues(t, "1234", v)
}
}
@ -38,14 +37,14 @@ func TestParseJSONMap(t *testing.T) {
"height": &tmp,
}
err = json.Unmarshal(input, &p2)
if assert.Nil(err) {
if assert.Nil(t, err) {
h, ok := p2["height"].(float64)
if assert.True(ok, "%#v", p2["height"]) {
assert.EqualValues(22, h)
if assert.True(t, ok, "%#v", p2["height"]) {
assert.EqualValues(t, 22, h)
}
v, ok := p2["value"].(string)
if assert.True(ok, "%#v", p2["value"]) {
assert.EqualValues("1234", v)
if assert.True(t, ok, "%#v", p2["value"]) {
assert.EqualValues(t, "1234", v)
}
}
@ -60,14 +59,14 @@ func TestParseJSONMap(t *testing.T) {
Value: &cmn.HexBytes{},
}
err = json.Unmarshal(input, &p3)
if assert.Nil(err) {
if assert.Nil(t, err) {
h, ok := p3.Height.(*int)
if assert.True(ok, "%#v", p3.Height) {
assert.Equal(22, *h)
if assert.True(t, ok, "%#v", p3.Height) {
assert.Equal(t, 22, *h)
}
v, ok := p3.Value.(*cmn.HexBytes)
if assert.True(ok, "%#v", p3.Value) {
assert.EqualValues([]byte{0x12, 0x34}, *v)
if assert.True(t, ok, "%#v", p3.Value) {
assert.EqualValues(t, []byte{0x12, 0x34}, *v)
}
}
@ -77,46 +76,44 @@ func TestParseJSONMap(t *testing.T) {
Height int `json:"height"`
}{}
err = json.Unmarshal(input, &p4)
if assert.Nil(err) {
assert.EqualValues(22, p4.Height)
assert.EqualValues([]byte{0x12, 0x34}, p4.Value)
if assert.Nil(t, err) {
assert.EqualValues(t, 22, p4.Height)
assert.EqualValues(t, []byte{0x12, 0x34}, p4.Value)
}
// so, let's use this trick...
// dynamic keys on map, and we can deserialize to the desired types
var p5 map[string]*json.RawMessage
err = json.Unmarshal(input, &p5)
if assert.Nil(err) {
if assert.Nil(t, err) {
var h int
err = json.Unmarshal(*p5["height"], &h)
if assert.Nil(err) {
assert.Equal(22, h)
if assert.Nil(t, err) {
assert.Equal(t, 22, h)
}
var v cmn.HexBytes
err = json.Unmarshal(*p5["value"], &v)
if assert.Nil(err) {
assert.Equal(cmn.HexBytes{0x12, 0x34}, v)
if assert.Nil(t, err) {
assert.Equal(t, cmn.HexBytes{0x12, 0x34}, v)
}
}
}
func TestParseJSONArray(t *testing.T) {
assert := assert.New(t)
input := []byte(`["1234",22]`)
// naive is float,string
var p1 []interface{}
err := json.Unmarshal(input, &p1)
if assert.Nil(err) {
if assert.Nil(t, err) {
v, ok := p1[0].(string)
if assert.True(ok, "%#v", p1[0]) {
assert.EqualValues("1234", v)
if assert.True(t, ok, "%#v", p1[0]) {
assert.EqualValues(t, "1234", v)
}
h, ok := p1[1].(float64)
if assert.True(ok, "%#v", p1[1]) {
assert.EqualValues(22, h)
if assert.True(t, ok, "%#v", p1[1]) {
assert.EqualValues(t, 22, h)
}
}
@ -124,22 +121,20 @@ func TestParseJSONArray(t *testing.T) {
tmp := 0
p2 := []interface{}{&cmn.HexBytes{}, &tmp}
err = json.Unmarshal(input, &p2)
if assert.Nil(err) {
if assert.Nil(t, err) {
v, ok := p2[0].(*cmn.HexBytes)
if assert.True(ok, "%#v", p2[0]) {
assert.EqualValues([]byte{0x12, 0x34}, *v)
if assert.True(t, ok, "%#v", p2[0]) {
assert.EqualValues(t, []byte{0x12, 0x34}, *v)
}
h, ok := p2[1].(*int)
if assert.True(ok, "%#v", p2[1]) {
assert.EqualValues(22, *h)
if assert.True(t, ok, "%#v", p2[1]) {
assert.EqualValues(t, 22, *h)
}
}
}
func TestParseJSONRPC(t *testing.T) {
assert := assert.New(t)
demo := func(height int, name string) {}
demo := func(ctx *types.Context, height int, name string) {}
call := NewRPCFunc(demo, "height,name")
cdc := amino.NewCodec()
@ -162,14 +157,14 @@ func TestParseJSONRPC(t *testing.T) {
for idx, tc := range cases {
i := strconv.Itoa(idx)
data := []byte(tc.raw)
vals, err := jsonParamsToArgs(call, cdc, data, 0)
vals, err := jsonParamsToArgs(call, cdc, data)
if tc.fail {
assert.NotNil(err, i)
assert.NotNil(t, err, i)
} else {
assert.Nil(err, "%s: %+v", i, err)
if assert.Equal(2, len(vals), i) {
assert.Equal(tc.height, vals[0].Int(), i)
assert.Equal(tc.name, vals[1].String(), i)
assert.Nil(t, err, "%s: %+v", i, err)
if assert.Equal(t, 2, len(vals), i) {
assert.Equal(t, tc.height, vals[0].Int(), i)
assert.Equal(t, tc.name, vals[1].String(), i)
}
}
@ -177,8 +172,7 @@ func TestParseJSONRPC(t *testing.T) {
}
func TestParseURI(t *testing.T) {
demo := func(height int, name string) {}
demo := func(ctx *types.Context, height int, name string) {}
call := NewRPCFunc(demo, "height,name")
cdc := amino.NewCodec()