mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-26 03:01:42 +00:00
premerge2: rpc -> rpc/tendermint
This commit is contained in:
@ -1,65 +0,0 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
merktest "github.com/tendermint/merkleeyes/testutil"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func TestHeaderEvents(t *testing.T) {
|
||||
require := require.New(t)
|
||||
for i, c := range GetClients() {
|
||||
// start for this test it if it wasn't already running
|
||||
if !c.IsRunning() {
|
||||
// if so, then we start it, listen, and stop it.
|
||||
st, err := c.Start()
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
require.True(st, "%d", i)
|
||||
defer c.Stop()
|
||||
}
|
||||
|
||||
evtTyp := types.EventStringNewBlockHeader()
|
||||
evt, err := client.WaitForOneEvent(c, evtTyp, 1*time.Second)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
_, ok := evt.(types.EventDataNewBlockHeader)
|
||||
require.True(ok, "%d: %#v", i, evt)
|
||||
// TODO: more checks...
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxEvents(t *testing.T) {
|
||||
require := require.New(t)
|
||||
for i, c := range GetClients() {
|
||||
// start for this test it if it wasn't already running
|
||||
if !c.IsRunning() {
|
||||
// if so, then we start it, listen, and stop it.
|
||||
st, err := c.Start()
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
require.True(st, "%d", i)
|
||||
defer c.Stop()
|
||||
}
|
||||
|
||||
// make the tx
|
||||
_, _, tx := merktest.MakeTxKV()
|
||||
evtTyp := types.EventStringTx(types.Tx(tx))
|
||||
|
||||
// send async
|
||||
txres, err := c.BroadcastTxAsync(tx)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.True(txres.Code.IsOK())
|
||||
|
||||
// and wait for confirmation
|
||||
evt, err := client.WaitForOneEvent(c, evtTyp, 1*time.Second)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
// and make sure it has the proper info
|
||||
txe, ok := evt.(types.EventDataTx)
|
||||
require.True(ok, "%d: %#v", i, evt)
|
||||
// make sure this is the proper tx
|
||||
require.EqualValues(tx, txe.Tx)
|
||||
require.True(txe.Code.IsOK())
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
cmn "github.com/tendermint/go-common"
|
||||
events "github.com/tendermint/go-events"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Waiter is informed of current height, decided whether to quit early
|
||||
type Waiter func(delta int) (abort error)
|
||||
|
||||
// DefaultWaitStrategy is the standard backoff algorithm,
|
||||
// but you can plug in another one
|
||||
func DefaultWaitStrategy(delta int) (abort error) {
|
||||
if delta > 10 {
|
||||
return errors.Errorf("Waiting for %d blocks... aborting", delta)
|
||||
} else if delta > 0 {
|
||||
// estimate of wait time....
|
||||
// wait half a second for the next block (in progress)
|
||||
// plus one second for every full block
|
||||
delay := time.Duration(delta-1)*time.Second + 500*time.Millisecond
|
||||
time.Sleep(delay)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for height will poll status at reasonable intervals until
|
||||
// the block at the given height is available.
|
||||
//
|
||||
// If waiter is nil, we use DefaultWaitStrategy, but you can also
|
||||
// provide your own implementation
|
||||
func WaitForHeight(c StatusClient, h int, waiter Waiter) error {
|
||||
if waiter == nil {
|
||||
waiter = DefaultWaitStrategy
|
||||
}
|
||||
delta := 1
|
||||
for delta > 0 {
|
||||
s, err := c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delta = h - s.LatestBlockHeight
|
||||
// wait for the time, or abort early
|
||||
if err := waiter(delta); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForOneEvent subscribes to a websocket event for the given
|
||||
// event time and returns upon receiving it one time, or
|
||||
// when the timeout duration has expired.
|
||||
//
|
||||
// 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)
|
||||
|
||||
evts, quit := make(chan events.EventData, 10), make(chan bool, 1)
|
||||
// start timeout count-down
|
||||
go func() {
|
||||
time.Sleep(timeout)
|
||||
quit <- true
|
||||
}()
|
||||
|
||||
// register for the next event of this type
|
||||
evsw.AddListenerForEvent(listener, evtTyp, func(data events.EventData) {
|
||||
evts <- data
|
||||
})
|
||||
// make sure to unregister after the test is over
|
||||
defer evsw.RemoveListenerForEvent(evtTyp, listener)
|
||||
// defer evsw.RemoveListener(listener) // this also works
|
||||
|
||||
select {
|
||||
case <-quit:
|
||||
return nil, errors.New("timed out waiting for event")
|
||||
case evt := <-evts:
|
||||
tmevt, ok := evt.(types.TMEventData)
|
||||
if ok {
|
||||
return tmevt, nil
|
||||
}
|
||||
return nil, errors.Errorf("Got unexpected event type: %#v", evt)
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
"github.com/tendermint/tendermint/rpc/client/mock"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
func TestWaitForHeight(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
// test with error result - immediate failure
|
||||
m := &mock.StatusMock{
|
||||
Call: mock.Call{
|
||||
Error: errors.New("bye"),
|
||||
},
|
||||
}
|
||||
r := mock.NewStatusRecorder(m)
|
||||
|
||||
// connection failure always leads to error
|
||||
err := client.WaitForHeight(r, 8, nil)
|
||||
require.NotNil(err)
|
||||
require.Equal("bye", err.Error())
|
||||
// we called status once to check
|
||||
require.Equal(1, len(r.Calls))
|
||||
|
||||
// now set current block height to 10
|
||||
m.Call = mock.Call{
|
||||
Response: &ctypes.ResultStatus{LatestBlockHeight: 10},
|
||||
}
|
||||
|
||||
// we will not wait for more than 10 blocks
|
||||
err = client.WaitForHeight(r, 40, nil)
|
||||
require.NotNil(err)
|
||||
require.True(strings.Contains(err.Error(), "aborting"))
|
||||
// we called status once more to check
|
||||
require.Equal(2, len(r.Calls))
|
||||
|
||||
// waiting for the past returns immediately
|
||||
err = client.WaitForHeight(r, 5, nil)
|
||||
require.Nil(err)
|
||||
// we called status once more to check
|
||||
require.Equal(3, len(r.Calls))
|
||||
|
||||
// since we can't update in a background goroutine (test --race)
|
||||
// we use the callback to update the status height
|
||||
myWaiter := func(delta int) error {
|
||||
// update the height for the next call
|
||||
m.Call.Response = &ctypes.ResultStatus{LatestBlockHeight: 15}
|
||||
return client.DefaultWaitStrategy(delta)
|
||||
}
|
||||
|
||||
// we wait for a few blocks
|
||||
err = client.WaitForHeight(r, 12, myWaiter)
|
||||
require.Nil(err)
|
||||
// we called status once to check
|
||||
require.Equal(5, len(r.Calls))
|
||||
|
||||
pre := r.Calls[3]
|
||||
require.Nil(pre.Error)
|
||||
prer, ok := pre.Response.(*ctypes.ResultStatus)
|
||||
require.True(ok)
|
||||
assert.Equal(10, prer.LatestBlockHeight)
|
||||
|
||||
post := r.Calls[4]
|
||||
require.Nil(post.Error)
|
||||
postr, ok := post.Response.(*ctypes.ResultStatus)
|
||||
require.True(ok)
|
||||
assert.Equal(15, postr.LatestBlockHeight)
|
||||
}
|
@ -1,367 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
data "github.com/tendermint/go-data"
|
||||
events "github.com/tendermint/go-events"
|
||||
"github.com/tendermint/go-rpc/client"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
/*
|
||||
HTTP is a Client implementation that communicates
|
||||
with a tendermint node over json rpc and websockets.
|
||||
|
||||
This is the main implementation you probably want to use in
|
||||
production code. There are other implementations when calling
|
||||
the tendermint node in-process (local), or when you want to mock
|
||||
out the server for test code (mock).
|
||||
*/
|
||||
type HTTP struct {
|
||||
remote string
|
||||
rpc *rpcclient.JSONRPCClient
|
||||
*WSEvents
|
||||
}
|
||||
|
||||
// New takes a remote endpoint in the form tcp://<host>:<port>
|
||||
// and the websocket path (which always seems to be "/websocket")
|
||||
func NewHTTP(remote, wsEndpoint string) *HTTP {
|
||||
return &HTTP{
|
||||
rpc: rpcclient.NewJSONRPCClient(remote),
|
||||
remote: remote,
|
||||
WSEvents: newWSEvents(remote, wsEndpoint),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HTTP) _assertIsClient() Client {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *HTTP) _assertIsNetworkClient() NetworkClient {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *HTTP) _assertIsEventSwitch() types.EventSwitch {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *HTTP) Status() (*ctypes.ResultStatus, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("status", map[string]interface{}{}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Status")
|
||||
}
|
||||
// note: panics if rpc doesn't match. okay???
|
||||
return (*tmResult).(*ctypes.ResultStatus), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("abci_info", map[string]interface{}{}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "ABCIInfo")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultABCIInfo), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("abci_query",
|
||||
map[string]interface{}{"path": path, "data": data, "prove": prove},
|
||||
tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "ABCIQuery")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultABCIQuery), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("broadcast_tx_commit", map[string]interface{}{"tx": tx}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "broadcast_tx_commit")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultBroadcastTxCommit), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
return c.broadcastTX("broadcast_tx_async", tx)
|
||||
}
|
||||
|
||||
func (c *HTTP) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
return c.broadcastTX("broadcast_tx_sync", tx)
|
||||
}
|
||||
|
||||
func (c *HTTP) broadcastTX(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call(route, map[string]interface{}{"tx": tx}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, route)
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultBroadcastTx), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("net_info", map[string]interface{}{}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "NetInfo")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultNetInfo), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("dump_consensus_state", map[string]interface{}{}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "DumpConsensusState")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultDumpConsensusState), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("blockchain",
|
||||
map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight},
|
||||
tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "BlockchainInfo")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultBlockchainInfo), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) Genesis() (*ctypes.ResultGenesis, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("genesis", map[string]interface{}{}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Genesis")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultGenesis), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) Block(height int) (*ctypes.ResultBlock, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("block", map[string]interface{}{"height": height}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Block")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultBlock), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) Commit(height int) (*ctypes.ResultCommit, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("commit", map[string]interface{}{"height": height}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Commit")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultCommit), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
query := map[string]interface{}{
|
||||
"hash": hash,
|
||||
"prove": prove,
|
||||
}
|
||||
_, err := c.rpc.Call("tx", query, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Tx")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultTx), nil
|
||||
}
|
||||
|
||||
func (c *HTTP) Validators() (*ctypes.ResultValidators, error) {
|
||||
tmResult := new(ctypes.TMResult)
|
||||
_, err := c.rpc.Call("validators", map[string]interface{}{}, tmResult)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Validators")
|
||||
}
|
||||
return (*tmResult).(*ctypes.ResultValidators), nil
|
||||
}
|
||||
|
||||
/** websocket event stuff here... **/
|
||||
|
||||
type WSEvents struct {
|
||||
types.EventSwitch
|
||||
remote string
|
||||
endpoint string
|
||||
ws *rpcclient.WSClient
|
||||
|
||||
// used for signaling the goroutine that feeds ws -> EventSwitch
|
||||
quit chan bool
|
||||
done chan bool
|
||||
|
||||
// used to maintain counts of actively listened events
|
||||
// so we can properly subscribe/unsubscribe
|
||||
// FIXME: thread-safety???
|
||||
// FIXME: reuse code from go-events???
|
||||
evtCount map[string]int // count how many time each event is subscribed
|
||||
listeners map[string][]string // keep track of which events each listener is listening to
|
||||
}
|
||||
|
||||
func newWSEvents(remote, endpoint string) *WSEvents {
|
||||
return &WSEvents{
|
||||
EventSwitch: types.NewEventSwitch(),
|
||||
endpoint: endpoint,
|
||||
remote: remote,
|
||||
quit: make(chan bool, 1),
|
||||
done: make(chan bool, 1),
|
||||
evtCount: map[string]int{},
|
||||
listeners: map[string][]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WSEvents) _assertIsEventSwitch() types.EventSwitch {
|
||||
return w
|
||||
}
|
||||
|
||||
// Start is the only way I could think the extend OnStart from
|
||||
// events.eventSwitch. If only it wasn't private...
|
||||
// BaseService.Start -> eventSwitch.OnStart -> WSEvents.Start
|
||||
func (w *WSEvents) Start() (bool, error) {
|
||||
st, err := w.EventSwitch.Start()
|
||||
// if we did start, then OnStart here...
|
||||
if st && err == nil {
|
||||
ws := rpcclient.NewWSClient(w.remote, w.endpoint)
|
||||
_, err = ws.Start()
|
||||
if err == nil {
|
||||
w.ws = ws
|
||||
go w.eventListener()
|
||||
}
|
||||
}
|
||||
return st, errors.Wrap(err, "StartWSEvent")
|
||||
}
|
||||
|
||||
// Stop wraps the BaseService/eventSwitch actions as Start does
|
||||
func (w *WSEvents) Stop() bool {
|
||||
stop := w.EventSwitch.Stop()
|
||||
if stop {
|
||||
// send a message to quit to stop the eventListener
|
||||
w.quit <- true
|
||||
<-w.done
|
||||
w.ws.Stop()
|
||||
w.ws = nil
|
||||
}
|
||||
return stop
|
||||
}
|
||||
|
||||
/** TODO: more intelligent subscriptions! **/
|
||||
func (w *WSEvents) AddListenerForEvent(listenerID, event string, cb events.EventCallback) {
|
||||
// no one listening -> subscribe
|
||||
if w.evtCount[event] == 0 {
|
||||
w.subscribe(event)
|
||||
}
|
||||
// if this listener was already listening to this event, return early
|
||||
for _, s := range w.listeners[listenerID] {
|
||||
if event == s {
|
||||
return
|
||||
}
|
||||
}
|
||||
// otherwise, add this event to this listener
|
||||
w.evtCount[event] += 1
|
||||
w.listeners[listenerID] = append(w.listeners[listenerID], event)
|
||||
w.EventSwitch.AddListenerForEvent(listenerID, event, cb)
|
||||
}
|
||||
|
||||
func (w *WSEvents) RemoveListenerForEvent(event string, listenerID string) {
|
||||
// if this listener is listening already, splice it out
|
||||
found := false
|
||||
l := w.listeners[listenerID]
|
||||
for i, s := range l {
|
||||
if event == s {
|
||||
found = true
|
||||
w.listeners[listenerID] = append(l[:i], l[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// if the listener wasn't already listening to the event, exit early
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
// now we can update the subscriptions
|
||||
w.evtCount[event] -= 1
|
||||
if w.evtCount[event] == 0 {
|
||||
w.unsubscribe(event)
|
||||
}
|
||||
w.EventSwitch.RemoveListenerForEvent(event, listenerID)
|
||||
}
|
||||
|
||||
func (w *WSEvents) RemoveListener(listenerID string) {
|
||||
// remove all counts for this listener
|
||||
for _, s := range w.listeners[listenerID] {
|
||||
w.evtCount[s] -= 1
|
||||
if w.evtCount[s] == 0 {
|
||||
w.unsubscribe(s)
|
||||
}
|
||||
}
|
||||
w.listeners[listenerID] = nil
|
||||
|
||||
// then let the switch do it's magic
|
||||
w.EventSwitch.RemoveListener(listenerID)
|
||||
}
|
||||
|
||||
// eventListener is an infinite loop pulling all websocket events
|
||||
// and pushing them to the EventSwitch.
|
||||
//
|
||||
// the goroutine only stops by closing quit
|
||||
func (w *WSEvents) eventListener() {
|
||||
for {
|
||||
select {
|
||||
case res := <-w.ws.ResultsCh:
|
||||
// res is json.RawMessage
|
||||
err := w.parseEvent(res)
|
||||
if err != nil {
|
||||
// FIXME: better logging/handling of errors??
|
||||
fmt.Printf("ws result: %+v\n", err)
|
||||
}
|
||||
case err := <-w.ws.ErrorsCh:
|
||||
// FIXME: better logging/handling of errors??
|
||||
fmt.Printf("ws err: %+v\n", err)
|
||||
case <-w.quit:
|
||||
// send a message so we can wait for the routine to exit
|
||||
// before cleaning up the w.ws stuff
|
||||
w.done <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseEvent unmarshals the json message and converts it into
|
||||
// some implementation of types.TMEventData, and sends it off
|
||||
// on the merry way to the EventSwitch
|
||||
func (w *WSEvents) parseEvent(data []byte) (err error) {
|
||||
result := new(ctypes.TMResult)
|
||||
wire.ReadJSONPtr(result, data, &err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
event, ok := (*result).(*ctypes.ResultEvent)
|
||||
if !ok {
|
||||
// ignore silently (eg. subscribe, unsubscribe and maybe other events)
|
||||
return nil
|
||||
}
|
||||
// looks good! let's fire this baby!
|
||||
w.EventSwitch.FireEvent(event.Name, event.Data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// no way of exposing these failures, so we panic.
|
||||
// is this right? or silently ignore???
|
||||
func (w *WSEvents) subscribe(event string) {
|
||||
err := w.ws.Subscribe(event)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WSEvents) unsubscribe(event string) {
|
||||
err := w.ws.Unsubscribe(event)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
package client provides a general purpose interface (Client) for connecting
|
||||
to a tendermint node, as well as higher-level functionality.
|
||||
|
||||
The main implementation for production code is client.HTTP, which
|
||||
connects via http to the jsonrpc interface of the tendermint node.
|
||||
|
||||
For connecting to a node running in the same process (eg. when
|
||||
compiling the abci app in the same process), you can use the client.Local
|
||||
implementation.
|
||||
|
||||
For mocking out server responses during testing to see behavior for
|
||||
arbitrary return values, use the mock package.
|
||||
|
||||
In addition to the Client interface, which should be used externally
|
||||
for maximum flexibility and testability, and two implementations,
|
||||
this package also provides helper functions that work on any Client
|
||||
implementation.
|
||||
*/
|
||||
package client
|
||||
|
||||
import (
|
||||
data "github.com/tendermint/go-data"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// ABCIClient groups together the functionality that principally
|
||||
// affects the ABCI app. In many cases this will be all we want,
|
||||
// so we can accept an interface which is easier to mock
|
||||
type ABCIClient interface {
|
||||
// reading from abci app
|
||||
ABCIInfo() (*ctypes.ResultABCIInfo, error)
|
||||
ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error)
|
||||
|
||||
// writing to abci app
|
||||
BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error)
|
||||
BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error)
|
||||
BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error)
|
||||
}
|
||||
|
||||
// SignClient groups together the interfaces need to get valid
|
||||
// signatures and prove anything about the chain
|
||||
type SignClient interface {
|
||||
Block(height int) (*ctypes.ResultBlock, error)
|
||||
Commit(height int) (*ctypes.ResultCommit, error)
|
||||
Validators() (*ctypes.ResultValidators, error)
|
||||
Tx(hash []byte, prove bool) (*ctypes.ResultTx, error)
|
||||
}
|
||||
|
||||
// HistoryClient shows us data from genesis to now in large chunks.
|
||||
type HistoryClient interface {
|
||||
Genesis() (*ctypes.ResultGenesis, error)
|
||||
BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error)
|
||||
}
|
||||
|
||||
type StatusClient interface {
|
||||
// general chain info
|
||||
Status() (*ctypes.ResultStatus, error)
|
||||
}
|
||||
|
||||
// Client wraps most important rpc calls a client would make
|
||||
// if you want to listen for events, test if it also
|
||||
// implements events.EventSwitch
|
||||
type Client interface {
|
||||
ABCIClient
|
||||
SignClient
|
||||
HistoryClient
|
||||
StatusClient
|
||||
|
||||
// this Client is reactive, you can subscribe to any TMEventData
|
||||
// type, given the proper string. see tendermint/types/events.go
|
||||
types.EventSwitch
|
||||
}
|
||||
|
||||
// NetworkClient is general info about the network state. May not
|
||||
// be needed usually.
|
||||
//
|
||||
// Not included in the Client interface, but generally implemented
|
||||
// by concrete implementations.
|
||||
type NetworkClient interface {
|
||||
NetInfo() (*ctypes.ResultNetInfo, error)
|
||||
DumpConsensusState() (*ctypes.ResultDumpConsensusState, error)
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
data "github.com/tendermint/go-data"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/rpc/core"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
/*
|
||||
Local is a Client implementation that directly executes the rpc
|
||||
functions on a given node, without going through HTTP or GRPC
|
||||
|
||||
This implementation is useful for:
|
||||
|
||||
* Running tests against a node in-process without the overhead
|
||||
of going through an http server
|
||||
* Communication between an ABCI app and tendermin core when they
|
||||
are compiled in process.
|
||||
|
||||
For real clients, you probably want to use client.HTTP. For more
|
||||
powerful control during testing, you probably want the "client/mock" package.
|
||||
*/
|
||||
type Local struct {
|
||||
node *nm.Node
|
||||
types.EventSwitch
|
||||
}
|
||||
|
||||
// NewLocal configures a client that calls the Node directly.
|
||||
//
|
||||
// Note that given how rpc/core works with package singletons, that
|
||||
// you can only have one node per process. So make sure test cases
|
||||
// don't run in parallel, or try to simulate an entire network in
|
||||
// one process...
|
||||
func NewLocal(node *nm.Node) Local {
|
||||
node.ConfigureRPC()
|
||||
return Local{
|
||||
node: node,
|
||||
EventSwitch: node.EventSwitch(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c Local) _assertIsClient() Client {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Local) _assertIsNetworkClient() NetworkClient {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Local) Status() (*ctypes.ResultStatus, error) {
|
||||
return core.Status()
|
||||
}
|
||||
|
||||
func (c Local) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
|
||||
return core.ABCIInfo()
|
||||
}
|
||||
|
||||
func (c Local) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) {
|
||||
return core.ABCIQuery(path, data, prove)
|
||||
}
|
||||
|
||||
func (c Local) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
return core.BroadcastTxCommit(tx)
|
||||
}
|
||||
|
||||
func (c Local) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
return core.BroadcastTxAsync(tx)
|
||||
}
|
||||
|
||||
func (c Local) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
return core.BroadcastTxSync(tx)
|
||||
}
|
||||
|
||||
func (c Local) NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||
return core.NetInfo()
|
||||
}
|
||||
|
||||
func (c Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
||||
return core.DumpConsensusState()
|
||||
}
|
||||
|
||||
func (c Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) {
|
||||
return core.UnsafeDialSeeds(seeds)
|
||||
}
|
||||
|
||||
func (c Local) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) {
|
||||
return core.BlockchainInfo(minHeight, maxHeight)
|
||||
}
|
||||
|
||||
func (c Local) Genesis() (*ctypes.ResultGenesis, error) {
|
||||
return core.Genesis()
|
||||
}
|
||||
|
||||
func (c Local) Block(height int) (*ctypes.ResultBlock, error) {
|
||||
return core.Block(height)
|
||||
}
|
||||
|
||||
func (c Local) Commit(height int) (*ctypes.ResultCommit, error) {
|
||||
return core.Commit(height)
|
||||
}
|
||||
|
||||
func (c Local) Validators() (*ctypes.ResultValidators, error) {
|
||||
return core.Validators()
|
||||
}
|
||||
|
||||
func (c Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
|
||||
return core.Tx(hash, prove)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
meapp "github.com/tendermint/merkleeyes/app"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
)
|
||||
|
||||
var node *nm.Node
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// start a tendermint node (and merkleeyes) in the background to test against
|
||||
app := meapp.NewMerkleEyesApp("", 100)
|
||||
node = rpctest.StartTendermint(app)
|
||||
code := m.Run()
|
||||
|
||||
// and shut down proper at the end
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
os.Exit(code)
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
data "github.com/tendermint/go-data"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// ABCIApp will send all abci related request to the named app,
|
||||
// so you can test app behavior from a client without needing
|
||||
// an entire tendermint node
|
||||
type ABCIApp struct {
|
||||
App abci.Application
|
||||
}
|
||||
|
||||
func (a ABCIApp) _assertABCIClient() client.ABCIClient {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a ABCIApp) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
|
||||
return &ctypes.ResultABCIInfo{a.App.Info()}, nil
|
||||
}
|
||||
|
||||
func (a ABCIApp) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) {
|
||||
q := a.App.Query(abci.RequestQuery{data, path, 0, prove})
|
||||
return &ctypes.ResultABCIQuery{q}, nil
|
||||
}
|
||||
|
||||
func (a ABCIApp) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
res := ctypes.ResultBroadcastTxCommit{}
|
||||
c := a.App.CheckTx(tx)
|
||||
res.CheckTx = &abci.ResponseCheckTx{c.Code, c.Data, c.Log}
|
||||
if !c.IsOK() {
|
||||
return &res, nil
|
||||
}
|
||||
d := a.App.DeliverTx(tx)
|
||||
res.DeliverTx = &abci.ResponseDeliverTx{d.Code, d.Data, d.Log}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (a ABCIApp) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
c := a.App.CheckTx(tx)
|
||||
// and this gets writen in a background thread...
|
||||
if c.IsOK() {
|
||||
go func() { a.App.DeliverTx(tx) }()
|
||||
}
|
||||
return &ctypes.ResultBroadcastTx{c.Code, c.Data, c.Log, tx.Hash()}, nil
|
||||
}
|
||||
|
||||
func (a ABCIApp) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
c := a.App.CheckTx(tx)
|
||||
// and this gets writen in a background thread...
|
||||
if c.IsOK() {
|
||||
go func() { a.App.DeliverTx(tx) }()
|
||||
}
|
||||
return &ctypes.ResultBroadcastTx{c.Code, c.Data, c.Log, tx.Hash()}, nil
|
||||
}
|
||||
|
||||
// ABCIMock will send all abci related request to the named app,
|
||||
// so you can test app behavior from a client without needing
|
||||
// an entire tendermint node
|
||||
type ABCIMock struct {
|
||||
Info Call
|
||||
Query Call
|
||||
BroadcastCommit Call
|
||||
Broadcast Call
|
||||
}
|
||||
|
||||
func (m ABCIMock) _assertABCIClient() client.ABCIClient {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m ABCIMock) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
|
||||
res, err := m.Info.GetResponse(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultABCIInfo{res.(abci.ResponseInfo)}, nil
|
||||
}
|
||||
|
||||
func (m ABCIMock) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) {
|
||||
res, err := m.Query.GetResponse(QueryArgs{path, data, prove})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ctypes.ResultABCIQuery{res.(abci.ResponseQuery)}, nil
|
||||
}
|
||||
|
||||
func (m ABCIMock) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
res, err := m.BroadcastCommit.GetResponse(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.(*ctypes.ResultBroadcastTxCommit), nil
|
||||
}
|
||||
|
||||
func (m ABCIMock) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
res, err := m.Broadcast.GetResponse(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.(*ctypes.ResultBroadcastTx), nil
|
||||
}
|
||||
|
||||
func (m ABCIMock) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
res, err := m.Broadcast.GetResponse(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.(*ctypes.ResultBroadcastTx), nil
|
||||
}
|
||||
|
||||
// ABCIRecorder can wrap another type (ABCIApp, ABCIMock, or Client)
|
||||
// and record all ABCI related calls.
|
||||
type ABCIRecorder struct {
|
||||
Client client.ABCIClient
|
||||
Calls []Call
|
||||
}
|
||||
|
||||
func NewABCIRecorder(client client.ABCIClient) *ABCIRecorder {
|
||||
return &ABCIRecorder{
|
||||
Client: client,
|
||||
Calls: []Call{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ABCIRecorder) _assertABCIClient() client.ABCIClient {
|
||||
return r
|
||||
}
|
||||
|
||||
type QueryArgs struct {
|
||||
Path string
|
||||
Data data.Bytes
|
||||
Prove bool
|
||||
}
|
||||
|
||||
func (r *ABCIRecorder) addCall(call Call) {
|
||||
r.Calls = append(r.Calls, call)
|
||||
}
|
||||
|
||||
func (r *ABCIRecorder) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
|
||||
res, err := r.Client.ABCIInfo()
|
||||
r.addCall(Call{
|
||||
Name: "abci_info",
|
||||
Response: res,
|
||||
Error: err,
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (r *ABCIRecorder) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) {
|
||||
res, err := r.Client.ABCIQuery(path, data, prove)
|
||||
r.addCall(Call{
|
||||
Name: "abci_query",
|
||||
Args: QueryArgs{path, data, prove},
|
||||
Response: res,
|
||||
Error: err,
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (r *ABCIRecorder) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
res, err := r.Client.BroadcastTxCommit(tx)
|
||||
r.addCall(Call{
|
||||
Name: "broadcast_tx_commit",
|
||||
Args: tx,
|
||||
Response: res,
|
||||
Error: err,
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (r *ABCIRecorder) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
res, err := r.Client.BroadcastTxAsync(tx)
|
||||
r.addCall(Call{
|
||||
Name: "broadcast_tx_async",
|
||||
Args: tx,
|
||||
Response: res,
|
||||
Error: err,
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (r *ABCIRecorder) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
res, err := r.Client.BroadcastTxSync(tx)
|
||||
r.addCall(Call{
|
||||
Name: "broadcast_tx_sync",
|
||||
Args: tx,
|
||||
Response: res,
|
||||
Error: err,
|
||||
})
|
||||
return res, err
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
package mock_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/abci/example/dummy"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
data "github.com/tendermint/go-data"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/tendermint/tendermint/rpc/client/mock"
|
||||
)
|
||||
|
||||
func TestABCIMock(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
key, value := []byte("foo"), []byte("bar")
|
||||
height := uint64(10)
|
||||
goodTx := types.Tx{0x01, 0xff}
|
||||
badTx := types.Tx{0x12, 0x21}
|
||||
|
||||
m := mock.ABCIMock{
|
||||
Info: mock.Call{Error: errors.New("foobar")},
|
||||
Query: mock.Call{Response: abci.ResponseQuery{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Height: height,
|
||||
}},
|
||||
// Broadcast commit depends on call
|
||||
BroadcastCommit: mock.Call{
|
||||
Args: goodTx,
|
||||
Response: &ctypes.ResultBroadcastTxCommit{
|
||||
CheckTx: &abci.ResponseCheckTx{Data: data.Bytes("stand")},
|
||||
DeliverTx: &abci.ResponseDeliverTx{Data: data.Bytes("deliver")},
|
||||
},
|
||||
Error: errors.New("bad tx"),
|
||||
},
|
||||
Broadcast: mock.Call{Error: errors.New("must commit")},
|
||||
}
|
||||
|
||||
// now, let's try to make some calls
|
||||
_, err := m.ABCIInfo()
|
||||
require.NotNil(err)
|
||||
assert.Equal("foobar", err.Error())
|
||||
|
||||
// query always returns the response
|
||||
query, err := m.ABCIQuery("/", nil, false)
|
||||
require.Nil(err)
|
||||
require.NotNil(query)
|
||||
assert.Equal(key, query.Response.GetKey())
|
||||
assert.Equal(value, query.Response.GetValue())
|
||||
assert.Equal(height, query.Response.GetHeight())
|
||||
|
||||
// non-commit calls always return errors
|
||||
_, err = m.BroadcastTxSync(goodTx)
|
||||
require.NotNil(err)
|
||||
assert.Equal("must commit", err.Error())
|
||||
_, err = m.BroadcastTxAsync(goodTx)
|
||||
require.NotNil(err)
|
||||
assert.Equal("must commit", err.Error())
|
||||
|
||||
// commit depends on the input
|
||||
_, err = m.BroadcastTxCommit(badTx)
|
||||
require.NotNil(err)
|
||||
assert.Equal("bad tx", err.Error())
|
||||
bres, err := m.BroadcastTxCommit(goodTx)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.EqualValues(0, bres.CheckTx.Code)
|
||||
assert.EqualValues("stand", bres.CheckTx.Data)
|
||||
assert.EqualValues("deliver", bres.DeliverTx.Data)
|
||||
}
|
||||
|
||||
func TestABCIRecorder(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
m := mock.ABCIMock{
|
||||
Info: mock.Call{Response: abci.ResponseInfo{
|
||||
Data: "data",
|
||||
Version: "v0.9.9",
|
||||
}},
|
||||
Query: mock.Call{Error: errors.New("query")},
|
||||
Broadcast: mock.Call{Error: errors.New("broadcast")},
|
||||
BroadcastCommit: mock.Call{Error: errors.New("broadcast_commit")},
|
||||
}
|
||||
r := mock.NewABCIRecorder(m)
|
||||
|
||||
require.Equal(0, len(r.Calls))
|
||||
|
||||
r.ABCIInfo()
|
||||
r.ABCIQuery("path", data.Bytes("data"), true)
|
||||
require.Equal(2, len(r.Calls))
|
||||
|
||||
info := r.Calls[0]
|
||||
assert.Equal("abci_info", info.Name)
|
||||
assert.Nil(info.Error)
|
||||
assert.Nil(info.Args)
|
||||
require.NotNil(info.Response)
|
||||
ir, ok := info.Response.(*ctypes.ResultABCIInfo)
|
||||
require.True(ok)
|
||||
assert.Equal("data", ir.Response.Data)
|
||||
assert.Equal("v0.9.9", ir.Response.Version)
|
||||
|
||||
query := r.Calls[1]
|
||||
assert.Equal("abci_query", query.Name)
|
||||
assert.Nil(query.Response)
|
||||
require.NotNil(query.Error)
|
||||
assert.Equal("query", query.Error.Error())
|
||||
require.NotNil(query.Args)
|
||||
qa, ok := query.Args.(mock.QueryArgs)
|
||||
require.True(ok)
|
||||
assert.Equal("path", qa.Path)
|
||||
assert.EqualValues("data", qa.Data)
|
||||
assert.True(qa.Prove)
|
||||
|
||||
// now add some broadcasts
|
||||
txs := []types.Tx{{1}, {2}, {3}}
|
||||
r.BroadcastTxCommit(txs[0])
|
||||
r.BroadcastTxSync(txs[1])
|
||||
r.BroadcastTxAsync(txs[2])
|
||||
|
||||
require.Equal(5, len(r.Calls))
|
||||
|
||||
bc := r.Calls[2]
|
||||
assert.Equal("broadcast_tx_commit", bc.Name)
|
||||
assert.Nil(bc.Response)
|
||||
require.NotNil(bc.Error)
|
||||
assert.EqualValues(bc.Args, txs[0])
|
||||
|
||||
bs := r.Calls[3]
|
||||
assert.Equal("broadcast_tx_sync", bs.Name)
|
||||
assert.Nil(bs.Response)
|
||||
require.NotNil(bs.Error)
|
||||
assert.EqualValues(bs.Args, txs[1])
|
||||
|
||||
ba := r.Calls[4]
|
||||
assert.Equal("broadcast_tx_async", ba.Name)
|
||||
assert.Nil(ba.Response)
|
||||
require.NotNil(ba.Error)
|
||||
assert.EqualValues(ba.Args, txs[2])
|
||||
}
|
||||
|
||||
func TestABCIApp(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
app := dummy.NewDummyApplication()
|
||||
m := mock.ABCIApp{app}
|
||||
|
||||
// get some info
|
||||
info, err := m.ABCIInfo()
|
||||
require.Nil(err)
|
||||
assert.Equal(`{"size":0}`, info.Response.GetData())
|
||||
|
||||
// add a key
|
||||
key, value := "foo", "bar"
|
||||
tx := fmt.Sprintf("%s=%s", key, value)
|
||||
res, err := m.BroadcastTxCommit(types.Tx(tx))
|
||||
require.Nil(err)
|
||||
assert.True(res.CheckTx.Code.IsOK())
|
||||
require.NotNil(res.DeliverTx)
|
||||
assert.True(res.DeliverTx.Code.IsOK())
|
||||
|
||||
// check the key
|
||||
qres, err := m.ABCIQuery("/key", data.Bytes(key), false)
|
||||
require.Nil(err)
|
||||
assert.EqualValues(value, qres.Response.Value)
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
/*
|
||||
package mock returns a Client implementation that
|
||||
accepts various (mock) implementations of the various methods.
|
||||
|
||||
This implementation is useful for using in tests, when you don't
|
||||
need a real server, but want a high-level of control about
|
||||
the server response you want to mock (eg. error handling),
|
||||
or if you just want to record the calls to verify in your tests.
|
||||
|
||||
For real clients, you probably want the "http" package. If you
|
||||
want to directly call a tendermint node in process, you can use the
|
||||
"local" package.
|
||||
*/
|
||||
package mock
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
data "github.com/tendermint/go-data"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
"github.com/tendermint/tendermint/rpc/core"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Client wraps arbitrary implementations of the various interfaces.
|
||||
//
|
||||
// We provide a few choices to mock out each one in this package.
|
||||
// Nothing hidden here, so no New function, just construct it from
|
||||
// some parts, and swap them out them during the tests.
|
||||
type Client struct {
|
||||
client.ABCIClient
|
||||
client.SignClient
|
||||
client.HistoryClient
|
||||
client.StatusClient
|
||||
// create a mock with types.NewEventSwitch()
|
||||
types.EventSwitch
|
||||
}
|
||||
|
||||
func (c Client) _assertIsClient() client.Client {
|
||||
return c
|
||||
}
|
||||
|
||||
// Call is used by recorders to save a call and response.
|
||||
// It can also be used to configure mock responses.
|
||||
//
|
||||
type Call struct {
|
||||
Name string
|
||||
Args interface{}
|
||||
Response interface{}
|
||||
Error error
|
||||
}
|
||||
|
||||
// GetResponse will generate the apporiate response for us, when
|
||||
// using the Call struct to configure a Mock handler.
|
||||
//
|
||||
// When configuring a response, if only one of Response or Error is
|
||||
// set then that will always be returned. If both are set, then
|
||||
// we return Response if the Args match the set args, Error otherwise.
|
||||
func (c Call) GetResponse(args interface{}) (interface{}, error) {
|
||||
// handle the case with no response
|
||||
if c.Response == nil {
|
||||
if c.Error == nil {
|
||||
panic("Misconfigured call, you must set either Response or Error")
|
||||
}
|
||||
return nil, c.Error
|
||||
}
|
||||
// response without error
|
||||
if c.Error == nil {
|
||||
return c.Response, nil
|
||||
}
|
||||
// have both, we must check args....
|
||||
if reflect.DeepEqual(args, c.Args) {
|
||||
return c.Response, nil
|
||||
}
|
||||
return nil, c.Error
|
||||
}
|
||||
|
||||
func (c Client) Status() (*ctypes.ResultStatus, error) {
|
||||
return core.Status()
|
||||
}
|
||||
|
||||
func (c Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
|
||||
return core.ABCIInfo()
|
||||
}
|
||||
|
||||
func (c Client) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) {
|
||||
return core.ABCIQuery(path, data, prove)
|
||||
}
|
||||
|
||||
func (c Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
return core.BroadcastTxCommit(tx)
|
||||
}
|
||||
|
||||
func (c Client) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
return core.BroadcastTxAsync(tx)
|
||||
}
|
||||
|
||||
func (c Client) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
||||
return core.BroadcastTxSync(tx)
|
||||
}
|
||||
|
||||
func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||
return core.NetInfo()
|
||||
}
|
||||
|
||||
func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) {
|
||||
return core.UnsafeDialSeeds(seeds)
|
||||
}
|
||||
|
||||
func (c Client) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) {
|
||||
return core.BlockchainInfo(minHeight, maxHeight)
|
||||
}
|
||||
|
||||
func (c Client) Genesis() (*ctypes.ResultGenesis, error) {
|
||||
return core.Genesis()
|
||||
}
|
||||
|
||||
func (c Client) Block(height int) (*ctypes.ResultBlock, error) {
|
||||
return core.Block(height)
|
||||
}
|
||||
|
||||
func (c Client) Commit(height int) (*ctypes.ResultCommit, error) {
|
||||
return core.Commit(height)
|
||||
}
|
||||
|
||||
func (c Client) Validators() (*ctypes.ResultValidators, error) {
|
||||
return core.Validators()
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// StatusMock returns the result specified by the Call
|
||||
type StatusMock struct {
|
||||
Call
|
||||
}
|
||||
|
||||
func (m *StatusMock) _assertStatusClient() client.StatusClient {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *StatusMock) Status() (*ctypes.ResultStatus, error) {
|
||||
res, err := m.GetResponse(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.(*ctypes.ResultStatus), nil
|
||||
}
|
||||
|
||||
// StatusRecorder can wrap another type (StatusMock, full client)
|
||||
// and record the status calls
|
||||
type StatusRecorder struct {
|
||||
Client client.StatusClient
|
||||
Calls []Call
|
||||
}
|
||||
|
||||
func NewStatusRecorder(client client.StatusClient) *StatusRecorder {
|
||||
return &StatusRecorder{
|
||||
Client: client,
|
||||
Calls: []Call{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *StatusRecorder) _assertStatusClient() client.StatusClient {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *StatusRecorder) addCall(call Call) {
|
||||
r.Calls = append(r.Calls, call)
|
||||
}
|
||||
|
||||
func (r *StatusRecorder) Status() (*ctypes.ResultStatus, error) {
|
||||
res, err := r.Client.Status()
|
||||
r.addCall(Call{
|
||||
Name: "status",
|
||||
Response: res,
|
||||
Error: err,
|
||||
})
|
||||
return res, err
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package mock_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
data "github.com/tendermint/go-data"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/tendermint/tendermint/rpc/client/mock"
|
||||
)
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
m := &mock.StatusMock{
|
||||
Call: mock.Call{
|
||||
Response: &ctypes.ResultStatus{
|
||||
LatestBlockHash: data.Bytes("block"),
|
||||
LatestAppHash: data.Bytes("app"),
|
||||
LatestBlockHeight: 10,
|
||||
}},
|
||||
}
|
||||
|
||||
r := mock.NewStatusRecorder(m)
|
||||
require.Equal(0, len(r.Calls))
|
||||
|
||||
// make sure response works proper
|
||||
status, err := r.Status()
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.EqualValues("block", status.LatestBlockHash)
|
||||
assert.EqualValues(10, status.LatestBlockHeight)
|
||||
|
||||
// make sure recorder works properly
|
||||
require.Equal(1, len(r.Calls))
|
||||
rs := r.Calls[0]
|
||||
assert.Equal("status", rs.Name)
|
||||
assert.Nil(rs.Args)
|
||||
assert.Nil(rs.Error)
|
||||
require.NotNil(rs.Response)
|
||||
st, ok := rs.Response.(*ctypes.ResultStatus)
|
||||
require.True(ok)
|
||||
assert.EqualValues("block", st.LatestBlockHash)
|
||||
assert.EqualValues(10, st.LatestBlockHeight)
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
package client_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
merkle "github.com/tendermint/go-merkle"
|
||||
merktest "github.com/tendermint/merkleeyes/testutil"
|
||||
"github.com/tendermint/tendermint/rpc/client"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func getHTTPClient() *client.HTTP {
|
||||
rpcAddr := rpctest.GetConfig().GetString("rpc_laddr")
|
||||
return client.NewHTTP(rpcAddr, "/websocket")
|
||||
}
|
||||
|
||||
func getLocalClient() client.Local {
|
||||
return client.NewLocal(node)
|
||||
}
|
||||
|
||||
// GetClients returns a slice of clients for table-driven tests
|
||||
func GetClients() []client.Client {
|
||||
return []client.Client{
|
||||
getHTTPClient(),
|
||||
getLocalClient(),
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure status is correct (we connect properly)
|
||||
func TestStatus(t *testing.T) {
|
||||
for i, c := range GetClients() {
|
||||
chainID := rpctest.GetConfig().GetString("chain_id")
|
||||
status, err := c.Status()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
assert.Equal(t, chainID, status.NodeInfo.Network)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure info is correct (we connect properly)
|
||||
func TestInfo(t *testing.T) {
|
||||
for i, c := range GetClients() {
|
||||
// status, err := c.Status()
|
||||
// require.Nil(t, err, "%+v", err)
|
||||
info, err := c.ABCIInfo()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
// TODO: this is not correct - fix merkleeyes!
|
||||
// assert.EqualValues(t, status.LatestBlockHeight, info.Response.LastBlockHeight)
|
||||
assert.True(t, strings.HasPrefix(info.Response.Data, "size"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetInfo(t *testing.T) {
|
||||
for i, c := range GetClients() {
|
||||
nc, ok := c.(client.NetworkClient)
|
||||
require.True(t, ok, "%d", i)
|
||||
netinfo, err := nc.NetInfo()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
assert.True(t, netinfo.Listening)
|
||||
assert.Equal(t, 0, len(netinfo.Peers))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpConsensusState(t *testing.T) {
|
||||
for i, c := range GetClients() {
|
||||
// FIXME: fix server so it doesn't panic on invalid input
|
||||
nc, ok := c.(client.NetworkClient)
|
||||
require.True(t, ok, "%d", i)
|
||||
cons, err := nc.DumpConsensusState()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
assert.NotEmpty(t, cons.RoundState)
|
||||
assert.Empty(t, cons.PeerRoundStates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesisAndValidators(t *testing.T) {
|
||||
for i, c := range GetClients() {
|
||||
chainID := rpctest.GetConfig().GetString("chain_id")
|
||||
|
||||
// make sure this is the right genesis file
|
||||
gen, err := c.Genesis()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
assert.Equal(t, chainID, gen.Genesis.ChainID)
|
||||
// get the genesis validator
|
||||
require.Equal(t, 1, len(gen.Genesis.Validators))
|
||||
gval := gen.Genesis.Validators[0]
|
||||
|
||||
// get the current validators
|
||||
vals, err := c.Validators()
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
require.Equal(t, 1, len(vals.Validators))
|
||||
val := vals.Validators[0]
|
||||
|
||||
// make sure the current set is also the genesis set
|
||||
assert.Equal(t, gval.Amount, val.VotingPower)
|
||||
assert.Equal(t, gval.PubKey, val.PubKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Make some app checks
|
||||
func TestAppCalls(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
for i, c := range GetClients() {
|
||||
|
||||
// get an offset of height to avoid racing and guessing
|
||||
s, err := c.Status()
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
// sh is start height or status height
|
||||
sh := s.LatestBlockHeight
|
||||
|
||||
// look for the future
|
||||
_, err = c.Block(sh + 2)
|
||||
assert.NotNil(err) // no block yet
|
||||
|
||||
// write something
|
||||
k, v, tx := merktest.MakeTxKV()
|
||||
bres, err := c.BroadcastTxCommit(tx)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
require.True(bres.DeliverTx.GetCode().IsOK())
|
||||
txh := bres.Height
|
||||
apph := txh + 1 // this is where the tx will be applied to the state
|
||||
|
||||
// wait before querying
|
||||
client.WaitForHeight(c, apph, nil)
|
||||
qres, err := c.ABCIQuery("/key", k, false)
|
||||
if assert.Nil(err) && assert.True(qres.Response.Code.IsOK()) {
|
||||
data := qres.Response
|
||||
// assert.Equal(k, data.GetKey()) // only returned for proofs
|
||||
assert.Equal(v, data.GetValue())
|
||||
}
|
||||
|
||||
// make sure we can lookup the tx with proof
|
||||
// ptx, err := c.Tx(bres.Hash, true)
|
||||
ptx, err := c.Tx(bres.Hash, true)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(txh, ptx.Height)
|
||||
assert.Equal(types.Tx(tx), ptx.Tx)
|
||||
|
||||
// and we can even check the block is added
|
||||
block, err := c.Block(apph)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
appHash := block.BlockMeta.Header.AppHash
|
||||
assert.True(len(appHash) > 0)
|
||||
assert.EqualValues(apph, block.BlockMeta.Header.Height)
|
||||
|
||||
// check blockchain info, now that we know there is info
|
||||
// TODO: is this commented somewhere that they are returned
|
||||
// in order of descending height???
|
||||
info, err := c.BlockchainInfo(apph, apph)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
assert.True(info.LastHeight >= apph)
|
||||
if assert.Equal(1, len(info.BlockMetas)) {
|
||||
lastMeta := info.BlockMetas[0]
|
||||
assert.EqualValues(apph, lastMeta.Header.Height)
|
||||
bMeta := block.BlockMeta
|
||||
assert.Equal(bMeta.Header.AppHash, lastMeta.Header.AppHash)
|
||||
assert.Equal(bMeta.BlockID, lastMeta.BlockID)
|
||||
}
|
||||
|
||||
// and get the corresponding commit with the same apphash
|
||||
commit, err := c.Commit(apph)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
cappHash := commit.Header.AppHash
|
||||
assert.Equal(appHash, cappHash)
|
||||
assert.NotNil(commit.Commit)
|
||||
|
||||
// compare the commits (note Commit(2) has commit from Block(3))
|
||||
commit2, err := c.Commit(apph - 1)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(block.Block.LastCommit, commit2.Commit)
|
||||
|
||||
// and we got a proof that works!
|
||||
pres, err := c.ABCIQuery("/key", k, true)
|
||||
if assert.Nil(err) && assert.True(pres.Response.Code.IsOK()) {
|
||||
proof, err := merkle.ReadProof(pres.Response.GetProof())
|
||||
if assert.Nil(err) {
|
||||
key := pres.Response.GetKey()
|
||||
value := pres.Response.GetValue()
|
||||
assert.Equal(appHash, proof.RootHash)
|
||||
valid := proof.Verify(key, value, appHash)
|
||||
assert.True(valid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user