github.com/evdatsion/aphelion-dpos-bft@v0.32.1/rpc/client/localclient.go (about)

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