github.com/fiagdao/tendermint@v0.32.11-0.20220824195748-2087fcc480c1/rpc/client/local/local.go (about)

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