mirror of
https://github.com/tendermint/tendermint.git
synced 2026-08-17 20:56:14 +00:00
This is the interface shared by types that can be used as event data in, for example, subscriptions via the RPC. To be compatible with the RPC service, data need to support JSON encoding. Require this as part of the interface.
153 lines
4.1 KiB
Go
153 lines
4.1 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
// Waiter is informed of current height, decided whether to quit early
|
|
type Waiter func(delta int64) (abort error)
|
|
|
|
// DefaultWaitStrategy is the standard backoff algorithm,
|
|
// but you can plug in another one
|
|
func DefaultWaitStrategy(delta int64) (abort error) {
|
|
if delta > 10 {
|
|
return fmt.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(ctx context.Context, c StatusClient, h int64, waiter Waiter) error {
|
|
if waiter == nil {
|
|
waiter = DefaultWaitStrategy
|
|
}
|
|
delta := int64(1)
|
|
for delta > 0 {
|
|
s, err := c.Status(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
delta = h - s.SyncInfo.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(c EventsClient, eventValue string, timeout time.Duration) (types.EventData, error) {
|
|
const subscriber = "helpers"
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
// register for the next event of this type
|
|
eventCh, err := c.Subscribe(ctx, subscriber, types.QueryForEvent(eventValue).String())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to subscribe: %w", err)
|
|
}
|
|
|
|
// make sure to un-register after the test is over
|
|
defer func() {
|
|
if deferErr := c.UnsubscribeAll(ctx, subscriber); deferErr != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case event := <-eventCh:
|
|
return event.Data, nil
|
|
case <-ctx.Done():
|
|
return nil, errors.New("timed out waiting for event")
|
|
}
|
|
}
|
|
|
|
var (
|
|
// ErrClientRunning is returned by Start when the client is already running.
|
|
ErrClientRunning = errors.New("client already running")
|
|
|
|
// ErrClientNotRunning is returned by Stop when the client is not running.
|
|
ErrClientNotRunning = errors.New("client is not running")
|
|
)
|
|
|
|
// RunState is a helper that a client implementation can embed to implement
|
|
// common plumbing for keeping track of run state and logging.
|
|
//
|
|
// TODO(creachadair): This type is a temporary measure, and will be removed.
|
|
// See the discussion on #6971.
|
|
type RunState struct {
|
|
Logger log.Logger
|
|
|
|
mu sync.Mutex
|
|
name string
|
|
isRunning bool
|
|
}
|
|
|
|
// NewRunState returns a new unstarted run state tracker with the given logging
|
|
// label and log sink. If logger == nil, a no-op logger is provided by default.
|
|
func NewRunState(name string, logger log.Logger) *RunState {
|
|
if logger == nil {
|
|
logger = log.NewNopLogger()
|
|
}
|
|
return &RunState{
|
|
name: name,
|
|
Logger: logger,
|
|
}
|
|
}
|
|
|
|
// Start sets the state to running, or reports an error.
|
|
func (r *RunState) Start(context.Context) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.isRunning {
|
|
r.Logger.Error("not starting client, it is already started", "client", r.name)
|
|
return ErrClientRunning
|
|
}
|
|
r.Logger.Info("starting client", "client", r.name)
|
|
r.isRunning = true
|
|
return nil
|
|
}
|
|
|
|
// Stop sets the state to not running, or reports an error.
|
|
func (r *RunState) Stop() error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if !r.isRunning {
|
|
r.Logger.Error("not stopping client; it is already stopped", "client", r.name)
|
|
return ErrClientNotRunning
|
|
}
|
|
r.Logger.Info("stopping client", "client", r.name)
|
|
r.isRunning = false
|
|
return nil
|
|
}
|
|
|
|
// IsRunning reports whether the state is running.
|
|
func (r *RunState) IsRunning() bool {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return r.isRunning
|
|
}
|