github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/rpc/client/local/local.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/tendermint/tendermint/libs/bytes"
     9  	"github.com/tendermint/tendermint/libs/log"
    10  	tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
    11  	tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
    12  	nm "github.com/tendermint/tendermint/node"
    13  	rpcclient "github.com/tendermint/tendermint/rpc/client"
    14  	"github.com/tendermint/tendermint/rpc/core"
    15  	ctypes "github.com/tendermint/tendermint/rpc/core/types"
    16  	rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
    17  	"github.com/tendermint/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) CheckTx(tx types.Tx) (*ctypes.ResultCheckTx, error) {
   108  	return core.CheckTx(c.ctx, tx)
   109  }
   110  
   111  func (c *Local) NetInfo() (*ctypes.ResultNetInfo, error) {
   112  	return core.NetInfo(c.ctx)
   113  }
   114  
   115  func (c *Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
   116  	return core.DumpConsensusState(c.ctx)
   117  }
   118  
   119  func (c *Local) ConsensusState() (*ctypes.ResultConsensusState, error) {
   120  	return core.ConsensusState(c.ctx)
   121  }
   122  
   123  func (c *Local) ConsensusParams(height *int64) (*ctypes.ResultConsensusParams, error) {
   124  	return core.ConsensusParams(c.ctx, height)
   125  }
   126  
   127  func (c *Local) Health() (*ctypes.ResultHealth, error) {
   128  	return core.Health(c.ctx)
   129  }
   130  
   131  func (c *Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) {
   132  	return core.UnsafeDialSeeds(c.ctx, seeds)
   133  }
   134  
   135  func (c *Local) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) {
   136  	return core.UnsafeDialPeers(c.ctx, peers, persistent)
   137  }
   138  
   139  func (c *Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
   140  	return core.BlockchainInfo(c.ctx, minHeight, maxHeight)
   141  }
   142  
   143  func (c *Local) Genesis() (*ctypes.ResultGenesis, error) {
   144  	return core.Genesis(c.ctx)
   145  }
   146  
   147  func (c *Local) Block(height *int64) (*ctypes.ResultBlock, error) {
   148  	return core.Block(c.ctx, height)
   149  }
   150  
   151  func (c *Local) BlockByHash(hash []byte) (*ctypes.ResultBlock, error) {
   152  	return core.BlockByHash(c.ctx, hash)
   153  }
   154  
   155  func (c *Local) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) {
   156  	return core.BlockResults(c.ctx, height)
   157  }
   158  
   159  func (c *Local) Commit(height *int64) (*ctypes.ResultCommit, error) {
   160  	return core.Commit(c.ctx, height)
   161  }
   162  
   163  func (c *Local) Validators(height *int64, page, perPage *int) (*ctypes.ResultValidators, error) {
   164  	return core.Validators(c.ctx, height, page, perPage)
   165  }
   166  
   167  func (c *Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
   168  	return core.Tx(c.ctx, hash, prove)
   169  }
   170  
   171  func (c *Local) TxSearch(query string, prove bool, page, perPage *int, orderBy string) (
   172  	*ctypes.ResultTxSearch, error) {
   173  	return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy)
   174  }
   175  
   176  func (c *Local) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
   177  	return core.BroadcastEvidence(c.ctx, ev)
   178  }
   179  
   180  func (c *Local) Subscribe(
   181  	ctx context.Context,
   182  	subscriber,
   183  	query string,
   184  	outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
   185  	q, err := tmquery.New(query)
   186  	if err != nil {
   187  		return nil, fmt.Errorf("failed to parse query: %w", err)
   188  	}
   189  
   190  	outCap := 1
   191  	if len(outCapacity) > 0 {
   192  		outCap = outCapacity[0]
   193  	}
   194  
   195  	var sub types.Subscription
   196  	if outCap > 0 {
   197  		sub, err = c.EventBus.Subscribe(ctx, subscriber, q, outCap)
   198  	} else {
   199  		sub, err = c.EventBus.SubscribeUnbuffered(ctx, subscriber, q)
   200  	}
   201  	if err != nil {
   202  		return nil, fmt.Errorf("failed to subscribe: %w", err)
   203  	}
   204  
   205  	outc := make(chan ctypes.ResultEvent, outCap)
   206  	go c.eventsRoutine(sub, subscriber, q, outc)
   207  
   208  	return outc, nil
   209  }
   210  
   211  func (c *Local) eventsRoutine(
   212  	sub types.Subscription,
   213  	subscriber string,
   214  	q tmpubsub.Query,
   215  	outc chan<- ctypes.ResultEvent) {
   216  	for {
   217  		select {
   218  		case msg := <-sub.Out():
   219  			result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
   220  			if cap(outc) == 0 {
   221  				outc <- result
   222  			} else {
   223  				select {
   224  				case outc <- result:
   225  				default:
   226  					c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query)
   227  				}
   228  			}
   229  		case <-sub.Cancelled():
   230  			if sub.Err() == tmpubsub.ErrUnsubscribed {
   231  				return
   232  			}
   233  
   234  			c.Logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String())
   235  			sub = c.resubscribe(subscriber, q)
   236  			if sub == nil { // client was stopped
   237  				return
   238  			}
   239  		case <-c.Quit():
   240  			return
   241  		}
   242  	}
   243  }
   244  
   245  // Try to resubscribe with exponential backoff.
   246  func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscription {
   247  	attempts := 0
   248  	for {
   249  		if !c.IsRunning() {
   250  			return nil
   251  		}
   252  
   253  		sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q)
   254  		if err == nil {
   255  			return sub
   256  		}
   257  
   258  		attempts++
   259  		time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
   260  	}
   261  }
   262  
   263  func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error {
   264  	q, err := tmquery.New(query)
   265  	if err != nil {
   266  		return fmt.Errorf("failed to parse query: %w", err)
   267  	}
   268  	return c.EventBus.Unsubscribe(ctx, subscriber, q)
   269  }
   270  
   271  func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
   272  	return c.EventBus.UnsubscribeAll(ctx, subscriber)
   273  }