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