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 }