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