github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/client/local/local.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/ari-anchor/sei-tendermint/internal/eventbus"
    10  	"github.com/ari-anchor/sei-tendermint/internal/pubsub"
    11  	"github.com/ari-anchor/sei-tendermint/internal/pubsub/query"
    12  	rpccore "github.com/ari-anchor/sei-tendermint/internal/rpc/core"
    13  	"github.com/ari-anchor/sei-tendermint/libs/bytes"
    14  	"github.com/ari-anchor/sei-tendermint/libs/log"
    15  	rpcclient "github.com/ari-anchor/sei-tendermint/rpc/client"
    16  	"github.com/ari-anchor/sei-tendermint/rpc/coretypes"
    17  	"github.com/ari-anchor/sei-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  	*eventbus.EventBus
    42  	Logger log.Logger
    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  	RPCEnvironment() *rpccore.Environment
    50  	EventBus() *eventbus.EventBus
    51  }
    52  
    53  // New configures a client that calls the Node directly.
    54  func New(logger log.Logger, node NodeService) (*Local, error) {
    55  	env := node.RPCEnvironment()
    56  	if env == nil {
    57  		return nil, errors.New("rpc is nil")
    58  	}
    59  	return &Local{
    60  		EventBus: node.EventBus(),
    61  		Logger:   logger,
    62  		env:      env,
    63  	}, nil
    64  }
    65  
    66  var _ rpcclient.Client = (*Local)(nil)
    67  
    68  func (c *Local) Status(ctx context.Context) (*coretypes.ResultStatus, error) {
    69  	return c.env.Status(ctx)
    70  }
    71  
    72  func (c *Local) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) {
    73  	return c.env.ABCIInfo(ctx)
    74  }
    75  
    76  func (c *Local) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) {
    77  	return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions)
    78  }
    79  
    80  func (c *Local) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) {
    81  	return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{
    82  		Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove,
    83  	})
    84  }
    85  
    86  func (c *Local) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) {
    87  	return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
    88  }
    89  
    90  func (c *Local) BroadcastTx(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
    91  	return c.env.BroadcastTx(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
    92  }
    93  
    94  func (c *Local) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
    95  	return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
    96  }
    97  
    98  func (c *Local) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
    99  	return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
   100  }
   101  
   102  func (c *Local) UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) {
   103  	return c.env.UnconfirmedTxs(ctx, &coretypes.RequestUnconfirmedTxs{
   104  		Page:    coretypes.Int64Ptr(page),
   105  		PerPage: coretypes.Int64Ptr(perPage),
   106  	})
   107  }
   108  
   109  func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) {
   110  	return c.env.NumUnconfirmedTxs(ctx)
   111  }
   112  
   113  func (c *Local) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) {
   114  	return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx})
   115  }
   116  
   117  func (c *Local) RemoveTx(ctx context.Context, txKey types.TxKey) error {
   118  	return c.env.Mempool.RemoveTxByKey(txKey)
   119  }
   120  
   121  func (c *Local) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) {
   122  	return c.env.NetInfo(ctx)
   123  }
   124  
   125  func (c *Local) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error) {
   126  	return c.env.DumpConsensusState(ctx)
   127  }
   128  
   129  func (c *Local) ConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error) {
   130  	return c.env.GetConsensusState(ctx)
   131  }
   132  
   133  func (c *Local) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) {
   134  	return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)})
   135  }
   136  
   137  func (c *Local) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) {
   138  	return c.env.Events(ctx, req)
   139  }
   140  
   141  func (c *Local) Health(ctx context.Context) (*coretypes.ResultHealth, error) {
   142  	return c.env.Health(ctx)
   143  }
   144  
   145  func (c *Local) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
   146  	return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{
   147  		MinHeight: coretypes.Int64(minHeight),
   148  		MaxHeight: coretypes.Int64(maxHeight),
   149  	})
   150  }
   151  
   152  func (c *Local) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) {
   153  	return c.env.Genesis(ctx)
   154  }
   155  
   156  func (c *Local) GenesisChunked(ctx context.Context, id uint) (*coretypes.ResultGenesisChunk, error) {
   157  	return c.env.GenesisChunked(ctx, &coretypes.RequestGenesisChunked{Chunk: coretypes.Int64(id)})
   158  }
   159  
   160  func (c *Local) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) {
   161  	return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
   162  }
   163  
   164  func (c *Local) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) {
   165  	return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash})
   166  }
   167  
   168  func (c *Local) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) {
   169  	return c.env.BlockResults(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
   170  }
   171  
   172  func (c *Local) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) {
   173  	return c.env.Header(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
   174  }
   175  
   176  func (c *Local) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) {
   177  	return c.env.HeaderByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash})
   178  }
   179  
   180  func (c *Local) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) {
   181  	return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
   182  }
   183  
   184  func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) {
   185  	return c.env.Validators(ctx, &coretypes.RequestValidators{
   186  		Height:  (*coretypes.Int64)(height),
   187  		Page:    coretypes.Int64Ptr(page),
   188  		PerPage: coretypes.Int64Ptr(perPage),
   189  	})
   190  }
   191  
   192  func (c *Local) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) {
   193  	return c.env.Tx(ctx, &coretypes.RequestTx{Hash: hash, Prove: prove})
   194  }
   195  
   196  func (c *Local) TxSearch(ctx context.Context, queryString string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) {
   197  	return c.env.TxSearch(ctx, &coretypes.RequestTxSearch{
   198  		Query:   queryString,
   199  		Prove:   prove,
   200  		Page:    coretypes.Int64Ptr(page),
   201  		PerPage: coretypes.Int64Ptr(perPage),
   202  		OrderBy: orderBy,
   203  	})
   204  }
   205  
   206  func (c *Local) BlockSearch(ctx context.Context, queryString string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) {
   207  	return c.env.BlockSearch(ctx, &coretypes.RequestBlockSearch{
   208  		Query:   queryString,
   209  		Page:    coretypes.Int64Ptr(page),
   210  		PerPage: coretypes.Int64Ptr(perPage),
   211  		OrderBy: orderBy,
   212  	})
   213  }
   214  
   215  func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) {
   216  	return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev})
   217  }
   218  
   219  func (c *Local) Subscribe(ctx context.Context, subscriber, queryString string, capacity ...int) (<-chan coretypes.ResultEvent, error) {
   220  	q, err := query.New(queryString)
   221  	if err != nil {
   222  		return nil, fmt.Errorf("failed to parse query: %w", err)
   223  	}
   224  
   225  	limit, quota := 1, 0
   226  	if len(capacity) > 0 {
   227  		limit = capacity[0]
   228  		if len(capacity) > 1 {
   229  			quota = capacity[1]
   230  		}
   231  	}
   232  
   233  	ctx, cancel := context.WithCancel(ctx)
   234  	go func() { c.Wait(); cancel() }()
   235  
   236  	subArgs := pubsub.SubscribeArgs{
   237  		ClientID: subscriber,
   238  		Query:    q,
   239  		Quota:    quota,
   240  		Limit:    limit,
   241  	}
   242  	sub, err := c.EventBus.SubscribeWithArgs(ctx, subArgs)
   243  	if err != nil {
   244  		return nil, fmt.Errorf("failed to subscribe: %w", err)
   245  	}
   246  
   247  	outc := make(chan coretypes.ResultEvent, 1)
   248  	go c.eventsRoutine(ctx, sub, subArgs, outc)
   249  
   250  	return outc, nil
   251  }
   252  
   253  func (c *Local) eventsRoutine(ctx context.Context, sub eventbus.Subscription, subArgs pubsub.SubscribeArgs, outc chan<- coretypes.ResultEvent) {
   254  	qstr := subArgs.Query.String()
   255  	for {
   256  		msg, err := sub.Next(ctx)
   257  		if errors.Is(err, pubsub.ErrUnsubscribed) {
   258  			return // client unsubscribed
   259  		} else if err != nil {
   260  			c.Logger.Error("subscription was canceled, resubscribing",
   261  				"err", err, "query", subArgs.Query.String())
   262  			sub = c.resubscribe(ctx, subArgs)
   263  			if sub == nil {
   264  				return // client terminated
   265  			}
   266  			continue
   267  		}
   268  		select {
   269  		case outc <- coretypes.ResultEvent{
   270  			SubscriptionID: msg.SubscriptionID(),
   271  			Query:          qstr,
   272  			Data:           msg.LegacyData(),
   273  			Events:         msg.Events(),
   274  		}:
   275  		case <-ctx.Done():
   276  			return
   277  		}
   278  	}
   279  }
   280  
   281  // Try to resubscribe with exponential backoff.
   282  func (c *Local) resubscribe(ctx context.Context, subArgs pubsub.SubscribeArgs) eventbus.Subscription {
   283  	timer := time.NewTimer(0)
   284  	defer timer.Stop()
   285  
   286  	attempts := 0
   287  	for {
   288  		if !c.IsRunning() {
   289  			return nil
   290  		}
   291  
   292  		sub, err := c.EventBus.SubscribeWithArgs(ctx, subArgs)
   293  		if err == nil {
   294  			return sub
   295  		}
   296  
   297  		attempts++
   298  		timer.Reset((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
   299  		select {
   300  		case <-timer.C:
   301  			continue
   302  		case <-ctx.Done():
   303  			return nil
   304  		}
   305  	}
   306  }
   307  
   308  func (c *Local) Unsubscribe(ctx context.Context, subscriber, queryString string) error {
   309  	args := pubsub.UnsubscribeArgs{Subscriber: subscriber}
   310  	var err error
   311  	args.Query, err = query.New(queryString)
   312  	if err != nil {
   313  		// if this isn't a valid query it might be an ID, so
   314  		// we'll try that. It'll turn into an error when we
   315  		// try to unsubscribe. Eventually, perhaps, we'll want
   316  		// to change the interface to only allow
   317  		// unsubscription by ID, but that's a larger change.
   318  		args.ID = queryString
   319  	}
   320  	return c.EventBus.Unsubscribe(ctx, args)
   321  }
   322  
   323  func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
   324  	return c.EventBus.UnsubscribeAll(ctx, subscriber)
   325  }