From c7b8ddc483fb327ad63192f7f85c4a8d67fce60a Mon Sep 17 00:00:00 2001 From: Prit Sheth <124409873+psheth9@users.noreply.github.com> Date: Thu, 2 May 2024 09:09:28 -0700 Subject: [PATCH] Add get_version_info endpoint (#132) * Add get_version endpoint * Add CaptiveCoreVersion Info * Add integration tests * update integration tests * remove protocol version * fetch captive core version from info endpoint * Add protocol version in response * Remove captive-core-info from makefile * Cache captive core version during bootup and add retry logic * Revert retry logic in daemon and call /info endpoint directly in handler * Fix integration tests * Isolate version population in its own test * Fix review comments --- cmd/soroban-rpc/internal/config/config.go | 2 + cmd/soroban-rpc/internal/config/options.go | 13 ++++ cmd/soroban-rpc/internal/jsonrpc.go | 7 ++ .../internal/methods/get_version_info.go | 61 +++++++++++++++++ .../internal/test/get_version_info_test.go | 67 +++++++++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 cmd/soroban-rpc/internal/methods/get_version_info.go create mode 100644 cmd/soroban-rpc/internal/test/get_version_info_test.go diff --git a/cmd/soroban-rpc/internal/config/config.go b/cmd/soroban-rpc/internal/config/config.go index 15e69f6b..5d753b45 100644 --- a/cmd/soroban-rpc/internal/config/config.go +++ b/cmd/soroban-rpc/internal/config/config.go @@ -44,6 +44,7 @@ type Config struct { RequestBacklogGetHealthQueueLimit uint RequestBacklogGetEventsQueueLimit uint RequestBacklogGetNetworkQueueLimit uint + RequestBacklogGetVersionInfoQueueLimit uint RequestBacklogGetLatestLedgerQueueLimit uint RequestBacklogGetLedgerEntriesQueueLimit uint RequestBacklogGetTransactionQueueLimit uint @@ -54,6 +55,7 @@ type Config struct { MaxGetHealthExecutionDuration time.Duration MaxGetEventsExecutionDuration time.Duration MaxGetNetworkExecutionDuration time.Duration + MaxGetVersionInfoExecutionDuration time.Duration MaxGetLatestLedgerExecutionDuration time.Duration MaxGetLedgerEntriesExecutionDuration time.Duration MaxGetTransactionExecutionDuration time.Duration diff --git a/cmd/soroban-rpc/internal/config/options.go b/cmd/soroban-rpc/internal/config/options.go index df503200..edef7968 100644 --- a/cmd/soroban-rpc/internal/config/options.go +++ b/cmd/soroban-rpc/internal/config/options.go @@ -302,6 +302,13 @@ func (cfg *Config) options() ConfigOptions { DefaultValue: uint(1000), Validate: positive, }, + { + TomlKey: strutils.KebabToConstantCase("request-backlog-get-version-info-queue-limit"), + Usage: "Maximum number of outstanding GetVersionInfo requests", + ConfigKey: &cfg.RequestBacklogGetVersionInfoQueueLimit, + DefaultValue: uint(1000), + Validate: positive, + }, { TomlKey: strutils.KebabToConstantCase("request-backlog-get-latest-ledger-queue-limit"), Usage: "Maximum number of outstanding GetLatestsLedger requests", @@ -367,6 +374,12 @@ func (cfg *Config) options() ConfigOptions { ConfigKey: &cfg.MaxGetNetworkExecutionDuration, DefaultValue: 5 * time.Second, }, + { + TomlKey: strutils.KebabToConstantCase("max-get-version-info-execution-duration"), + Usage: "The maximum duration of time allowed for processing a getVersionInfo request. When that time elapses, the rpc server would return -32001 and abort the request's execution", + ConfigKey: &cfg.MaxGetVersionInfoExecutionDuration, + DefaultValue: 5 * time.Second, + }, { TomlKey: strutils.KebabToConstantCase("max-get-latest-ledger-execution-duration"), Usage: "The maximum duration of time allowed for processing a getLatestLedger request. When that time elapses, the rpc server would return -32001 and abort the request's execution", diff --git a/cmd/soroban-rpc/internal/jsonrpc.go b/cmd/soroban-rpc/internal/jsonrpc.go index 59d83420..3b302aa7 100644 --- a/cmd/soroban-rpc/internal/jsonrpc.go +++ b/cmd/soroban-rpc/internal/jsonrpc.go @@ -171,6 +171,13 @@ func NewJSONRPCHandler(cfg *config.Config, params HandlerParams) Handler { queueLimit: cfg.RequestBacklogGetNetworkQueueLimit, requestDurationLimit: cfg.MaxGetNetworkExecutionDuration, }, + { + methodName: "getVersionInfo", + underlyingHandler: methods.NewGetVersionInfoHandler(params.Logger, params.LedgerEntryReader, params.LedgerReader, params.Daemon), + longName: "get_version_info", + queueLimit: cfg.RequestBacklogGetVersionInfoQueueLimit, + requestDurationLimit: cfg.MaxGetVersionInfoExecutionDuration, + }, { methodName: "getLatestLedger", underlyingHandler: methods.NewGetLatestLedgerHandler(params.LedgerEntryReader, params.LedgerReader), diff --git a/cmd/soroban-rpc/internal/methods/get_version_info.go b/cmd/soroban-rpc/internal/methods/get_version_info.go new file mode 100644 index 00000000..9aa562a1 --- /dev/null +++ b/cmd/soroban-rpc/internal/methods/get_version_info.go @@ -0,0 +1,61 @@ +package methods + +import ( + "context" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" + "github.com/stellar/go/support/log" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/config" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/daemon/interfaces" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/db" +) + +type GetVersionInfoResponse struct { + Version string `json:"version"` + CommitHash string `json:"commit_hash"` + BuildTimestamp string `json:"build_time_stamp"` + CaptiveCoreVersion string `json:"captive_core_version"` + ProtocolVersion uint32 `json:"protocol_version"` +} + +func NewGetVersionInfoHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntryReader, ledgerReader db.LedgerReader, daemon interfaces.Daemon) jrpc2.Handler { + coreClient := daemon.CoreClient() + return handler.New(func(ctx context.Context) (GetVersionInfoResponse, error) { + + var captiveCoreVersion string + info, err := coreClient.Info(ctx) + if err != nil { + logger.WithError(err).Info("error occurred while calling Info endpoint of core") + } else { + captiveCoreVersion = info.Info.Build + } + + // Fetch Protocol version + var protocolVersion uint32 + readTx, err := ledgerEntryReader.NewCachedTx(ctx) + if err != nil { + logger.WithError(err).Info("Cannot create read transaction") + } + defer func() { + _ = readTx.Done() + }() + + latestLedger, err := readTx.GetLatestLedgerSequence() + if err != nil { + logger.WithError(err).Info("error occurred while getting latest ledger") + } + + _, protocolVersion, err = getBucketListSizeAndProtocolVersion(ctx, ledgerReader, latestLedger) + if err != nil { + logger.WithError(err).Info("error occurred while fetching protocol version") + } + + return GetVersionInfoResponse{ + Version: config.Version, + CommitHash: config.CommitHash, + BuildTimestamp: config.BuildTimestamp, + CaptiveCoreVersion: captiveCoreVersion, + ProtocolVersion: protocolVersion, + }, nil + }) +} diff --git a/cmd/soroban-rpc/internal/test/get_version_info_test.go b/cmd/soroban-rpc/internal/test/get_version_info_test.go new file mode 100644 index 00000000..9f406b8d --- /dev/null +++ b/cmd/soroban-rpc/internal/test/get_version_info_test.go @@ -0,0 +1,67 @@ +package test + +import ( + "context" + "fmt" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/config" + "os/exec" + "testing" + + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/jhttp" + "github.com/stretchr/testify/assert" + + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/methods" +) + +func TestGetVersionInfoSucceeds(t *testing.T) { + test := NewTest(t, nil) + + version, commitHash, buildTimeStamp := config.Version, config.CommitHash, config.BuildTimestamp + + populateVersionInfo(test) + + // reset to previous config values + t.Cleanup(func() { + config.Version = version + config.CommitHash = commitHash + config.BuildTimestamp = buildTimeStamp + }) + + ch := jhttp.NewChannel(test.sorobanRPCURL(), nil) + client := jrpc2.NewClient(ch, nil) + + var result methods.GetVersionInfoResponse + err := client.CallResult(context.Background(), "getVersionInfo", nil, &result) + assert.NoError(t, err) + + assert.Equal(t, config.Version, result.Version) + assert.Equal(t, config.BuildTimestamp, result.BuildTimestamp) + assert.Equal(t, config.CommitHash, result.CommitHash) + assert.Equal(t, test.protocolVersion, result.ProtocolVersion) + assert.NotEmpty(t, result.CaptiveCoreVersion) + +} + +// Runs git commands to fetch version information +func populateVersionInfo(test *Test) { + + execFunction := func(command string, args ...string) string { + cmd := exec.Command(command, args...) + test.t.Log("Running", cmd.Env, cmd.Args) + out, innerErr := cmd.Output() + if exitErr, ok := innerErr.(*exec.ExitError); ok { + fmt.Printf("stdout:\n%s\n", string(out)) + fmt.Printf("stderr:\n%s\n", string(exitErr.Stderr)) + } + + if innerErr != nil { + test.t.Fatalf("Command %s failed: %v", cmd.Env, innerErr) + } + return string(out) + } + + config.Version = execFunction("git", "describe", "--tags", "--always", "--abbrev=0", "--match='v[0-9]*.[0-9]*.[0-9]*'") + config.CommitHash = execFunction("git", "rev-parse", "HEAD") + config.BuildTimestamp = execFunction("date", "+%Y-%m-%dT%H:%M:%S") +}