github.com/noirx94/tendermintmp@v0.0.1/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  	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) GenesisChunked(ctx context.Context, id uint) (*ctypes.ResultGenesisChunk, error) {
   157  	return core.GenesisChunked(c.ctx, id)
   158  }
   159  
   160  func (c *Local) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) {
   161  	return core.Block(c.ctx, height)
   162  }
   163  
   164  func (c *Local) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) {
   165  	return core.BlockByHash(c.ctx, hash)
   166  }
   167  
   168  func (c *Local) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) {
   169  	return core.BlockResults(c.ctx, height)
   170  }
   171  
   172  func (c *Local) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) {
   173  	return core.Commit(c.ctx, height)
   174  }
   175  
   176  func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) {
   177  	return core.Validators(c.ctx, height, page, perPage)
   178  }
   179  
   180  func (c *Local) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
   181  	return core.Tx(c.ctx, hash, prove)
   182  }
   183  
   184  func (c *Local) TxSearch(
   185  	_ context.Context,
   186  	query string,
   187  	prove bool,
   188  	page,
   189  	perPage *int,
   190  	orderBy string,
   191  ) (*ctypes.ResultTxSearch, error) {
   192  	return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy)
   193  }
   194  
   195  func (c *Local) BlockSearch(
   196  	_ context.Context,
   197  	query string,
   198  	page, perPage *int,
   199  	orderBy string,
   200  ) (*ctypes.ResultBlockSearch, error) {
   201  	return core.BlockSearch(c.ctx, query, page, perPage, orderBy)
   202  }
   203  
   204  func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
   205  	return core.BroadcastEvidence(c.ctx, ev)
   206  }
   207  
   208  func (c *Local) Subscribe(
   209  	ctx context.Context,
   210  	subscriber,
   211  	query string,
   212  	outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
   213  	q, err := tmquery.New(query)
   214  	if err != nil {
   215  		return nil, fmt.Errorf("failed to parse query: %w", err)
   216  	}
   217  
   218  	outCap := 1
   219  	if len(outCapacity) > 0 {
   220  		outCap = outCapacity[0]
   221  	}
   222  
   223  	var sub types.Subscription
   224  	if outCap > 0 {
   225  		sub, err = c.EventBus.Subscribe(ctx, subscriber, q, outCap)
   226  	} else {
   227  		sub, err = c.EventBus.SubscribeUnbuffered(ctx, subscriber, q)
   228  	}
   229  	if err != nil {
   230  		return nil, fmt.Errorf("failed to subscribe: %w", err)
   231  	}
   232  
   233  	outc := make(chan ctypes.ResultEvent, outCap)
   234  	go c.eventsRoutine(sub, subscriber, q, outc)
   235  
   236  	return outc, nil
   237  }
   238  
   239  func (c *Local) eventsRoutine(
   240  	sub types.Subscription,
   241  	subscriber string,
   242  	q tmpubsub.Query,
   243  	outc chan<- ctypes.ResultEvent) {
   244  	for {
   245  		select {
   246  		case msg := <-sub.Out():
   247  			result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
   248  			if cap(outc) == 0 {
   249  				outc <- result
   250  			} else {
   251  				select {
   252  				case outc <- result:
   253  				default:
   254  					c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query)
   255  				}
   256  			}
   257  		case <-sub.Cancelled():
   258  			if sub.Err() == tmpubsub.ErrUnsubscribed {
   259  				return
   260  			}
   261  
   262  			c.Logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String())
   263  			sub = c.resubscribe(subscriber, q)
   264  			if sub == nil { // client was stopped
   265  				return
   266  			}
   267  		case <-c.Quit():
   268  			return
   269  		}
   270  	}
   271  }
   272  
   273  // Try to resubscribe with exponential backoff.
   274  func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscription {
   275  	attempts := 0
   276  	for {
   277  		if !c.IsRunning() {
   278  			return nil
   279  		}
   280  
   281  		sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q)
   282  		if err == nil {
   283  			return sub
   284  		}
   285  
   286  		attempts++
   287  		time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
   288  	}
   289  }
   290  
   291  func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error {
   292  	q, err := tmquery.New(query)
   293  	if err != nil {
   294  		return fmt.Errorf("failed to parse query: %w", err)
   295  	}
   296  	return c.EventBus.Unsubscribe(ctx, subscriber, q)
   297  }
   298  
   299  func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
   300  	return c.EventBus.UnsubscribeAll(ctx, subscriber)
   301  }