github.com/evdatsion/aphelion-dpos-bft@v0.32.1/rpc/client/localclient.go (about) 1 package client 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/pkg/errors" 8 9 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 10 "github.com/evdatsion/aphelion-dpos-bft/libs/log" 11 tmpubsub "github.com/evdatsion/aphelion-dpos-bft/libs/pubsub" 12 tmquery "github.com/evdatsion/aphelion-dpos-bft/libs/pubsub/query" 13 nm "github.com/evdatsion/aphelion-dpos-bft/node" 14 "github.com/evdatsion/aphelion-dpos-bft/rpc/core" 15 ctypes "github.com/evdatsion/aphelion-dpos-bft/rpc/core/types" 16 rpctypes "github.com/evdatsion/aphelion-dpos-bft/rpc/lib/types" 17 "github.com/evdatsion/aphelion-dpos-bft/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 NewLocal(node *nm.Node) *Local { 53 node.ConfigureRPC() 54 return &Local{ 55 EventBus: node.EventBus(), 56 Logger: log.NewNopLogger(), 57 ctx: &rpctypes.Context{}, 58 } 59 } 60 61 var _ Client = (*Local)(nil) 62 63 // SetLogger allows to set a logger on the client. 64 func (c *Local) SetLogger(l log.Logger) { 65 c.Logger = l 66 } 67 68 func (c *Local) Status() (*ctypes.ResultStatus, error) { 69 return core.Status(c.ctx) 70 } 71 72 func (c *Local) ABCIInfo() (*ctypes.ResultABCIInfo, error) { 73 return core.ABCIInfo(c.ctx) 74 } 75 76 func (c *Local) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { 77 return c.ABCIQueryWithOptions(path, data, DefaultABCIQueryOptions) 78 } 79 80 func (c *Local) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 81 return core.ABCIQuery(c.ctx, path, data, opts.Height, opts.Prove) 82 } 83 84 func (c *Local) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { 85 return core.BroadcastTxCommit(c.ctx, tx) 86 } 87 88 func (c *Local) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 89 return core.BroadcastTxAsync(c.ctx, tx) 90 } 91 92 func (c *Local) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 93 return core.BroadcastTxSync(c.ctx, tx) 94 } 95 96 func (c *Local) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { 97 return core.UnconfirmedTxs(c.ctx, limit) 98 } 99 100 func (c *Local) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { 101 return core.NumUnconfirmedTxs(c.ctx) 102 } 103 104 func (c *Local) NetInfo() (*ctypes.ResultNetInfo, error) { 105 return core.NetInfo(c.ctx) 106 } 107 108 func (c *Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { 109 return core.DumpConsensusState(c.ctx) 110 } 111 112 func (c *Local) ConsensusState() (*ctypes.ResultConsensusState, error) { 113 return core.ConsensusState(c.ctx) 114 } 115 116 func (c *Local) Health() (*ctypes.ResultHealth, error) { 117 return core.Health(c.ctx) 118 } 119 120 func (c *Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { 121 return core.UnsafeDialSeeds(c.ctx, seeds) 122 } 123 124 func (c *Local) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { 125 return core.UnsafeDialPeers(c.ctx, peers, persistent) 126 } 127 128 func (c *Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { 129 return core.BlockchainInfo(c.ctx, minHeight, maxHeight) 130 } 131 132 func (c *Local) Genesis() (*ctypes.ResultGenesis, error) { 133 return core.Genesis(c.ctx) 134 } 135 136 func (c *Local) Block(height *int64) (*ctypes.ResultBlock, error) { 137 return core.Block(c.ctx, height) 138 } 139 140 func (c *Local) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { 141 return core.BlockResults(c.ctx, height) 142 } 143 144 func (c *Local) Commit(height *int64) (*ctypes.ResultCommit, error) { 145 return core.Commit(c.ctx, height) 146 } 147 148 func (c *Local) Validators(height *int64) (*ctypes.ResultValidators, error) { 149 return core.Validators(c.ctx, height) 150 } 151 152 func (c *Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { 153 return core.Tx(c.ctx, hash, prove) 154 } 155 156 func (c *Local) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { 157 return core.TxSearch(c.ctx, query, prove, page, perPage) 158 } 159 160 func (c *Local) Subscribe(ctx context.Context, subscriber, query string, outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { 161 q, err := tmquery.New(query) 162 if err != nil { 163 return nil, errors.Wrap(err, "failed to parse query") 164 } 165 sub, err := c.EventBus.Subscribe(ctx, subscriber, q) 166 if err != nil { 167 return nil, errors.Wrap(err, "failed to subscribe") 168 } 169 170 outCap := 1 171 if len(outCapacity) > 0 { 172 outCap = outCapacity[0] 173 } 174 175 outc := make(chan ctypes.ResultEvent, outCap) 176 go c.eventsRoutine(sub, subscriber, q, outc) 177 178 return outc, nil 179 } 180 181 func (c *Local) eventsRoutine(sub types.Subscription, subscriber string, q tmpubsub.Query, outc chan<- ctypes.ResultEvent) { 182 for { 183 select { 184 case msg := <-sub.Out(): 185 result := ctypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()} 186 if cap(outc) == 0 { 187 outc <- result 188 } else { 189 select { 190 case outc <- result: 191 default: 192 c.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query) 193 } 194 } 195 case <-sub.Cancelled(): 196 if sub.Err() == tmpubsub.ErrUnsubscribed { 197 return 198 } 199 200 c.Logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String()) 201 sub = c.resubscribe(subscriber, q) 202 if sub == nil { // client was stopped 203 return 204 } 205 case <-c.Quit(): 206 return 207 } 208 } 209 } 210 211 // Try to resubscribe with exponential backoff. 212 func (c *Local) resubscribe(subscriber string, q tmpubsub.Query) types.Subscription { 213 attempts := 0 214 for { 215 if !c.IsRunning() { 216 return nil 217 } 218 219 sub, err := c.EventBus.Subscribe(context.Background(), subscriber, q) 220 if err == nil { 221 return sub 222 } 223 224 attempts++ 225 time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms 226 } 227 } 228 229 func (c *Local) Unsubscribe(ctx context.Context, subscriber, query string) error { 230 q, err := tmquery.New(query) 231 if err != nil { 232 return errors.Wrap(err, "failed to parse query") 233 } 234 return c.EventBus.Unsubscribe(ctx, subscriber, q) 235 } 236 237 func (c *Local) UnsubscribeAll(ctx context.Context, subscriber string) error { 238 return c.EventBus.UnsubscribeAll(ctx, subscriber) 239 }