github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/rpc/client/local/local.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/lazyledger/lazyledger-core/libs/bytes"
     9  	"github.com/lazyledger/lazyledger-core/libs/log"
    10  	tmpubsub "github.com/lazyledger/lazyledger-core/libs/pubsub"
    11  	tmquery "github.com/lazyledger/lazyledger-core/libs/pubsub/query"
    12  	nm "github.com/lazyledger/lazyledger-core/node"
    13  	rpcclient "github.com/lazyledger/lazyledger-core/rpc/client"
    14  	"github.com/lazyledger/lazyledger-core/rpc/core"
    15  	ctypes "github.com/lazyledger/lazyledger-core/rpc/core/types"
    16  	rpctypes "github.com/lazyledger/lazyledger-core/rpc/jsonrpc/types"
    17  	"github.com/lazyledger/lazyledger-core/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) DataAvailabilityHeader(
   173  	ctx context.Context,
   174  	height *int64,
   175  ) (*ctypes.ResultDataAvailabilityHeader, error) {
   176  	return core.DataAvailabilityHeader(c.ctx, height)
   177  }
   178  
   179  func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) {
   180  	return core.Validators(c.ctx, height, page, perPage)
   181  }
   182  
   183  func (c *Local) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
   184  	return core.Tx(c.ctx, hash, prove)
   185  }
   186  
   187  func (c *Local) TxSearch(
   188  	ctx context.Context,
   189  	query string,
   190  	prove bool,
   191  	page,
   192  	perPage *int,
   193  	orderBy string,
   194  ) (*ctypes.ResultTxSearch, error) {
   195  	return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy)
   196  }
   197  
   198  func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
   199  	return core.BroadcastEvidence(c.ctx, ev)
   200  }
   201  
   202  func (c *Local) Subscribe(
   203  	ctx context.Context,
   204  	subscriber,
   205  	query string,
   206  	outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
   207  	q, err := tmquery.New(query)
   208  	if err != nil {
   209  		return nil, fmt.Errorf("failed to parse query: %w", err)
   210  	}
   211  
   212  	outCap := 1
   213  	if len(outCapacity) > 0 {
   214  		outCap = outCapacity[0]
   215  	}
   216  
   217  	var sub types.Subscription
   218  	if outCap > 0 {
   219  		sub, err = c.EventBus.Subscribe(ctx, subscriber, q, outCap)
   220  	} else {
   221  		sub, err = c.EventBus.SubscribeUnbuffered(ctx, subscriber, q)
   222  	}
   223  	if err != nil {
   224  		return nil, fmt.Errorf("failed to subscribe: %w", err)
   225  	}
   226  
   227  	outc := make(chan ctypes.ResultEvent, outCap)
   228  	go c.eventsRoutine(sub, subscriber, q, outc)
   229  
   230  	return outc, nil
   231  }
   232  
   233  func (c *Local) eventsRoutine(
   234  	sub types.Subscription,
   235  	subscriber string,
   236  	q tmpubsub.Query,
   237  	outc chan<- ctypes.ResultEvent) {
   238  	for {
   239  		select {
   240  		case msg := <-sub.Out():
   241  			result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
   242  			if cap(outc) == 0 {
   243  				outc <- result
   244  			} else {
   245  				select {
   246  				case outc <- result:
   247  				default:
   248  					c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query)
   249  				}
   250  			}
   251  		case <-sub.Cancelled():
   252  			if sub.Err() == tmpubsub.ErrUnsubscribed {
   253  				return
   254  			}
   255  
   256  			c.Logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String())
   257  			sub = c.resubscribe(subscriber, q)
   258  			if sub == nil { // client was stopped
   259  				return
   260  			}
   261  		case <-c.Quit():
   262  			return
   263  		}
   264  	}
   265  }
   266  
   267  // Try to resubscribe with exponential backoff.
   268  func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscription {
   269  	attempts := 0
   270  	for {
   271  		if !c.IsRunning() {
   272  			return nil
   273  		}
   274  
   275  		sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q)
   276  		if err == nil {
   277  			return sub
   278  		}
   279  
   280  		attempts++
   281  		time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
   282  	}
   283  }
   284  
   285  func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error {
   286  	q, err := tmquery.New(query)
   287  	if err != nil {
   288  		return fmt.Errorf("failed to parse query: %w", err)
   289  	}
   290  	return c.EventBus.Unsubscribe(ctx, subscriber, q)
   291  }
   292  
   293  func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
   294  	return c.EventBus.UnsubscribeAll(ctx, subscriber)
   295  }