Add flexible rpc client to tendermint#418
Conversation
|
Forgot to mention, this is in response to issue #416 |
Codecov Report
@@ Coverage Diff @@
## develop #418 +/- ##
==========================================
+ Coverage 50.08% 51.6% +1.51%
==========================================
Files 38 44 +6
Lines 4460 4897 +437
==========================================
+ Hits 2234 2527 +293
- Misses 2028 2156 +128
- Partials 198 214 +16Continue to review full report at Codecov.
|
|
Wow! So much code! |
rpc/client/http/rpc_test.go
Outdated
|
|
||
| // subscribe to a transaction event | ||
| _, _, tx := MakeTxKV() | ||
| // this causes a panic in tendermint core!!! |
There was a problem hiding this comment.
old comment... I think I pushed a fix to tendermint in October, before I got hired even.
this client code was born out of a personal project. But yeah, due to some typos and crazy interface magic, subscribing to a tx event would cause tendermint to panic:
#285
Removing this comment now.
rpc/client/local/app_test.go
Outdated
| ) | ||
|
|
||
| // MakeTxKV returns a text transaction, allong with expected key, value pair | ||
| func MakeTxKV() ([]byte, []byte, []byte) { |
There was a problem hiding this comment.
not a fan of go-common, but code duplication here hurts me a bit.
There was a problem hiding this comment.
Didn't know this was in go-common, will take a look and fix
There was a problem hiding this comment.
No, I meant that we should probably move this into one package and reuse rather than duplicating the code.
There was a problem hiding this comment.
that code belongs in merkleeyes actually... will put it there.
There was a problem hiding this comment.
RandAsciiBytes is almost the same as common.RandStr, will use RandStr instead
rpc/client/local/client.go
Outdated
| package local | ||
|
|
||
| import ( | ||
| nm "github.com/tendermint/tendermint/node" |
There was a problem hiding this comment.
yup, i use node too much as a local variable and didn't want naming conflict
rpc/client/local/rpc_test.go
Outdated
| @@ -0,0 +1,183 @@ | |||
| package local_test | |||
There was a problem hiding this comment.
also, this testing suite is duplicated in 2 places. I understand we want to test both implementations. Not sure what is the best way to deal with this.
There was a problem hiding this comment.
Yes, this hurts me a bit too. If you have a clean solution so I can convert this to a table-driven test, let me know.
Also, for code coverage, it only really measures the coverage from tests in the same package.
I'll think of a way to do code reuse. Let me know if it is too ugly.
| App abci.Application | ||
| } | ||
|
|
||
| func (a ABCIApp) _assertABCIClient() client.ABCIClient { |
There was a problem hiding this comment.
Why do we need such methods (_assertIsClient, _assertABCI...)?
There was a problem hiding this comment.
Not needed by nice helpers. Talked with Bucky about them, he liked the idea too.
Basically, when you create Struct explicitly to fulfill an interface, you can assert so if the methods don't match, you get a compile error in the module itself, not when someone else tried to use it.
Matt had this problem with endblock changing SIG and then quietly being ignored in basecoin without any compiler warnings.
There was a problem hiding this comment.
we don't need this anymore. It was only an issue because there was an embedded interface type in abci.Application (the BlockchainAware bit). Now that has been moved into the abci.Application, so as long as that interface is being passed around (it is, everywhere), then the compiler already type checks
There was a problem hiding this comment.
In this case, I want to make sure my mock structs properly implement the client interface.
the abci app was just to explain to anton why the idea came up.
rpc/client/mock/abci.go
Outdated
| } | ||
|
|
||
| func (a ABCIApp) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | ||
| d := a.App.DeliverTx(tx) |
There was a problem hiding this comment.
I think it should be CheckTx here, no? and empty result in this case
// Returns right away, with no response
func BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
err := mempool.CheckTx(tx, nil)
if err != nil {
return nil, fmt.Errorf("Error broadcasting transaction: %v", err)
}
return &ctypes.ResultBroadcastTx{}, nil
}
There was a problem hiding this comment.
Yes, will fix. Good eyes.
There was a problem hiding this comment.
We want this action to actually happen in the mock, but I change it to return the results of CheckTx, and trigger DeliverTx in a go-routine on success, so we can actually simulate this race in test cases if we want.
rpc/client/mock/abci.go
Outdated
| } | ||
|
|
||
| func (a ABCIApp) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { | ||
| d := a.App.DeliverTx(tx) |
| clientJSON = client.NewClientJSONRPC(requestAddr) | ||
| clientGRPC = core_grpc.StartGRPCClient(grpcAddr) | ||
| // f**ing long, but unique for each test | ||
| func makePathname() string { |
There was a problem hiding this comment.
9bcf3a3 to
7fe9ff5
Compare
|
@melekes I pulled local and http subpackages into client, renamed all structs, rewrote the rpc_tests to be table driven and accept any number of client.Client implementations. Oh, and rebased everything on develop (which is why all commits are "new") Please take a look at 7fe9ff5 and let me know what you think. |
|
@ebuchman I did lots of cleanup from anton's comments, please take a look. I'd like to pull out the code in light-client. |
|
LGTM |
ebuchman
left a comment
There was a problem hiding this comment.
Fantastic improvements. Love that we have a merkle proof test in there now. Thanks Frey.
rpc/client/interface.go
Outdated
| type NetworkClient interface { | ||
| NetInfo() (*ctypes.ResultNetInfo, error) | ||
| // remove this??? | ||
| DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) |
There was a problem hiding this comment.
remove it.
maybe we should add DumpConsensusState tho - some reason not too?
rpc/client/localclient.go
Outdated
| } | ||
|
|
||
| /** websocket event stuff here... **/ | ||
|
|
There was a problem hiding this comment.
To be implemented with the local event system?
rpc/client/mock/client.go
Outdated
| return core.Validators() | ||
| } | ||
|
|
||
| /** websocket event stuff here... **/ |
| assert.EqualValues(h, block.BlockMeta.Header.Height) | ||
|
|
||
| // check blockchain info, now that we know there is info | ||
| // TODO: is this commented somewhere that they are returned |
There was a problem hiding this comment.
No - we ought to add more detail here https://tendermint.com/docs/internals/rpc
| require.Nil(err, "%d: %+v", i, err) | ||
| assert.Equal(block.Block.LastCommit, commit2.Commit) | ||
|
|
||
| // and we got a proof that works! |
rpc/client/rpc_test.go
Outdated
| select { | ||
| case res := <-r: | ||
| checkData(res, ctypes.ResultTypeEvent) | ||
| // this is good.. let's get the data... ugh... |
There was a problem hiding this comment.
hm ... so what's up here?
|
I'll clean this up tomorrow. Thanks for the review. Yeah, all the event stuff issue stuff is not really supported well. I added what I had to the client.HTTP only and the user has to do all the casting. I want to figure out a good way of exposing events, getting feedback from people trying to use it, and then make a nice event system for 0.10 and add this to the Client interface. Note I had trouble parsing the event that came out - some json blob... maybe we can overhaul the json format for 0.10 as well... |
ee9671b to
98450ee
Compare
|
Addressed some changes. Docs missing and thinking about how/if to expose websockets? Maybe just expose an EventSwitch on the client? Does it make sense to work on that today? Or rather targeting 0.9.1? |
rpc/client/helpers.go
Outdated
| evts, quit := make(chan events.EventData, 10), make(chan bool, 1) | ||
| // start timeout count-down | ||
| go func() { | ||
| time.Sleep(1 * time.Second) |
There was a problem hiding this comment.
huh? comment on the name of the channel?
I have a timeout as time.Duration, so I picked another name. Don't mind changing names, if you have an idea.
There was a problem hiding this comment.
shouldn't time.Sleep(1 * time.Second) be time.Sleep(timeout * time.Second)
| // This handles subscribing and unsubscribing under the hood | ||
| func WaitForOneEvent(evsw types.EventSwitch, | ||
| evtTyp string, timeout time.Duration) (types.TMEventData, error) { | ||
| listener := cmn.RandStr(12) |
There was a problem hiding this comment.
this might not be a good idea. maybe we push it up to the interface and let the tests pass in random junk
There was a problem hiding this comment.
Which part is a bad idea? Waiting? subscribing for the events?
There was a problem hiding this comment.
sorry I just meant using a random string for the listener. I'm just thinking of the day there might be many calls to WaitForOneEvent and no one will know which listener is which
There was a problem hiding this comment.
the "listener" is just for that one call-back. I can let the caller decide, but it will probably be hard-coded various places. and duplicate names is worse, then second might not get the message.
| }) | ||
| // make sure to unregister after the test is over | ||
| // TODO: why doesn't the other call work??? | ||
| // defer evsw.RemoveListenerForEvent(listener, evtTyp) |
There was a problem hiding this comment.
On client.Local, if I just call RemoveListener, it doesn't properly deregister. And in the end the listener fills up the channel and the node blocks and tests hang.
It seemed to deep to debug (Node.EventSwitch()), so I just put a TODO.
In any case, the fix is out of scope of this pr.
| // used to maintain counts of actively listened events | ||
| // so we can properly subscribe/unsubscribe | ||
| // FIXME: thread-safety??? | ||
| // FIXME: reuse code from go-events??? |
There was a problem hiding this comment.
hmmm - why are we replicating all this code?
There was a problem hiding this comment.
Because it is impossible to reuse code from go-event. All private (package local). I tried... then I did it. As above, I am down to improve, but changes to go-event are out of scope. Otherwise this PR will never get finished.
Signed-off-by: Thane Thomson <connect@thanethomson.com>

This code to call the rpc was in light-client, but I abstracted it to make it more testable, and provided mocks, so we can actually can simulate the server.
A bit of clean up of the existing code to test the rpc interface, and then a lot of code in
rpc/client/*I would like to get this in by 0.9.0 and then use it as the basis for light-client (so I can clean up that code as well).
Please let me know if I should change anything. Mocks are not finished, especially as faking sigs is hard, but they are halfway done and already provide a boon for testing (
client/helpers_test.go).