github.com/number571/tendermint@v0.34.11-gost/rpc/client/local/local.go (about) 1 package local 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/number571/tendermint/libs/bytes" 9 "github.com/number571/tendermint/libs/log" 10 tmpubsub "github.com/number571/tendermint/libs/pubsub" 11 tmquery "github.com/number571/tendermint/libs/pubsub/query" 12 rpcclient "github.com/number571/tendermint/rpc/client" 13 rpccore "github.com/number571/tendermint/rpc/core" 14 ctypes "github.com/number571/tendermint/rpc/core/types" 15 rpctypes "github.com/number571/tendermint/rpc/jsonrpc/types" 16 "github.com/number571/tendermint/types" 17 ) 18 19 /* 20 Local is a Client implementation that directly executes the rpc 21 functions on a given node, without going through HTTP or GRPC. 22 23 This implementation is useful for: 24 25 * Running tests against a node in-process without the overhead 26 of going through an http server 27 * Communication between an ABCI app and Tendermint core when they 28 are compiled in process. 29 30 For real clients, you probably want to use client.HTTP. For more 31 powerful control during testing, you probably want the "client/mock" package. 32 33 You can subscribe for any event published by Tendermint using Subscribe method. 34 Note delivery is best-effort. If you don't read events fast enough, Tendermint 35 might cancel the subscription. The client will attempt to resubscribe (you 36 don't need to do anything). It will keep trying indefinitely with exponential 37 backoff (10ms -> 20ms -> 40ms) until successful. 38 */ 39 type Local struct { 40 *types.EventBus 41 Logger log.Logger 42 ctx *rpctypes.Context 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 ConfigureRPC() (*rpccore.Environment, error) 50 EventBus() *types.EventBus 51 } 52 53 // New configures a client that calls the Node directly. 54 func New(node NodeService) (*Local, error) { 55 env, err := node.ConfigureRPC() 56 if err != nil { 57 return nil, err 58 } 59 return &Local{ 60 EventBus: node.EventBus(), 61 Logger: log.NewNopLogger(), 62 ctx: &rpctypes.Context{}, 63 env: env, 64 }, nil 65 } 66 67 var _ rpcclient.Client = (*Local)(nil) 68 69 // SetLogger allows to set a logger on the client. 70 func (c *Local) SetLogger(l log.Logger) { 71 c.Logger = l 72 } 73 74 func (c *Local) Status(ctx context.Context) (*ctypes.ResultStatus, error) { 75 return c.env.Status(c.ctx) 76 } 77 78 func (c *Local) ABCIInfo(ctx context.Context) (*ctypes.ResultABCIInfo, error) { 79 return c.env.ABCIInfo(c.ctx) 80 } 81 82 func (c *Local) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) { 83 return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) 84 } 85 86 func (c *Local) ABCIQueryWithOptions( 87 ctx context.Context, 88 path string, 89 data bytes.HexBytes, 90 opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 91 return c.env.ABCIQuery(c.ctx, path, data, opts.Height, opts.Prove) 92 } 93 94 func (c *Local) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { 95 return c.env.BroadcastTxCommit(c.ctx, tx) 96 } 97 98 func (c *Local) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 99 return c.env.BroadcastTxAsync(c.ctx, tx) 100 } 101 102 func (c *Local) BroadcastTxSync(ctx context.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 103 return c.env.BroadcastTxSync(c.ctx, tx) 104 } 105 106 func (c *Local) UnconfirmedTxs(ctx context.Context, limit *int) (*ctypes.ResultUnconfirmedTxs, error) { 107 return c.env.UnconfirmedTxs(c.ctx, limit) 108 } 109 110 func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*ctypes.ResultUnconfirmedTxs, error) { 111 return c.env.NumUnconfirmedTxs(c.ctx) 112 } 113 114 func (c *Local) CheckTx(ctx context.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) { 115 return c.env.CheckTx(c.ctx, tx) 116 } 117 118 func (c *Local) NetInfo(ctx context.Context) (*ctypes.ResultNetInfo, error) { 119 return c.env.NetInfo(c.ctx) 120 } 121 122 func (c *Local) DumpConsensusState(ctx context.Context) (*ctypes.ResultDumpConsensusState, error) { 123 return c.env.DumpConsensusState(c.ctx) 124 } 125 126 func (c *Local) ConsensusState(ctx context.Context) (*ctypes.ResultConsensusState, error) { 127 return c.env.GetConsensusState(c.ctx) 128 } 129 130 func (c *Local) ConsensusParams(ctx context.Context, height *int64) (*ctypes.ResultConsensusParams, error) { 131 return c.env.ConsensusParams(c.ctx, height) 132 } 133 134 func (c *Local) Health(ctx context.Context) (*ctypes.ResultHealth, error) { 135 return c.env.Health(c.ctx) 136 } 137 138 func (c *Local) DialSeeds(ctx context.Context, seeds []string) (*ctypes.ResultDialSeeds, error) { 139 return c.env.UnsafeDialSeeds(c.ctx, seeds) 140 } 141 142 func (c *Local) DialPeers( 143 ctx context.Context, 144 peers []string, 145 persistent, 146 unconditional, 147 private bool, 148 ) (*ctypes.ResultDialPeers, error) { 149 return c.env.UnsafeDialPeers(c.ctx, peers, persistent, unconditional, private) 150 } 151 152 func (c *Local) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { 153 return c.env.BlockchainInfo(c.ctx, minHeight, maxHeight) 154 } 155 156 func (c *Local) Genesis(ctx context.Context) (*ctypes.ResultGenesis, error) { 157 return c.env.Genesis(c.ctx) 158 } 159 160 func (c *Local) GenesisChunked(ctx context.Context, id uint) (*ctypes.ResultGenesisChunk, error) { 161 return c.env.GenesisChunked(c.ctx, id) 162 } 163 164 func (c *Local) Block(ctx context.Context, height *int64) (*ctypes.ResultBlock, error) { 165 return c.env.Block(c.ctx, height) 166 } 167 168 func (c *Local) BlockByHash(ctx context.Context, hash []byte) (*ctypes.ResultBlock, error) { 169 return c.env.BlockByHash(c.ctx, hash) 170 } 171 172 func (c *Local) BlockResults(ctx context.Context, height *int64) (*ctypes.ResultBlockResults, error) { 173 return c.env.BlockResults(c.ctx, height) 174 } 175 176 func (c *Local) Commit(ctx context.Context, height *int64) (*ctypes.ResultCommit, error) { 177 return c.env.Commit(c.ctx, height) 178 } 179 180 func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) { 181 return c.env.Validators(c.ctx, height, page, perPage) 182 } 183 184 func (c *Local) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) { 185 return c.env.Tx(c.ctx, hash, prove) 186 } 187 188 func (c *Local) TxSearch( 189 _ context.Context, 190 query string, 191 prove bool, 192 page, 193 perPage *int, 194 orderBy string, 195 ) (*ctypes.ResultTxSearch, error) { 196 return c.env.TxSearch(c.ctx, query, prove, page, perPage, orderBy) 197 } 198 199 func (c *Local) BlockSearch( 200 _ context.Context, 201 query string, 202 page, perPage *int, 203 orderBy string, 204 ) (*ctypes.ResultBlockSearch, error) { 205 return c.env.BlockSearch(c.ctx, query, page, perPage, orderBy) 206 } 207 208 func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) { 209 return c.env.BroadcastEvidence(c.ctx, ev) 210 } 211 212 func (c *Local) Subscribe( 213 ctx context.Context, 214 subscriber, 215 query string, 216 outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { 217 q, err := tmquery.New(query) 218 if err != nil { 219 return nil, fmt.Errorf("failed to parse query: %w", err) 220 } 221 222 outCap := 1 223 if len(outCapacity) > 0 { 224 outCap = outCapacity[0] 225 } 226 227 var sub types.Subscription 228 if outCap > 0 { 229 sub, err = c.EventBus.Subscribe(ctx, subscriber, q, outCap) 230 } else { 231 sub, err = c.EventBus.SubscribeUnbuffered(ctx, subscriber, q) 232 } 233 if err != nil { 234 return nil, fmt.Errorf("failed to subscribe: %w", err) 235 } 236 237 outc := make(chan ctypes.ResultEvent, outCap) 238 go c.eventsRoutine(sub, subscriber, q, outc) 239 240 return outc, nil 241 } 242 243 func (c *Local) eventsRoutine( 244 sub types.Subscription, 245 subscriber string, 246 q tmpubsub.Query, 247 outc chan<- ctypes.ResultEvent) { 248 for { 249 select { 250 case msg := <-sub.Out(): 251 result := ctypes.ResultEvent{ 252 SubscriptionID: msg.SubscriptionID(), 253 Query: q.String(), 254 Data: msg.Data(), 255 Events: msg.Events(), 256 } 257 258 if cap(outc) == 0 { 259 outc <- result 260 } else { 261 select { 262 case outc <- result: 263 default: 264 c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query) 265 } 266 } 267 case <-sub.Canceled(): 268 if sub.Err() == tmpubsub.ErrUnsubscribed { 269 return 270 } 271 272 c.Logger.Error("subscription was canceled, resubscribing...", "err", sub.Err(), "query", q.String()) 273 sub = c.resubscribe(subscriber, q) 274 if sub == nil { // client was stopped 275 return 276 } 277 case <-c.Quit(): 278 return 279 } 280 } 281 } 282 283 // Try to resubscribe with exponential backoff. 284 func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscription { 285 attempts := 0 286 for { 287 if !c.IsRunning() { 288 return nil 289 } 290 291 sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q) 292 if err == nil { 293 return sub 294 } 295 296 attempts++ 297 time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms 298 } 299 } 300 301 func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error { 302 args := tmpubsub.UnsubscribeArgs{Subscriber: subscriber} 303 var err error 304 args.Query, err = tmquery.New(query) 305 if err != nil { 306 // if this isn't a valid query it might be an ID, so 307 // we'll try that. It'll turn into an error when we 308 // try to unsubscribe. Eventually, perhaps, we'll want 309 // to change the interface to only allow 310 // unsubscription by ID, but that's a larger change. 311 args.ID = query 312 } 313 return c.EventBus.Unsubscribe(ctx, args) 314 } 315 316 func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error { 317 return c.EventBus.UnsubscribeAll(ctx, subscriber) 318 }