From d3f0c653aed4d1063f9db640a91f7b172cc2bf49 Mon Sep 17 00:00:00 2001 From: Prit Sheth <124409873+psheth9@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:22:41 -0700 Subject: [PATCH] Refactor getVersionInfo and getNetwork RPC to not use HTTP endpoint /info on core (#198) * Refactor getVersionInfo and getNetwork to not use HTTP endpoint /info * Fix lint errors * Fix lint errors part2 * Change log level * Fix review comments * Add benchmark and tests for getProtocolVersion --- .../internal/daemon/interfaces/interfaces.go | 2 + .../internal/daemon/interfaces/noOpDaemon.go | 6 ++ cmd/soroban-rpc/internal/daemon/metrics.go | 5 + cmd/soroban-rpc/internal/jsonrpc.go | 9 +- .../internal/methods/get_network.go | 19 ++-- .../internal/methods/get_version_info.go | 38 +++----- .../internal/methods/simulate_transaction.go | 6 +- cmd/soroban-rpc/internal/methods/util.go | 32 +++++++ cmd/soroban-rpc/internal/methods/util_test.go | 92 +++++++++++++++++++ 9 files changed, 172 insertions(+), 37 deletions(-) create mode 100644 cmd/soroban-rpc/internal/methods/util.go create mode 100644 cmd/soroban-rpc/internal/methods/util_test.go diff --git a/cmd/soroban-rpc/internal/daemon/interfaces/interfaces.go b/cmd/soroban-rpc/internal/daemon/interfaces/interfaces.go index b97cb376..7cb08168 100644 --- a/cmd/soroban-rpc/internal/daemon/interfaces/interfaces.go +++ b/cmd/soroban-rpc/internal/daemon/interfaces/interfaces.go @@ -5,6 +5,7 @@ import ( "github.com/prometheus/client_golang/prometheus" + "github.com/stellar/go/ingest/ledgerbackend" proto "github.com/stellar/go/protocols/stellarcore" ) @@ -15,6 +16,7 @@ type Daemon interface { MetricsRegistry() *prometheus.Registry MetricsNamespace() string CoreClient() CoreClient + GetCore() *ledgerbackend.CaptiveStellarCore } type CoreClient interface { diff --git a/cmd/soroban-rpc/internal/daemon/interfaces/noOpDaemon.go b/cmd/soroban-rpc/internal/daemon/interfaces/noOpDaemon.go index db8d513d..a5ba0db3 100644 --- a/cmd/soroban-rpc/internal/daemon/interfaces/noOpDaemon.go +++ b/cmd/soroban-rpc/internal/daemon/interfaces/noOpDaemon.go @@ -5,6 +5,7 @@ import ( "github.com/prometheus/client_golang/prometheus" + "github.com/stellar/go/ingest/ledgerbackend" proto "github.com/stellar/go/protocols/stellarcore" ) @@ -14,6 +15,7 @@ type NoOpDaemon struct { metricsRegistry *prometheus.Registry metricsNamespace string coreClient noOpCoreClient + core *ledgerbackend.CaptiveStellarCore } func MakeNoOpDeamon() *NoOpDaemon { @@ -36,6 +38,10 @@ func (d *NoOpDaemon) CoreClient() CoreClient { return d.coreClient } +func (d *NoOpDaemon) GetCore() *ledgerbackend.CaptiveStellarCore { + return d.core +} + type noOpCoreClient struct{} func (s noOpCoreClient) Info(context.Context) (*proto.InfoResponse, error) { diff --git a/cmd/soroban-rpc/internal/daemon/metrics.go b/cmd/soroban-rpc/internal/daemon/metrics.go index 2e409cf4..f283a3b7 100644 --- a/cmd/soroban-rpc/internal/daemon/metrics.go +++ b/cmd/soroban-rpc/internal/daemon/metrics.go @@ -9,6 +9,7 @@ import ( "github.com/prometheus/client_golang/prometheus/collectors" "github.com/stellar/go/clients/stellarcore" + "github.com/stellar/go/ingest/ledgerbackend" proto "github.com/stellar/go/protocols/stellarcore" supportlog "github.com/stellar/go/support/log" "github.com/stellar/go/support/logmetrics" @@ -106,6 +107,10 @@ func (d *Daemon) CoreClient() interfaces.CoreClient { return d.coreClient } +func (d *Daemon) GetCore() *ledgerbackend.CaptiveStellarCore { + return d.core +} + func (d *Daemon) Logger() *supportlog.Entry { return d.logger } diff --git a/cmd/soroban-rpc/internal/jsonrpc.go b/cmd/soroban-rpc/internal/jsonrpc.go index a6329456..677b7c46 100644 --- a/cmd/soroban-rpc/internal/jsonrpc.go +++ b/cmd/soroban-rpc/internal/jsonrpc.go @@ -168,8 +168,13 @@ func NewJSONRPCHandler(cfg *config.Config, params HandlerParams) Handler { requestDurationLimit: cfg.MaxGetEventsExecutionDuration, }, { - methodName: "getNetwork", - underlyingHandler: methods.NewGetNetworkHandler(params.Daemon, cfg.NetworkPassphrase, cfg.FriendbotURL), + methodName: "getNetwork", + underlyingHandler: methods.NewGetNetworkHandler( + cfg.NetworkPassphrase, + cfg.FriendbotURL, + params.LedgerEntryReader, + params.LedgerReader, + ), longName: "get_network", queueLimit: cfg.RequestBacklogGetNetworkQueueLimit, requestDurationLimit: cfg.MaxGetNetworkExecutionDuration, diff --git a/cmd/soroban-rpc/internal/methods/get_network.go b/cmd/soroban-rpc/internal/methods/get_network.go index f706e7c6..16b2b650 100644 --- a/cmd/soroban-rpc/internal/methods/get_network.go +++ b/cmd/soroban-rpc/internal/methods/get_network.go @@ -5,7 +5,7 @@ import ( "github.com/creachadair/jrpc2" - "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/daemon/interfaces" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/db" ) type GetNetworkRequest struct{} @@ -17,20 +17,25 @@ type GetNetworkResponse struct { } // NewGetNetworkHandler returns a json rpc handler to for the getNetwork method -func NewGetNetworkHandler(daemon interfaces.Daemon, networkPassphrase, friendbotURL string) jrpc2.Handler { - coreClient := daemon.CoreClient() +func NewGetNetworkHandler( + networkPassphrase string, + friendbotURL string, + ledgerEntryReader db.LedgerEntryReader, + ledgerReader db.LedgerReader, +) jrpc2.Handler { return NewHandler(func(ctx context.Context, request GetNetworkRequest) (GetNetworkResponse, error) { - info, err := coreClient.Info(ctx) + protocolVersion, err := getProtocolVersion(ctx, ledgerEntryReader, ledgerReader) if err != nil { - return GetNetworkResponse{}, (&jrpc2.Error{ + return GetNetworkResponse{}, &jrpc2.Error{ Code: jrpc2.InternalError, Message: err.Error(), - }) + } } + return GetNetworkResponse{ FriendbotURL: friendbotURL, Passphrase: networkPassphrase, - ProtocolVersion: info.Info.ProtocolVersion, + ProtocolVersion: int(protocolVersion), }, nil }) } diff --git a/cmd/soroban-rpc/internal/methods/get_version_info.go b/cmd/soroban-rpc/internal/methods/get_version_info.go index 6c3053a3..1d868ef2 100644 --- a/cmd/soroban-rpc/internal/methods/get_version_info.go +++ b/cmd/soroban-rpc/internal/methods/get_version_info.go @@ -22,35 +22,19 @@ type GetVersionInfoResponse struct { ProtocolVersion uint32 `json:"protocol_version"` //nolint:tagliatelle } -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() - }() +func NewGetVersionInfoHandler( + logger *log.Entry, + ledgerEntryReader db.LedgerEntryReader, + ledgerReader db.LedgerReader, + daemon interfaces.Daemon, +) jrpc2.Handler { + core := daemon.GetCore() - latestLedger, err := readTx.GetLatestLedgerSequence() - if err != nil { - logger.WithError(err).Info("error occurred while getting latest ledger") - } - - _, protocolVersion, err = getBucketListSizeAndProtocolVersion(ctx, ledgerReader, latestLedger) + return handler.New(func(ctx context.Context) (GetVersionInfoResponse, error) { + captiveCoreVersion := core.GetCoreVersion() + protocolVersion, err := getProtocolVersion(ctx, ledgerEntryReader, ledgerReader) if err != nil { - logger.WithError(err).Info("error occurred while fetching protocol version") + logger.WithError(err).Error("failed to fetch protocol version") } return GetVersionInfoResponse{ diff --git a/cmd/soroban-rpc/internal/methods/simulate_transaction.go b/cmd/soroban-rpc/internal/methods/simulate_transaction.go index 264f386b..dfe5b9d2 100644 --- a/cmd/soroban-rpc/internal/methods/simulate_transaction.go +++ b/cmd/soroban-rpc/internal/methods/simulate_transaction.go @@ -286,7 +286,11 @@ func base64EncodeSlice(in [][]byte) []string { return result } -func getBucketListSizeAndProtocolVersion(ctx context.Context, ledgerReader db.LedgerReader, latestLedger uint32) (uint64, uint32, error) { +func getBucketListSizeAndProtocolVersion( + ctx context.Context, + ledgerReader db.LedgerReader, + latestLedger uint32, +) (uint64, uint32, error) { // obtain bucket size closeMeta, ok, err := ledgerReader.GetLedger(ctx, latestLedger) if err != nil { diff --git a/cmd/soroban-rpc/internal/methods/util.go b/cmd/soroban-rpc/internal/methods/util.go new file mode 100644 index 00000000..eafad857 --- /dev/null +++ b/cmd/soroban-rpc/internal/methods/util.go @@ -0,0 +1,32 @@ +package methods + +import ( + "context" + "fmt" + + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/db" +) + +func getProtocolVersion( + ctx context.Context, + ledgerEntryReader db.LedgerEntryReader, + ledgerReader db.LedgerReader, +) (uint32, error) { + latestLedger, err := ledgerEntryReader.GetLatestLedgerSequence(ctx) + if err != nil { + return 0, err + } + + // obtain bucket size + closeMeta, ok, err := ledgerReader.GetLedger(ctx, latestLedger) + if err != nil { + return 0, err + } + if !ok { + return 0, fmt.Errorf("missing meta for latest ledger (%d)", latestLedger) + } + if closeMeta.V != 1 { + return 0, fmt.Errorf("latest ledger (%d) meta has unexpected verion (%d)", latestLedger, closeMeta.V) + } + return uint32(closeMeta.V1.LedgerHeader.Header.LedgerVersion), nil +} diff --git a/cmd/soroban-rpc/internal/methods/util_test.go b/cmd/soroban-rpc/internal/methods/util_test.go new file mode 100644 index 00000000..520a1bae --- /dev/null +++ b/cmd/soroban-rpc/internal/methods/util_test.go @@ -0,0 +1,92 @@ +package methods + +import ( + "context" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/stellar/go/support/log" + "github.com/stellar/go/xdr" + + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/daemon/interfaces" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/db" +) + +func BenchmarkGetProtocolVersion(b *testing.B) { + dbx := NewTestDB(b) + daemon := interfaces.MakeNoOpDeamon() + + ledgerReader := db.NewLedgerReader(dbx) + _, exists, err := ledgerReader.GetLedger(context.Background(), 1) + require.NoError(b, err) + assert.False(b, exists) + + ledgerSequence := uint32(1) + tx, err := db.NewReadWriter(log.DefaultLogger, dbx, daemon, 150, 15, "passphrase").NewTx(context.Background()) + require.NoError(b, err) + require.NoError(b, tx.LedgerWriter().InsertLedger(createMockLedgerCloseMeta(ledgerSequence))) + require.NoError(b, tx.Commit(ledgerSequence)) + + ledgerEntryReader := db.NewLedgerEntryReader(dbx) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := getProtocolVersion(context.TODO(), ledgerEntryReader, ledgerReader) + if err != nil { + b.Fatalf("getProtocolVersion failed: %v", err) + } + } +} + +func TestGetProtocolVersion(t *testing.T) { + dbx := NewTestDB(t) + daemon := interfaces.MakeNoOpDeamon() + + ledgerReader := db.NewLedgerReader(dbx) + _, exists, err := ledgerReader.GetLedger(context.Background(), 1) + require.NoError(t, err) + assert.False(t, exists) + + ledgerSequence := uint32(1) + tx, err := db.NewReadWriter(log.DefaultLogger, dbx, daemon, 150, 15, "passphrase").NewTx(context.Background()) + require.NoError(t, err) + require.NoError(t, tx.LedgerWriter().InsertLedger(createMockLedgerCloseMeta(ledgerSequence))) + require.NoError(t, tx.Commit(ledgerSequence)) + + ledgerEntryReader := db.NewLedgerEntryReader(dbx) + protocolVersion, err := getProtocolVersion(context.TODO(), ledgerEntryReader, ledgerReader) + require.NoError(t, err) + require.Equal(t, uint32(20), protocolVersion) +} + +func createMockLedgerCloseMeta(ledgerSequence uint32) xdr.LedgerCloseMeta { + return xdr.LedgerCloseMeta{ + V: 1, + V1: &xdr.LedgerCloseMetaV1{ + LedgerHeader: xdr.LedgerHeaderHistoryEntry{ + Hash: xdr.Hash{}, + Header: xdr.LedgerHeader{ + LedgerSeq: xdr.Uint32(ledgerSequence), + LedgerVersion: xdr.Uint32(20), + }, + }, + TxSet: xdr.GeneralizedTransactionSet{ + V: 1, + V1TxSet: &xdr.TransactionSetV1{}, + }, + }, + } +} + +func NewTestDB(tb testing.TB) *db.DB { + tmp := tb.TempDir() + dbPath := path.Join(tmp, "db.sqlite") + db, err := db.OpenSQLiteDB(dbPath) + require.NoError(tb, err) + tb.Cleanup(func() { + require.NoError(tb, db.Close()) + }) + return db +}