@ -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 | |||
} |
@ -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) | |||
} |
@ -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 | |||
} |
@ -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) | |||
} |