github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/rpc/client/local/local.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/badrootd/celestia-core/libs/bytes"
     9  	"github.com/badrootd/celestia-core/libs/log"
    10  	cmtpubsub "github.com/badrootd/celestia-core/libs/pubsub"
    11  	cmtquery "github.com/badrootd/celestia-core/libs/pubsub/query"
    12  	nm "github.com/badrootd/celestia-core/node"
    13  	rpcclient "github.com/badrootd/celestia-core/rpc/client"
    14  	"github.com/badrootd/celestia-core/rpc/core"
    15  	ctypes "github.com/badrootd/celestia-core/rpc/core/types"
    16  	rpctypes "github.com/badrootd/celestia-core/rpc/jsonrpc/types"
    17  	"github.com/badrootd/celestia-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 CometBFT 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 CometBFT using Subscribe method.
    35  Note delivery is best-effort. If you don't read events fast enough, CometBFT
    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) SignedBlock(ctx context.Context, height *int64) (*ctypes.ResultSignedBlock, error) {
   165  	return core.SignedBlock(c.ctx, height)
   166  }
   167  
   168  func (c *Local) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) {
   169  	return core.BlockByHash(c.ctx, hash)
   170  }
   171  
   172  func (c *Local) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) {
   173  	return core.BlockResults(c.ctx, height)
   174  }
   175  
   176  func (c *Local) Header(ctx context.Context, height *int64) (*ctypes.ResultHeader, error) {
   177  	return core.Header(c.ctx, height)
   178  }
   179  
   180  func (c *Local) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*ctypes.ResultHeader, error) {
   181  	return core.HeaderByHash(c.ctx, hash)
   182  }
   183  
   184  func (c *Local) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) {
   185  	return core.Commit(c.ctx, height)
   186  }
   187  
   188  func (c *Local) DataCommitment(
   189  	_ context.Context,
   190  	start uint64,
   191  	end uint64,
   192  ) (*ctypes.ResultDataCommitment, error) {
   193  	return core.DataCommitment(c.ctx, start, end)
   194  }
   195  
   196  func (c *Local) DataRootInclusionProof(
   197  	_ context.Context,
   198  	height uint64,
   199  	start uint64,
   200  	end uint64,
   201  ) (*ctypes.ResultDataRootInclusionProof, error) {
   202  	return core.DataRootInclusionProof(c.ctx, int64(height), start, end)
   203  }
   204  
   205  func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) {
   206  	return core.Validators(c.ctx, height, page, perPage)
   207  }
   208  
   209  func (c *Local) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
   210  	return core.Tx(c.ctx, hash, prove)
   211  }
   212  
   213  func (c *Local) ProveShares(
   214  	ctx context.Context,
   215  	height uint64,
   216  	startShare uint64,
   217  	endShare uint64,
   218  ) (types.ShareProof, error) {
   219  	return core.ProveShares(c.ctx, int64(height), startShare, endShare)
   220  }
   221  
   222  func (c *Local) TxSearch(
   223  	_ context.Context,
   224  	query string,
   225  	prove bool,
   226  	page,
   227  	perPage *int,
   228  	orderBy string,
   229  ) (*ctypes.ResultTxSearch, error) {
   230  	return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy)
   231  }
   232  
   233  func (c *Local) BlockSearch(
   234  	_ context.Context,
   235  	query string,
   236  	page, perPage *int,
   237  	orderBy string,
   238  ) (*ctypes.ResultBlockSearch, error) {
   239  	return core.BlockSearch(c.ctx, query, page, perPage, orderBy)
   240  }
   241  
   242  func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) {
   243  	return core.BroadcastEvidence(c.ctx, ev)
   244  }
   245  
   246  func (c *Local) Subscribe(
   247  	ctx context.Context,
   248  	subscriber,
   249  	query string,
   250  	outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) {
   251  	q, err := cmtquery.New(query)
   252  	if err != nil {
   253  		return nil, fmt.Errorf("failed to parse query: %w", err)
   254  	}
   255  
   256  	outCap := 1
   257  	if len(outCapacity) > 0 {
   258  		outCap = outCapacity[0]
   259  	}
   260  
   261  	var sub types.Subscription
   262  	if outCap > 0 {
   263  		sub, err = c.EventBus.Subscribe(ctx, subscriber, q, outCap)
   264  	} else {
   265  		sub, err = c.EventBus.SubscribeUnbuffered(ctx, subscriber, q)
   266  	}
   267  	if err != nil {
   268  		return nil, fmt.Errorf("failed to subscribe: %w", err)
   269  	}
   270  
   271  	outc := make(chan ctypes.ResultEvent, outCap)
   272  	go c.eventsRoutine(sub, subscriber, q, outc)
   273  
   274  	return outc, nil
   275  }
   276  
   277  func (c *Local) eventsRoutine(
   278  	sub types.Subscription,
   279  	subscriber string,
   280  	q cmtpubsub.Query,
   281  	outc chan<- ctypes.ResultEvent) {
   282  	for {
   283  		select {
   284  		case msg := <-sub.Out():
   285  			result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
   286  			if cap(outc) == 0 {
   287  				outc <- result
   288  			} else {
   289  				select {
   290  				case outc <- result:
   291  				default:
   292  					c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query)
   293  				}
   294  			}
   295  		case <-sub.Cancelled():
   296  			if sub.Err() == cmtpubsub.ErrUnsubscribed {
   297  				return
   298  			}
   299  
   300  			c.Logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String())
   301  			sub = c.resubscribe(subscriber, q)
   302  			if sub == nil { // client was stopped
   303  				return
   304  			}
   305  		case <-c.Quit():
   306  			return
   307  		}
   308  	}
   309  }
   310  
   311  // Try to resubscribe with exponential backoff.
   312  func (c *Local) resubscribe(subscriber string, q cmtpubsub.Query) types.Subscription {
   313  	attempts := 0
   314  	for {
   315  		if !c.IsRunning() {
   316  			return nil
   317  		}
   318  
   319  		sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q)
   320  		if err == nil {
   321  			return sub
   322  		}
   323  
   324  		attempts++
   325  		time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
   326  	}
   327  }
   328  
   329  func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error {
   330  	q, err := cmtquery.New(query)
   331  	if err != nil {
   332  		return fmt.Errorf("failed to parse query: %w", err)
   333  	}
   334  	return c.EventBus.Unsubscribe(ctx, subscriber, q)
   335  }
   336  
   337  func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error {
   338  	return c.EventBus.UnsubscribeAll(ctx, subscriber)
   339  }