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  }