github.com/soomindae/tendermint@v0.0.5-0.20210528140126-84a0c70c8162/rpc/client/local/local.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/soomindae/tendermint/libs/bytes"
     9  	"github.com/soomindae/tendermint/libs/log"
    10  	tmpubsub "github.com/soomindae/tendermint/libs/pubsub"
    11  	tmquery "github.com/soomindae/tendermint/libs/pubsub/query"
    12  	nm "github.com/soomindae/tendermint/node"
    13  	rpcclient "github.com/soomindae/tendermint/rpc/client"
    14  	"github.com/soomindae/tendermint/rpc/core"
    15  	ctypes "github.com/soomindae/tendermint/rpc/core/types"
    16  	rpctypes "github.com/soomindae/tendermint/rpc/jsonrpc/types"
    17  	"github.com/soomindae/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  	if err := node.ConfigureRPC(); err != nil {
    54  		node.Logger.Error("Error configuring RPC", "err", err)
    55  	}
    56  	return &Local{
    57  		EventBus: node.EventBus(),
    58  		Logger:   log.NewNopLogger(),
    59  		ctx:      &rpctypes.Context{},
    60  	}
    61  }
    62  
    63  var _ rpcclient.Client = (*Local)(nil)
    64  
    65  // SetLogger allows to set a logger on the client.
    66  func (c *Local) SetLogger(l log.Logger) {
    67  	c.Logger = l
    68  }
    69  
    70  func (c *Local) Status(ctx context.Context) (*ctypes.ResultStatus, error) {
    71  	return core.Status(c.ctx)
    72  }
    73  
    74  func (c *Local) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) {
    75  	return core.ABCIInfo(c.ctx)
    76  }
    77  
    78  func (c *Local) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) {
    79  	return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions)
    80  }
    81  
    82  func (c *Local) ABCIQueryWithOptions(
    83  	ctx context.Context,
    84  	path string,
    85  	data bytes.HexBytes,
    86  	opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
    87  	return core.ABCIQuery(c.ctx, path, data, opts.Height, opts.Prove)
    88  }
    89  
    90  func (c *Local) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
    91  	return core.BroadcastTxCommit(c.ctx, tx)
    92  }
    93  
    94  func (c *Local) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    95  	return core.BroadcastTxAsync(c.ctx, tx)
    96  }
    97  
    98  func (c *Local) BroadcastTxSync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    99  	return core.BroadcastTxSync(c.ctx, tx)
   100  }
   101  
   102  func (c *Local) UnconfirmedTxs(ctx context.Context, limit *int) (*ctypes.ResultUnconfirmedTxs, error) {
   103  	return core.UnconfirmedTxs(c.ctx, limit)
   104  }
   105  
   106  func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*ctypes.ResultUnconfirmedTxs, error) {
   107  	return core.NumUnconfirmedTxs(c.ctx)
   108  }
   109  
   110  func (c *Local) CheckTx(ctx context.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) {
   111  	return core.CheckTx(c.ctx, tx)
   112  }
   113  
   114  func (c *Local) NetInfo(ctx context.Context) (*ctypes.ResultNetInfo, error) {
   115  	return core.NetInfo(c.ctx)
   116  }
   117  
   118  func (c *Local) DumpConsensusState(ctx context.Context) (*ctypes.ResultDumpConsensusState, error) {
   119  	return core.DumpConsensusState(c.ctx)
   120  }
   121  
   122  func (c *Local) ConsensusState(ctx context.Context) (*ctypes.ResultConsensusState, error) {
   123  	return core.ConsensusState(c.ctx)
   124  }
   125  
   126  func (c *Local) ConsensusParams(ctx context.Context, height *int64) (*ctypes.ResultConsensusParams, error) {
   127  	return core.ConsensusParams(c.ctx, height)
   128  }
   129  
   130  func (c *Local) Health(ctx context.Context) (*ctypes.ResultHealth, error) {
   131  	return core.Health(c.ctx)
   132  }
   133  
   134  func (c *Local) DialSeeds(ctx context.Context, seeds []string) (*ctypes.ResultDialSeeds, error) {
   135  	return core.UnsafeDialSeeds(c.ctx, seeds)
   136  }
   137  
   138  func (c *Local) DialPeers(
   139  	ctx context.Context,
   140  	peers []string,
   141  	persistent,
   142  	unconditional,
   143  	private bool,
   144  ) (*ctypes.ResultDialPeers, error) {
   145  	return core.UnsafeDialPeers(c.ctx, peers, persistent, unconditional, private)
   146  }
   147  
   148  func (c *Local) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
   149  	return core.BlockchainInfo(c.ctx, minHeight, maxHeight)
   150  }
   151  
   152  func (c *Local) Genesis(ctx context.Context) (*ctypes.ResultGenesis, error) {
   153  	return core.Genesis(c.ctx)
   154  }
   155  
   156  func (c *Local) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) {
   157  	return core.Block(c.ctx, height)
   158  }
   159  
   160  func (c *Local) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) {
   161  	return core.BlockByHash(c.ctx, hash)
   162  }
   163  
   164  func (c *Local) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) {
   165  	return core.BlockResults(c.ctx, height)
   166  }
   167  
   168  func (c *Local) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) {
   169  	return core.Commit(c.ctx, height)
   170  }
   171  
   172  func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) {
   173  	return core.Validators(c.ctx, height, page, perPage)
   174  }
   175  
   176  func (c *Local) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
   177  	return core.Tx(c.ctx, hash, prove)
   178  }
   179  
   180  func (c *Local) TxSearch(
   181  	_ context.Context,
   182  	query string,
   183  	prove bool,
   184  	page,
   185  	perPage *int,
   186  	orderBy string,
   187  ) (*ctypes.ResultTxSearch, error) {
   188  	return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy)
   189  }
   190  
   191  func (c *Local) BlockSearch(
   192  	_ context.Context,
   193  	query string,
   194  	page, perPage *int,
   195  	orderBy string,
   196  ) (*ctypes.ResultBlockSearch, error) {
   197  	return core.BlockSearch(c.ctx, query, page, perPage, orderBy)
   198  }
   199  
   200  func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
   201  	return core.BroadcastEvidence(c.ctx, ev)
   202  }
   203  
   204  func (c *Local) Subscribe(
   205  	ctx context.Context,
   206  	subscriber,
   207  	query string,
   208  	outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
   209  	q, err := tmquery.New(query)
   210  	if err != nil {
   211  		return nil, fmt.Errorf("failed to parse query: %w", err)
   212  	}
   213  
   214  	outCap := 1
   215  	if len(outCapacity) > 0 {
   216  		outCap = outCapacity[0]
   217  	}
   218  
   219  	var sub types.Subscription
   220  	if outCap > 0 {
   221  		sub, err = c.EventBus.Subscribe(ctx, subscriber, q, outCap)
   222  	} else {
   223  		sub, err = c.EventBus.SubscribeUnbuffered(ctx, subscriber, q)
   224  	}
   225  	if err != nil {
   226  		return nil, fmt.Errorf("failed to subscribe: %w", err)
   227  	}
   228  
   229  	outc := make(chan ctypes.ResultEvent, outCap)
   230  	go c.eventsRoutine(sub, subscriber, q, outc)
   231  
   232  	return outc, nil
   233  }
   234  
   235  func (c *Local) eventsRoutine(
   236  	sub types.Subscription,
   237  	subscriber string,
   238  	q tmpubsub.Query,
   239  	outc chan<- ctypes.ResultEvent) {
   240  	for {
   241  		select {
   242  		case msg := <-sub.Out():
   243  			result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
   244  			if cap(outc) == 0 {
   245  				outc <- result
   246  			} else {
   247  				select {
   248  				case outc <- result:
   249  				default:
   250  					c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query)
   251  				}
   252  			}
   253  		case <-sub.Cancelled():
   254  			if sub.Err() == tmpubsub.ErrUnsubscribed {
   255  				return
   256  			}
   257  
   258  			c.Logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String())
   259  			sub = c.resubscribe(subscriber, q)
   260  			if sub == nil { // client was stopped
   261  				return
   262  			}
   263  		case <-c.Quit():
   264  			return
   265  		}
   266  	}
   267  }
   268  
   269  // Try to resubscribe with exponential backoff.
   270  func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscription {
   271  	attempts := 0
   272  	for {
   273  		if !c.IsRunning() {
   274  			return nil
   275  		}
   276  
   277  		sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q)
   278  		if err == nil {
   279  			return sub
   280  		}
   281  
   282  		attempts++
   283  		time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
   284  	}
   285  }
   286  
   287  func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error {
   288  	q, err := tmquery.New(query)
   289  	if err != nil {
   290  		return fmt.Errorf("failed to parse query: %w", err)
   291  	}
   292  	return c.EventBus.Unsubscribe(ctx, subscriber, q)
   293  }
   294  
   295  func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
   296  	return c.EventBus.UnsubscribeAll(ctx, subscriber)
   297  }