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