github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/rpc/client/local/local.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/franono/tendermint/libs/bytes"
     9  	"github.com/franono/tendermint/libs/log"
    10  	tmpubsub "github.com/franono/tendermint/libs/pubsub"
    11  	tmquery "github.com/franono/tendermint/libs/pubsub/query"
    12  	nm "github.com/franono/tendermint/node"
    13  	rpcclient "github.com/franono/tendermint/rpc/client"
    14  	"github.com/franono/tendermint/rpc/core"
    15  	ctypes "github.com/franono/tendermint/rpc/core/types"
    16  	rpctypes "github.com/franono/tendermint/rpc/jsonrpc/types"
    17  	"github.com/franono/tendermint/types"
    18  )
    19  
    20  /*
    21  Local is a Client implementation that directly executes the rpc
    22  functions on a given node, without going through HTTP or GRPC.
    23  
    24  This implementation is useful for:
    25  
    26  * Running tests against a node in-process without the overhead
    27  of going through an http server
    28  * Communication between an ABCI app and Tendermint core when they
    29  are compiled in process.
    30  
    31  For real clients, you probably want to use client.HTTP.  For more
    32  powerful control during testing, you probably want the "client/mock" package.
    33  
    34  You can subscribe for any event published by Tendermint using Subscribe method.
    35  Note delivery is best-effort. If you don't read events fast enough, Tendermint
    36  might cancel the subscription. The client will attempt to resubscribe (you
    37  don't need to do anything). It will keep trying indefinitely with exponential
    38  backoff (10ms -> 20ms -> 40ms) until successful.
    39  */
    40  type Local struct {
    41  	*types.EventBus
    42  	Logger log.Logger
    43  	ctx    *rpctypes.Context
    44  }
    45  
    46  // NewLocal configures a client that calls the Node directly.
    47  //
    48  // Note that given how rpc/core works with package singletons, that
    49  // you can only have one node per process.  So make sure test cases
    50  // don't run in parallel, or try to simulate an entire network in
    51  // one process...
    52  func New(node *nm.Node) *Local {
    53  	node.ConfigureRPC()
    54  	return &Local{
    55  		EventBus: node.EventBus(),
    56  		Logger:   log.NewNopLogger(),
    57  		ctx:      &rpctypes.Context{},
    58  	}
    59  }
    60  
    61  var _ rpcclient.Client = (*Local)(nil)
    62  
    63  // SetLogger allows to set a logger on the client.
    64  func (c *Local) SetLogger(l log.Logger) {
    65  	c.Logger = l
    66  }
    67  
    68  func (c *Local) Status() (*ctypes.ResultStatus, error) {
    69  	return core.Status(c.ctx)
    70  }
    71  
    72  func (c *Local) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
    73  	return core.ABCIInfo(c.ctx)
    74  }
    75  
    76  func (c *Local) ABCIQuery(path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) {
    77  	return c.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions)
    78  }
    79  
    80  func (c *Local) ABCIQueryWithOptions(
    81  	path string,
    82  	data bytes.HexBytes,
    83  	opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
    84  	return core.ABCIQuery(c.ctx, path, data, opts.Height, opts.Prove)
    85  }
    86  
    87  func (c *Local) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
    88  	return core.BroadcastTxCommit(c.ctx, tx)
    89  }
    90  
    91  func (c *Local) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    92  	return core.BroadcastTxAsync(c.ctx, tx)
    93  }
    94  
    95  func (c *Local) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    96  	return core.BroadcastTxSync(c.ctx, tx)
    97  }
    98  
    99  func (c *Local) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) {
   100  	return core.UnconfirmedTxs(c.ctx, limit)
   101  }
   102  
   103  func (c *Local) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) {
   104  	return core.NumUnconfirmedTxs(c.ctx)
   105  }
   106  
   107  func (c *Local) NetInfo() (*ctypes.ResultNetInfo, error) {
   108  	return core.NetInfo(c.ctx)
   109  }
   110  
   111  func (c *Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
   112  	return core.DumpConsensusState(c.ctx)
   113  }
   114  
   115  func (c *Local) ConsensusState() (*ctypes.ResultConsensusState, error) {
   116  	return core.ConsensusState(c.ctx)
   117  }
   118  
   119  func (c *Local) ConsensusParams(height *int64) (*ctypes.ResultConsensusParams, error) {
   120  	return core.ConsensusParams(c.ctx, height)
   121  }
   122  
   123  func (c *Local) Health() (*ctypes.ResultHealth, error) {
   124  	return core.Health(c.ctx)
   125  }
   126  
   127  func (c *Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) {
   128  	return core.UnsafeDialSeeds(c.ctx, seeds)
   129  }
   130  
   131  func (c *Local) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) {
   132  	return core.UnsafeDialPeers(c.ctx, peers, persistent)
   133  }
   134  
   135  func (c *Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
   136  	return core.BlockchainInfo(c.ctx, minHeight, maxHeight)
   137  }
   138  
   139  func (c *Local) Genesis() (*ctypes.ResultGenesis, error) {
   140  	return core.Genesis(c.ctx)
   141  }
   142  
   143  func (c *Local) Block(height *int64) (*ctypes.ResultBlock, error) {
   144  	return core.Block(c.ctx, height)
   145  }
   146  
   147  func (c *Local) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) {
   148  	return core.BlockResults(c.ctx, height)
   149  }
   150  
   151  func (c *Local) Commit(height *int64) (*ctypes.ResultCommit, error) {
   152  	return core.Commit(c.ctx, height)
   153  }
   154  
   155  func (c *Local) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) {
   156  	return core.Validators(c.ctx, height, page, perPage)
   157  }
   158  
   159  func (c *Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
   160  	return core.Tx(c.ctx, hash, prove)
   161  }
   162  
   163  func (c *Local) TxSearch(query string, prove bool, page, perPage int, orderBy string) (
   164  	*ctypes.ResultTxSearch, error) {
   165  	return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy)
   166  }
   167  
   168  func (c *Local) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
   169  	return core.BroadcastEvidence(c.ctx, ev)
   170  }
   171  
   172  func (c *Local) Subscribe(
   173  	ctx context.Context,
   174  	subscriber,
   175  	query string,
   176  	outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
   177  	q, err := tmquery.New(query)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("failed to parse query: %w", err)
   180  	}
   181  
   182  	outCap := 1
   183  	if len(outCapacity) > 0 {
   184  		outCap = outCapacity[0]
   185  	}
   186  
   187  	var sub types.Subscription
   188  	if outCap > 0 {
   189  		sub, err = c.EventBus.Subscribe(ctx, subscriber, q, outCap)
   190  	} else {
   191  		sub, err = c.EventBus.SubscribeUnbuffered(ctx, subscriber, q)
   192  	}
   193  	if err != nil {
   194  		return nil, fmt.Errorf("failed to subscribe: %w", err)
   195  	}
   196  
   197  	outc := make(chan ctypes.ResultEvent, outCap)
   198  	go c.eventsRoutine(sub, subscriber, q, outc)
   199  
   200  	return outc, nil
   201  }
   202  
   203  func (c *Local) eventsRoutine(
   204  	sub types.Subscription,
   205  	subscriber string,
   206  	q tmpubsub.Query,
   207  	outc chan<- ctypes.ResultEvent) {
   208  	for {
   209  		select {
   210  		case msg := <-sub.Out():
   211  			result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
   212  			if cap(outc) == 0 {
   213  				outc <- result
   214  			} else {
   215  				select {
   216  				case outc <- result:
   217  				default:
   218  					c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query)
   219  				}
   220  			}
   221  		case <-sub.Cancelled():
   222  			if sub.Err() == tmpubsub.ErrUnsubscribed {
   223  				return
   224  			}
   225  
   226  			c.Logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String())
   227  			sub = c.resubscribe(subscriber, q)
   228  			if sub == nil { // client was stopped
   229  				return
   230  			}
   231  		case <-c.Quit():
   232  			return
   233  		}
   234  	}
   235  }
   236  
   237  // Try to resubscribe with exponential backoff.
   238  func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscription {
   239  	attempts := 0
   240  	for {
   241  		if !c.IsRunning() {
   242  			return nil
   243  		}
   244  
   245  		sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q)
   246  		if err == nil {
   247  			return sub
   248  		}
   249  
   250  		attempts++
   251  		time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
   252  	}
   253  }
   254  
   255  func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error {
   256  	q, err := tmquery.New(query)
   257  	if err != nil {
   258  		return fmt.Errorf("failed to parse query: %w", err)
   259  	}
   260  	return c.EventBus.Unsubscribe(ctx, subscriber, q)
   261  }
   262  
   263  func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
   264  	return c.EventBus.UnsubscribeAll(ctx, subscriber)
   265  }