mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-14 22:01:20 +00:00
rpc: add support for batched requests/responses (#3534)
Continues from #3280 in building support for batched requests/responses in the JSON RPC (as per issue #3213). * Add JSON RPC batching for client and server As per #3213, this adds support for [JSON RPC batch requests and responses](https://www.jsonrpc.org/specification#batch). * Add additional checks to ensure client responses are the same as results * Fix case where a notification is sent and no response is expected * Add test to check that JSON RPC notifications in a batch are left out in responses * Update CHANGELOG_PENDING.md * Update PR number now that PR has been created * Make errors start with lowercase letter * Refactor batch functionality to be standalone This refactors the batching functionality to rather act in a standalone way. In light of supporting concurrent goroutines making use of the same client, it would make sense to have batching functionality where one could create a batch of requests per goroutine and send that batch without interfering with a batch from another goroutine. * Add examples for simple and batch HTTP client usage * Check errors from writer and remove nolinter directives * Make error strings start with lowercase letter * Refactor examples to make them testable * Use safer deferred shutdown for example Tendermint test node * Recompose rpcClient interface from pre-existing interface components * Rename WaitGroup for brevity * Replace empty ID string with request ID * Remove extraneous test case * Convert first letter of errors.Wrap() messages to lowercase * Remove extraneous function parameter * Make variable declaration terse * Reorder WaitGroup.Done call to help prevent race conditions in the face of failure * Swap mutex to value representation and remove initialization * Restore empty JSONRPC string ID in response to prevent nil * Make JSONRPCBufferedRequest private * Revert PR hard link in CHANGELOG_PENDING * Add client ID for JSONRPCClient This adds code to automatically generate a randomized client ID for the JSONRPCClient, and adds a check of the IDs in the responses (if one was set in the requests). * Extract response ID validation into separate function * Remove extraneous comments * Reorder fields to indicate clearly which are protected by the mutex * Refactor for loop to remove indexing * Restructure and combine loop * Flatten conditional block for better readability * Make multi-variable declaration slightly more readable * Change for loop style * Compress error check statements * Make function description more generic to show that we support different protocols * Preallocate memory for request and result objects
This commit is contained in:
committed by
Anton Kaliaev
parent
621c0e629d
commit
90465f727f
@ -23,7 +23,18 @@ import (
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/lib/client"
|
||||
)
|
||||
|
||||
// Options helps with specifying some parameters for our RPC testing for greater
|
||||
// control.
|
||||
type Options struct {
|
||||
suppressStdout bool
|
||||
recreateConfig bool
|
||||
}
|
||||
|
||||
var globalConfig *cfg.Config
|
||||
var defaultOptions = Options{
|
||||
suppressStdout: false,
|
||||
recreateConfig: false,
|
||||
}
|
||||
|
||||
func waitForRPC() {
|
||||
laddr := GetConfig().RPC.ListenAddress
|
||||
@ -77,19 +88,24 @@ func makeAddrs() (string, string, string) {
|
||||
fmt.Sprintf("tcp://0.0.0.0:%d", randPort())
|
||||
}
|
||||
|
||||
// GetConfig returns a config for the test cases as a singleton
|
||||
func GetConfig() *cfg.Config {
|
||||
if globalConfig == nil {
|
||||
pathname := makePathname()
|
||||
globalConfig = cfg.ResetTestRoot(pathname)
|
||||
func createConfig() *cfg.Config {
|
||||
pathname := makePathname()
|
||||
c := cfg.ResetTestRoot(pathname)
|
||||
|
||||
// and we use random ports to run in parallel
|
||||
tm, rpc, grpc := makeAddrs()
|
||||
globalConfig.P2P.ListenAddress = tm
|
||||
globalConfig.RPC.ListenAddress = rpc
|
||||
globalConfig.RPC.CORSAllowedOrigins = []string{"https://tendermint.com/"}
|
||||
globalConfig.RPC.GRPCListenAddress = grpc
|
||||
globalConfig.TxIndex.IndexTags = "app.creator,tx.height" // see kvstore application
|
||||
// and we use random ports to run in parallel
|
||||
tm, rpc, grpc := makeAddrs()
|
||||
c.P2P.ListenAddress = tm
|
||||
c.RPC.ListenAddress = rpc
|
||||
c.RPC.CORSAllowedOrigins = []string{"https://tendermint.com/"}
|
||||
c.RPC.GRPCListenAddress = grpc
|
||||
c.TxIndex.IndexTags = "app.creator,tx.height" // see kvstore application
|
||||
return c
|
||||
}
|
||||
|
||||
// GetConfig returns a config for the test cases as a singleton
|
||||
func GetConfig(forceCreate ...bool) *cfg.Config {
|
||||
if globalConfig == nil || (len(forceCreate) > 0 && forceCreate[0]) {
|
||||
globalConfig = createConfig()
|
||||
}
|
||||
return globalConfig
|
||||
}
|
||||
@ -100,8 +116,12 @@ func GetGRPCClient() core_grpc.BroadcastAPIClient {
|
||||
}
|
||||
|
||||
// StartTendermint starts a test tendermint server in a go routine and returns when it is initialized
|
||||
func StartTendermint(app abci.Application) *nm.Node {
|
||||
node := NewTendermint(app)
|
||||
func StartTendermint(app abci.Application, opts ...func(*Options)) *nm.Node {
|
||||
nodeOpts := defaultOptions
|
||||
for _, opt := range opts {
|
||||
opt(&nodeOpts)
|
||||
}
|
||||
node := NewTendermint(app, &nodeOpts)
|
||||
err := node.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -111,7 +131,9 @@ func StartTendermint(app abci.Application) *nm.Node {
|
||||
waitForRPC()
|
||||
waitForGRPC()
|
||||
|
||||
fmt.Println("Tendermint running!")
|
||||
if !nodeOpts.suppressStdout {
|
||||
fmt.Println("Tendermint running!")
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
@ -125,11 +147,16 @@ func StopTendermint(node *nm.Node) {
|
||||
}
|
||||
|
||||
// NewTendermint creates a new tendermint server and sleeps forever
|
||||
func NewTendermint(app abci.Application) *nm.Node {
|
||||
func NewTendermint(app abci.Application, opts *Options) *nm.Node {
|
||||
// Create & start node
|
||||
config := GetConfig()
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
logger = log.NewFilter(logger, log.AllowError())
|
||||
config := GetConfig(opts.recreateConfig)
|
||||
var logger log.Logger
|
||||
if opts.suppressStdout {
|
||||
logger = log.NewNopLogger()
|
||||
} else {
|
||||
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
logger = log.NewFilter(logger, log.AllowError())
|
||||
}
|
||||
pvKeyFile := config.PrivValidatorKeyFile()
|
||||
pvKeyStateFile := config.PrivValidatorStateFile()
|
||||
pv := privval.LoadOrGenFilePV(pvKeyFile, pvKeyStateFile)
|
||||
@ -148,3 +175,15 @@ func NewTendermint(app abci.Application) *nm.Node {
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// SuppressStdout is an option that tries to make sure the RPC test Tendermint
|
||||
// node doesn't log anything to stdout.
|
||||
func SuppressStdout(o *Options) {
|
||||
o.suppressStdout = true
|
||||
}
|
||||
|
||||
// RecreateConfig instructs the RPC test to recreate the configuration each
|
||||
// time, instead of treating it as a global singleton.
|
||||
func RecreateConfig(o *Options) {
|
||||
o.recreateConfig = true
|
||||
}
|
||||
|
Reference in New Issue
Block a user