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