github.com/number571/tendermint@v0.34.11-gost/rpc/client/local/local.go (about)

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