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 }