github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/client/client.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/gnolang/gno/tm2/pkg/amino" 9 ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" 10 rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/client" 11 "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/client/batch" 12 "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/client/http" 13 "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/client/ws" 14 rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types" 15 "github.com/gnolang/gno/tm2/pkg/bft/types" 16 "github.com/rs/xid" 17 ) 18 19 const defaultTimeout = 60 * time.Second 20 21 const ( 22 statusMethod = "status" 23 abciInfoMethod = "abci_info" 24 abciQueryMethod = "abci_query" 25 broadcastTxCommitMethod = "broadcast_tx_commit" 26 broadcastTxAsyncMethod = "broadcast_tx_async" 27 broadcastTxSyncMethod = "broadcast_tx_sync" 28 unconfirmedTxsMethod = "unconfirmed_txs" 29 numUnconfirmedTxsMethod = "num_unconfirmed_txs" 30 netInfoMethod = "net_info" 31 dumpConsensusStateMethod = "dump_consensus_state" 32 consensusStateMethod = "consensus_state" 33 consensusParamsMethod = "consensus_params" 34 healthMethod = "health" 35 blockchainMethod = "blockchain" 36 genesisMethod = "genesis" 37 blockMethod = "block" 38 blockResultsMethod = "block_results" 39 commitMethod = "commit" 40 txMethod = "tx" 41 validatorsMethod = "validators" 42 ) 43 44 // RPCClient encompasses common RPC client methods 45 type RPCClient struct { 46 requestTimeout time.Duration 47 48 caller rpcclient.Client 49 } 50 51 // NewRPCClient creates a new RPC client instance with the given caller 52 func NewRPCClient(caller rpcclient.Client, opts ...Option) *RPCClient { 53 c := &RPCClient{ 54 requestTimeout: defaultTimeout, 55 caller: caller, 56 } 57 58 for _, opt := range opts { 59 opt(c) 60 } 61 62 return c 63 } 64 65 // NewHTTPClient takes a remote endpoint in the form <protocol>://<host>:<port>, 66 // and returns an HTTP client that communicates with a Tendermint node over 67 // JSON RPC. 68 // 69 // Request batching is available for JSON RPC requests over HTTP, which conforms to 70 // the JSON RPC specification (https://www.jsonrpc.org/specification#batch). See 71 // the example for more details 72 func NewHTTPClient(rpcURL string) (*RPCClient, error) { 73 httpClient, err := http.NewClient(rpcURL) 74 if err != nil { 75 return nil, err 76 } 77 78 return NewRPCClient(httpClient), nil 79 } 80 81 // NewWSClient takes a remote endpoint in the form <protocol>://<host>:<port>, 82 // and returns a WS client that communicates with a Tendermint node over 83 // WS connection. 84 // 85 // Request batching is available for JSON RPC requests over WS, which conforms to 86 // the JSON RPC specification (https://www.jsonrpc.org/specification#batch). See 87 // the example for more details 88 func NewWSClient(rpcURL string) (*RPCClient, error) { 89 wsClient, err := ws.NewClient(rpcURL) 90 if err != nil { 91 return nil, err 92 } 93 94 return NewRPCClient(wsClient), nil 95 } 96 97 // Close attempts to gracefully close the RPC client 98 func (c *RPCClient) Close() error { 99 return c.caller.Close() 100 } 101 102 // NewBatch creates a new RPC batch 103 func (c *RPCClient) NewBatch() *RPCBatch { 104 return &RPCBatch{ 105 batch: batch.NewBatch(c.caller), 106 resultMap: make(map[string]any), 107 } 108 } 109 110 func (c *RPCClient) Status() (*ctypes.ResultStatus, error) { 111 return sendRequestCommon[ctypes.ResultStatus]( 112 c.caller, 113 c.requestTimeout, 114 statusMethod, 115 map[string]any{}, 116 ) 117 } 118 119 func (c *RPCClient) ABCIInfo() (*ctypes.ResultABCIInfo, error) { 120 return sendRequestCommon[ctypes.ResultABCIInfo]( 121 c.caller, 122 c.requestTimeout, 123 abciInfoMethod, 124 map[string]any{}, 125 ) 126 } 127 128 func (c *RPCClient) ABCIQuery(path string, data []byte) (*ctypes.ResultABCIQuery, error) { 129 return c.ABCIQueryWithOptions(path, data, DefaultABCIQueryOptions) 130 } 131 132 func (c *RPCClient) ABCIQueryWithOptions(path string, data []byte, opts ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 133 return sendRequestCommon[ctypes.ResultABCIQuery]( 134 c.caller, 135 c.requestTimeout, 136 abciQueryMethod, 137 map[string]any{ 138 "path": path, 139 "data": data, 140 "height": opts.Height, 141 "prove": opts.Prove, 142 }, 143 ) 144 } 145 146 func (c *RPCClient) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { 147 return sendRequestCommon[ctypes.ResultBroadcastTxCommit]( 148 c.caller, 149 c.requestTimeout, 150 broadcastTxCommitMethod, 151 map[string]any{"tx": tx}, 152 ) 153 } 154 155 func (c *RPCClient) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 156 return c.broadcastTX(broadcastTxAsyncMethod, tx) 157 } 158 159 func (c *RPCClient) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 160 return c.broadcastTX(broadcastTxSyncMethod, tx) 161 } 162 163 func (c *RPCClient) broadcastTX(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 164 return sendRequestCommon[ctypes.ResultBroadcastTx]( 165 c.caller, 166 c.requestTimeout, 167 route, 168 map[string]any{"tx": tx}, 169 ) 170 } 171 172 func (c *RPCClient) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { 173 return sendRequestCommon[ctypes.ResultUnconfirmedTxs]( 174 c.caller, 175 c.requestTimeout, 176 unconfirmedTxsMethod, 177 map[string]any{"limit": limit}, 178 ) 179 } 180 181 func (c *RPCClient) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { 182 return sendRequestCommon[ctypes.ResultUnconfirmedTxs]( 183 c.caller, 184 c.requestTimeout, 185 numUnconfirmedTxsMethod, 186 map[string]any{}, 187 ) 188 } 189 190 func (c *RPCClient) NetInfo() (*ctypes.ResultNetInfo, error) { 191 return sendRequestCommon[ctypes.ResultNetInfo]( 192 c.caller, 193 c.requestTimeout, 194 netInfoMethod, 195 map[string]any{}, 196 ) 197 } 198 199 func (c *RPCClient) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { 200 return sendRequestCommon[ctypes.ResultDumpConsensusState]( 201 c.caller, 202 c.requestTimeout, 203 dumpConsensusStateMethod, 204 map[string]any{}, 205 ) 206 } 207 208 func (c *RPCClient) ConsensusState() (*ctypes.ResultConsensusState, error) { 209 return sendRequestCommon[ctypes.ResultConsensusState]( 210 c.caller, 211 c.requestTimeout, 212 consensusStateMethod, 213 map[string]any{}, 214 ) 215 } 216 217 func (c *RPCClient) ConsensusParams(height *int64) (*ctypes.ResultConsensusParams, error) { 218 params := map[string]any{} 219 if height != nil { 220 params["height"] = height 221 } 222 223 return sendRequestCommon[ctypes.ResultConsensusParams]( 224 c.caller, 225 c.requestTimeout, 226 consensusParamsMethod, 227 params, 228 ) 229 } 230 231 func (c *RPCClient) Health() (*ctypes.ResultHealth, error) { 232 return sendRequestCommon[ctypes.ResultHealth]( 233 c.caller, 234 c.requestTimeout, 235 healthMethod, 236 map[string]any{}, 237 ) 238 } 239 240 func (c *RPCClient) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { 241 return sendRequestCommon[ctypes.ResultBlockchainInfo]( 242 c.caller, 243 c.requestTimeout, 244 blockchainMethod, 245 map[string]any{ 246 "minHeight": minHeight, 247 "maxHeight": maxHeight, 248 }, 249 ) 250 } 251 252 func (c *RPCClient) Genesis() (*ctypes.ResultGenesis, error) { 253 return sendRequestCommon[ctypes.ResultGenesis]( 254 c.caller, 255 c.requestTimeout, 256 genesisMethod, 257 map[string]any{}, 258 ) 259 } 260 261 func (c *RPCClient) Block(height *int64) (*ctypes.ResultBlock, error) { 262 params := map[string]any{} 263 if height != nil { 264 params["height"] = height 265 } 266 267 return sendRequestCommon[ctypes.ResultBlock]( 268 c.caller, 269 c.requestTimeout, 270 blockMethod, 271 params, 272 ) 273 } 274 275 func (c *RPCClient) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { 276 params := map[string]any{} 277 if height != nil { 278 params["height"] = height 279 } 280 281 return sendRequestCommon[ctypes.ResultBlockResults]( 282 c.caller, 283 c.requestTimeout, 284 blockResultsMethod, 285 params, 286 ) 287 } 288 289 func (c *RPCClient) Commit(height *int64) (*ctypes.ResultCommit, error) { 290 params := map[string]any{} 291 if height != nil { 292 params["height"] = height 293 } 294 295 return sendRequestCommon[ctypes.ResultCommit]( 296 c.caller, 297 c.requestTimeout, 298 commitMethod, 299 params, 300 ) 301 } 302 303 func (c *RPCClient) Tx(hash []byte) (*ctypes.ResultTx, error) { 304 return sendRequestCommon[ctypes.ResultTx]( 305 c.caller, 306 c.requestTimeout, 307 txMethod, 308 map[string]interface{}{ 309 "hash": hash, 310 }, 311 ) 312 } 313 314 func (c *RPCClient) Validators(height *int64) (*ctypes.ResultValidators, error) { 315 params := map[string]any{} 316 if height != nil { 317 params["height"] = height 318 } 319 320 return sendRequestCommon[ctypes.ResultValidators]( 321 c.caller, 322 c.requestTimeout, 323 validatorsMethod, 324 params, 325 ) 326 } 327 328 // newRequest creates a new request based on the method 329 // and given params 330 func newRequest(method string, params map[string]any) (rpctypes.RPCRequest, error) { 331 id := rpctypes.JSONRPCStringID(xid.New().String()) 332 333 return rpctypes.MapToRequest(id, method, params) 334 } 335 336 // sendRequestCommon is the common request creation, sending, and parsing middleware 337 func sendRequestCommon[T any]( 338 caller rpcclient.Client, 339 timeout time.Duration, 340 method string, 341 params map[string]any, 342 ) (*T, error) { 343 // Prepare the RPC request 344 request, err := newRequest(method, params) 345 if err != nil { 346 return nil, err 347 } 348 349 // Send the request 350 ctx, cancelFn := context.WithTimeout(context.Background(), timeout) 351 defer cancelFn() 352 353 response, err := caller.SendRequest(ctx, request) 354 if err != nil { 355 return nil, fmt.Errorf("unable to call RPC method %s, %w", method, err) 356 } 357 358 // Parse the response 359 if response.Error != nil { 360 return nil, response.Error 361 } 362 363 // Unmarshal the RPC response 364 return unmarshalResponseBytes[T](response.Result) 365 } 366 367 // unmarshalResponseBytes Amino JSON-unmarshals the RPC response data 368 func unmarshalResponseBytes[T any](responseBytes []byte) (*T, error) { 369 var result T 370 371 // Amino JSON-unmarshal the RPC response data 372 if err := amino.UnmarshalJSON(responseBytes, &result); err != nil { 373 return nil, fmt.Errorf("unable to unmarshal response bytes, %w", err) 374 } 375 376 return &result, nil 377 }