diff --git a/rpc/client/helpers.go b/rpc/client/helpers.go new file mode 100644 index 000000000..555fa386e --- /dev/null +++ b/rpc/client/helpers.go @@ -0,0 +1,31 @@ +package client + +import ( + "time" + + "github.com/pkg/errors" +) + +// Wait for height will poll status at reasonable intervals until +// the block at the given height is available. +func WaitForHeight(c StatusClient, h int) error { + wait := 1 + for wait > 0 { + s, err := c.Status() + if err != nil { + return err + } + wait = h - s.LatestBlockHeight + if wait > 10 { + return errors.Errorf("Waiting for %d block... aborting", wait) + } else if wait > 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(wait-1)*time.Second + 500*time.Millisecond + time.Sleep(delay) + } + } + // guess we waited long enough + return nil +} diff --git a/rpc/client/helpers_test.go b/rpc/client/helpers_test.go new file mode 100644 index 000000000..9b3607a8c --- /dev/null +++ b/rpc/client/helpers_test.go @@ -0,0 +1,75 @@ +package client_test + +import ( + "errors" + "strings" + "testing" + "time" + + "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) + 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) + 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) + require.Nil(err) + // we called status once more to check + require.Equal(3, len(r.Calls)) + + // next tick in background while we wait + go func() { + time.Sleep(500 * time.Millisecond) + m.Call.Response = &ctypes.ResultStatus{LatestBlockHeight: 15} + }() + + // we wait for a few blocks + err = client.WaitForHeight(r, 12) + 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) +} diff --git a/rpc/client/mock/abci_test.go b/rpc/client/mock/abci_test.go index ce9a12728..77ad496b9 100644 --- a/rpc/client/mock/abci_test.go +++ b/rpc/client/mock/abci_test.go @@ -1,10 +1,11 @@ package mock_test import ( - "errors" "fmt" "testing" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/abci/example/dummy" diff --git a/rpc/client/mock/status.go b/rpc/client/mock/status.go new file mode 100644 index 000000000..af0f5335d --- /dev/null +++ b/rpc/client/mock/status.go @@ -0,0 +1,55 @@ +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 +} diff --git a/rpc/client/mock/status_test.go b/rpc/client/mock/status_test.go new file mode 100644 index 000000000..3e695cd57 --- /dev/null +++ b/rpc/client/mock/status_test.go @@ -0,0 +1,45 @@ +package mock_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + 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: []byte("block"), + LatestAppHash: []byte("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) +}